fix: clients sync to server terminals instead of creating new ones

- Remove auto-creation of terminal sessions from init/selectSession/switchAgent
- Clients only connect to existing alive terminals from server registry
- Remove localStorage persistence (agent/sessionId) — state derived from server
- Refine session-state types: new AgentStatus values, LastError interface
- UI improvements: AgentBadge, ChatContainer, UserInput, BashCard updates
- Simplify claude-hook routes, update session-state service
This commit is contained in:
2026-02-20 22:26:17 -06:00
parent 653c4e6d23
commit a6c68f1b9e
17 changed files with 1036 additions and 189 deletions

View File

@@ -56,6 +56,29 @@
] ]
} }
], ],
"PostToolUseFailure": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "powershell -NoProfile -File hooks/forward-hook.ps1 ejecutor",
"timeout": 5000
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "powershell -NoProfile -File hooks/forward-hook.ps1 ejecutor",
"timeout": 5000
}
]
}
],
"SessionStart": [ "SessionStart": [
{ {
"hooks": [ "hooks": [

View File

@@ -5,7 +5,7 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"processing\\\",\\\"agent\\\":\\\"ejecutor\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"", "command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"thinking\\\",\\\"agent\\\":\\\"ejecutor\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"",
"timeout": 5000 "timeout": 5000
} }
] ]
@@ -29,19 +29,30 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"toolDone\\\",\\\"tool\\\":\\\"$CLAUDE_TOOL_NAME\\\",\\\"agent\\\":\\\"ejecutor\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"", "command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"thinking\\\",\\\"tool\\\":\\\"$CLAUDE_TOOL_NAME\\\",\\\"agent\\\":\\\"ejecutor\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"",
"timeout": 5000 "timeout": 5000
} }
] ]
} }
], ],
"Notification": [ "PostToolUseFailure": [
{ {
"matcher": ".*", "matcher": ".*",
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"notification\\\",\\\"agent\\\":\\\"ejecutor\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"", "command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"error\\\",\\\"tool\\\":\\\"$CLAUDE_TOOL_NAME\\\",\\\"agent\\\":\\\"ejecutor\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"",
"timeout": 5000
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "powershell -NoProfile -Command \"try { Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-status' -Method POST -Body '{\\\"status\\\":\\\"sessionEnd\\\",\\\"agent\\\":\\\"ejecutor\\\"}' -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch {}\"",
"timeout": 5000 "timeout": 5000
} }
] ]

View File

