@provos/ironcurtain 0.7.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -100
- package/dist/auth/auth-command.d.ts +15 -0
- package/dist/auth/auth-command.js +424 -0
- package/dist/auth/auth-command.js.map +1 -0
- package/dist/auth/oauth-flow.d.ts +46 -0
- package/dist/auth/oauth-flow.js +279 -0
- package/dist/auth/oauth-flow.js.map +1 -0
- package/dist/auth/oauth-provider.d.ts +55 -0
- package/dist/auth/oauth-provider.js +73 -0
- package/dist/auth/oauth-provider.js.map +1 -0
- package/dist/auth/oauth-registry.d.ts +34 -0
- package/dist/auth/oauth-registry.js +69 -0
- package/dist/auth/oauth-registry.js.map +1 -0
- package/dist/auth/oauth-token-provider.d.ts +52 -0
- package/dist/auth/oauth-token-provider.js +159 -0
- package/dist/auth/oauth-token-provider.js.map +1 -0
- package/dist/auth/oauth-token-store.d.ts +30 -0
- package/dist/auth/oauth-token-store.js +93 -0
- package/dist/auth/oauth-token-store.js.map +1 -0
- package/dist/auth/providers/google-scopes.d.ts +35 -0
- package/dist/auth/providers/google-scopes.js +183 -0
- package/dist/auth/providers/google-scopes.js.map +1 -0
- package/dist/auth/providers/google.d.ts +2 -0
- package/dist/auth/providers/google.js +28 -0
- package/dist/auth/providers/google.js.map +1 -0
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/dist/config/config-command.js +39 -1
- package/dist/config/config-command.js.map +1 -1
- package/dist/config/constitution-readonly.md +14 -11
- package/dist/config/constitution-user-base.md +15 -0
- package/dist/config/first-start.js +4 -1
- package/dist/config/first-start.js.map +1 -1
- package/dist/config/generated/test-scenarios.json +1 -1
- package/dist/config/generated/tool-annotations.json +10617 -296
- package/dist/config/generated-readonly/compiled-policy.json +787 -244
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +8 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/mcp-servers.json +31 -2
- package/dist/config/paths.d.ts +20 -0
- package/dist/config/paths.js +43 -0
- package/dist/config/paths.js.map +1 -1
- package/dist/config/types.d.ts +8 -0
- package/dist/config/user-config.d.ts +29 -0
- package/dist/config/user-config.js +30 -0
- package/dist/config/user-config.js.map +1 -1
- package/dist/cron/constitution-generator.d.ts +2 -1
- package/dist/cron/constitution-generator.js +35 -36
- package/dist/cron/constitution-generator.js.map +1 -1
- package/dist/cron/headless-transport.d.ts +4 -0
- package/dist/cron/headless-transport.js +1 -1
- package/dist/cron/headless-transport.js.map +1 -1
- package/dist/daemon/ironcurtain-daemon.js +7 -1
- package/dist/daemon/ironcurtain-daemon.js.map +1 -1
- package/dist/docker/adapters/claude-code.js +32 -5
- package/dist/docker/adapters/claude-code.js.map +1 -1
- package/dist/docker/agent-adapter.d.ts +40 -0
- package/dist/docker/claude-md-seed.d.ts +27 -0
- package/dist/docker/claude-md-seed.js +42 -0
- package/dist/docker/claude-md-seed.js.map +1 -0
- package/dist/docker/docker-agent-session.d.ts +3 -1
- package/dist/docker/docker-agent-session.js +51 -28
- package/dist/docker/docker-agent-session.js.map +1 -1
- package/dist/docker/docker-infrastructure.d.ts +15 -1
- package/dist/docker/docker-infrastructure.js +105 -9
- package/dist/docker/docker-infrastructure.js.map +1 -1
- package/dist/docker/docker-manager.d.ts +2 -0
- package/dist/docker/docker-manager.js +9 -1
- package/dist/docker/docker-manager.js.map +1 -1
- package/dist/docker/mitm-proxy.d.ts +65 -0
- package/dist/docker/mitm-proxy.js +288 -31
- package/dist/docker/mitm-proxy.js.map +1 -1
- package/dist/docker/package-types.d.ts +127 -0
- package/dist/docker/package-types.js +8 -0
- package/dist/docker/package-types.js.map +1 -0
- package/dist/docker/package-validator.d.ts +41 -0
- package/dist/docker/package-validator.js +90 -0
- package/dist/docker/package-validator.js.map +1 -0
- package/dist/docker/platform.d.ts +6 -6
- package/dist/docker/platform.js +8 -6
- package/dist/docker/platform.js.map +1 -1
- package/dist/docker/proxy-tools.d.ts +80 -0
- package/dist/docker/proxy-tools.js +248 -0
- package/dist/docker/proxy-tools.js.map +1 -0
- package/dist/docker/pty-session.d.ts +15 -0
- package/dist/docker/pty-session.js +253 -73
- package/dist/docker/pty-session.js.map +1 -1
- package/dist/docker/pty-types.d.ts +28 -0
- package/dist/docker/pty-types.js +2 -0
- package/dist/docker/pty-types.js.map +1 -1
- package/dist/docker/registry-proxy.d.ts +142 -0
- package/dist/docker/registry-proxy.js +856 -0
- package/dist/docker/registry-proxy.js.map +1 -0
- package/dist/escalation/escalation-watcher.d.ts +3 -1
- package/dist/escalation/escalation-watcher.js +6 -2
- package/dist/escalation/escalation-watcher.js.map +1 -1
- package/dist/escalation/listener-command.js +24 -10
- package/dist/escalation/listener-command.js.map +1 -1
- package/dist/escalation/listener-lock.d.ts +5 -0
- package/dist/escalation/listener-lock.js +14 -0
- package/dist/escalation/listener-lock.js.map +1 -1
- package/dist/index.js +16 -3
- package/dist/index.js.map +1 -1
- package/dist/memory/auto-save.d.ts +25 -0
- package/dist/memory/auto-save.js +89 -0
- package/dist/memory/auto-save.js.map +1 -0
- package/dist/memory/memory-annotations.d.ts +47 -0
- package/dist/memory/memory-annotations.js +87 -0
- package/dist/memory/memory-annotations.js.map +1 -0
- package/dist/memory/memory-prompt.d.ts +12 -0
- package/dist/memory/memory-prompt.js +20 -0
- package/dist/memory/memory-prompt.js.map +1 -0
- package/dist/memory/resolve-memory-path.d.ts +20 -0
- package/dist/memory/resolve-memory-path.js +30 -0
- package/dist/memory/resolve-memory-path.js.map +1 -0
- package/dist/mux/mux-app.d.ts +4 -0
- package/dist/mux/mux-app.js +246 -28
- package/dist/mux/mux-app.js.map +1 -1
- package/dist/mux/mux-command.d.ts +2 -2
- package/dist/mux/mux-command.js +14 -22
- package/dist/mux/mux-command.js.map +1 -1
- package/dist/mux/mux-escalation-manager.d.ts +9 -3
- package/dist/mux/mux-escalation-manager.js +69 -19
- package/dist/mux/mux-escalation-manager.js.map +1 -1
- package/dist/mux/mux-input-handler.d.ts +59 -3
- package/dist/mux/mux-input-handler.js +270 -32
- package/dist/mux/mux-input-handler.js.map +1 -1
- package/dist/mux/mux-renderer.d.ts +19 -1
- package/dist/mux/mux-renderer.js +466 -11
- package/dist/mux/mux-renderer.js.map +1 -1
- package/dist/mux/persona-scanner.d.ts +19 -0
- package/dist/mux/persona-scanner.js +53 -0
- package/dist/mux/persona-scanner.js.map +1 -0
- package/dist/mux/pty-bridge.d.ts +8 -0
- package/dist/mux/pty-bridge.js +19 -1
- package/dist/mux/pty-bridge.js.map +1 -1
- package/dist/mux/session-scanner.d.ts +18 -0
- package/dist/mux/session-scanner.js +65 -0
- package/dist/mux/session-scanner.js.map +1 -0
- package/dist/mux/types.d.ts +36 -1
- package/dist/mux/types.js +14 -1
- package/dist/mux/types.js.map +1 -1
- package/dist/persona/persona-command.js +130 -93
- package/dist/persona/persona-command.js.map +1 -1
- package/dist/persona/persona-prompt.d.ts +7 -7
- package/dist/persona/persona-prompt.js +14 -29
- package/dist/persona/persona-prompt.js.map +1 -1
- package/dist/persona/resolve.js +5 -2
- package/dist/persona/resolve.js.map +1 -1
- package/dist/pipeline/annotate.js +1 -7
- package/dist/pipeline/annotate.js.map +1 -1
- package/dist/pipeline/compile.d.ts +5 -2
- package/dist/pipeline/compile.js +23 -11
- package/dist/pipeline/compile.js.map +1 -1
- package/dist/pipeline/constitution-compiler.d.ts +302 -3
- package/dist/pipeline/constitution-compiler.js +285 -23
- package/dist/pipeline/constitution-compiler.js.map +1 -1
- package/dist/pipeline/generate-with-repair.d.ts +2 -2
- package/dist/pipeline/generate-with-repair.js +2 -2
- package/dist/pipeline/handwritten-scenarios.d.ts +20 -7
- package/dist/pipeline/handwritten-scenarios.js +20 -41
- package/dist/pipeline/handwritten-scenarios.js.map +1 -1
- package/dist/pipeline/list-resolver.d.ts +18 -20
- package/dist/pipeline/list-resolver.js +28 -36
- package/dist/pipeline/list-resolver.js.map +1 -1
- package/dist/pipeline/pipeline-runner.d.ts +102 -13
- package/dist/pipeline/pipeline-runner.js +497 -237
- package/dist/pipeline/pipeline-runner.js.map +1 -1
- package/dist/pipeline/pipeline-shared.d.ts +7 -9
- package/dist/pipeline/pipeline-shared.js +8 -18
- package/dist/pipeline/pipeline-shared.js.map +1 -1
- package/dist/pipeline/policy-verifier.d.ts +32 -5
- package/dist/pipeline/policy-verifier.js +137 -8
- package/dist/pipeline/policy-verifier.js.map +1 -1
- package/dist/pipeline/proxy-mcp-connections.d.ts +25 -0
- package/dist/pipeline/proxy-mcp-connections.js +121 -0
- package/dist/pipeline/proxy-mcp-connections.js.map +1 -0
- package/dist/pipeline/refresh-lists.js +21 -13
- package/dist/pipeline/refresh-lists.js.map +1 -1
- package/dist/pipeline/scenario-generator.d.ts +29 -49
- package/dist/pipeline/scenario-generator.js +180 -143
- package/dist/pipeline/scenario-generator.js.map +1 -1
- package/dist/pipeline/scenario-schema-validator.d.ts +51 -0
- package/dist/pipeline/scenario-schema-validator.js +132 -0
- package/dist/pipeline/scenario-schema-validator.js.map +1 -0
- package/dist/pipeline/tool-annotator.d.ts +9 -8
- package/dist/pipeline/tool-annotator.js +48 -95
- package/dist/pipeline/tool-annotator.js.map +1 -1
- package/dist/pipeline/types.d.ts +31 -24
- package/dist/sandbox/index.d.ts +26 -10
- package/dist/sandbox/index.js +151 -40
- package/dist/sandbox/index.js.map +1 -1
- package/dist/servers/fetch-server.js +10 -5
- package/dist/servers/fetch-server.js.map +1 -1
- package/dist/session/agent-session.d.ts +8 -20
- package/dist/session/agent-session.js +22 -70
- package/dist/session/agent-session.js.map +1 -1
- package/dist/session/base-transport.d.ts +17 -1
- package/dist/session/base-transport.js +25 -1
- package/dist/session/base-transport.js.map +1 -1
- package/dist/session/cli-transport.d.ts +5 -0
- package/dist/session/cli-transport.js +48 -25
- package/dist/session/cli-transport.js.map +1 -1
- package/dist/session/index.d.ts +25 -1
- package/dist/session/index.js +57 -8
- package/dist/session/index.js.map +1 -1
- package/dist/session/prompts.js +8 -9
- package/dist/session/prompts.js.map +1 -1
- package/dist/session/session-manager.d.ts +6 -0
- package/dist/session/session-manager.js +12 -0
- package/dist/session/session-manager.js.map +1 -1
- package/dist/session/types.d.ts +11 -1
- package/dist/session/types.js.map +1 -1
- package/dist/session/workspace-validation.js +37 -6
- package/dist/session/workspace-validation.js.map +1 -1
- package/dist/signal/signal-bot-daemon.js +24 -8
- package/dist/signal/signal-bot-daemon.js.map +1 -1
- package/dist/signal/signal-transport.d.ts +5 -2
- package/dist/signal/signal-transport.js +3 -3
- package/dist/signal/signal-transport.js.map +1 -1
- package/dist/trusted-process/approval-whitelist.d.ts +117 -0
- package/dist/trusted-process/approval-whitelist.js +237 -0
- package/dist/trusted-process/approval-whitelist.js.map +1 -0
- package/dist/trusted-process/gworkspace-credentials.d.ts +40 -0
- package/dist/trusted-process/gworkspace-credentials.js +45 -0
- package/dist/trusted-process/gworkspace-credentials.js.map +1 -0
- package/dist/trusted-process/index.d.ts +11 -1
- package/dist/trusted-process/index.js +83 -53
- package/dist/trusted-process/index.js.map +1 -1
- package/dist/trusted-process/mcp-proxy-server.d.ts +23 -0
- package/dist/trusted-process/mcp-proxy-server.js +409 -94
- package/dist/trusted-process/mcp-proxy-server.js.map +1 -1
- package/dist/trusted-process/policy-engine.d.ts +12 -3
- package/dist/trusted-process/policy-engine.js +33 -23
- package/dist/trusted-process/policy-engine.js.map +1 -1
- package/dist/trusted-process/policy-types.d.ts +17 -0
- package/dist/trusted-process/sandbox-integration.d.ts +23 -0
- package/dist/trusted-process/sandbox-integration.js +115 -3
- package/dist/trusted-process/sandbox-integration.js.map +1 -1
- package/dist/trusted-process/token-file-refresher.d.ts +69 -0
- package/dist/trusted-process/token-file-refresher.js +120 -0
- package/dist/trusted-process/token-file-refresher.js.map +1 -0
- package/dist/types/argument-roles.d.ts +7 -1
- package/dist/types/argument-roles.js +50 -0
- package/dist/types/argument-roles.js.map +1 -1
- package/dist/types/audit.d.ts +11 -0
- package/docker/Dockerfile.base +22 -0
- package/docker/Dockerfile.base.arm64 +44 -4
- package/docker/Dockerfile.claude-code +2 -2
- package/docker/entrypoint-claude-code.sh +24 -6
- package/package.json +8 -2
- package/src/config/constitution-user-base.md +15 -0
- package/src/config/generated/test-scenarios.json +1 -1
- package/src/config/generated/tool-annotations.json +10617 -296
- package/src/config/mcp-servers.json +31 -2
- package/dist/pipeline/mcp-connections.d.ts +0 -16
- package/dist/pipeline/mcp-connections.js +0 -61
- package/dist/pipeline/mcp-connections.js.map +0 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory MCP server name constant and server configuration builder.
|
|
3
|
+
*
|
|
4
|
+
* The memory server is registered as a "trusted server" in the PolicyEngine,
|
|
5
|
+
* bypassing annotation lookup and policy evaluation entirely. No annotations
|
|
6
|
+
* or policy rules are needed here.
|
|
7
|
+
*/
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
export const MEMORY_SERVER_NAME = 'memory';
|
|
10
|
+
/**
|
|
11
|
+
* Resolved path to the memory-mcp-server entry point (computed once).
|
|
12
|
+
*
|
|
13
|
+
* Uses Node module resolution so the path works both in development
|
|
14
|
+
* (workspace symlink) and production (installed from npm).
|
|
15
|
+
*/
|
|
16
|
+
export const MEMORY_SERVER_ENTRY = createRequire(import.meta.url).resolve('@provos/memory-mcp-server');
|
|
17
|
+
/**
|
|
18
|
+
* Verifies that a server config named 'memory' matches the exact shape
|
|
19
|
+
* produced by buildMemoryServerConfig(). If the config exists but doesn't
|
|
20
|
+
* match, the process cannot safely trust it — this is a fatal error.
|
|
21
|
+
*
|
|
22
|
+
* Returns true if the config is verified as our memory server,
|
|
23
|
+
* false if no 'memory' server is configured.
|
|
24
|
+
* Throws if a 'memory' server exists but has an unexpected shape.
|
|
25
|
+
*/
|
|
26
|
+
export function verifyMemoryServerConfig(mcpServers) {
|
|
27
|
+
if (!(MEMORY_SERVER_NAME in mcpServers))
|
|
28
|
+
return false;
|
|
29
|
+
const config = mcpServers[MEMORY_SERVER_NAME];
|
|
30
|
+
if (config.command === 'node' && config.args.length === 1 && config.args[0] === MEMORY_SERVER_ENTRY) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
throw new Error(`MCP server "${MEMORY_SERVER_NAME}" has unexpected config ` +
|
|
34
|
+
`(command=${JSON.stringify(config.command)}, args=${JSON.stringify(config.args)}). ` +
|
|
35
|
+
`Expected command="node" with args=[${JSON.stringify(MEMORY_SERVER_ENTRY)}]. ` +
|
|
36
|
+
`The memory server config appears tampered or misconfigured.`);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Builds the set of trusted server names from the MCP server config.
|
|
40
|
+
* Currently only the memory server can be trusted, verified by exact
|
|
41
|
+
* command/args match. Throws if a 'memory' server exists with an
|
|
42
|
+
* unexpected shape.
|
|
43
|
+
*/
|
|
44
|
+
export function buildTrustedServerSet(mcpServers) {
|
|
45
|
+
const trusted = new Set();
|
|
46
|
+
if (verifyMemoryServerConfig(mcpServers)) {
|
|
47
|
+
trusted.add(MEMORY_SERVER_NAME);
|
|
48
|
+
}
|
|
49
|
+
return trusted;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Builds the MCP server config for the memory server.
|
|
53
|
+
*
|
|
54
|
+
* The server runs as a Node.js subprocess using the compiled
|
|
55
|
+
* memory-mcp-server package entry point. Configuration is passed
|
|
56
|
+
* via environment variables.
|
|
57
|
+
*/
|
|
58
|
+
export function buildMemoryServerConfig(opts) {
|
|
59
|
+
const env = {
|
|
60
|
+
MEMORY_DB_PATH: opts.dbPath,
|
|
61
|
+
};
|
|
62
|
+
if (opts.namespace) {
|
|
63
|
+
env.MEMORY_NAMESPACE = opts.namespace;
|
|
64
|
+
}
|
|
65
|
+
// LLM config for summarization, dedup, and compaction.
|
|
66
|
+
// Falls back to Anthropic API key via their OpenAI-compatible endpoint.
|
|
67
|
+
if (opts.llmBaseUrl) {
|
|
68
|
+
env.MEMORY_LLM_BASE_URL = opts.llmBaseUrl;
|
|
69
|
+
}
|
|
70
|
+
if (opts.llmApiKey) {
|
|
71
|
+
env.MEMORY_LLM_API_KEY = opts.llmApiKey;
|
|
72
|
+
}
|
|
73
|
+
else if (opts.anthropicApiKey) {
|
|
74
|
+
env.MEMORY_LLM_API_KEY = opts.anthropicApiKey;
|
|
75
|
+
if (!opts.llmBaseUrl) {
|
|
76
|
+
env.MEMORY_LLM_BASE_URL = 'https://api.anthropic.com/v1/';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
command: 'node',
|
|
81
|
+
args: [MEMORY_SERVER_ENTRY],
|
|
82
|
+
env,
|
|
83
|
+
description: 'Persistent memory with semantic search',
|
|
84
|
+
sandbox: false,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=memory-annotations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-annotations.js","sourceRoot":"","sources":["../../src/memory/memory-annotations.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,MAAM,CAAC,MAAM,kBAAkB,GAAG,QAAQ,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;AAEvG;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CAAC,UAA2C;IAClF,IAAI,CAAC,CAAC,kBAAkB,IAAI,UAAU,CAAC;QAAE,OAAO,KAAK,CAAC;IAEtD,MAAM,MAAM,GAAG,UAAU,CAAC,kBAAkB,CAAC,CAAC;IAC9C,IAAI,MAAM,CAAC,OAAO,KAAK,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,mBAAmB,EAAE,CAAC;QACpG,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,IAAI,KAAK,CACb,eAAe,kBAAkB,0BAA0B;QACzD,YAAY,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK;QACpF,sCAAsC,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,KAAK;QAC9E,6DAA6D,CAChE,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,UAA2C;IAC/E,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,IAAI,wBAAwB,CAAC,UAAU,CAAC,EAAE,CAAC;QACzC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAMvC;IACC,MAAM,GAAG,GAA2B;QAClC,cAAc,EAAE,IAAI,CAAC,MAAM;KAC5B,CAAC;IAEF,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,GAAG,CAAC,gBAAgB,GAAG,IAAI,CAAC,SAAS,CAAC;IACxC,CAAC;IAED,uDAAuD;IACvD,wEAAwE;IACxE,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,GAAG,CAAC,mBAAmB,GAAG,IAAI,CAAC,UAAU,CAAC;IAC5C,CAAC;IACD,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,GAAG,CAAC,kBAAkB,GAAG,IAAI,CAAC,SAAS,CAAC;IAC1C,CAAC;SAAM,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;QAChC,GAAG,CAAC,kBAAkB,GAAG,IAAI,CAAC,eAAe,CAAC;QAC9C,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,GAAG,CAAC,mBAAmB,GAAG,+BAA+B,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,OAAO;QACL,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,CAAC,mBAAmB,CAAC;QAC3B,GAAG;QACH,WAAW,EAAE,wCAAwC;QACrD,OAAO,EAAE,KAAK;KACf,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports the memory system prompt builder from the memory MCP server package.
|
|
3
|
+
*
|
|
4
|
+
* This thin wrapper centralizes the import so that persona-prompt.ts and
|
|
5
|
+
* session/index.ts don't need to know the package export path.
|
|
6
|
+
*/
|
|
7
|
+
export { buildMemorySystemPrompt } from '@provos/memory-mcp-server/prompts';
|
|
8
|
+
/**
|
|
9
|
+
* Rewrites raw MCP tool names (memory_context, memory_store, …) to the
|
|
10
|
+
* Code Mode namespace-aliased format (memory.context, memory.store, …).
|
|
11
|
+
*/
|
|
12
|
+
export declare function adaptMemoryToolNames(prompt: string): string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports the memory system prompt builder from the memory MCP server package.
|
|
3
|
+
*
|
|
4
|
+
* This thin wrapper centralizes the import so that persona-prompt.ts and
|
|
5
|
+
* session/index.ts don't need to know the package export path.
|
|
6
|
+
*/
|
|
7
|
+
export { buildMemorySystemPrompt } from '@provos/memory-mcp-server/prompts';
|
|
8
|
+
/**
|
|
9
|
+
* Rewrites raw MCP tool names (memory_context, memory_store, …) to the
|
|
10
|
+
* Code Mode namespace-aliased format (memory.context, memory.store, …).
|
|
11
|
+
*/
|
|
12
|
+
export function adaptMemoryToolNames(prompt) {
|
|
13
|
+
return prompt
|
|
14
|
+
.replaceAll('memory_context', 'memory.context')
|
|
15
|
+
.replaceAll('memory_store', 'memory.store')
|
|
16
|
+
.replaceAll('memory_recall', 'memory.recall')
|
|
17
|
+
.replaceAll('memory_forget', 'memory.forget')
|
|
18
|
+
.replaceAll('memory_inspect', 'memory.inspect');
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=memory-prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-prompt.js","sourceRoot":"","sources":["../../src/memory/memory-prompt.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAE5E;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAc;IACjD,OAAO,MAAM;SACV,UAAU,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;SAC9C,UAAU,CAAC,cAAc,EAAE,cAAc,CAAC;SAC1C,UAAU,CAAC,eAAe,EAAE,eAAe,CAAC;SAC5C,UAAU,CAAC,eAAe,EAAE,eAAe,CAAC;SAC5C,UAAU,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the SQLite database path for the memory MCP server
|
|
3
|
+
* based on the session context (persona or cron job).
|
|
4
|
+
*
|
|
5
|
+
* Memory is only available for persona and cron job sessions,
|
|
6
|
+
* not for ad-hoc default sessions.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Returns the absolute path to the memory SQLite database.
|
|
10
|
+
*
|
|
11
|
+
* - Persona sessions: ~/.ironcurtain/personas/{name}/memory.db
|
|
12
|
+
* - Cron jobs (no persona): ~/.ironcurtain/jobs/{jobId}/memory.db
|
|
13
|
+
*
|
|
14
|
+
* Uses the established path helpers and validators (createPersonaName,
|
|
15
|
+
* getJobDir) to ensure consistent validation and path construction.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveMemoryDbPath(opts: {
|
|
18
|
+
persona?: string;
|
|
19
|
+
jobId?: string;
|
|
20
|
+
}): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the SQLite database path for the memory MCP server
|
|
3
|
+
* based on the session context (persona or cron job).
|
|
4
|
+
*
|
|
5
|
+
* Memory is only available for persona and cron job sessions,
|
|
6
|
+
* not for ad-hoc default sessions.
|
|
7
|
+
*/
|
|
8
|
+
import { resolve } from 'node:path';
|
|
9
|
+
import { getJobDir } from '../config/paths.js';
|
|
10
|
+
import { getPersonaDir } from '../persona/resolve.js';
|
|
11
|
+
import { createPersonaName } from '../persona/types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Returns the absolute path to the memory SQLite database.
|
|
14
|
+
*
|
|
15
|
+
* - Persona sessions: ~/.ironcurtain/personas/{name}/memory.db
|
|
16
|
+
* - Cron jobs (no persona): ~/.ironcurtain/jobs/{jobId}/memory.db
|
|
17
|
+
*
|
|
18
|
+
* Uses the established path helpers and validators (createPersonaName,
|
|
19
|
+
* getJobDir) to ensure consistent validation and path construction.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveMemoryDbPath(opts) {
|
|
22
|
+
if (opts.persona) {
|
|
23
|
+
return resolve(getPersonaDir(createPersonaName(opts.persona)), 'memory.db');
|
|
24
|
+
}
|
|
25
|
+
if (opts.jobId) {
|
|
26
|
+
return resolve(getJobDir(opts.jobId), 'memory.db');
|
|
27
|
+
}
|
|
28
|
+
throw new Error('resolveMemoryDbPath requires either persona or jobId');
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=resolve-memory-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-memory-path.js","sourceRoot":"","sources":["../../src/memory/resolve-memory-path.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAA0C;IAC5E,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,OAAO,OAAO,CAAC,aAAa,CAAC,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IAC9E,CAAC;IAED,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,CAAC;IACrD,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;AAC1E,CAAC"}
|
package/dist/mux/mux-app.d.ts
CHANGED
|
@@ -17,6 +17,10 @@ export interface MuxAppOptions {
|
|
|
17
17
|
readonly autoSpawn?: boolean;
|
|
18
18
|
/** Protected paths for workspace validation. */
|
|
19
19
|
readonly protectedPaths?: string[];
|
|
20
|
+
/** Unique mux instance ID for session ownership. */
|
|
21
|
+
readonly muxId?: string;
|
|
22
|
+
/** PID of this mux process (for orphan detection by other mux instances). */
|
|
23
|
+
readonly muxPid?: number;
|
|
20
24
|
}
|
|
21
25
|
/**
|
|
22
26
|
* Creates and returns a MuxApp.
|
package/dist/mux/mux-app.js
CHANGED
|
@@ -12,6 +12,10 @@ import { createMuxRenderer } from './mux-renderer.js';
|
|
|
12
12
|
import { writeTrustedUserContext } from './trusted-input.js';
|
|
13
13
|
import { createPasteInterceptor } from './paste-interceptor.js';
|
|
14
14
|
import { validateWorkspacePath } from '../session/workspace-validation.js';
|
|
15
|
+
import { scanResumableSessions } from './session-scanner.js';
|
|
16
|
+
import { scanPersonas } from './persona-scanner.js';
|
|
17
|
+
import ora from 'ora';
|
|
18
|
+
import chalk from 'chalk';
|
|
15
19
|
import * as logger from '../logger.js';
|
|
16
20
|
/**
|
|
17
21
|
* Creates and returns a MuxApp.
|
|
@@ -49,23 +53,29 @@ export function createMuxApp(options) {
|
|
|
49
53
|
// If running a compiled JS file or via an installed bin, use it directly
|
|
50
54
|
return { bin: script || 'ironcurtain', prefixArgs: [] };
|
|
51
55
|
}
|
|
52
|
-
async function spawnSession(
|
|
56
|
+
async function spawnSession(opts) {
|
|
53
57
|
const { columns } = process.stdout;
|
|
54
58
|
const ptyRows = renderer.layout.ptyViewportRows;
|
|
55
59
|
const ptyCols = columns || 80;
|
|
60
|
+
const sessionAgent = opts?.agentOverride ?? agent;
|
|
56
61
|
const { bin, prefixArgs } = resolveIroncurtainBin();
|
|
57
62
|
const bridge = await createPtyBridge({
|
|
58
63
|
cols: ptyCols,
|
|
59
64
|
rows: ptyRows,
|
|
60
65
|
ironcurtainBin: bin,
|
|
61
66
|
prefixArgs,
|
|
62
|
-
agent,
|
|
63
|
-
workspacePath,
|
|
67
|
+
agent: sessionAgent,
|
|
68
|
+
workspacePath: opts?.workspacePath,
|
|
69
|
+
resumeSessionId: opts?.resumeSessionId,
|
|
70
|
+
persona: opts?.persona,
|
|
71
|
+
muxId: options.muxId,
|
|
72
|
+
muxPid: options.muxPid,
|
|
64
73
|
});
|
|
65
74
|
const tab = {
|
|
66
75
|
number: nextTabNumber++,
|
|
67
76
|
bridge,
|
|
68
|
-
label:
|
|
77
|
+
label: opts?.persona ? `${sessionAgent} (${opts.persona})` : sessionAgent,
|
|
78
|
+
persona: opts?.persona,
|
|
69
79
|
status: 'running',
|
|
70
80
|
escalationAvailable: false,
|
|
71
81
|
scrollOffset: null,
|
|
@@ -96,7 +106,7 @@ export function createMuxApp(options) {
|
|
|
96
106
|
if (registration && tab.status === 'running') {
|
|
97
107
|
tab.escalationAvailable = true;
|
|
98
108
|
escalationManager.addSession(registration);
|
|
99
|
-
tab.label = registration.label;
|
|
109
|
+
tab.label = tab.persona ? `${registration.label} [${tab.persona}]` : registration.label;
|
|
100
110
|
renderer.redrawTabBar();
|
|
101
111
|
}
|
|
102
112
|
else if (!registration) {
|
|
@@ -125,7 +135,8 @@ export function createMuxApp(options) {
|
|
|
125
135
|
}
|
|
126
136
|
tabs.splice(index, 1);
|
|
127
137
|
if (tabs.length === 0) {
|
|
128
|
-
|
|
138
|
+
activeTabIndex = 0;
|
|
139
|
+
renderer.fullRedraw();
|
|
129
140
|
return;
|
|
130
141
|
}
|
|
131
142
|
if (activeTabIndex >= tabs.length) {
|
|
@@ -151,6 +162,27 @@ export function createMuxApp(options) {
|
|
|
151
162
|
logger.info(message);
|
|
152
163
|
renderer.showMessage(message);
|
|
153
164
|
}
|
|
165
|
+
function navigateEscalationTab(direction) {
|
|
166
|
+
const eps = inputHandler.escalationPickerState;
|
|
167
|
+
if (!eps)
|
|
168
|
+
return;
|
|
169
|
+
const sortedNums = escalationManager.sortedDisplayNumbers();
|
|
170
|
+
if (sortedNums.length === 0)
|
|
171
|
+
return;
|
|
172
|
+
const currentIdx = sortedNums.indexOf(eps.focusedDisplayNumber);
|
|
173
|
+
let newIdx;
|
|
174
|
+
if (currentIdx === -1) {
|
|
175
|
+
// Focused escalation was resolved/expired -- snap to first
|
|
176
|
+
newIdx = 0;
|
|
177
|
+
}
|
|
178
|
+
else if (direction === 'next') {
|
|
179
|
+
newIdx = (currentIdx + 1) % sortedNums.length;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
newIdx = (currentIdx - 1 + sortedNums.length) % sortedNums.length;
|
|
183
|
+
}
|
|
184
|
+
eps.focusedDisplayNumber = sortedNums[newIdx];
|
|
185
|
+
}
|
|
154
186
|
async function handleAction(action) {
|
|
155
187
|
switch (action.kind) {
|
|
156
188
|
case 'none':
|
|
@@ -206,15 +238,16 @@ export function createMuxApp(options) {
|
|
|
206
238
|
validatedPath = validateWorkspacePath(action.workspacePath, protectedPaths);
|
|
207
239
|
}
|
|
208
240
|
catch (err) {
|
|
209
|
-
inputHandler.enterBrowseWithError(action.workspacePath, err instanceof Error ? err.message : String(err));
|
|
241
|
+
inputHandler.enterBrowseWithError(action.workspacePath, err instanceof Error ? err.message : String(err), action.persona);
|
|
210
242
|
renderer.fullRedraw();
|
|
211
243
|
break;
|
|
212
244
|
}
|
|
213
245
|
}
|
|
214
|
-
const tab = await spawnSession(validatedPath);
|
|
246
|
+
const tab = await spawnSession({ workspacePath: validatedPath, persona: action.persona });
|
|
215
247
|
activeTabIndex = tabs.length - 1;
|
|
248
|
+
const personaPrefix = action.persona ? `persona "${action.persona}" ` : '';
|
|
216
249
|
const suffix = validatedPath ? ` in ${validatedPath}` : '';
|
|
217
|
-
showMessage(`Spawned session #${tab.number}${suffix}`);
|
|
250
|
+
showMessage(`Spawned ${personaPrefix}session #${tab.number}${suffix}`);
|
|
218
251
|
inputHandler.exitPickerMode();
|
|
219
252
|
renderer.fullRedraw();
|
|
220
253
|
break;
|
|
@@ -222,9 +255,62 @@ export function createMuxApp(options) {
|
|
|
222
255
|
case 'picker-cancel':
|
|
223
256
|
renderer.fullRedraw();
|
|
224
257
|
break;
|
|
258
|
+
case 'resume-spawn': {
|
|
259
|
+
const resumeTab = await spawnSession({
|
|
260
|
+
resumeSessionId: action.sessionId,
|
|
261
|
+
agentOverride: action.agent,
|
|
262
|
+
});
|
|
263
|
+
activeTabIndex = tabs.length - 1;
|
|
264
|
+
showMessage(`Resuming session ${action.sessionId.substring(0, 8)} as tab #${resumeTab.number}`);
|
|
265
|
+
renderer.fullRedraw();
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
case 'persona-spawn': {
|
|
269
|
+
const personaTab = await spawnSession({ persona: action.persona });
|
|
270
|
+
activeTabIndex = tabs.length - 1;
|
|
271
|
+
showMessage(`Spawned persona "${action.persona}" as tab #${personaTab.number}`);
|
|
272
|
+
renderer.fullRedraw();
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
225
275
|
case 'redraw-picker':
|
|
226
276
|
renderer.redrawCommandArea();
|
|
227
277
|
break;
|
|
278
|
+
case 'escalation-open': {
|
|
279
|
+
if (escalationManager.pendingCount === 0) {
|
|
280
|
+
showMessage('No pending escalations');
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
const sortedNums = escalationManager.sortedDisplayNumbers();
|
|
284
|
+
const previousMode = inputHandler.mode === 'command' ? 'command' : 'pty';
|
|
285
|
+
inputHandler.enterEscalationPickerMode(sortedNums[0], previousMode);
|
|
286
|
+
renderer.fullRedraw();
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
case 'escalation-dismiss': {
|
|
290
|
+
const sortedNums = escalationManager.sortedDisplayNumbers();
|
|
291
|
+
const highestPending = sortedNums.length > 0 ? sortedNums[sortedNums.length - 1] : 0;
|
|
292
|
+
inputHandler.dismissEscalationPicker(highestPending, action.targetMode);
|
|
293
|
+
renderer.fullRedraw();
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
case 'escalation-navigate': {
|
|
297
|
+
navigateEscalationTab(action.direction);
|
|
298
|
+
renderer.redrawCommandArea();
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
case 'escalation-resolve': {
|
|
302
|
+
const message = escalationManager.resolve(action.displayNumber, action.decision, action.whitelist);
|
|
303
|
+
showMessage(message);
|
|
304
|
+
// onChange callback (fired synchronously by resolve) handles focus adjustment,
|
|
305
|
+
// auto-close, and redraws — no additional redraw needed here.
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
case 'escalation-resolve-all': {
|
|
309
|
+
const message = escalationManager.resolveAll(action.decision, action.whitelist);
|
|
310
|
+
showMessage(message);
|
|
311
|
+
// onChange callback handles auto-close and redraws.
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
228
314
|
case 'scroll-up':
|
|
229
315
|
case 'scroll-down': {
|
|
230
316
|
const active = getActiveTab();
|
|
@@ -243,8 +329,13 @@ export function createMuxApp(options) {
|
|
|
243
329
|
function handleCommand(command, args) {
|
|
244
330
|
switch (command) {
|
|
245
331
|
case 'approve':
|
|
246
|
-
case '
|
|
247
|
-
|
|
332
|
+
case 'approve+':
|
|
333
|
+
case 'deny':
|
|
334
|
+
case 'deny+': {
|
|
335
|
+
const baseCommand = command.endsWith('+') ? command.slice(0, -1) : command;
|
|
336
|
+
// Only approve+ enables whitelisting; deny+ is treated as plain deny.
|
|
337
|
+
const withWhitelist = command === 'approve+';
|
|
338
|
+
const decision = baseCommand === 'approve' ? 'approved' : 'denied';
|
|
248
339
|
const arg = args[0];
|
|
249
340
|
if (!arg) {
|
|
250
341
|
showMessage(`Usage: /${command} <number> or /${command} all`);
|
|
@@ -252,7 +343,7 @@ export function createMuxApp(options) {
|
|
|
252
343
|
}
|
|
253
344
|
let message;
|
|
254
345
|
if (arg === 'all') {
|
|
255
|
-
message = escalationManager.resolveAll(decision);
|
|
346
|
+
message = escalationManager.resolveAll(decision, withWhitelist);
|
|
256
347
|
}
|
|
257
348
|
else {
|
|
258
349
|
const num = parseInt(arg, 10);
|
|
@@ -260,7 +351,7 @@ export function createMuxApp(options) {
|
|
|
260
351
|
showMessage('Invalid escalation number');
|
|
261
352
|
break;
|
|
262
353
|
}
|
|
263
|
-
message = escalationManager.resolve(num, decision);
|
|
354
|
+
message = escalationManager.resolve(num, decision, withWhitelist);
|
|
264
355
|
}
|
|
265
356
|
showMessage(message);
|
|
266
357
|
renderer.redrawTabBar();
|
|
@@ -268,7 +359,48 @@ export function createMuxApp(options) {
|
|
|
268
359
|
break;
|
|
269
360
|
}
|
|
270
361
|
case 'new': {
|
|
271
|
-
|
|
362
|
+
const personaArg = args[0];
|
|
363
|
+
if (personaArg) {
|
|
364
|
+
// Validate persona before spawning
|
|
365
|
+
const personas = scanPersonas();
|
|
366
|
+
const match = personas.find((p) => p.name === personaArg);
|
|
367
|
+
if (!match) {
|
|
368
|
+
showMessage(`Unknown persona: "${personaArg}"`);
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
if (!match.compiled) {
|
|
372
|
+
showMessage(`Persona "${personaArg}" is not compiled. Run: ironcurtain persona compile ${personaArg}`);
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
void handleAction({ kind: 'persona-spawn', persona: personaArg });
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
const personas = scanPersonas();
|
|
379
|
+
inputHandler.enterPickerMode(personas);
|
|
380
|
+
renderer.fullRedraw();
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
case 'resume': {
|
|
384
|
+
const directId = args[0];
|
|
385
|
+
if (directId) {
|
|
386
|
+
// Direct resume by session ID
|
|
387
|
+
const sessions = scanResumableSessions();
|
|
388
|
+
const match = sessions.find((s) => s.sessionId.startsWith(directId));
|
|
389
|
+
if (!match) {
|
|
390
|
+
showMessage(`No resumable session matching "${directId}"`);
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
void handleAction({ kind: 'resume-spawn', sessionId: match.sessionId, agent: match.agent });
|
|
394
|
+
}
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
// Open the resume picker
|
|
398
|
+
const sessions = scanResumableSessions();
|
|
399
|
+
if (sessions.length === 0) {
|
|
400
|
+
showMessage('No resumable sessions found');
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
inputHandler.enterResumePickerMode(sessions);
|
|
272
404
|
renderer.fullRedraw();
|
|
273
405
|
break;
|
|
274
406
|
}
|
|
@@ -310,17 +442,15 @@ export function createMuxApp(options) {
|
|
|
310
442
|
showMessage(`Unknown command: /${command}`);
|
|
311
443
|
}
|
|
312
444
|
}
|
|
445
|
+
let resolveShutdown = null;
|
|
313
446
|
function doShutdown() {
|
|
314
447
|
if (!running)
|
|
315
448
|
return;
|
|
316
449
|
running = false;
|
|
317
450
|
pasteInterceptor?.uninstall();
|
|
318
|
-
for (const tab of tabs) {
|
|
319
|
-
if (tab.bridge.alive) {
|
|
320
|
-
tab.bridge.kill();
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
451
|
escalationManager.stop();
|
|
452
|
+
// Exit fullscreen and destroy renderer before any async work
|
|
453
|
+
// so the normal terminal is restored immediately.
|
|
324
454
|
if (term) {
|
|
325
455
|
term.grabInput(false);
|
|
326
456
|
term.hideCursor(false);
|
|
@@ -328,6 +458,47 @@ export function createMuxApp(options) {
|
|
|
328
458
|
term.styleReset();
|
|
329
459
|
}
|
|
330
460
|
renderer.destroy();
|
|
461
|
+
// Collect alive tabs that need to be waited on
|
|
462
|
+
const aliveTabs = tabs.filter((t) => t.bridge.alive);
|
|
463
|
+
if (aliveTabs.length === 0) {
|
|
464
|
+
resolveShutdown?.();
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
// Show a spinner on the real terminal while children shut down
|
|
468
|
+
const spinner = ora({
|
|
469
|
+
text: `Shutting down ${aliveTabs.length} PTY session${aliveTabs.length > 1 ? 's' : ''}...`,
|
|
470
|
+
stream: process.stderr,
|
|
471
|
+
discardStdin: false,
|
|
472
|
+
}).start();
|
|
473
|
+
// Kill all children
|
|
474
|
+
for (const tab of aliveTabs) {
|
|
475
|
+
tab.bridge.kill();
|
|
476
|
+
}
|
|
477
|
+
// Wait for all children to exit, with a safety timeout
|
|
478
|
+
let remaining = aliveTabs.length;
|
|
479
|
+
let settled = false;
|
|
480
|
+
const settle = () => {
|
|
481
|
+
if (settled)
|
|
482
|
+
return;
|
|
483
|
+
settled = true;
|
|
484
|
+
clearTimeout(deadline);
|
|
485
|
+
spinner.succeed(chalk.dim('All sessions ended'));
|
|
486
|
+
resolveShutdown?.();
|
|
487
|
+
};
|
|
488
|
+
const deadline = setTimeout(() => {
|
|
489
|
+
if (settled)
|
|
490
|
+
return;
|
|
491
|
+
settled = true;
|
|
492
|
+
spinner.warn(chalk.dim(`Timed out waiting for ${remaining} session(s)`));
|
|
493
|
+
resolveShutdown?.();
|
|
494
|
+
}, 10_000);
|
|
495
|
+
for (const tab of aliveTabs) {
|
|
496
|
+
tab.bridge.onExit(() => {
|
|
497
|
+
remaining--;
|
|
498
|
+
if (remaining === 0)
|
|
499
|
+
settle();
|
|
500
|
+
});
|
|
501
|
+
}
|
|
331
502
|
}
|
|
332
503
|
return {
|
|
333
504
|
async start() {
|
|
@@ -347,7 +518,7 @@ export function createMuxApp(options) {
|
|
|
347
518
|
void handleAction(action);
|
|
348
519
|
});
|
|
349
520
|
pasteInterceptor.install();
|
|
350
|
-
escalationManager = createMuxEscalationManager();
|
|
521
|
+
escalationManager = createMuxEscalationManager({ muxId: options.muxId });
|
|
351
522
|
const { columns, rows } = process.stdout;
|
|
352
523
|
const cols = columns || 80;
|
|
353
524
|
const totalRows = rows || 24;
|
|
@@ -361,15 +532,18 @@ export function createMuxApp(options) {
|
|
|
361
532
|
getEscalationState: () => escalationManager.state,
|
|
362
533
|
getPendingCount: () => escalationManager.pendingCount,
|
|
363
534
|
getPickerState: () => inputHandler.pickerState,
|
|
535
|
+
getResumePickerState: () => inputHandler.resumePickerState,
|
|
536
|
+
getPersonaPickerState: () => inputHandler.personaPickerState,
|
|
537
|
+
getEscalationPickerState: () => inputHandler.escalationPickerState,
|
|
364
538
|
getScrollOffset: () => {
|
|
365
539
|
const active = getActiveTab();
|
|
366
540
|
return active?.scrollOffset ?? null;
|
|
367
541
|
},
|
|
368
542
|
});
|
|
369
|
-
term.on('key', (key) => {
|
|
543
|
+
term.on('key', (key, _matches, data) => {
|
|
370
544
|
if (!running)
|
|
371
545
|
return;
|
|
372
|
-
const action = inputHandler.handleKey(key);
|
|
546
|
+
const action = inputHandler.handleKey(key, data.code);
|
|
373
547
|
void handleAction(action);
|
|
374
548
|
});
|
|
375
549
|
// Mouse events come through a separate 'mouse' emitter, not 'key'
|
|
@@ -397,6 +571,51 @@ export function createMuxApp(options) {
|
|
|
397
571
|
renderer.fullRedraw();
|
|
398
572
|
});
|
|
399
573
|
escalationManager.onChange(() => {
|
|
574
|
+
const pendingCount = escalationManager.pendingCount;
|
|
575
|
+
const mode = inputHandler.mode;
|
|
576
|
+
// Auto-close: picker is open but nothing left
|
|
577
|
+
if (mode === 'escalation-picker' && pendingCount === 0) {
|
|
578
|
+
inputHandler.exitEscalationPickerMode();
|
|
579
|
+
renderer.fullRedraw();
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
// Auto-open: new escalation arrived, picker not already open
|
|
583
|
+
if (pendingCount > 0 && mode !== 'escalation-picker') {
|
|
584
|
+
const sortedNums = escalationManager.sortedDisplayNumbers();
|
|
585
|
+
const highestPending = sortedNums[sortedNums.length - 1];
|
|
586
|
+
// Only auto-open if the user hasn't dismissed, OR if a genuinely new
|
|
587
|
+
// escalation arrived (higher display number than when they dismissed).
|
|
588
|
+
const shouldAutoOpen = !inputHandler.escalationDismissed || highestPending > inputHandler.escalationDismissedAtNumber;
|
|
589
|
+
if (shouldAutoOpen) {
|
|
590
|
+
const previousMode = mode === 'pty' ? 'pty' : mode === 'command' ? 'command' : 'pty'; // if in another picker, treat as pty
|
|
591
|
+
// If user is in another picker, cancel it first
|
|
592
|
+
if (mode === 'picker') {
|
|
593
|
+
inputHandler.exitPickerMode();
|
|
594
|
+
}
|
|
595
|
+
else if (mode === 'resume-picker') {
|
|
596
|
+
inputHandler.exitResumePickerMode();
|
|
597
|
+
}
|
|
598
|
+
else if (mode === 'persona-picker') {
|
|
599
|
+
inputHandler.exitPersonaPickerMode();
|
|
600
|
+
}
|
|
601
|
+
inputHandler.enterEscalationPickerMode(highestPending, previousMode);
|
|
602
|
+
renderer.fullRedraw();
|
|
603
|
+
// BEL is already emitted by the escalation manager's onEscalation handler.
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
// Live update while picker is open: re-validate focused tab
|
|
608
|
+
if (mode === 'escalation-picker') {
|
|
609
|
+
const eps = inputHandler.escalationPickerState;
|
|
610
|
+
if (eps && !escalationManager.state.pendingEscalations.has(eps.focusedDisplayNumber)) {
|
|
611
|
+
// Focused escalation was resolved or expired -- snap to nearest
|
|
612
|
+
const sortedNums = escalationManager.sortedDisplayNumbers();
|
|
613
|
+
if (sortedNums.length > 0) {
|
|
614
|
+
eps.focusedDisplayNumber = sortedNums[0];
|
|
615
|
+
}
|
|
616
|
+
// If sortedNums is empty, auto-close above will handle it on next tick
|
|
617
|
+
}
|
|
618
|
+
}
|
|
400
619
|
renderer.redrawTabBar();
|
|
401
620
|
renderer.redrawCommandArea();
|
|
402
621
|
});
|
|
@@ -433,12 +652,11 @@ export function createMuxApp(options) {
|
|
|
433
652
|
}
|
|
434
653
|
renderer.fullRedraw();
|
|
435
654
|
await new Promise((resolve) => {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}, 100);
|
|
655
|
+
if (!running) {
|
|
656
|
+
resolve();
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
resolveShutdown = resolve;
|
|
442
660
|
});
|
|
443
661
|
},
|
|
444
662
|
// eslint-disable-next-line @typescript-eslint/require-await
|