@provos/ironcurtain 0.4.1 → 0.5.1
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 +219 -248
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -1
- package/dist/config/config-command.js +144 -6
- package/dist/config/config-command.js.map +1 -1
- package/dist/config/constitution-user-base.md +2 -0
- package/dist/config/first-start.js +77 -12
- package/dist/config/first-start.js.map +1 -1
- package/dist/config/generated/compiled-policy.json +199 -72
- package/dist/config/generated/tool-annotations.json +1018 -36
- package/dist/config/mcp-servers.json +10 -0
- package/dist/config/paths.d.ts +31 -0
- package/dist/config/paths.js +52 -2
- package/dist/config/paths.js.map +1 -1
- package/dist/config/types.d.ts +8 -0
- package/dist/config/user-config.d.ts +21 -3
- package/dist/config/user-config.js +34 -3
- package/dist/config/user-config.js.map +1 -1
- package/dist/docker/adapters/claude-code.js +104 -23
- package/dist/docker/adapters/claude-code.js.map +1 -1
- package/dist/docker/agent-adapter.d.ts +24 -1
- package/dist/docker/agent-adapter.js +6 -1
- package/dist/docker/agent-adapter.js.map +1 -1
- package/dist/docker/docker-agent-session.d.ts +16 -9
- package/dist/docker/docker-agent-session.js +64 -90
- package/dist/docker/docker-agent-session.js.map +1 -1
- package/dist/docker/docker-infrastructure.d.ts +51 -0
- package/dist/docker/docker-infrastructure.js +261 -0
- package/dist/docker/docker-infrastructure.js.map +1 -0
- package/dist/docker/docker-manager.js +3 -0
- package/dist/docker/docker-manager.js.map +1 -1
- package/dist/docker/mitm-proxy.js +4 -0
- package/dist/docker/mitm-proxy.js.map +1 -1
- package/dist/docker/oauth-credentials.d.ts +81 -0
- package/dist/docker/oauth-credentials.js +178 -0
- package/dist/docker/oauth-credentials.js.map +1 -0
- package/dist/docker/orientation.d.ts +1 -1
- package/dist/docker/orientation.js +3 -2
- package/dist/docker/orientation.js.map +1 -1
- package/dist/docker/provider-config.d.ts +3 -0
- package/dist/docker/provider-config.js +28 -0
- package/dist/docker/provider-config.js.map +1 -1
- package/dist/docker/pty-session.d.ts +27 -0
- package/dist/docker/pty-session.js +510 -0
- package/dist/docker/pty-session.js.map +1 -0
- package/dist/docker/pty-types.d.ts +24 -0
- package/dist/docker/pty-types.js +12 -0
- package/dist/docker/pty-types.js.map +1 -0
- package/dist/docker/types.d.ts +6 -0
- package/dist/escalation/escalation-watcher.d.ts +44 -0
- package/dist/escalation/escalation-watcher.js +92 -0
- package/dist/escalation/escalation-watcher.js.map +1 -0
- package/dist/escalation/listener-command.d.ts +10 -0
- package/dist/escalation/listener-command.js +407 -0
- package/dist/escalation/listener-command.js.map +1 -0
- package/dist/escalation/listener-state.d.ts +49 -0
- package/dist/escalation/listener-state.js +104 -0
- package/dist/escalation/listener-state.js.map +1 -0
- package/dist/escalation/session-registry.d.ts +17 -0
- package/dist/escalation/session-registry.js +98 -0
- package/dist/escalation/session-registry.js.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.js +2 -3
- package/dist/logger.js.map +1 -1
- package/dist/pipeline/annotate.js +35 -15
- package/dist/pipeline/annotate.js.map +1 -1
- package/dist/pipeline/constitution-customizer.d.ts +2 -1
- package/dist/pipeline/constitution-customizer.js +36 -3
- package/dist/pipeline/constitution-customizer.js.map +1 -1
- package/dist/pipeline/github-identity.d.ts +38 -0
- package/dist/pipeline/github-identity.js +64 -0
- package/dist/pipeline/github-identity.js.map +1 -0
- package/dist/pipeline/handwritten-scenarios.js +34 -0
- package/dist/pipeline/handwritten-scenarios.js.map +1 -1
- package/dist/sandbox/index.js +14 -3
- package/dist/sandbox/index.js.map +1 -1
- package/dist/servers/fetch-server.js +2 -2
- package/dist/session/base-transport.d.ts +19 -0
- package/dist/session/base-transport.js +36 -0
- package/dist/session/base-transport.js.map +1 -0
- package/dist/session/cli-transport.d.ts +3 -3
- package/dist/session/cli-transport.js +7 -4
- package/dist/session/cli-transport.js.map +1 -1
- package/dist/session/index.js +24 -78
- package/dist/session/index.js.map +1 -1
- package/dist/session/interaction-log.d.ts +13 -0
- package/dist/session/interaction-log.js +28 -0
- package/dist/session/interaction-log.js.map +1 -0
- package/dist/session/preflight.d.ts +3 -0
- package/dist/session/preflight.js +39 -18
- package/dist/session/preflight.js.map +1 -1
- package/dist/session/types.d.ts +2 -0
- package/dist/signal/bot-command.js +5 -0
- package/dist/signal/bot-command.js.map +1 -1
- package/dist/signal/format.d.ts +21 -1
- package/dist/signal/format.js +41 -3
- package/dist/signal/format.js.map +1 -1
- package/dist/signal/signal-bot-daemon.d.ts +53 -18
- package/dist/signal/signal-bot-daemon.js +344 -106
- package/dist/signal/signal-bot-daemon.js.map +1 -1
- package/dist/signal/signal-config.d.ts +4 -0
- package/dist/signal/signal-config.js +2 -0
- package/dist/signal/signal-config.js.map +1 -1
- package/dist/signal/signal-transport.d.ts +13 -7
- package/dist/signal/signal-transport.js +36 -12
- package/dist/signal/signal-transport.js.map +1 -1
- package/dist/trusted-process/audit-log.d.ts +6 -1
- package/dist/trusted-process/audit-log.js +22 -2
- package/dist/trusted-process/audit-log.js.map +1 -1
- package/dist/trusted-process/audit-redactor.d.ts +25 -0
- package/dist/trusted-process/audit-redactor.js +121 -0
- package/dist/trusted-process/audit-redactor.js.map +1 -0
- package/dist/trusted-process/escalation.d.ts +1 -1
- package/dist/trusted-process/escalation.js +6 -1
- package/dist/trusted-process/escalation.js.map +1 -1
- package/dist/trusted-process/index.d.ts +2 -1
- package/dist/trusted-process/index.js +47 -6
- package/dist/trusted-process/index.js.map +1 -1
- package/dist/trusted-process/mcp-client-manager.js +14 -2
- package/dist/trusted-process/mcp-client-manager.js.map +1 -1
- package/dist/trusted-process/mcp-error-utils.d.ts +11 -0
- package/dist/trusted-process/mcp-error-utils.js +50 -0
- package/dist/trusted-process/mcp-error-utils.js.map +1 -0
- package/dist/trusted-process/mcp-proxy-server.d.ts +4 -2
- package/dist/trusted-process/mcp-proxy-server.js +56 -13
- package/dist/trusted-process/mcp-proxy-server.js.map +1 -1
- package/dist/trusted-process/path-utils.d.ts +15 -0
- package/dist/trusted-process/path-utils.js +38 -0
- package/dist/trusted-process/path-utils.js.map +1 -1
- package/dist/trusted-process/sandbox-integration.d.ts +5 -0
- package/dist/trusted-process/sandbox-integration.js +1 -1
- package/dist/trusted-process/sandbox-integration.js.map +1 -1
- package/dist/trusted-process/server-context.d.ts +21 -0
- package/dist/trusted-process/server-context.js +42 -0
- package/dist/trusted-process/server-context.js.map +1 -0
- package/dist/types/argument-roles.d.ts +5 -2
- package/dist/types/argument-roles.js +25 -2
- package/dist/types/argument-roles.js.map +1 -1
- package/docker/Dockerfile.claude-code +8 -3
- package/docker/entrypoint-claude-code.sh +64 -4
- package/package.json +16 -8
- package/src/config/constitution-user-base.md +2 -0
- package/src/config/generated/compiled-policy.json +199 -72
- package/src/config/generated/tool-annotations.json +1018 -36
- package/src/config/mcp-servers.json +10 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared escalation watcher for polling escalation directories.
|
|
3
|
+
*
|
|
4
|
+
* Pure data module -- no I/O presentation. Consumers (DockerAgentSession,
|
|
5
|
+
* EscalationListener) handle display. Polls a session's escalation
|
|
6
|
+
* directory for new request files and handles resolution via response files.
|
|
7
|
+
*/
|
|
8
|
+
import { readdirSync, readFileSync, existsSync, writeFileSync, renameSync } from 'node:fs';
|
|
9
|
+
import { resolve } from 'node:path';
|
|
10
|
+
const DEFAULT_POLL_INTERVAL_MS = 300;
|
|
11
|
+
/**
|
|
12
|
+
* Atomically writes a JSON file using write-to-temp-then-rename.
|
|
13
|
+
* Prevents partial reads by other processes polling the same directory.
|
|
14
|
+
*/
|
|
15
|
+
export function atomicWriteJsonSync(filePath, data) {
|
|
16
|
+
const tmpPath = `${filePath}.tmp`;
|
|
17
|
+
writeFileSync(tmpPath, JSON.stringify(data, null, 2));
|
|
18
|
+
renameSync(tmpPath, filePath);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Creates an escalation watcher for a single session's escalation directory.
|
|
22
|
+
*/
|
|
23
|
+
export function createEscalationWatcher(escalationDir, events, options) {
|
|
24
|
+
const pollIntervalMs = options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
25
|
+
let pendingEscalation;
|
|
26
|
+
const seenEscalationIds = new Set();
|
|
27
|
+
let pollInterval = null;
|
|
28
|
+
function extractEscalationId(filename) {
|
|
29
|
+
return filename.replace(/^request-/, '').replace(/\.json$/, '');
|
|
30
|
+
}
|
|
31
|
+
function checkEscalationExpiry() {
|
|
32
|
+
if (!pendingEscalation)
|
|
33
|
+
return;
|
|
34
|
+
const escalationId = pendingEscalation.escalationId;
|
|
35
|
+
const requestExists = existsSync(resolve(escalationDir, `request-${escalationId}.json`));
|
|
36
|
+
const responseExists = existsSync(resolve(escalationDir, `response-${escalationId}.json`));
|
|
37
|
+
if (!requestExists && !responseExists) {
|
|
38
|
+
const expiredId = escalationId;
|
|
39
|
+
pendingEscalation = undefined;
|
|
40
|
+
events.onEscalationExpired(expiredId);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function pollEscalationDirectory() {
|
|
44
|
+
if (pendingEscalation) {
|
|
45
|
+
checkEscalationExpiry();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const files = readdirSync(escalationDir);
|
|
50
|
+
const requestFile = files.find((f) => f.startsWith('request-') && f.endsWith('.json') && !seenEscalationIds.has(extractEscalationId(f)));
|
|
51
|
+
if (!requestFile)
|
|
52
|
+
return;
|
|
53
|
+
const requestPath = resolve(escalationDir, requestFile);
|
|
54
|
+
const request = JSON.parse(readFileSync(requestPath, 'utf-8'));
|
|
55
|
+
seenEscalationIds.add(request.escalationId);
|
|
56
|
+
pendingEscalation = request;
|
|
57
|
+
events.onEscalation(request);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Directory may not exist yet or be empty
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
start() {
|
|
65
|
+
if (pollInterval)
|
|
66
|
+
return;
|
|
67
|
+
pollInterval = setInterval(pollEscalationDirectory, pollIntervalMs);
|
|
68
|
+
},
|
|
69
|
+
stop() {
|
|
70
|
+
if (pollInterval) {
|
|
71
|
+
clearInterval(pollInterval);
|
|
72
|
+
pollInterval = null;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
getPending() {
|
|
76
|
+
return pendingEscalation;
|
|
77
|
+
},
|
|
78
|
+
resolve(escalationId, decision) {
|
|
79
|
+
if (!pendingEscalation || pendingEscalation.escalationId !== escalationId) {
|
|
80
|
+
throw new Error(`No pending escalation with ID: ${escalationId}`);
|
|
81
|
+
}
|
|
82
|
+
const responsePath = resolve(escalationDir, `response-${escalationId}.json`);
|
|
83
|
+
atomicWriteJsonSync(responsePath, { decision });
|
|
84
|
+
pendingEscalation = undefined;
|
|
85
|
+
// Stale detection: verify the request file still exists after writing the response.
|
|
86
|
+
// If the proxy already timed out and cleaned up, the response was too late.
|
|
87
|
+
const requestStillExists = existsSync(resolve(escalationDir, `request-${escalationId}.json`));
|
|
88
|
+
return requestStillExists;
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=escalation-watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escalation-watcher.js","sourceRoot":"","sources":["../../src/escalation/escalation-watcher.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC3F,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAgCrC;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,IAAa;IACjE,MAAM,OAAO,GAAG,GAAG,QAAQ,MAAM,CAAC;IAClC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACtD,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,uBAAuB,CACrC,aAAqB,EACrB,MAA+B,EAC/B,OAAkC;IAElC,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,wBAAwB,CAAC;IAE3E,IAAI,iBAAgD,CAAC;IACrD,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAU,CAAC;IAC5C,IAAI,YAAY,GAA0C,IAAI,CAAC;IAE/D,SAAS,mBAAmB,CAAC,QAAgB;QAC3C,OAAO,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,SAAS,qBAAqB;QAC5B,IAAI,CAAC,iBAAiB;YAAE,OAAO;QAE/B,MAAM,YAAY,GAAG,iBAAiB,CAAC,YAAY,CAAC;QACpD,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,CAAC,aAAa,EAAE,WAAW,YAAY,OAAO,CAAC,CAAC,CAAC;QACzF,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,CAAC,aAAa,EAAE,YAAY,YAAY,OAAO,CAAC,CAAC,CAAC;QAE3F,IAAI,CAAC,aAAa,IAAI,CAAC,cAAc,EAAE,CAAC;YACtC,MAAM,SAAS,GAAG,YAAY,CAAC;YAC/B,iBAAiB,GAAG,SAAS,CAAC;YAC9B,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED,SAAS,uBAAuB;QAC9B,IAAI,iBAAiB,EAAE,CAAC;YACtB,qBAAqB,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,WAAW,CAAC,aAAa,CAAC,CAAC;YACzC,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAC5B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CACzG,CAAC;YACF,IAAI,CAAC,WAAW;gBAAE,OAAO;YAEzB,MAAM,WAAW,GAAG,OAAO,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;YACxD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAsB,CAAC;YACpF,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;YAC5C,iBAAiB,GAAG,OAAO,CAAC;YAC5B,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,0CAA0C;QAC5C,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK;YACH,IAAI,YAAY;gBAAE,OAAO;YACzB,YAAY,GAAG,WAAW,CAAC,uBAAuB,EAAE,cAAc,CAAC,CAAC;QACtE,CAAC;QAED,IAAI;YACF,IAAI,YAAY,EAAE,CAAC;gBACjB,aAAa,CAAC,YAAY,CAAC,CAAC;gBAC5B,YAAY,GAAG,IAAI,CAAC;YACtB,CAAC;QACH,CAAC;QAED,UAAU;YACR,OAAO,iBAAiB,CAAC;QAC3B,CAAC;QAED,OAAO,CAAC,YAAoB,EAAE,QAA+B;YAC3D,IAAI,CAAC,iBAAiB,IAAI,iBAAiB,CAAC,YAAY,KAAK,YAAY,EAAE,CAAC;gBAC1E,MAAM,IAAI,KAAK,CAAC,kCAAkC,YAAY,EAAE,CAAC,CAAC;YACpE,CAAC;YAED,MAAM,YAAY,GAAG,OAAO,CAAC,aAAa,EAAE,YAAY,YAAY,OAAO,CAAC,CAAC;YAC7E,mBAAmB,CAAC,YAAY,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;YAChD,iBAAiB,GAAG,SAAS,CAAC;YAE9B,oFAAoF;YACpF,4EAA4E;YAC5E,MAAM,kBAAkB,GAAG,UAAU,CAAC,OAAO,CAAC,aAAa,EAAE,WAAW,YAAY,OAAO,CAAC,CAAC,CAAC;YAC9F,OAAO,kBAAkB,CAAC;QAC5B,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escalation listener CLI command.
|
|
3
|
+
*
|
|
4
|
+
* Polls the PTY session registry directory for active sessions, watches
|
|
5
|
+
* each session's escalation directory for new requests, and presents
|
|
6
|
+
* a terminal dashboard where the user can approve or deny escalations.
|
|
7
|
+
*
|
|
8
|
+
* Usage: ironcurtain escalation-listener
|
|
9
|
+
*/
|
|
10
|
+
export declare function main(): Promise<void>;
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escalation listener CLI command.
|
|
3
|
+
*
|
|
4
|
+
* Polls the PTY session registry directory for active sessions, watches
|
|
5
|
+
* each session's escalation directory for new requests, and presents
|
|
6
|
+
* a terminal dashboard where the user can approve or deny escalations.
|
|
7
|
+
*
|
|
8
|
+
* Usage: ironcurtain escalation-listener
|
|
9
|
+
*/
|
|
10
|
+
import { createInterface } from 'node:readline';
|
|
11
|
+
import { existsSync, mkdirSync, writeSync, readFileSync, unlinkSync, openSync, closeSync, constants } from 'node:fs';
|
|
12
|
+
import chalk from 'chalk';
|
|
13
|
+
import { getPtyRegistryDir, getListenerLockPath } from '../config/paths.js';
|
|
14
|
+
import { readActiveRegistrations } from './session-registry.js';
|
|
15
|
+
import { createEscalationWatcher } from './escalation-watcher.js';
|
|
16
|
+
import { createInitialState, addSession, removeSession, addEscalation, resolveEscalation, expireEscalation, } from './listener-state.js';
|
|
17
|
+
/** Poll interval for the session registry directory (ms). */
|
|
18
|
+
const REGISTRY_POLL_INTERVAL_MS = 1000;
|
|
19
|
+
export async function main() {
|
|
20
|
+
// Single-instance enforcement
|
|
21
|
+
const lockPath = getListenerLockPath();
|
|
22
|
+
if (!acquireLock(lockPath)) {
|
|
23
|
+
process.stderr.write(chalk.red('Another escalation listener is already running.\n'));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
const registryDir = getPtyRegistryDir();
|
|
27
|
+
mkdirSync(registryDir, { recursive: true, mode: 0o700 });
|
|
28
|
+
let state = createInitialState();
|
|
29
|
+
let running = true;
|
|
30
|
+
let lastRenderedContent = '';
|
|
31
|
+
// Set up readline for command input
|
|
32
|
+
const rl = createInterface({
|
|
33
|
+
input: process.stdin,
|
|
34
|
+
output: process.stderr,
|
|
35
|
+
terminal: true,
|
|
36
|
+
});
|
|
37
|
+
/**
|
|
38
|
+
* Renders the dashboard only if the content has actually changed.
|
|
39
|
+
* Uses cursor repositioning (move to home) instead of screen clear
|
|
40
|
+
* to avoid destroying readline's input buffer.
|
|
41
|
+
*/
|
|
42
|
+
const render = () => {
|
|
43
|
+
const content = buildDashboard(state);
|
|
44
|
+
if (content === lastRenderedContent)
|
|
45
|
+
return;
|
|
46
|
+
lastRenderedContent = content;
|
|
47
|
+
// Move cursor to home position, write content with per-line clearing,
|
|
48
|
+
// then clear any remaining lines below. The per-line \x1b[K (clear to
|
|
49
|
+
// end of line) is needed because shorter new lines would otherwise leave
|
|
50
|
+
// leftover characters from the previous (longer) dashboard render.
|
|
51
|
+
process.stderr.write('\x1b[H\x1b[K' + content.replace(/\n/g, '\x1b[K\n') + '\x1b[J');
|
|
52
|
+
// Force readline to redisplay its prompt + current input buffer
|
|
53
|
+
rl.prompt(true);
|
|
54
|
+
};
|
|
55
|
+
// Poll for new/removed sessions
|
|
56
|
+
const registryPollInterval = setInterval(() => {
|
|
57
|
+
const registrations = readActiveRegistrations(registryDir);
|
|
58
|
+
const currentIds = new Set(registrations.map((r) => r.sessionId));
|
|
59
|
+
const stateIds = new Set(state.sessions.keys());
|
|
60
|
+
let changed = false;
|
|
61
|
+
// Add new sessions
|
|
62
|
+
for (const reg of registrations) {
|
|
63
|
+
if (!stateIds.has(reg.sessionId)) {
|
|
64
|
+
const watcher = createWatcherForSession(reg, (newState) => {
|
|
65
|
+
state = newState;
|
|
66
|
+
render();
|
|
67
|
+
}, () => state);
|
|
68
|
+
state = addSession(state, reg, watcher);
|
|
69
|
+
watcher.start();
|
|
70
|
+
changed = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Remove gone sessions
|
|
74
|
+
for (const id of stateIds) {
|
|
75
|
+
if (!currentIds.has(id)) {
|
|
76
|
+
const session = state.sessions.get(id);
|
|
77
|
+
session?.watcher.stop();
|
|
78
|
+
state = removeSession(state, id);
|
|
79
|
+
changed = true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (changed)
|
|
83
|
+
render();
|
|
84
|
+
}, REGISTRY_POLL_INTERVAL_MS);
|
|
85
|
+
// Initial render: clear screen once, then use repositioning from now on
|
|
86
|
+
rl.setPrompt(' > ');
|
|
87
|
+
process.stderr.write('\x1b[2J');
|
|
88
|
+
render();
|
|
89
|
+
// Handle commands
|
|
90
|
+
rl.on('line', (line) => {
|
|
91
|
+
const trimmed = line.trim();
|
|
92
|
+
if (!trimmed) {
|
|
93
|
+
render();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const result = handleCommand(trimmed, state);
|
|
97
|
+
state = result.state;
|
|
98
|
+
if (result.quit) {
|
|
99
|
+
if (result.message) {
|
|
100
|
+
process.stderr.write(result.message + '\n');
|
|
101
|
+
}
|
|
102
|
+
running = false;
|
|
103
|
+
rl.close();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Force a re-render by invalidating the cache
|
|
107
|
+
lastRenderedContent = '';
|
|
108
|
+
if (result.message) {
|
|
109
|
+
// Render first so the message appears below the dashboard
|
|
110
|
+
render();
|
|
111
|
+
process.stderr.write(result.message + '\n');
|
|
112
|
+
rl.prompt(true);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
render();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
rl.on('close', () => {
|
|
119
|
+
running = false;
|
|
120
|
+
});
|
|
121
|
+
// Wait until quit
|
|
122
|
+
await new Promise((resolve) => {
|
|
123
|
+
const checkInterval = setInterval(() => {
|
|
124
|
+
if (!running) {
|
|
125
|
+
clearInterval(checkInterval);
|
|
126
|
+
resolve();
|
|
127
|
+
}
|
|
128
|
+
}, 100);
|
|
129
|
+
});
|
|
130
|
+
// Cleanup
|
|
131
|
+
clearInterval(registryPollInterval);
|
|
132
|
+
for (const session of state.sessions.values()) {
|
|
133
|
+
session.watcher.stop();
|
|
134
|
+
}
|
|
135
|
+
releaseLock(lockPath);
|
|
136
|
+
}
|
|
137
|
+
// --- Lock management ---
|
|
138
|
+
/**
|
|
139
|
+
* Acquires the listener lock file using O_EXCL for atomicity.
|
|
140
|
+
* Returns true if the lock was acquired, false if another instance holds it.
|
|
141
|
+
*/
|
|
142
|
+
function acquireLock(lockPath) {
|
|
143
|
+
if (existsSync(lockPath)) {
|
|
144
|
+
// Check if the PID in the lock file is still alive
|
|
145
|
+
try {
|
|
146
|
+
const content = readFileSync(lockPath, 'utf-8');
|
|
147
|
+
const pid = parseInt(content.trim(), 10);
|
|
148
|
+
if (!isNaN(pid) && isPidAlive(pid)) {
|
|
149
|
+
return false; // Another instance is running
|
|
150
|
+
}
|
|
151
|
+
// Stale lock -- remove and try again
|
|
152
|
+
unlinkSync(lockPath);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// If we can't read the lock, try to recreate it
|
|
156
|
+
try {
|
|
157
|
+
unlinkSync(lockPath);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const fd = openSync(lockPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
|
|
166
|
+
const content = Buffer.from(String(process.pid));
|
|
167
|
+
writeSync(fd, content);
|
|
168
|
+
closeSync(fd);
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function releaseLock(lockPath) {
|
|
176
|
+
try {
|
|
177
|
+
unlinkSync(lockPath);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
/* best effort */
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function isPidAlive(pid) {
|
|
184
|
+
try {
|
|
185
|
+
process.kill(pid, 0);
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// --- Watcher creation ---
|
|
193
|
+
function createWatcherForSession(registration, onStateChange, getState) {
|
|
194
|
+
return createEscalationWatcher(registration.escalationDir, {
|
|
195
|
+
onEscalation: (request) => {
|
|
196
|
+
const newState = addEscalation(getState(), registration.sessionId, request);
|
|
197
|
+
onStateChange(newState);
|
|
198
|
+
// BEL character alerts the user
|
|
199
|
+
process.stderr.write('\x07');
|
|
200
|
+
},
|
|
201
|
+
onEscalationExpired: (escalationId) => {
|
|
202
|
+
const newState = expireEscalation(getState(), registration.sessionId, escalationId);
|
|
203
|
+
onStateChange(newState);
|
|
204
|
+
},
|
|
205
|
+
}, { pollIntervalMs: 300 });
|
|
206
|
+
}
|
|
207
|
+
function handleCommand(input, state) {
|
|
208
|
+
const parts = input.split(/\s+/);
|
|
209
|
+
const command = parts[0].toLowerCase();
|
|
210
|
+
switch (command) {
|
|
211
|
+
case '/approve': {
|
|
212
|
+
const arg = parts[1];
|
|
213
|
+
if (!arg)
|
|
214
|
+
return { state, message: chalk.yellow('Usage: /approve <number> or /approve all') };
|
|
215
|
+
if (arg === 'all') {
|
|
216
|
+
return approveOrDenyAll(state, 'approved');
|
|
217
|
+
}
|
|
218
|
+
const num = parseInt(arg, 10);
|
|
219
|
+
if (isNaN(num))
|
|
220
|
+
return { state, message: chalk.yellow('Invalid escalation number') };
|
|
221
|
+
return approveOrDeny(state, num, 'approved');
|
|
222
|
+
}
|
|
223
|
+
case '/deny': {
|
|
224
|
+
const arg = parts[1];
|
|
225
|
+
if (!arg)
|
|
226
|
+
return { state, message: chalk.yellow('Usage: /deny <number> or /deny all') };
|
|
227
|
+
if (arg === 'all') {
|
|
228
|
+
return approveOrDenyAll(state, 'denied');
|
|
229
|
+
}
|
|
230
|
+
const num = parseInt(arg, 10);
|
|
231
|
+
if (isNaN(num))
|
|
232
|
+
return { state, message: chalk.yellow('Invalid escalation number') };
|
|
233
|
+
return approveOrDeny(state, num, 'denied');
|
|
234
|
+
}
|
|
235
|
+
case '/sessions':
|
|
236
|
+
return { state, message: formatSessionDetails(state) };
|
|
237
|
+
case '/quit':
|
|
238
|
+
case '/q':
|
|
239
|
+
return { state, quit: true };
|
|
240
|
+
default:
|
|
241
|
+
return {
|
|
242
|
+
state,
|
|
243
|
+
message: chalk.yellow('Unknown command. Available: /approve <N>, /deny <N>, /approve all, /deny all, /sessions, /quit'),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function approveOrDeny(state, displayNumber, decision) {
|
|
248
|
+
const escalation = state.pendingEscalations.get(displayNumber);
|
|
249
|
+
if (!escalation) {
|
|
250
|
+
return { state, message: chalk.yellow(`No pending escalation #${displayNumber}`) };
|
|
251
|
+
}
|
|
252
|
+
const session = state.sessions.get(escalation.sessionId);
|
|
253
|
+
if (!session) {
|
|
254
|
+
return { state, message: chalk.yellow(`Session for escalation #${displayNumber} no longer active`) };
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
const accepted = session.watcher.resolve(escalation.request.escalationId, decision);
|
|
258
|
+
const newState = resolveEscalation(state, displayNumber, decision);
|
|
259
|
+
if (!accepted) {
|
|
260
|
+
return {
|
|
261
|
+
state: newState,
|
|
262
|
+
message: chalk.yellow(`Escalation #${displayNumber} expired (response was too late)`),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const label = decision === 'approved' ? chalk.green('APPROVED') : chalk.red('DENIED');
|
|
266
|
+
return {
|
|
267
|
+
state: newState,
|
|
268
|
+
message: `Escalation #${displayNumber} ${label}`,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
return { state, message: chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function approveOrDenyAll(state, decision) {
|
|
276
|
+
if (state.pendingEscalations.size === 0) {
|
|
277
|
+
return { state, message: chalk.yellow('No pending escalations') };
|
|
278
|
+
}
|
|
279
|
+
let currentState = state;
|
|
280
|
+
const messages = [];
|
|
281
|
+
const nums = [...state.pendingEscalations.keys()].sort((a, b) => a - b);
|
|
282
|
+
for (const num of nums) {
|
|
283
|
+
const result = approveOrDeny(currentState, num, decision);
|
|
284
|
+
currentState = result.state;
|
|
285
|
+
if (result.message)
|
|
286
|
+
messages.push(result.message);
|
|
287
|
+
}
|
|
288
|
+
return { state: currentState, message: messages.join('\n') };
|
|
289
|
+
}
|
|
290
|
+
function formatSessionDetails(state) {
|
|
291
|
+
if (state.sessions.size === 0) {
|
|
292
|
+
return chalk.dim('No active sessions');
|
|
293
|
+
}
|
|
294
|
+
const lines = [];
|
|
295
|
+
for (const session of state.sessions.values()) {
|
|
296
|
+
const reg = session.registration;
|
|
297
|
+
const ago = formatTimeAgo(new Date(reg.startedAt));
|
|
298
|
+
lines.push(` [${session.displayNumber}] ${reg.sessionId.substring(0, 8)} ${reg.label} ${chalk.dim(ago)}`);
|
|
299
|
+
}
|
|
300
|
+
return lines.join('\n');
|
|
301
|
+
}
|
|
302
|
+
// --- Rendering ---
|
|
303
|
+
/**
|
|
304
|
+
* Builds the dashboard content as a string.
|
|
305
|
+
* Pure function — no side effects. The caller handles writing to the terminal
|
|
306
|
+
* and coordinating with readline's prompt.
|
|
307
|
+
*/
|
|
308
|
+
function buildDashboard(state) {
|
|
309
|
+
const lines = [];
|
|
310
|
+
const sessionCount = state.sessions.size;
|
|
311
|
+
const pendingCount = state.pendingEscalations.size;
|
|
312
|
+
// Header
|
|
313
|
+
lines.push(chalk.bold.cyan(' IronCurtain Escalation Listener') +
|
|
314
|
+
chalk.dim(` ${sessionCount} session${sessionCount !== 1 ? 's' : ''} active`));
|
|
315
|
+
lines.push(chalk.dim(' ' + '\u2500'.repeat(60)));
|
|
316
|
+
// Active Sessions
|
|
317
|
+
lines.push('');
|
|
318
|
+
lines.push(chalk.bold(' Active Sessions:'));
|
|
319
|
+
if (sessionCount === 0) {
|
|
320
|
+
lines.push(chalk.dim(' Waiting for PTY sessions...'));
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
for (const session of state.sessions.values()) {
|
|
324
|
+
const reg = session.registration;
|
|
325
|
+
const ago = formatTimeAgo(new Date(reg.startedAt));
|
|
326
|
+
const sessionPending = countPendingForSession(state, reg.sessionId);
|
|
327
|
+
const pendingLabel = sessionPending > 0 ? chalk.yellow(` ${sessionPending} pending`) : chalk.dim(' 0 pending');
|
|
328
|
+
lines.push(` [${chalk.bold(String(session.displayNumber))}] ` +
|
|
329
|
+
`${reg.sessionId.substring(0, 8)} ` +
|
|
330
|
+
`${reg.label.substring(0, 40)} ` +
|
|
331
|
+
chalk.dim(ago) +
|
|
332
|
+
pendingLabel);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Pending Escalations
|
|
336
|
+
lines.push('');
|
|
337
|
+
lines.push(chalk.dim(' ' + '\u2500'.repeat(60)));
|
|
338
|
+
lines.push('');
|
|
339
|
+
lines.push(chalk.bold(' Pending Escalations:') + (pendingCount === 0 ? chalk.dim(' (none)') : ''));
|
|
340
|
+
if (pendingCount > 0) {
|
|
341
|
+
const sorted = [...state.pendingEscalations.values()].sort((a, b) => a.displayNumber - b.displayNumber);
|
|
342
|
+
for (const esc of sorted) {
|
|
343
|
+
lines.push('');
|
|
344
|
+
lines.push(` [${chalk.bold.yellow(String(esc.displayNumber))}] ` +
|
|
345
|
+
`Session #${esc.sessionDisplayNumber} (${esc.sessionId.substring(0, 8)})`);
|
|
346
|
+
lines.push(` Tool: ${chalk.cyan(esc.request.serverName + '/' + esc.request.toolName)}`);
|
|
347
|
+
const argPreview = formatArgPreview(esc.request.arguments);
|
|
348
|
+
if (argPreview) {
|
|
349
|
+
lines.push(` Args: ${argPreview}`);
|
|
350
|
+
}
|
|
351
|
+
lines.push(` Reason: ${esc.request.reason}`);
|
|
352
|
+
if (esc.request.context) {
|
|
353
|
+
for (const [k, v] of Object.entries(esc.request.context)) {
|
|
354
|
+
lines.push(` ${k}: ${chalk.yellow(v)}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// History
|
|
360
|
+
if (state.history.length > 0) {
|
|
361
|
+
lines.push('');
|
|
362
|
+
lines.push(chalk.dim(' ' + '\u2500'.repeat(60)));
|
|
363
|
+
lines.push('');
|
|
364
|
+
lines.push(chalk.bold(' History (last 10):'));
|
|
365
|
+
const recent = state.history.slice(0, 10);
|
|
366
|
+
for (const entry of recent) {
|
|
367
|
+
const time = entry.resolvedAt.toLocaleTimeString();
|
|
368
|
+
const decisionLabel = entry.decision === 'approved' ? chalk.green('APPROVED') : chalk.red('DENIED');
|
|
369
|
+
lines.push(` [${chalk.dim(time)}] ` + `${entry.serverName}/${entry.toolName} ` + decisionLabel);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// Command hint (prompt itself is handled by readline)
|
|
373
|
+
lines.push('');
|
|
374
|
+
lines.push(chalk.dim(' ' + '\u2500'.repeat(60)));
|
|
375
|
+
lines.push(chalk.dim(' /approve N | /deny N | /approve all | /deny all | /quit'));
|
|
376
|
+
return lines.join('\n') + '\n';
|
|
377
|
+
}
|
|
378
|
+
// --- Helpers ---
|
|
379
|
+
function countPendingForSession(state, sessionId) {
|
|
380
|
+
let count = 0;
|
|
381
|
+
for (const esc of state.pendingEscalations.values()) {
|
|
382
|
+
if (esc.sessionId === sessionId)
|
|
383
|
+
count++;
|
|
384
|
+
}
|
|
385
|
+
return count;
|
|
386
|
+
}
|
|
387
|
+
function formatTimeAgo(date) {
|
|
388
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
389
|
+
if (seconds < 60)
|
|
390
|
+
return `${seconds}s ago`;
|
|
391
|
+
const minutes = Math.floor(seconds / 60);
|
|
392
|
+
if (minutes < 60)
|
|
393
|
+
return `${minutes}m ago`;
|
|
394
|
+
const hours = Math.floor(minutes / 60);
|
|
395
|
+
return `${hours}h ago`;
|
|
396
|
+
}
|
|
397
|
+
function formatArgPreview(args) {
|
|
398
|
+
const parts = [];
|
|
399
|
+
for (const [key, value] of Object.entries(args)) {
|
|
400
|
+
if (typeof value === 'string') {
|
|
401
|
+
const display = value.length > 60 ? value.substring(0, 57) + '...' : value;
|
|
402
|
+
parts.push(`${key}: ${display}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return parts.slice(0, 3).join(', ');
|
|
406
|
+
}
|
|
407
|
+
//# sourceMappingURL=listener-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listener-command.js","sourceRoot":"","sources":["../../src/escalation/listener-command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State management for the escalation listener TUI.
|
|
3
|
+
*
|
|
4
|
+
* Immutable state updates -- each mutation returns a new state object.
|
|
5
|
+
* The rendering layer reads state and redraws on change.
|
|
6
|
+
*/
|
|
7
|
+
import type { PtySessionRegistration } from '../docker/pty-types.js';
|
|
8
|
+
import type { EscalationWatcher } from './escalation-watcher.js';
|
|
9
|
+
import type { EscalationRequest } from '../session/types.js';
|
|
10
|
+
export interface ActiveSession {
|
|
11
|
+
readonly registration: PtySessionRegistration;
|
|
12
|
+
readonly watcher: EscalationWatcher;
|
|
13
|
+
/** Sequential display number assigned when the session was first detected. */
|
|
14
|
+
readonly displayNumber: number;
|
|
15
|
+
}
|
|
16
|
+
export interface PendingEscalation {
|
|
17
|
+
readonly sessionId: string;
|
|
18
|
+
readonly sessionDisplayNumber: number;
|
|
19
|
+
readonly request: EscalationRequest;
|
|
20
|
+
/** Monotonically increasing display number for /approve and /deny commands. */
|
|
21
|
+
readonly displayNumber: number;
|
|
22
|
+
readonly receivedAt: Date;
|
|
23
|
+
}
|
|
24
|
+
export interface ResolvedEscalation {
|
|
25
|
+
readonly sessionId: string;
|
|
26
|
+
readonly toolName: string;
|
|
27
|
+
readonly serverName: string;
|
|
28
|
+
readonly decision: 'approved' | 'denied';
|
|
29
|
+
readonly resolvedAt: Date;
|
|
30
|
+
}
|
|
31
|
+
export interface ListenerState {
|
|
32
|
+
readonly sessions: ReadonlyMap<string, ActiveSession>;
|
|
33
|
+
readonly pendingEscalations: ReadonlyMap<number, PendingEscalation>;
|
|
34
|
+
readonly history: readonly ResolvedEscalation[];
|
|
35
|
+
readonly nextEscalationNumber: number;
|
|
36
|
+
readonly nextSessionNumber: number;
|
|
37
|
+
}
|
|
38
|
+
/** Creates an empty initial state. */
|
|
39
|
+
export declare function createInitialState(): ListenerState;
|
|
40
|
+
/** Adds a new session to the state. */
|
|
41
|
+
export declare function addSession(state: ListenerState, registration: PtySessionRegistration, watcher: EscalationWatcher): ListenerState;
|
|
42
|
+
/** Removes a session and its pending escalations from the state. */
|
|
43
|
+
export declare function removeSession(state: ListenerState, sessionId: string): ListenerState;
|
|
44
|
+
/** Adds a new pending escalation to the state. */
|
|
45
|
+
export declare function addEscalation(state: ListenerState, sessionId: string, request: EscalationRequest): ListenerState;
|
|
46
|
+
/** Resolves a pending escalation and moves it to history. */
|
|
47
|
+
export declare function resolveEscalation(state: ListenerState, displayNumber: number, decision: 'approved' | 'denied'): ListenerState;
|
|
48
|
+
/** Removes a pending escalation that expired. */
|
|
49
|
+
export declare function expireEscalation(state: ListenerState, sessionId: string, escalationId: string): ListenerState;
|