@@ -0,0 +1,584 @@
/c/Users/jodar/agent-ui/frontend/node_modules/.vite/deps/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@apideck/better-ajv-errors/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/code-frame/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/compat-data/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/core/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/generator/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-annotate-as-pure/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-compilation-targets/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-create-class-features-plugin/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-create-regexp-features-plugin/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-define-polyfill-provider/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-globals/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-member-expression-to-functions/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-module-imports/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-module-transforms/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-optimise-call-expression/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-plugin-utils/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-remap-async-to-generator/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-replace-supers/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-skip-transparent-expression-wrappers/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-string-parser/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-validator-identifier/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-validator-option/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helper-wrap-function/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/helpers/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/parser/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-proposal-private-property-in-object/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-syntax-import-assertions/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-syntax-import-attributes/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-syntax-unicode-sets-regex/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-arrow-functions/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-async-generator-functions/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-async-to-generator/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-block-scoped-functions/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-block-scoping/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-class-properties/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-class-static-block/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-classes/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-computed-properties/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-destructuring/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-dotall-regex/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-duplicate-keys/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-dynamic-import/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-explicit-resource-management/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-exponentiation-operator/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-export-namespace-from/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-for-of/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-function-name/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-json-strings/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-literals/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-logical-assignment-operators/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-member-expression-literals/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-modules-amd/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-modules-commonjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-modules-systemjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-modules-umd/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-named-capturing-groups-regex/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-new-target/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-nullish-coalescing-operator/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-numeric-separator/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-object-rest-spread/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-object-super/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-optional-catch-binding/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-optional-chaining/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-parameters/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-private-methods/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-private-property-in-object/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-property-literals/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-regenerator/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-regexp-modifiers/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-reserved-words/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-shorthand-properties/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-spread/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-sticky-regex/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-template-literals/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-typeof-symbol/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-unicode-escapes/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-unicode-property-regex/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-unicode-regex/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/plugin-transform-unicode-sets-regex/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/preset-env/data/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/preset-env/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/preset-modules/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/runtime/helpers/esm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/runtime/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/template/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/traverse/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@babel/types/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@esbuild/win32-x64/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@hono/node-server/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@img/colour/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@img/sharp-win32-x64/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@isaacs/cliui/dist/commonjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@isaacs/cliui/dist/esm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@isaacs/cliui/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@jridgewell/gen-mapping/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@jridgewell/remapping/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@jridgewell/resolve-uri/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@jridgewell/source-map/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@jridgewell/sourcemap-codec/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@jridgewell/trace-mapping/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@modelcontextprotocol/sdk/dist/cjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@modelcontextprotocol/sdk/dist/esm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@modelcontextprotocol/sdk/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@nucleoriofrio/webmcp/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rolldown/pluginutils/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rollup/plugin-babel/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rollup/plugin-node-resolve/dist/es/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils/dist/es/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rollup/plugin-node-resolve/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rollup/plugin-replace/node_modules/magic-string/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rollup/plugin-replace/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rollup/plugin-terser/dist/es/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rollup/plugin-terser/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rollup/pluginutils/dist/es/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rollup/pluginutils/node_modules/@types/estree/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rollup/pluginutils/node_modules/estree-walker/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rollup/pluginutils/node_modules/picomatch/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rollup/pluginutils/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rollup/rollup-win32-x64-gnu/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@rollup/rollup-win32-x64-msvc/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@surma/rollup-plugin-off-main-thread/node_modules/magic-string/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@surma/rollup-plugin-off-main-thread/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@types/estree/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@types/node/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@types/resolve/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@types/trusted-types/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@vitejs/plugin-vue/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@volar/language-core/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@volar/source-map/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@volar/typescript/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@vue/compiler-core/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@vue/compiler-dom/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@vue/compiler-sfc/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@vue/compiler-ssr/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@vue/devtools-api/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@vue/devtools-kit/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@vue/devtools-shared/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@vue/language-core/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@vue/reactivity/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@vue/runtime-core/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@vue/runtime-dom/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@vue/server-renderer/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@vue/shared/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@vue/tsconfig/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@xterm/addon-fit/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@xterm/addon-web-links/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@xterm/addon-webgl/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/@xterm/xterm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/accepts/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/acorn/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/ajv/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/ajv-formats/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/alien-signals/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/array-buffer-byte-length/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/arraybuffer.prototype.slice/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/async/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/async-function/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/at-least-node/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/available-typed-arrays/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/babel-plugin-polyfill-corejs2/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/babel-plugin-polyfill-corejs3/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/babel-plugin-polyfill-regenerator/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/balanced-match/dist/commonjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/balanced-match/dist/esm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/balanced-match/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/baseline-browser-mapping/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/birpc/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/body-parser/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/brace-expansion/dist/commonjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/brace-expansion/dist/esm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/brace-expansion/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/browserslist/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/buffer-from/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/bytes/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/call-bind/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/call-bind-apply-helpers/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/call-bound/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/caniuse-lite/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/child_process/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/commander/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/common-tags/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/content-disposition/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/content-type/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/convert-source-map/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/cookie/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/cookie-signature/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/copy-anything/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/core-js-compat/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/cors/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/cross-spawn/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/crypto/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/crypto-random-string/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/csstype/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/data-view-buffer/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/data-view-byte-length/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/data-view-byte-offset/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/debug/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/deepmerge/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/define-data-property/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/define-properties/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/depd/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/detect-libc/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/dotenv/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/dunder-proto/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/ee-first/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/ejs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/electron-to-chromium/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/encodeurl/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/entities/dist/commonjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/entities/dist/esm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/entities/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/env-paths/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/es-abstract/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/es-define-property/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/es-errors/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/es-object-atoms/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/es-set-tostringtag/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/es-to-primitive/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/esbuild/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/escalade/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/escape-html/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/estree-walker/dist/esm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/estree-walker/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/estree-walker/src/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/esutils/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/etag/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/eventsource/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/eventsource-parser/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/express/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/express-rate-limit/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/fast-deep-equal/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/fast-json-stable-stringify/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/fast-uri/benchmark/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/fast-uri/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/fdir/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/filelist/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/filelist/node_modules/minimatch/node_modules/brace-expansion/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/filelist/node_modules/minimatch/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/filelist/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/finalhandler/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/for-each/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/foreground-child/dist/commonjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/foreground-child/dist/esm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/foreground-child/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/forwarded/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/fresh/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/fs-extra/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/function-bind/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/function.prototype.name/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/functions-have-names/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/generator-function/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/gensync/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/get-intrinsic/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/get-own-enumerable-property-symbols/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/get-proto/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/get-symbol-description/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/glob/dist/commonjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/glob/dist/esm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/glob/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/globalthis/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/gopd/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/graceful-fs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/has-bigints/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/has-property-descriptors/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/has-proto/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/has-symbols/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/has-tostringtag/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/hasown/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/hono/dist/cjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/hono/dist/types/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/hono/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/hookable/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/http/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/http-errors/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/iconv-lite/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/idb/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/inherits/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/internal-slot/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/ip-address/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/ipaddr.js/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-array-buffer/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-async-function/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-bigint/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-boolean-object/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-callable/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-core-module/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-data-view/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-date-object/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-finalizationregistry/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-generator-function/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-map/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-module/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-negative-zero/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-number-object/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-obj/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-promise/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-regex/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-regexp/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-set/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-shared-array-buffer/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-stream/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-string/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-symbol/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-typed-array/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-weakmap/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-weakref/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-weakset/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/is-what/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/isarray/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/isexe/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/jackspeak/dist/commonjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/jackspeak/dist/esm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/jackspeak/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/jake/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/jose/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/js-tokens/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/jsesc/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/json-schema/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/json-schema-traverse/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/json-schema-typed/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/json5/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/jsonfile/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/jsonpointer/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/leven/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/lodash/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/lodash.debounce/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/lodash.sortby/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/lru-cache/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/magic-string/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/math-intrinsics/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/media-typer/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/merge-descriptors/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/mime-db/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/mime-types/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/minimatch/dist/commonjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/minimatch/dist/esm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/minimatch/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/minipass/dist/commonjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/minipass/dist/esm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/minipass/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/mitt/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/ms/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/muggle-string/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/nanoid/async/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/nanoid/non-secure/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/nanoid/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/nanoid/url-alphabet/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/negotiator/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/node-releases/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/object-assign/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/object-inspect/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/object-keys/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/object.assign/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/on-finished/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/once/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/os/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/own-keys/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/package-json-from-dist/dist/commonjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/package-json-from-dist/dist/esm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/package-json-from-dist/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/parseurl/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/path/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/path-browserify/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/path-key/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/path-parse/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/path-scurry/dist/commonjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/path-scurry/dist/esm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/path-scurry/node_modules/lru-cache/dist/esm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/path-scurry/node_modules/lru-cache/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/path-scurry/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/path-to-regexp/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/perfect-debounce/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/picocolors/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/picomatch/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/pinia/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/pkce-challenge/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/possible-typed-array-names/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/postcss/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/pretty-bytes/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/process/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/proxy-addr/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/punycode/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/qs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/randombytes/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/range-parser/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/raw-body/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/reflect.getprototypeof/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/regenerate/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/regenerate-unicode-properties/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/regexp.prototype.flags/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/regexpu-core/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/regjsgen/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/regjsparser/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/require-from-string/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/resolve/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/resolve/test/module_dir/zmodules/bbb/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/resolve/test/resolver/baz/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/resolve/test/resolver/browser_field/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/resolve/test/resolver/dot_main/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/resolve/test/resolver/dot_slash_main/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/resolve/test/resolver/false_main/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/resolve/test/resolver/incorrect_main/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/resolve/test/resolver/invalid_main/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/resolve/test/resolver/multirepo/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/resolve/test/resolver/multirepo/packages/package-a/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/resolve/test/resolver/multirepo/packages/package-b/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/resolve/test/resolver/nested_symlinks/mylib/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/resolve/test/resolver/symlinked/package/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/rfdc/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/rollup/dist/es/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/rollup/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/router/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/safe-array-concat/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/safe-buffer/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/safe-push-apply/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/safe-regex-test/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/safer-buffer/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/semver/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/send/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/serialize-javascript/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/serve-static/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/set-function-length/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/set-function-name/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/set-proto/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/setprototypeof/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/sharp/node_modules/semver/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/sharp/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/shebang-command/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/shebang-regex/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/side-channel/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/side-channel-list/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/side-channel-map/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/side-channel-weakmap/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/signal-exit/dist/cjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/signal-exit/dist/mjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/signal-exit/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/smob/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/source-map/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/source-map-js/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/source-map-support/node_modules/source-map/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/source-map-support/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/sourcemap-codec/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/speakingurl/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/statuses/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/stop-iteration-iterator/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/string.prototype.matchall/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/string.prototype.trim/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/string.prototype.trimend/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/string.prototype.trimstart/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/stringify-object/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/strip-comments/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/superjson/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/supports-preserve-symlinks-flag/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/temp-dir/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/tempy/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/terser/bin/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/terser/dist/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/terser/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/tinyglobby/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/toidentifier/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/tr46/node_modules/punycode/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/tr46/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/type-fest/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/type-is/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/typed-array-buffer/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/typed-array-byte-length/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/typed-array-byte-offset/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/typed-array-length/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/typescript/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/unbox-primitive/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/undici-types/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/unicode-canonical-property-names-ecmascript/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/unicode-match-property-ecmascript/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/unicode-match-property-value-ecmascript/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/unicode-property-aliases-ecmascript/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/unique-string/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/universalify/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/unpipe/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/upath/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/update-browserslist-db/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/url/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/util/node_modules/inherits/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/util/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/vary/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/vite/node_modules/rollup/dist/es/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/vite/node_modules/rollup/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/vite/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/vite/types/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/vite-plugin-pwa/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/vite-plugin-pwa/types/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/vscode-uri/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/vue/compiler-sfc/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/vue/jsx-runtime/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/vue/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/vue/server-renderer/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/vue-router/node_modules/@vue/devtools-api/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/vue-router/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/vue-tsc/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/webidl-conversions/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/whatwg-url/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/which/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/which-boxed-primitive/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/which-builtin-type/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/which-collection/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/which-typed-array/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-background-sync/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-broadcast-update/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-build/node_modules/pretty-bytes/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-build/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-cacheable-response/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-core/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-expiration/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-google-analytics/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-navigation-preload/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-precaching/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-range-requests/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-recipes/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-routing/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-strategies/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-streams/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-sw/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/workbox-window/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/wrappy/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/ws/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/yallist/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/zod/locales/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/zod/mini/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/zod/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/zod/v3/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/zod/v4/classic/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/zod/v4/core/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/zod/v4/locales/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/zod/v4/mini/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/zod/v4/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/zod/v4-mini/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/zod-to-json-schema/dist/cjs/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/zod-to-json-schema/dist/esm/package.json
/c/Users/jodar/agent-ui/frontend/node_modules/zod-to-json-schema/package.json
/c/Users/jodar/agent-ui/frontend/package.json
/c/Users/jodar/agent-ui/node_modules/ansi-regex/package.json
/c/Users/jodar/agent-ui/node_modules/ansi-styles/package.json
/c/Users/jodar/agent-ui/node_modules/chalk/node_modules/supports-color/package.json
/c/Users/jodar/agent-ui/node_modules/chalk/package.json
/c/Users/jodar/agent-ui/node_modules/cliui/package.json
/c/Users/jodar/agent-ui/node_modules/color-convert/package.json
/c/Users/jodar/agent-ui/node_modules/color-name/package.json
/c/Users/jodar/agent-ui/node_modules/concurrently/package.json
/c/Users/jodar/agent-ui/node_modules/emoji-regex/package.json
/c/Users/jodar/agent-ui/node_modules/escalade/package.json
/c/Users/jodar/agent-ui/node_modules/get-caller-file/package.json
/c/Users/jodar/agent-ui/node_modules/has-flag/package.json
/c/Users/jodar/agent-ui/node_modules/is-fullwidth-code-point/package.json
/c/Users/jodar/agent-ui/node_modules/require-directory/package.json
/c/Users/jodar/agent-ui/node_modules/rxjs/ajax/package.json
/c/Users/jodar/agent-ui/node_modules/rxjs/fetch/package.json
/c/Users/jodar/agent-ui/node_modules/rxjs/operators/package.json
/c/Users/jodar/agent-ui/node_modules/rxjs/package.json
/c/Users/jodar/agent-ui/node_modules/rxjs/testing/package.json
/c/Users/jodar/agent-ui/node_modules/rxjs/webSocket/package.json
/c/Users/jodar/agent-ui/node_modules/shell-quote/package.json
/c/Users/jodar/agent-ui/node_modules/string-width/package.json
/c/Users/jodar/agent-ui/node_modules/strip-ansi/package.json
/c/Users/jodar/agent-ui/node_modules/supports-color/package.json
/c/Users/jodar/agent-ui/node_modules/tree-kill/package.json
/c/Users/jodar/agent-ui/node_modules/tslib/modules/package.json
/c/Users/jodar/agent-ui/node_modules/tslib/package.json
/c/Users/jodar/agent-ui/node_modules/wrap-ansi/package.json
/c/Users/jodar/agent-ui/node_modules/y18n/package.json
/c/Users/jodar/agent-ui/node_modules/yargs/helpers/package.json
/c/Users/jodar/agent-ui/node_modules/yargs/package.json
/c/Users/jodar/agent-ui/node_modules/yargs-parser/package.json
/c/Users/jodar/agent-ui/package.json
/c/Users/jodar/agent-ui/server/node_modules/@skitee3000/bun-pty/package.json
/c/Users/jodar/agent-ui/server/node_modules/node-addon-api/package.json
/c/Users/jodar/agent-ui/server/package.json

View File

@@ -42,6 +42,29 @@
] ]
} }
], ],
"PostToolUseFailure": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "powershell -NoProfile -File hooks/forward-hook.ps1 nucleo000",
"timeout": 5000
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "powershell -NoProfile -File hooks/forward-hook.ps1 nucleo000",
"timeout": 5000
}
]
}
],
"SessionStart": [ "SessionStart": [
{ {
"hooks": [ "hooks": [

View File

@@ -87,7 +87,8 @@
"mcp__agent-ui__z590_nucleoriofrio_com-read_component", "mcp__agent-ui__z590_nucleoriofrio_com-read_component",
"mcp__agent-ui__z590_nucleoriofrio_com-edit_component", "mcp__agent-ui__z590_nucleoriofrio_com-edit_component",
"mcp__agent-ui__z590_nucleoriofrio_com-list_fs_components", "mcp__agent-ui__z590_nucleoriofrio_com-list_fs_components",
"mcp__agent-ui__z590_nucleoriofrio_com-load_fs_component" "mcp__agent-ui__z590_nucleoriofrio_com-load_fs_component",
"Bash(grep:*)"
] ]
}, },
"enableAllProjectMcpServers": true, "enableAllProjectMcpServers": true,
@@ -100,7 +101,7 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "powershell -NoProfile -Command \"try{$b=[Console]::In.ReadToEnd();Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-hook' -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3|Out-Null}catch{}\"", "command": "powershell -NoProfile -Command \"try{$b=[Console]::In.ReadToEnd();Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-hook?agent=claude' -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3|Out-Null}catch{}\"",
"timeout": 5000 "timeout": 5000
} }
] ]
@@ -112,7 +113,7 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "powershell -NoProfile -Command \"try{$b=[Console]::In.ReadToEnd();Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-hook' -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3|Out-Null}catch{}\"", "command": "powershell -NoProfile -Command \"try{$b=[Console]::In.ReadToEnd();Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-hook?agent=claude' -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3|Out-Null}catch{}\"",
"timeout": 5000 "timeout": 5000
} }
] ]
@@ -124,7 +125,30 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "powershell -NoProfile -Command \"try{$b=[Console]::In.ReadToEnd();Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-hook' -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3|Out-Null}catch{}\"", "command": "powershell -NoProfile -Command \"try{$b=[Console]::In.ReadToEnd();Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-hook?agent=claude' -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3|Out-Null}catch{}\"",
"timeout": 5000
}
]
}
],
"PostToolUseFailure": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "powershell -NoProfile -Command \"try{$b=[Console]::In.ReadToEnd();Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-hook?agent=claude' -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3|Out-Null}catch{}\"",
"timeout": 5000
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "powershell -NoProfile -Command \"try{$b=[Console]::In.ReadToEnd();Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-hook?agent=claude' -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3|Out-Null}catch{}\"",
"timeout": 5000 "timeout": 5000
} }
] ]
@@ -135,7 +159,7 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "powershell -NoProfile -Command \"try{$b=[Console]::In.ReadToEnd();Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-hook' -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3|Out-Null}catch{}\"", "command": "powershell -NoProfile -Command \"try{$b=[Console]::In.ReadToEnd();Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-hook?agent=claude' -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3|Out-Null}catch{}\"",
"timeout": 5000 "timeout": 5000
} }
] ]
@@ -147,7 +171,7 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "powershell -NoProfile -Command \"try{$b=[Console]::In.ReadToEnd();Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-hook' -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3|Out-Null}catch{}\"", "command": "powershell -NoProfile -Command \"try{$b=[Console]::In.ReadToEnd();Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-hook?agent=claude' -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3|Out-Null}catch{}\"",
"timeout": 5000 "timeout": 5000
} }
] ]
@@ -169,7 +193,7 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "powershell -NoProfile -Command \"try{$b=[Console]::In.ReadToEnd();Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-hook' -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3|Out-Null}catch{}\"", "command": "powershell -NoProfile -Command \"try{$b=[Console]::In.ReadToEnd();Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-hook?agent=claude' -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3|Out-Null}catch{}\"",
"timeout": 5000 "timeout": 5000
} }
] ]
@@ -180,7 +204,7 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "powershell -NoProfile -Command \"try{$b=[Console]::In.ReadToEnd();Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-hook' -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3|Out-Null}catch{}\"", "command": "powershell -NoProfile -Command \"try{$b=[Console]::In.ReadToEnd();Invoke-RestMethod -Uri 'http://localhost:4101/api/claude-hook?agent=claude' -Method POST -Body $b -ContentType 'application/json' -TimeoutSec 3|Out-Null}catch{}\"",
"timeout": 10000 "timeout": 10000
} }
] ]

View File

@@ -1,14 +1,43 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount } from 'vue' import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
import type { TerminalSlot } from '@/types/transcript-debug' import type { TerminalSlot } from '@/types/transcript-debug'
import { useSessionState, type AgentStatus } from '@/stores/session-state'
const props = defineProps<{ const props = defineProps<{
agent: string agent: string
connected: boolean connected: boolean
terminals: TerminalSlot[] terminals: TerminalSlot[]
activeSessionId: string | null activeSessionId: string | null
model?: string
version?: string
}>() }>()
const sessionStore = useSessionState()
const STATUS_COLORS: Record<AgentStatus, string> = {
idle: '#6b7280',
thinking: '#60a5fa',
reading: '#22d3ee',
writing: '#4ade80',
toolUse: '#fbbf24',
permissionRequest: '#fb923c',
interrupted: '#f87171',
error: '#f87171',
sessionStart: '#60a5fa',
sessionEnd: '#6b7280',
}
const agentStatusColor = computed(() => {
const state = sessionStore.agents[props.agent]
if (!state) return null
return STATUS_COLORS[state.status] || '#6b7280'
})
const agentStatusClass = computed(() => {
const state = sessionStore.agents[props.agent]
return state?.status || 'idle'
})
const activeIndex = computed(() => { const activeIndex = computed(() => {
if (!props.activeSessionId) return -1 if (!props.activeSessionId) return -1
return props.terminals.findIndex(t => t.sessionId === props.activeSessionId) return props.terminals.findIndex(t => t.sessionId === props.activeSessionId)
@@ -60,7 +89,8 @@ onBeforeUnmount(() => document.removeEventListener('mousedown', onClickOutside))
<template> <template>
<div ref="wrapperRef" class="agent-badge-wrapper" :class="{ connected }" @click="toggle"> <div ref="wrapperRef" class="agent-badge-wrapper" :class="{ connected }" @click="toggle">
<span v-if="activeSlot" class="state-dot badge-dot" :style="{ background: slotColor(activeSlot) }" /> <span v-if="agentStatusColor" class="state-dot status-dot" :class="agentStatusClass" :style="{ background: agentStatusColor }" />
<span v-else-if="activeSlot" class="state-dot badge-dot" :style="{ background: slotColor(activeSlot) }" />
<span class="agent-label">{{ agent }}</span> <span class="agent-label">{{ agent }}</span>
<span v-if="terminals.length" class="term-count">{{ activeIndex >= 0 ? `${activeIndex + 1}/${terminals.length}` : terminals.length }}</span> <span v-if="terminals.length" class="term-count">{{ activeIndex >= 0 ? `${activeIndex + 1}/${terminals.length}` : terminals.length }}</span>
<svg class="caret" :class="{ open: isOpen }" width="6" height="6" viewBox="0 0 6 6" shape-rendering="crispEdges"> <svg class="caret" :class="{ open: isOpen }" width="6" height="6" viewBox="0 0 6 6" shape-rendering="crispEdges">
@@ -70,6 +100,10 @@ onBeforeUnmount(() => document.removeEventListener('mousedown', onClickOutside))
</svg> </svg>
<Transition name="dropdown"> <Transition name="dropdown">
<div v-if="isOpen" class="dropdown"> <div v-if="isOpen" class="dropdown">
<div v-if="model || version" class="dropdown-meta">
<span v-if="model" class="meta-model">{{ model }}</span>
<span v-if="version" class="meta-version">v{{ version }}</span>
</div>
<div v-if="terminals.length === 0" class="dropdown-item empty">No terminals</div> <div v-if="terminals.length === 0" class="dropdown-item empty">No terminals</div>
<div <div
v-for="(t, idx) in terminals" v-for="(t, idx) in terminals"
@@ -136,12 +170,22 @@ onBeforeUnmount(() => document.removeEventListener('mousedown', onClickOutside))
color: #86efac; color: #86efac;
} }
.badge-dot { .badge-dot,
.status-dot {
width: 5px; width: 5px;
height: 5px; height: 5px;
flex-shrink: 0; flex-shrink: 0;
} }
.status-dot.thinking {
animation: pulse-badge 1.5s ease-in-out infinite;
}
@keyframes pulse-badge {
0%, 100% { opacity: 0.5; }
50% { opacity: 1; }
}
.term-count { .term-count {
font-size: 8px; font-size: 8px;
font-weight: 700; font-weight: 700;
@@ -188,6 +232,30 @@ onBeforeUnmount(() => document.removeEventListener('mousedown', onClickOutside))
padding: 3px 0; padding: 3px 0;
} }
.dropdown-meta {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 10px 3px;
border-bottom: 1px solid rgba(99, 102, 241, 0.1);
margin-bottom: 2px;
}
.meta-model {
font-size: 8px;
font-family: 'Courier New', monospace;
color: rgba(165, 180, 252, 0.7);
background: rgba(99, 102, 241, 0.12);
padding: 0 4px;
line-height: 14px;
}
.meta-version {
font-size: 8px;
font-family: 'Courier New', monospace;
color: rgba(255, 255, 255, 0.3);
}
.dropdown-item { .dropdown-item {
padding: 5px 10px; padding: 5px 10px;
font-size: 9px; font-size: 9px;

View File

@@ -10,6 +10,7 @@ import type {
SectionSummary SectionSummary
} from '@/types/transcript-debug' } from '@/types/transcript-debug'
import type { EphemeralTerminal } from '@/composables/useEphemeralTerminal' import type { EphemeralTerminal } from '@/composables/useEphemeralTerminal'
import { useSessionState, type AgentStatus } from '@/stores/session-state'
import UserMessageBubble from './UserMessageBubble.vue' import UserMessageBubble from './UserMessageBubble.vue'
import AssistantMessageBubble from './AssistantMessageBubble.vue' import AssistantMessageBubble from './AssistantMessageBubble.vue'
import ProgressEvent from './ProgressEvent.vue' import ProgressEvent from './ProgressEvent.vue'
@@ -21,7 +22,7 @@ import ResumeTerminalButton from './ResumeTerminalButton.vue'
const props = defineProps<{ const props = defineProps<{
conversation: ParsedConversation conversation: ParsedConversation
processing?: boolean processing?: boolean
terminalReady?: boolean terminalReady?: boolean | null
terminal?: EphemeralTerminal | null terminal?: EphemeralTerminal | null
showSelector?: boolean showSelector?: boolean
agents?: { id: AgentName; label: string }[] agents?: { id: AgentName; label: string }[]
@@ -44,6 +45,36 @@ const props = defineProps<{
hookPermissionMode?: string hookPermissionMode?: string
}>() }>()
// ── Agent status display ──
const sessionStore = useSessionState()
const STATUS_DISPLAY: Record<AgentStatus, { color: string; label: string }> = {
idle: { color: '#6b7280', label: 'Available' },
thinking: { color: '#60a5fa', label: 'Thinking...' },
reading: { color: '#22d3ee', label: 'Reading' },
writing: { color: '#4ade80', label: 'Writing' },
toolUse: { color: '#fbbf24', label: 'Using' },
permissionRequest: { color: '#fb923c', label: 'Waiting approval' },
interrupted: { color: '#f87171', label: 'Interrupted' },
error: { color: '#f87171', label: 'Error' },
sessionStart: { color: '#60a5fa', label: 'Starting' },
sessionEnd: { color: '#6b7280', label: 'Session ended' },
}
const agentStatus = computed(() => {
const agent = props.selectedAgent
if (!agent) return null
const state = sessionStore.agents[agent]
if (!state) return null
const display = STATUS_DISPLAY[state.status] || STATUS_DISPLAY.idle
const toolSuffix = state.currentTool ? ` ${state.currentTool.name}` : ''
return {
...display,
label: display.label + toolSuffix,
status: state.status,
}
})
// ── Derived display values ── // ── Derived display values ──
const permissionMode = computed(() => props.hookPermissionMode || '') const permissionMode = computed(() => props.hookPermissionMode || '')
const fullCwd = computed(() => props.conversation.metadata.cwd || '') const fullCwd = computed(() => props.conversation.metadata.cwd || '')
@@ -629,8 +660,12 @@ function formatDuration(start: string, end: string): string {
/> />
<div class="status-bar"> <div class="status-bar">
<span v-if="agentStatus" class="agent-status-indicator" :title="agentStatus.label">
<span class="agent-status-dot" :class="agentStatus.status" :style="{ background: agentStatus.color }" />
<span class="agent-status-label">{{ agentStatus.label }}</span>
</span>
<span v-if="permissionMode" class="meta-badge mode">{{ permissionMode }}</span> <span v-if="permissionMode" class="meta-badge mode">{{ permissionMode }}</span>
<span v-if="displayCwd" class="meta-badge origin" :title="fullCwd">{{ displayCwd }}</span> <span v-if="displayCwd" class="meta-badge origin" tabindex="0">{{ fullCwd }}</span>
<span class="meta-count">{{ conversation.messages.length }} msgs</span> <span class="meta-count">{{ conversation.messages.length }} msgs</span>
<button class="new-session-status-btn" @click="emit('createSession')" title="New session"> <button class="new-session-status-btn" @click="emit('createSession')" title="New session">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"> <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
@@ -888,9 +923,21 @@ function formatDuration(start: string, end: string): string {
.meta-badge.origin { .meta-badge.origin {
background: rgba(99, 102, 241, 0.1); background: rgba(99, 102, 241, 0.1);
color: var(--accent, #6366f1); color: var(--accent, #6366f1);
max-width: 120px; max-width: 80px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
direction: rtl;
text-align: left;
cursor: default;
transition: max-width 0.35s ease;
outline: none;
}
.meta-badge.origin:hover,
.meta-badge.origin:focus {
max-width: 400px;
direction: ltr;
background: rgba(99, 102, 241, 0.18);
} }
.meta-duration { .meta-duration {
@@ -1300,4 +1347,40 @@ function formatDuration(start: string, end: string): string {
flex-shrink: 0; flex-shrink: 0;
} }
/* ── Agent status indicator ── */
.agent-status-indicator {
display: inline-flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
}
.agent-status-dot {
width: 5px;
height: 5px;
flex-shrink: 0;
border-radius: 0;
transition: background 0.2s;
}
.agent-status-dot.thinking {
animation: pulse-status 1.5s ease-in-out infinite;
}
@keyframes pulse-status {
0%, 100% { opacity: 0.5; }
50% { opacity: 1; }
}
.agent-status-label {
font-size: 9px;
font-weight: 600;
font-family: 'Courier New', monospace;
color: var(--text-muted, rgba(255,255,255,0.4));
white-space: nowrap;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
}
</style> </style>

View File

@@ -4,7 +4,7 @@ import VoiceMicButton from './VoiceMicButton.vue'
const props = defineProps<{ const props = defineProps<{
processing?: boolean processing?: boolean
terminalReady?: boolean terminalReady?: boolean | null // null = no terminal, false = starting, true = ready
voiceTranscript?: string voiceTranscript?: string
isRecording?: boolean isRecording?: boolean
voiceMode?: 'web' | 'whisper' voiceMode?: 'web' | 'whisper'
@@ -25,12 +25,15 @@ const emit = defineEmits<{
const input = ref('') const input = ref('')
const notReady = computed(() => props.terminalReady === false) // terminalReady: null = no terminal (show "no terminal"), false = starting, true = ready
const isDisabled = computed(() => !input.value.trim() || props.processing || notReady.value) const terminalStarting = computed(() => props.terminalReady === false) // explicitly false, not null
const noTerminal = computed(() => props.terminalReady === null)
const canSend = computed(() => props.terminalReady === true && !props.processing)
const isDisabled = computed(() => !input.value.trim() || !canSend.value)
function handleSend() { function handleSend() {
const msg = input.value.trim() const msg = input.value.trim()
if (!msg || props.processing || notReady.value) return if (!msg || !canSend.value) return
emit('send', msg) emit('send', msg)
input.value = '' input.value = ''
} }
@@ -63,7 +66,7 @@ watch(() => props.voiceTranscript, (newText) => {
<template> <template>
<div class="user-input"> <div class="user-input">
<div v-if="notReady" class="processing-bar starting"> <div v-if="terminalStarting" class="processing-bar starting">
<span class="processing-dots"> <span class="processing-dots">
<span class="dot"></span><span class="dot"></span><span class="dot"></span> <span class="dot"></span><span class="dot"></span><span class="dot"></span>
</span> </span>
@@ -75,14 +78,14 @@ watch(() => props.voiceTranscript, (newText) => {
</span> </span>
<span>Agent is processing...</span> <span>Agent is processing...</span>
</div> </div>
<div class="input-container" :class="{ disabled: processing || notReady }"> <div class="input-container" :class="{ disabled: !canSend }">
<textarea <textarea
v-model="input" v-model="input"
class="input-field" class="input-field"
:style="{ maxHeight: maxH }" :style="{ maxHeight: maxH }"
:placeholder="notReady ? 'Starting terminal...' : processing ? 'Wait for agent to finish...' : 'Continue this conversation...'" :placeholder="terminalStarting ? 'Starting terminal...' : noTerminal ? 'No terminal — use + to create session' : processing ? 'Wait for agent to finish...' : 'Continue this conversation...'"
rows="1" rows="1"
:disabled="processing || notReady" :disabled="!canSend"
@keydown="handleKeydown" @keydown="handleKeydown"
/> />
<VoiceMicButton <VoiceMicButton
@@ -90,7 +93,7 @@ watch(() => props.voiceTranscript, (newText) => {
:is-recording="isRecording ?? false" :is-recording="isRecording ?? false"
:voice-mode="voiceMode" :voice-mode="voiceMode"
:whisper-status="whisperStatus ?? 'offline'" :whisper-status="whisperStatus ?? 'offline'"
:disabled="processing || notReady" :disabled="!canSend"
@start="emit('startRecording')" @start="emit('startRecording')"
@stop="emit('stopRecording')" @stop="emit('stopRecording')"
/> />

View File

@@ -19,9 +19,14 @@ const highlightedCommand = computed(() => highlightCode(command.value, 'bash'))
const expanded = ref(false) const expanded = ref(false)
const descPreview = computed(() => {
const d = description.value.trim()
return d.length > 50 ? d.slice(0, 50) + '...' : d
})
const cmdPreview = computed(() => { const cmdPreview = computed(() => {
const c = command.value.replace(/\n/g, ' ').trim() const c = command.value.replace(/\n/g, ' ').trim()
return c.length > 60 ? c.slice(0, 60) + '...' : c return c.length > 40 ? c.slice(0, 40) + '...' : c
}) })
</script> </script>
@@ -35,6 +40,7 @@ const cmdPreview = computed(() => {
</svg> </svg>
</span> </span>
<span class="card-label">Bash</span> <span class="card-label">Bash</span>
<span v-if="description" class="desc-preview" :title="description">{{ descPreview }}</span>
<code class="cmd-preview" :title="command">$ {{ cmdPreview }}</code> <code class="cmd-preview" :title="command">$ {{ cmdPreview }}</code>
<span v-if="runInBackground" class="info-badge">bg</span> <span v-if="runInBackground" class="info-badge">bg</span>
<span v-if="timeout" class="info-badge">{{ timeout }}ms</span> <span v-if="timeout" class="info-badge">{{ timeout }}ms</span>
@@ -90,7 +96,7 @@ const cmdPreview = computed(() => {
} }
.bash-card.error .card-label { color: rgba(239, 68, 68, 0.7); } .bash-card.error .card-label { color: rgba(239, 68, 68, 0.7); }
.cmd-preview { .desc-preview {
font-size: 11px; font-size: 11px;
font-family: 'SF Mono', 'Fira Code', monospace; font-family: 'SF Mono', 'Fira Code', monospace;
color: var(--text-secondary); color: var(--text-secondary);
@@ -101,6 +107,17 @@ const cmdPreview = computed(() => {
opacity: 0.7; opacity: 0.7;
} }
.cmd-preview {
font-size: 11px;
font-family: 'SF Mono', 'Fira Code', monospace;
color: var(--text-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
opacity: 0.4;
}
.info-badge, .error-badge { .info-badge, .error-badge {
font-size: 9px; font-size: 9px;
padding: 0.05rem 0.3rem; padding: 0.05rem 0.3rem;

View File

@@ -27,39 +27,14 @@ import type {
export function useTranscriptDebug() { export function useTranscriptDebug() {
// ── Centralized session state ── // ── Centralized session state ──
const sessionStore = useSessionState() const sessionStore = useSessionState()
// ── Persistence ──
const STORAGE_KEY = 'transcript-debug-selection'
function restoreState(): { agent: AgentName; sessionId: string | null } | null { const selectedAgent = ref<AgentName>('ejecutor')
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (!raw) return null
const data = JSON.parse(raw)
if (data.agent === 'ejecutor' || data.agent === 'nucleo000' || data.agent === 'claude') {
return { agent: data.agent, sessionId: data.sessionId || null }
}
} catch {}
return null
}
function saveState() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({
agent: selectedAgent.value,
sessionId: selectedSessionId.value
}))
} catch {}
}
const saved = restoreState()
const selectedAgent = ref<AgentName>(saved?.agent || 'ejecutor')
const sessions = ref<SessionInfo[]>([]) const sessions = ref<SessionInfo[]>([])
const selectedSessionId = ref<string | null>(saved?.sessionId || null) const selectedSessionId = ref<string | null>(null)
const rawContent = ref<string>('') const rawContent = ref<string>('')
const conversation = ref<ParsedConversation | null>(null) const conversation = ref<ParsedConversation | null>(null)
const loading = ref(false) const loading = ref(false)
const transitioning = ref(!!saved?.sessionId) const transitioning = ref(false)
const error = ref<string | null>(null) const error = ref<string | null>(null)
const isRealtime = ref(false) const isRealtime = ref(false)
@@ -92,7 +67,11 @@ export function useTranscriptDebug() {
return localTerminals.get(activeTerminalSessionId.value) ?? null return localTerminals.get(activeTerminalSessionId.value) ?? null
}) })
const terminalReady = computed(() => ephemeral.value?.state.value === 'running') // null = no terminal exists, false = terminal connecting/starting, true = running
const terminalReady = computed<boolean | null>(() => {
if (!ephemeral.value) return null
return ephemeral.value.state.value === 'running'
})
// Computed: terminal list for AgentBadge dropdown (from server registry) // Computed: terminal list for AgentBadge dropdown (from server registry)
const openTerminals = computed<TerminalSlot[]>(() => const openTerminals = computed<TerminalSlot[]>(() =>
@@ -276,7 +255,6 @@ export function useTranscriptDebug() {
// Load the target session's transcript // Load the target session's transcript
selectedSessionId.value = transcriptSessionId selectedSessionId.value = transcriptSessionId
saveState()
await fetchSessionContent(transcriptSessionId) await fetchSessionContent(transcriptSessionId)
// Connect to the terminal // Connect to the terminal
@@ -386,7 +364,6 @@ export function useTranscriptDebug() {
} }
selectedSessionId.value = changedSessionId selectedSessionId.value = changedSessionId
saveState()
await fetchSessionContent(changedSessionId) await fetchSessionContent(changedSessionId)
// Auto-send queued initial prompt // Auto-send queued initial prompt
@@ -449,7 +426,7 @@ export function useTranscriptDebug() {
// Clear optimistic processing if server state is now idle // Clear optimistic processing if server state is now idle
if (optimisticProcessing.value) { if (optimisticProcessing.value) {
const agentState = sessionStore.agents[selectedAgent.value] const agentState = sessionStore.agents[selectedAgent.value]
if (agentState && (agentState.status === 'idle' || agentState.status === 'sessionStart')) { if (agentState && ['idle', 'sessionStart', 'sessionEnd'].includes(agentState.status)) {
optimisticProcessing.value = false optimisticProcessing.value = false
} }
} }
@@ -498,13 +475,17 @@ export function useTranscriptDebug() {
if (optimisticProcessing.value) return true if (optimisticProcessing.value) return true
const agentState = sessionStore.agents[selectedAgent.value] const agentState = sessionStore.agents[selectedAgent.value]
if (!agentState) return false if (!agentState) return false
return agentState.status !== 'idle' && agentState.status !== 'sessionStart' return !['idle', 'sessionStart', 'sessionEnd'].includes(agentState.status)
}) })
async function sendPrompt(text: string) { async function sendPrompt(text: string) {
if (!text.trim() || !selectedSessionId.value) return if (!text.trim() || !selectedSessionId.value) return
if (!ephemeral.value || ephemeral.value.state.value !== 'running') { if (!ephemeral.value) {
error.value = 'No terminal — use + to create a new session'
return
}
if (ephemeral.value.state.value !== 'running') {
error.value = 'Terminal not ready — wait for it to start' error.value = 'Terminal not ready — wait for it to start'
return return
} }
@@ -557,38 +538,43 @@ export function useTranscriptDebug() {
} }
async function init() { async function init() {
await fetchSessions() // Derive initial state from server registry: if there's an alive terminal, sync to it
const aliveEntry = serverRegistry.value.find(e => e.alive)
let targetSession = selectedSessionId.value if (aliveEntry) {
selectedAgent.value = (aliveEntry.agent as AgentName) || 'ejecutor'
// Validate saved session still exists
if (targetSession && !sessions.value.some(s => s.id === targetSession)) {
targetSession = null
} }
// Fall back to newest await fetchSessions()
let targetSession: string | null = null
if (aliveEntry) {
// Sync to the alive terminal's transcript session
targetSession = aliveEntry.transcriptSessionId
// Validate it exists in sessions list
if (targetSession && !sessions.value.some(s => s.id === targetSession)) {
targetSession = null
}
}
// Fall back to newest session
if (!targetSession && sessions.value.length > 0) { if (!targetSession && sessions.value.length > 0) {
targetSession = sessions.value[0].id targetSession = sessions.value[0].id
} }
if (targetSession) { if (targetSession) {
selectedSessionId.value = targetSession selectedSessionId.value = targetSession
saveState()
await fetchSessionContent(targetSession) await fetchSessionContent(targetSession)
// Check if there's already a terminal for this session in the registry // Connect to existing terminal if one exists (clients never create terminals — only the server does)
const existing = serverRegistry.value.find( const existing = serverRegistry.value.find(
e => e.transcriptSessionId === targetSession e => e.transcriptSessionId === targetSession && e.alive
) )
if (existing && existing.alive) { if (existing) {
// Connect to existing terminal (may have been created by another client)
connectToTerminal(targetSession) connectToTerminal(targetSession)
} else {
startTerminal(targetSession)
} }
} else { } else {
selectedSessionId.value = null selectedSessionId.value = null
saveState()
} }
transitioning.value = false transitioning.value = false
@@ -617,20 +603,17 @@ export function useTranscriptDebug() {
selectedSessionId.value = sessions.value[0].id selectedSessionId.value = sessions.value[0].id
await fetchSessionContent(sessions.value[0].id) await fetchSessionContent(sessions.value[0].id)
// Check if there's already a terminal for this session // Connect to existing terminal if one exists (clients never create terminals)
const existing = serverRegistry.value.find( const existing = serverRegistry.value.find(
e => e.transcriptSessionId === sessions.value[0].id && e.alive e => e.transcriptSessionId === sessions.value[0].id && e.alive
) )
if (existing) { if (existing) {
connectToTerminal(sessions.value[0].id) connectToTerminal(sessions.value[0].id)
} else {
startTerminal(sessions.value[0].id)
} }
} else { } else {
selectedSessionId.value = null selectedSessionId.value = null
} }
saveState()
transitioning.value = false transitioning.value = false
loading.value = false loading.value = false
} }
@@ -647,20 +630,17 @@ export function useTranscriptDebug() {
await new Promise(r => setTimeout(r, 150)) await new Promise(r => setTimeout(r, 150))
selectedSessionId.value = sessionId selectedSessionId.value = sessionId
saveState()
await fetchSessionContent(sessionId) await fetchSessionContent(sessionId)
transitioning.value = false transitioning.value = false
loading.value = false loading.value = false
// Check if there's already a terminal for this session in the registry // Connect to existing terminal if one exists (clients never create terminals)
const existing = serverRegistry.value.find( const existing = serverRegistry.value.find(
e => e.transcriptSessionId === sessionId && e.alive e => e.transcriptSessionId === sessionId && e.alive
) )
if (existing) { if (existing) {
connectToTerminal(sessionId) connectToTerminal(sessionId)
} else {
startTerminal(sessionId)
} }
} }

View File

@@ -42,7 +42,10 @@ export interface EphemeralTerminal {
export function useEphemeralTerminal( export function useEphemeralTerminal(
command: string, command: string,
existingSessionId?: string existingSessionId?: string,
options?: {
onExtraMessage?: (msg: Record<string, any>) => void
}
): EphemeralTerminal { ): EphemeralTerminal {
const containerRef = ref<HTMLElement | null>(null) const containerRef = ref<HTMLElement | null>(null)
const connected = ref(false) const connected = ref(false)
@@ -141,6 +144,9 @@ export function useEphemeralTerminal(
case 'error': case 'error':
renderer.writeln(`\x1b[31mError: ${msg.message}\x1b[0m`) renderer.writeln(`\x1b[31mError: ${msg.message}\x1b[0m`)
break break
default:
options?.onExtraMessage?.(msg)
break
} }
} catch { /* ignore parse errors */ } } catch { /* ignore parse errors */ }
} }

View File

@@ -20,6 +20,7 @@ const {
processing, processing,
ephemeral, ephemeral,
terminalReady, terminalReady,
hookMeta,
init, init,
switchAgent, switchAgent,
selectSession, selectSession,
@@ -144,6 +145,7 @@ onUnmounted(() => {
:voice-transcript="voiceTranscript + voiceInterim" :voice-transcript="voiceTranscript + voiceInterim"
:last-audio-url="lastAudioUrl" :last-audio-url="lastAudioUrl"
:is-playing-audio="isPlayingAudio" :is-playing-audio="isPlayingAudio"
:hook-permission-mode="hookMeta.permissionMode"
@send="handleSend" @send="handleSend"
@create-session="handleCreateSession" @create-session="handleCreateSession"
@start-recording="voice.startRecording()" @start-recording="voice.startRecording()"

View File

@@ -5,14 +5,15 @@ import { ref, computed } from 'vue'
export type AgentStatus = export type AgentStatus =
| 'idle' | 'idle'
| 'processing' | 'thinking'
| 'reading' | 'reading'
| 'writing' | 'writing'
| 'toolUse' | 'toolUse'
| 'toolDone'
| 'permissionRequest' | 'permissionRequest'
| 'notification' | 'interrupted'
| 'error'
| 'sessionStart' | 'sessionStart'
| 'sessionEnd'
export interface ActiveTool { export interface ActiveTool {
name: string name: string
@@ -48,6 +49,13 @@ export interface AgentTerminalInfo {
connectedClients: number connectedClients: number
} }
export interface LastError {
tool: string
message: string
interrupted: boolean
timestamp: number
}
export interface AgentSessionState { export interface AgentSessionState {
agent: string agent: string
sessionId: string | null sessionId: string | null
@@ -59,6 +67,7 @@ export interface AgentSessionState {
currentTool: ActiveTool | null currentTool: ActiveTool | null
lastActivity: number lastActivity: number
lastStopResponse: string | null lastStopResponse: string | null
lastError: LastError | null
pendingApprovals: PendingApproval[] pendingApprovals: PendingApproval[]
terminal: AgentTerminalInfo terminal: AgentTerminalInfo
notifications: SessionNotification[] notifications: SessionNotification[]
@@ -130,6 +139,14 @@ export const useSessionState = defineStore('session-state', () => {
) )
) )
const isProcessing = computed(() =>
agentList.value.some(a => !['idle', 'sessionStart', 'sessionEnd'].includes(a.status))
)
const hasErrors = computed(() =>
agentList.value.some(a => a.status === 'error' || a.status === 'interrupted')
)
const visibleNotifications = computed(() => { const visibleNotifications = computed(() => {
const now = Date.now() const now = Date.now()
return agentList.value return agentList.value
@@ -199,6 +216,8 @@ export const useSessionState = defineStore('session-state', () => {
anyPendingApprovals, anyPendingApprovals,
totalPendingApprovals, totalPendingApprovals,
allPendingApprovals, allPendingApprovals,
isProcessing,
hasErrors,
visibleNotifications, visibleNotifications,
handleMessage, handleMessage,
respondApproval, respondApproval,

View File

@@ -2,56 +2,7 @@ import { jsonResponse, errorResponse } from '../utils/cors'
import { PORT_TERMINAL } from '../config' import { PORT_TERMINAL } from '../config'
import { existsSync, readFileSync } from 'fs' import { existsSync, readFileSync } from 'fs'
import { setActiveSession, getIncrementalMessages } from '../services/transcript-engine' import { setActiveSession, getIncrementalMessages } from '../services/transcript-engine'
import { deriveStatus, type HookPayload } from '../services/session-state'
type ClaudeStatus = 'idle' | 'processing' | 'toolUse' | 'toolDone' | 'reading' | 'writing' | 'sessionStart' | 'subagentStart' | 'subagentStop' | 'notification' | 'permissionRequest' | 'thinking'
interface HookPayload {
hook_event_name?: string
session_id?: string
tool_name?: string
tool_input?: unknown
tool_response?: unknown
prompt?: string
cwd?: string
model?: string
source?: string
transcript_path?: string
message?: string
notification_type?: string
stop_hook_active?: boolean
tool_use_id?: string
[key: string]: unknown
}
function deriveStatus(payload: HookPayload): { status: ClaudeStatus, tool?: string } {
const event = payload.hook_event_name
const toolName = payload.tool_name
switch (event) {
case 'SessionStart':
return { status: 'sessionStart' }
case 'UserPromptSubmit':
return { status: 'processing' }
case 'PreToolUse':
if (toolName && /^(Read|Glob|Grep)$/.test(toolName)) {
return { status: 'reading', tool: toolName }
}
if (toolName && /^(Edit|Write)$/.test(toolName)) {
return { status: 'writing', tool: toolName }
}
return { status: 'toolUse', tool: toolName }
case 'PostToolUse':
return { status: 'toolDone', tool: toolName }
case 'PermissionRequest':
return { status: 'permissionRequest', tool: toolName }
case 'Notification':
return { status: 'notification' }
case 'Stop':
return { status: 'idle' }
default:
return { status: 'processing' }
}
}
export async function handleClaudeHook(req: Request): Promise<Response | null> { export async function handleClaudeHook(req: Request): Promise<Response | null> {
if (req.method !== 'POST') return null if (req.method !== 'POST') return null
@@ -121,15 +72,17 @@ export async function handleClaudeHook(req: Request): Promise<Response | null> {
} }
// 3. Derive status and broadcast via WebSocket // 3. Derive status and broadcast via WebSocket
const { status, tool } = deriveStatus(body) const derived = deriveStatus(body)
try { if (derived) {
await fetch(`http://localhost:${PORT_TERMINAL}/claude-status`, { try {
method: 'POST', await fetch(`http://localhost:${PORT_TERMINAL}/claude-status`, {
headers: { 'Content-Type': 'application/json' }, method: 'POST',
body: JSON.stringify({ status, tool, agent: agent || 'main' }) headers: { 'Content-Type': 'application/json' },
}) body: JSON.stringify({ status: derived.status, tool: derived.tool, agent: agent || 'main' })
} catch (e) { })
console.error('[claude-hook] Failed to forward status to terminal server:', e) } catch (e) {
console.error('[claude-hook] Failed to forward status to terminal server:', e)
}
} }
// 4. Incremental transcript reading for real-time chat // 4. Incremental transcript reading for real-time chat

View File

@@ -3,17 +3,15 @@ import { PORT_TERMINAL } from '../config'
type ClaudeStatus = type ClaudeStatus =
| 'idle' | 'idle'
| 'processing' // UserPromptSubmit - Claude is processing user input | 'thinking' // UserPromptSubmit / PostToolUse - Claude is thinking
| 'toolUse' // PreToolUse - Using a tool (generic) | 'toolUse' // PreToolUse - Using a tool (generic)
| 'toolDone' // PostToolUse - Tool finished | 'reading' // PreToolUse(Read/Glob/Grep) - Reading files
| 'reading' // PreToolUse(Read/Glob/Grep) - Reading files | 'writing' // PreToolUse(Edit/Write) - Writing files
| 'writing' // PreToolUse(Edit/Write) - Writing files | 'sessionStart' // SessionStart - Session just started
| 'sessionStart' // SessionStart - Session just started | 'sessionEnd' // SessionEnd - Session ended
| 'subagentStart' // SubagentStart - Spawning subagent | 'permissionRequest' // PermissionRequest - Waiting for user permission
| 'subagentStop' // SubagentStop - Subagent finished | 'interrupted' // PostToolUseFailure with is_interrupt
| 'notification' // Notification - Claude sent notification | 'error' // PostToolUseFailure without is_interrupt
| 'permissionRequest' // PermissionRequest - Waiting for user permission
| 'thinking' // Legacy support
interface ClaudeStatusPayload { interface ClaudeStatusPayload {
status: ClaudeStatus status: ClaudeStatus
@@ -28,9 +26,9 @@ export async function handleClaudeStatus(req: Request): Promise<Response | null>
const body = await req.json() as ClaudeStatusPayload const body = await req.json() as ClaudeStatusPayload
const validStatuses: ClaudeStatus[] = [ const validStatuses: ClaudeStatus[] = [
'idle', 'processing', 'toolUse', 'toolDone', 'reading', 'writing', 'idle', 'thinking', 'toolUse', 'reading', 'writing',
'sessionStart', 'subagentStart', 'subagentStop', 'notification', 'sessionStart', 'sessionEnd', 'permissionRequest',
'permissionRequest', 'thinking' 'interrupted', 'error'
] ]
if (!body.status || !validStatuses.includes(body.status)) { if (!body.status || !validStatuses.includes(body.status)) {
return errorResponse(`Invalid status. Must be one of: ${validStatuses.join(', ')}`, 400) return errorResponse(`Invalid status. Must be one of: ${validStatuses.join(', ')}`, 400)

View File

@@ -6,14 +6,15 @@
export type AgentStatus = export type AgentStatus =
| 'idle' | 'idle'
| 'processing' | 'thinking'
| 'reading' | 'reading'
| 'writing' | 'writing'
| 'toolUse' | 'toolUse'
| 'toolDone'
| 'permissionRequest' | 'permissionRequest'
| 'notification' | 'interrupted'
| 'error'
| 'sessionStart' | 'sessionStart'
| 'sessionEnd'
export interface ActiveTool { export interface ActiveTool {
name: string name: string
@@ -49,6 +50,13 @@ export interface AgentTerminalInfo {
connectedClients: number connectedClients: number
} }
export interface LastError {
tool: string
message: string
interrupted: boolean
timestamp: number
}
export interface AgentSessionState { export interface AgentSessionState {
agent: string agent: string
sessionId: string | null sessionId: string | null
@@ -60,6 +68,7 @@ export interface AgentSessionState {
currentTool: ActiveTool | null currentTool: ActiveTool | null
lastActivity: number lastActivity: number
lastStopResponse: string | null lastStopResponse: string | null
lastError: LastError | null
pendingApprovals: PendingApproval[] pendingApprovals: PendingApproval[]
terminal: AgentTerminalInfo terminal: AgentTerminalInfo
notifications: SessionNotification[] notifications: SessionNotification[]
@@ -82,6 +91,9 @@ export interface HookPayload {
tool_use_id?: string tool_use_id?: string
assistant_response?: string assistant_response?: string
agent_name?: string agent_name?: string
is_interrupt?: boolean
error?: string
reason?: string
[key: string]: unknown [key: string]: unknown
} }
@@ -107,9 +119,11 @@ const TTL_WARNING = 5000
const NOTIFICATION_MAP: Record<string, { title: string, type: SessionNotification['type'], persistent?: boolean }> = { const NOTIFICATION_MAP: Record<string, { title: string, type: SessionNotification['type'], persistent?: boolean }> = {
SessionStart: { title: 'Session started', type: 'info' }, SessionStart: { title: 'Session started', type: 'info' },
SessionEnd: { title: 'Session ended', type: 'info' },
UserPromptSubmit: { title: 'Processing prompt', type: 'info' }, UserPromptSubmit: { title: 'Processing prompt', type: 'info' },
PreToolUse: { title: 'Using tool', type: 'info' }, PreToolUse: { title: 'Using tool', type: 'info' },
PostToolUse: { title: 'Tool complete', type: 'success' }, PostToolUse: { title: 'Tool complete', type: 'success' },
PostToolUseFailure: { title: 'Tool failed', type: 'error' },
PermissionRequest: { title: 'Permission required', type: 'warning', persistent: true }, PermissionRequest: { title: 'Permission required', type: 'warning', persistent: true },
Notification: { title: 'Notification', type: 'info' }, Notification: { title: 'Notification', type: 'info' },
Stop: { title: 'Session stopped', type: 'info' }, Stop: { title: 'Session stopped', type: 'info' },
@@ -149,15 +163,17 @@ function buildNotification(payload: HookPayload): SessionNotification {
// ── Status derivation (moved from claude-hook.ts) ── // ── Status derivation (moved from claude-hook.ts) ──
export function deriveStatus(payload: HookPayload): { status: AgentStatus, tool?: string } { export function deriveStatus(payload: HookPayload): { status: AgentStatus, tool?: string } | null {
const event = payload.hook_event_name const event = payload.hook_event_name
const toolName = payload.tool_name const toolName = payload.tool_name
switch (event) { switch (event) {
case 'SessionStart': case 'SessionStart':
return { status: 'sessionStart' } return { status: 'sessionStart' }
case 'SessionEnd':
return { status: 'sessionEnd' }
case 'UserPromptSubmit': case 'UserPromptSubmit':
return { status: 'processing' } return { status: 'thinking' }
case 'PreToolUse': case 'PreToolUse':
if (toolName && /^(Read|Glob|Grep)$/.test(toolName)) if (toolName && /^(Read|Glob|Grep)$/.test(toolName))
return { status: 'reading', tool: toolName } return { status: 'reading', tool: toolName }
@@ -165,15 +181,18 @@ export function deriveStatus(payload: HookPayload): { status: AgentStatus, tool?
return { status: 'writing', tool: toolName } return { status: 'writing', tool: toolName }
return { status: 'toolUse', tool: toolName } return { status: 'toolUse', tool: toolName }
case 'PostToolUse': case 'PostToolUse':
return { status: 'toolDone', tool: toolName } return { status: 'thinking', tool: toolName }
case 'PostToolUseFailure':
if (payload.is_interrupt) return { status: 'interrupted', tool: toolName }
return { status: 'error', tool: toolName }
case 'PermissionRequest': case 'PermissionRequest':
return { status: 'permissionRequest', tool: toolName } return { status: 'permissionRequest', tool: toolName }
case 'Notification': case 'Notification':
return { status: 'notification' } return null // Side-effect only, does not change agent status
case 'Stop': case 'Stop':
return { status: 'idle' } return { status: 'idle' }
default: default:
return { status: 'processing' } return { status: 'thinking' }
} }
} }
@@ -191,6 +210,7 @@ function createDefaultState(agent: string): AgentSessionState {
currentTool: null, currentTool: null,
lastActivity: Date.now(), lastActivity: Date.now(),
lastStopResponse: null, lastStopResponse: null,
lastError: null,
pendingApprovals: [], pendingApprovals: [],
terminal: { terminal: {
ptySessionId: null, ptySessionId: null,
@@ -220,15 +240,52 @@ class SessionStateManager {
const state = this.getOrCreateAgent(agentName) const state = this.getOrCreateAgent(agentName)
const now = Date.now() const now = Date.now()
// Derive status // Derive status (null means side-effect only, e.g. Notification)
const { status, tool } = deriveStatus(payload) const derived = deriveStatus(payload)
// Build patch // Build patch
const patch: Partial<AgentSessionState> = { const patch: Partial<AgentSessionState> = {
status,
lastActivity: now, lastActivity: now,
} }
// Only update status/tool if deriveStatus returned a result
if (derived) {
const { status, tool } = derived
patch.status = status
// Current tool tracking
if (status === 'toolUse' || status === 'reading' || status === 'writing' || status === 'permissionRequest') {
patch.currentTool = {
name: payload.tool_name || 'unknown',
input: payload.tool_input,
startedAt: now,
}
} else if (status === 'thinking' || status === 'idle' || status === 'sessionEnd') {
patch.currentTool = null
}
// Track errors from PostToolUseFailure
if (status === 'error' || status === 'interrupted') {
patch.lastError = {
tool: payload.tool_name || 'unknown',
message: (payload.error as string) || '',
interrupted: status === 'interrupted',
timestamp: now,
}
}
// Clear lastError on successful flow resumption
if (status === 'thinking' || status === 'reading' || status === 'writing' || status === 'toolUse') {
patch.lastError = null
}
// SessionEnd: clean up session state
if (payload.hook_event_name === 'SessionEnd') {
patch.currentTool = null
patch.pendingApprovals = []
}
}
// Update session identity fields // Update session identity fields
if (payload.session_id) patch.sessionId = payload.session_id if (payload.session_id) patch.sessionId = payload.session_id
if (payload.model) patch.model = payload.model as string if (payload.model) patch.model = payload.model as string
@@ -236,17 +293,6 @@ class SessionStateManager {
if (payload.transcript_path) patch.transcriptPath = payload.transcript_path as string if (payload.transcript_path) patch.transcriptPath = payload.transcript_path as string
if (payload.permission_mode) patch.permissionMode = payload.permission_mode as string if (payload.permission_mode) patch.permissionMode = payload.permission_mode as string
// Current tool tracking
if (status === 'toolUse' || status === 'reading' || status === 'writing' || status === 'permissionRequest') {
patch.currentTool = {
name: payload.tool_name || 'unknown',
input: payload.tool_input,
startedAt: now,
}
} else if (status === 'toolDone' || status === 'idle' || status === 'processing') {
patch.currentTool = null
}
// Last stop response // Last stop response
if (payload.hook_event_name === 'Stop' && payload.assistant_response) { if (payload.hook_event_name === 'Stop' && payload.assistant_response) {
patch.lastStopResponse = payload.assistant_response as string patch.lastStopResponse = payload.assistant_response as string
@@ -255,6 +301,7 @@ class SessionStateManager {
// Reset session fields on SessionStart // Reset session fields on SessionStart
if (payload.hook_event_name === 'SessionStart') { if (payload.hook_event_name === 'SessionStart') {
patch.lastStopResponse = null patch.lastStopResponse = null
patch.lastError = null
patch.pendingApprovals = [] patch.pendingApprovals = []
} }
@@ -290,9 +337,9 @@ class SessionStateManager {
const idx = state.pendingApprovals.findIndex(a => a.requestId === requestId) const idx = state.pendingApprovals.findIndex(a => a.requestId === requestId)
if (idx !== -1) { if (idx !== -1) {
state.pendingApprovals = state.pendingApprovals.filter(a => a.requestId !== requestId) state.pendingApprovals = state.pendingApprovals.filter(a => a.requestId !== requestId)
// If no more pending approvals, go back to processing // If no more pending approvals, go back to thinking
if (state.pendingApprovals.length === 0 && state.status === 'permissionRequest') { if (state.pendingApprovals.length === 0 && state.status === 'permissionRequest') {
state.status = 'processing' state.status = 'thinking'
} }
state.lastActivity = Date.now() state.lastActivity = Date.now()
return { return {

View File

@@ -608,19 +608,25 @@ export function startTerminalServer() {
} }
// Claude status types // Claude status types
type ClaudeStatus = 'idle' | 'processing' | 'toolUse' | 'toolDone' | 'reading' | 'writing' | 'sessionStart' | 'subagentStart' | 'subagentStop' | 'notification' | 'permissionRequest' | 'thinking' type ClaudeStatus = 'idle' | 'thinking' | 'toolUse' | 'reading' | 'writing' | 'sessionStart' | 'sessionEnd' | 'permissionRequest' | 'interrupted' | 'error'
// Broadcast Claude status to ALL clients across ALL sessions // Broadcast Claude status to ALL clients across ALL sessions
export function broadcastClaudeStatus(status: ClaudeStatus, tool?: string, agent?: string) { export function broadcastClaudeStatus(status: ClaudeStatus, tool?: string, agent?: string) {
const agentName = agent || 'main' const agentName = agent || 'main'
// Track agent running state from sessionStart // Track agent running state
if (status === 'sessionStart') { if (status === 'sessionStart') {
const state = agentSessions.get(agentName) const state = agentSessions.get(agentName)
if (state) { if (state) {
state.isAgentRunning = true state.isAgentRunning = true
console.log(`[Terminal] Agent ${agentName} marked as running (sessionStart)`) console.log(`[Terminal] Agent ${agentName} marked as running (sessionStart)`)
} }
} else if (status === 'sessionEnd') {
const state = agentSessions.get(agentName)
if (state) {
state.isAgentRunning = false
console.log(`[Terminal] Agent ${agentName} marked as stopped (sessionEnd)`)
}
} }
const message = JSON.stringify({ const message = JSON.stringify({