@cjhyy/code-shell-server 0.9.51 → 0.10.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/dist/account/cli.d.ts +23 -0
- package/dist/account/cli.js +249 -0
- package/dist/account/cloud-contract.d.ts +39 -0
- package/dist/account/cloud-contract.js +134 -0
- package/dist/account/cloud-types.d.ts +47 -0
- package/dist/account/cloud-types.js +1 -0
- package/dist/account/feature-service.d.ts +47 -0
- package/dist/account/feature-service.js +184 -0
- package/dist/account/http.d.ts +17 -0
- package/dist/account/http.js +158 -0
- package/dist/account/links.d.ts +13 -0
- package/dist/account/links.js +137 -0
- package/dist/account/manager.d.ts +96 -0
- package/dist/account/manager.js +976 -0
- package/dist/account/native-lock.d.ts +6 -0
- package/dist/account/native-lock.js +26 -0
- package/dist/account/native-store.d.ts +32 -0
- package/dist/account/native-store.js +162 -0
- package/dist/account/profile.d.ts +4 -0
- package/dist/account/profile.js +46 -0
- package/dist/account/store.d.ts +34 -0
- package/dist/account/store.js +118 -0
- package/dist/account/types.d.ts +138 -0
- package/dist/account/types.js +2 -0
- package/dist/desktop-web/http-api.d.ts +4 -0
- package/dist/desktop-web/http-api.js +12 -4
- package/dist/environment-identity.d.ts +1 -1
- package/dist/environment-identity.js +2 -1
- package/dist/headless-worker-env.d.ts +2 -0
- package/dist/headless-worker-env.js +15 -0
- package/dist/hub/approval-lease.js +6 -1
- package/dist/hub/configuration.d.ts +11 -0
- package/dist/hub/configuration.js +34 -1
- package/dist/hub/model-parameter-metadata.d.ts +26 -0
- package/dist/hub/model-parameter-metadata.js +92 -0
- package/dist/hub/run-replay.js +1 -1
- package/dist/hub/session-management.js +1 -1
- package/dist/index.account.d.ts +12 -0
- package/dist/index.account.js +12 -0
- package/dist/index.mobile-remote.d.ts +2 -0
- package/dist/index.mobile-remote.js +1 -0
- package/dist/index.worker.d.ts +1 -0
- package/dist/index.worker.js +1 -0
- package/dist/mobile-remote/account-relay-access.d.ts +59 -0
- package/dist/mobile-remote/account-relay-access.js +174 -0
- package/dist/mobile-remote/approval-clock.d.ts +3 -0
- package/dist/mobile-remote/approval-clock.js +15 -0
- package/dist/mobile-remote/codex-room-agent.d.ts +6 -0
- package/dist/mobile-remote/codex-room-agent.js +17 -1
- package/dist/mobile-remote/mobile-chat-turn.d.ts +4 -0
- package/dist/mobile-remote/mobile-chat-turn.js +3 -0
- package/dist/mobile-remote/mobile-client-event-validator.js +47 -2
- package/dist/mobile-remote/mobile-upload-service.d.ts +9 -0
- package/dist/mobile-remote/mobile-upload-service.js +25 -0
- package/dist/mobile-remote/remote-host-manager.d.ts +21 -4
- package/dist/mobile-remote/remote-host-manager.js +142 -25
- package/dist/mobile-remote/resident-agent.d.ts +14 -0
- package/dist/mobile-remote/resident-agent.js +38 -11
- package/dist/mobile-remote/room-history.d.ts +7 -0
- package/dist/mobile-remote/room-history.js +56 -1
- package/dist/mobile-remote/room-manager.d.ts +216 -9
- package/dist/mobile-remote/room-manager.js +700 -65
- package/dist/mobile-remote/room-process-stop.d.ts +15 -0
- package/dist/mobile-remote/room-process-stop.js +41 -0
- package/dist/mobile-remote/trusted-device-store.d.ts +12 -0
- package/dist/mobile-remote/trusted-device-store.js +54 -1
- package/dist/mobile-remote/turn-queue.d.ts +183 -0
- package/dist/mobile-remote/turn-queue.js +422 -0
- package/dist/mobile-remote/types.d.ts +1 -1
- package/dist/panels/agent-task-host.js +6 -12
- package/dist/panels/agent-task-service.d.ts +2 -0
- package/dist/panels/agent-task-service.js +7 -0
- package/dist/panels/browser-capture.d.ts +1 -15
- package/dist/panels/browser-capture.js +1 -35
- package/dist/panels/directory-picker.d.ts +48 -0
- package/dist/panels/directory-picker.js +247 -0
- package/dist/panels/http.d.ts +15 -15
- package/dist/panels/management-http.d.ts +9 -9
- package/dist/panels/process-service.d.ts +5 -4
- package/dist/panels/process-service.js +54 -16
- package/dist/panels/runtime.js +148 -30
- package/dist/panels/tool-jobs-storage.d.ts +4 -0
- package/dist/panels/tool-jobs-storage.js +111 -34
- package/dist/panels/types.d.ts +1 -97
- package/dist/remote-relay/connector.d.ts +8 -0
- package/dist/remote-relay/connector.js +29 -2
- package/dist/remote-relay/protocol.d.ts +21 -0
- package/dist/remote-relay/protocol.js +31 -0
- package/dist/serve/cli.js +6 -0
- package/dist/serve/headless-server.js +24 -19
- package/dist/sessions-service.d.ts +4 -0
- package/dist/sessions-service.js +14 -1
- package/package.json +10 -6
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { releaseStdioAfterExit, stopRoomProcess } from "./room-process-stop.js";
|
|
2
|
+
import { releaseStdioAfterExit, stopRoomProcess, stopRoomProcessAndWait, waitForRoomProcessSpawn, } from "./room-process-stop.js";
|
|
3
3
|
import { createInterface } from "node:readline";
|
|
4
4
|
import { pathWithCommonBins } from "./path-bins.js";
|
|
5
5
|
/**
|
|
@@ -119,12 +119,15 @@ export function parseStreamJsonLine(line) {
|
|
|
119
119
|
export class ResidentAgentProcess {
|
|
120
120
|
opts;
|
|
121
121
|
child;
|
|
122
|
+
/** The most recently spawned process, kept after stop() so a caller can
|
|
123
|
+
* wait for it to actually exit before starting a replacement. */
|
|
124
|
+
lastChild;
|
|
125
|
+
spawned;
|
|
122
126
|
constructor(opts) {
|
|
123
127
|
this.opts = opts;
|
|
124
128
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return;
|
|
129
|
+
/** Exact argv for one resident process. Pure → unit-testable. */
|
|
130
|
+
static argsFor(opts) {
|
|
128
131
|
const args = [
|
|
129
132
|
"--print",
|
|
130
133
|
"--verbose",
|
|
@@ -135,13 +138,17 @@ export class ResidentAgentProcess {
|
|
|
135
138
|
"--permission-prompt-tool",
|
|
136
139
|
"stdio",
|
|
137
140
|
];
|
|
138
|
-
if (
|
|
139
|
-
args.push("--append-system-prompt",
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
if (opts.appendSystemPrompt)
|
|
142
|
+
args.push("--append-system-prompt", opts.appendSystemPrompt);
|
|
143
|
+
if (opts.resumeSessionId)
|
|
144
|
+
args.push("--resume", opts.resumeSessionId);
|
|
145
|
+
args.push("--permission-mode", opts.permissionMode);
|
|
146
|
+
return args;
|
|
147
|
+
}
|
|
148
|
+
start() {
|
|
149
|
+
if (this.child)
|
|
150
|
+
return;
|
|
151
|
+
const args = ResidentAgentProcess.argsFor(this.opts);
|
|
145
152
|
const child = spawn(this.opts.command, args, {
|
|
146
153
|
cwd: this.opts.cwd,
|
|
147
154
|
env: { ...process.env, PATH: pathWithCommonBins() },
|
|
@@ -149,6 +156,10 @@ export class ResidentAgentProcess {
|
|
|
149
156
|
stdio: ["pipe", "pipe", "pipe"],
|
|
150
157
|
});
|
|
151
158
|
this.child = child;
|
|
159
|
+
this.lastChild = child;
|
|
160
|
+
this.spawned = waitForRoomProcessSpawn(child);
|
|
161
|
+
// Observed by whenStarted(); an unobserved failed spawn is reported via onEvent.
|
|
162
|
+
this.spawned.catch(() => undefined);
|
|
152
163
|
// Settle on `close` so trailing output is read, without waiting on grandchildren.
|
|
153
164
|
releaseStdioAfterExit(child);
|
|
154
165
|
if (child.stdout) {
|
|
@@ -231,4 +242,20 @@ export class ResidentAgentProcess {
|
|
|
231
242
|
if (child)
|
|
232
243
|
stopRoomProcess(child);
|
|
233
244
|
}
|
|
245
|
+
/** Resolves once the spawned process exists; rejects when the spawn failed. */
|
|
246
|
+
whenStarted() {
|
|
247
|
+
return this.spawned ?? Promise.resolve();
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Stop, then resolve true only after the last spawned process has exited.
|
|
251
|
+
* A process already retired by a pipe error is still waited for: it may be
|
|
252
|
+
* alive for up to the SIGKILL grace period.
|
|
253
|
+
*/
|
|
254
|
+
stopAndWait(timeoutMs) {
|
|
255
|
+
const child = this.child ?? this.lastChild;
|
|
256
|
+
this.child = undefined;
|
|
257
|
+
if (!child)
|
|
258
|
+
return Promise.resolve(true);
|
|
259
|
+
return stopRoomProcessAndWait(child, timeoutMs);
|
|
260
|
+
}
|
|
234
261
|
}
|
|
@@ -14,6 +14,13 @@ export interface RoomHistoryRequest {
|
|
|
14
14
|
export declare function scanRoomHistory<T extends {
|
|
15
15
|
seq: number;
|
|
16
16
|
}>(file: string, parse: (value: unknown) => T | undefined, visit?: (message: T) => void): number;
|
|
17
|
+
/** Same strict snapshot proof as scanRoomHistory, yielding between bounded reads. */
|
|
18
|
+
export declare function scanRoomHistoryAsync<T extends {
|
|
19
|
+
seq: number;
|
|
20
|
+
}>(file: string, parse: (value: unknown) => T | undefined, visit?: (message: T) => void): Promise<{
|
|
21
|
+
sequence: number;
|
|
22
|
+
stamp: string;
|
|
23
|
+
}>;
|
|
17
24
|
/** Durable immutable copies keep a frozen page boundary valid across Host restarts. */
|
|
18
25
|
export declare function readRoomHistoryPage<T extends {
|
|
19
26
|
seq: number;
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { closeSync, constants, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, writeFileSync, writeSync, } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { lstat, open } from "node:fs/promises";
|
|
4
5
|
const MAX_BYTES = 256 * 1024 * 1024;
|
|
5
6
|
const MAX_LINE = 4 * 1024 * 1024;
|
|
6
7
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
7
8
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
8
9
|
function regular(path) {
|
|
9
|
-
|
|
10
|
+
return assertRegular(lstatSync(path));
|
|
11
|
+
}
|
|
12
|
+
function assertRegular(stat) {
|
|
10
13
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1) {
|
|
11
14
|
throw new Error("room history must be a private regular file");
|
|
12
15
|
}
|
|
@@ -99,6 +102,58 @@ export function scanRoomHistory(file, parse, visit) {
|
|
|
99
102
|
closeSync(fd);
|
|
100
103
|
}
|
|
101
104
|
}
|
|
105
|
+
/** Same strict snapshot proof as scanRoomHistory, yielding between bounded reads. */
|
|
106
|
+
export async function scanRoomHistoryAsync(file, parse, visit) {
|
|
107
|
+
const initial = assertRegular(await lstat(file));
|
|
108
|
+
const handle = await open(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
109
|
+
try {
|
|
110
|
+
const before = assertRegular(await handle.stat());
|
|
111
|
+
if (stamp(initial) !== stamp(before))
|
|
112
|
+
throw new Error("room history changed while opening");
|
|
113
|
+
if (before.size > MAX_BYTES)
|
|
114
|
+
throw new Error("room history exceeds 256 MiB; archive it before continuing");
|
|
115
|
+
let offset = 0, seq = 0, partialBytes = 0;
|
|
116
|
+
let parts = [];
|
|
117
|
+
while (offset < before.size) {
|
|
118
|
+
const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, before.size - offset));
|
|
119
|
+
const { bytesRead } = await handle.read(chunk, 0, chunk.length, offset);
|
|
120
|
+
if (!bytesRead)
|
|
121
|
+
throw new Error("room history was truncated while reading");
|
|
122
|
+
offset += bytesRead;
|
|
123
|
+
let from = 0;
|
|
124
|
+
while (from < bytesRead) {
|
|
125
|
+
const next = chunk.indexOf(0x0a, from);
|
|
126
|
+
const newline = next >= 0 && next < bytesRead ? next : -1;
|
|
127
|
+
const end = newline < 0 ? bytesRead : newline;
|
|
128
|
+
const piece = chunk.subarray(from, end);
|
|
129
|
+
partialBytes += piece.length;
|
|
130
|
+
if (partialBytes > MAX_LINE)
|
|
131
|
+
throw new Error("room history record exceeds size limit");
|
|
132
|
+
parts.push(piece);
|
|
133
|
+
if (newline < 0)
|
|
134
|
+
break;
|
|
135
|
+
const bytes = parts.length === 1 ? parts[0] : Buffer.concat(parts, partialBytes);
|
|
136
|
+
const message = parse(JSON.parse(decoder.decode(bytes)));
|
|
137
|
+
if (!message || message.seq !== seq + 1)
|
|
138
|
+
throw new Error(`room history is corrupt at sequence ${seq + 1}`);
|
|
139
|
+
seq = message.seq;
|
|
140
|
+
visit?.(message);
|
|
141
|
+
parts = [];
|
|
142
|
+
partialBytes = 0;
|
|
143
|
+
from = newline + 1;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (partialBytes)
|
|
147
|
+
throw new Error("room history has an incomplete final record");
|
|
148
|
+
if (stamp(before) !== stamp(await handle.stat()) ||
|
|
149
|
+
stamp(before) !== stamp(assertRegular(await lstat(file))))
|
|
150
|
+
throw new Error("room history changed while scanning");
|
|
151
|
+
return { sequence: seq, stamp: stamp(before) };
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
await handle.close();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
102
157
|
function validateManifest(value, roomId, snapshotId) {
|
|
103
158
|
if (!value ||
|
|
104
159
|
value.version !== 1 ||
|
|
@@ -57,6 +57,54 @@ export interface RoomMeta {
|
|
|
57
57
|
linkedSessionMode?: "observe-only";
|
|
58
58
|
}
|
|
59
59
|
export type RoomOpenStatus = "running" | "missing" | "observing";
|
|
60
|
+
/** Why a room cannot change its spawn-time permission mode right now. */
|
|
61
|
+
export type RoomBusyReason = "switching" | "retiring" | "turn" | "approval" | "queued";
|
|
62
|
+
export declare class RoomBusyError extends Error {
|
|
63
|
+
readonly reason: RoomBusyReason;
|
|
64
|
+
constructor(reason: RoomBusyReason);
|
|
65
|
+
}
|
|
66
|
+
/** Stable code of RoomInputAcceptedError; callers branch on it, never on text. */
|
|
67
|
+
export declare const ROOM_INPUT_ACCEPTED = "ROOM_INPUT_ACCEPTED";
|
|
68
|
+
/**
|
|
69
|
+
* The CLI accepted the input, but recording it (history, trace, echo) failed.
|
|
70
|
+
* The turn may be running: it must never be retried as if it were rejected.
|
|
71
|
+
*/
|
|
72
|
+
export declare class RoomInputAcceptedError extends Error {
|
|
73
|
+
readonly code = "ROOM_INPUT_ACCEPTED";
|
|
74
|
+
constructor(cause: unknown);
|
|
75
|
+
}
|
|
76
|
+
/** The room or its process changed between planning and applying a mode change. */
|
|
77
|
+
export declare class RoomStateChangedError extends Error {
|
|
78
|
+
constructor(message?: string);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Read-only plan for opening an external CLI session under a mode. A Host may
|
|
82
|
+
* ask for authorization between plan and apply; apply re-validates every field.
|
|
83
|
+
*/
|
|
84
|
+
export interface RoomSessionOpenPlan {
|
|
85
|
+
/** Existing room id, or an id reserved for the room apply() will create. */
|
|
86
|
+
roomId: string;
|
|
87
|
+
created: boolean;
|
|
88
|
+
sessionId: string;
|
|
89
|
+
/** Canonical directory the CLI process will run in. */
|
|
90
|
+
cwd: string;
|
|
91
|
+
kind: RoomKind;
|
|
92
|
+
/** Live process mode, else persisted mode; undefined for a new room. */
|
|
93
|
+
fromMode?: RoomPermissionMode;
|
|
94
|
+
toMode: RoomPermissionMode;
|
|
95
|
+
/** False when metadata and any live process already run in toMode. */
|
|
96
|
+
change: boolean;
|
|
97
|
+
/** True when a process is live now (an unchanged open will reuse it). */
|
|
98
|
+
live: boolean;
|
|
99
|
+
observing: boolean;
|
|
100
|
+
revision: number;
|
|
101
|
+
}
|
|
102
|
+
export interface RoomSessionOpenResult {
|
|
103
|
+
roomId: string;
|
|
104
|
+
status: RoomOpenStatus;
|
|
105
|
+
mode: RoomPermissionMode;
|
|
106
|
+
changed: boolean;
|
|
107
|
+
}
|
|
60
108
|
export interface RoomMessage {
|
|
61
109
|
seq: number;
|
|
62
110
|
ts: number;
|
|
@@ -84,6 +132,15 @@ export interface RoomMessage {
|
|
|
84
132
|
size: number;
|
|
85
133
|
path: string;
|
|
86
134
|
}>;
|
|
135
|
+
/** Exact mobile intent receipt, durable alongside its accepted user turn. */
|
|
136
|
+
clientMessageId?: string;
|
|
137
|
+
clientDeviceId?: string;
|
|
138
|
+
clientIntentHash?: string;
|
|
139
|
+
}
|
|
140
|
+
export interface RoomInputReceipt {
|
|
141
|
+
clientMessageId: string;
|
|
142
|
+
clientDeviceId: string;
|
|
143
|
+
clientIntentHash: string;
|
|
87
144
|
}
|
|
88
145
|
export declare function roomTurnText(text: string, attachments: InputAttachmentMeta[]): string;
|
|
89
146
|
export declare function stripRoomAttachmentBlock(text: string): string;
|
|
@@ -96,6 +153,15 @@ export interface RoomAgent {
|
|
|
96
153
|
send(text: string): boolean;
|
|
97
154
|
isRunning(): boolean;
|
|
98
155
|
stop(): void;
|
|
156
|
+
/** Stop, then resolve true only after the process has really exited. A
|
|
157
|
+
* replacement for the same external session must not start before that. */
|
|
158
|
+
stopAndWait?(timeoutMs: number): Promise<boolean>;
|
|
159
|
+
/** True while a turn process is alive. Codex spawns one child per turn and
|
|
160
|
+
* that child can outlive the parsed turn_end. Agents without this hook are
|
|
161
|
+
* tracked by accepted-input / turn_end accounting. */
|
|
162
|
+
isBusy?(): boolean;
|
|
163
|
+
/** Resolves once the spawned process exists; rejects for a failed spawn. */
|
|
164
|
+
whenStarted?(): Promise<void>;
|
|
99
165
|
respondControl?(requestId: string, decision: {
|
|
100
166
|
behavior: "allow";
|
|
101
167
|
updatedInput?: unknown;
|
|
@@ -123,6 +189,19 @@ export interface RoomManagerOptions {
|
|
|
123
189
|
* external session. Checked before any resident agent can start on it, so
|
|
124
190
|
* desktop and phone entry points share one ownership gate. */
|
|
125
191
|
assertExternalSessionAvailable?: (externalSessionId: string, kind: RoomKind) => void;
|
|
192
|
+
/** Host start policy, checked before ANY resident agent is created (open,
|
|
193
|
+
* send's implicit open, session open, takeover). Throwing refuses the start.
|
|
194
|
+
* Persisted metadata alone is never an authorization: the Host decides. */
|
|
195
|
+
authorizeStart?: (room: Readonly<RoomMeta>) => void;
|
|
196
|
+
/** Bound on waiting for a retired process during a mode handoff. */
|
|
197
|
+
processExitTimeoutMs?: number;
|
|
198
|
+
/** Called after an explicit close() (not after a process exit). */
|
|
199
|
+
onRoomClosed?: (roomId: string) => void;
|
|
200
|
+
/** Called when a room's process state changed without a caller to report
|
|
201
|
+
* it: a failed mode handoff after the old process exited, a failed first
|
|
202
|
+
* start, or a retired process finally proven gone. Hosts republish the
|
|
203
|
+
* room's authoritative state to every view. */
|
|
204
|
+
onRoomStateChanged?: (roomId: string) => void;
|
|
126
205
|
/** Called whenever a room gains a new persisted message (push to phone). */
|
|
127
206
|
onMessage: (roomId: string, msg: RoomMessage) => void;
|
|
128
207
|
/** Called when a room's resident agent requests tool-use approval. For
|
|
@@ -156,6 +235,21 @@ export declare class RoomManager {
|
|
|
156
235
|
/** Permission mode each live agent was spawned with. The mode is a spawn-time
|
|
157
236
|
* CLI argument, so room metadata alone cannot prove what a process runs. */
|
|
158
237
|
private agentModes;
|
|
238
|
+
/** Accepted inputs minus turn_end per room, for agents without isBusy(). A
|
|
239
|
+
* Claude resident process queues several inputs; each ends with a result. */
|
|
240
|
+
private turnCounts;
|
|
241
|
+
/** In-flight mode handoff per room. close() deletes it, so a handoff that is
|
|
242
|
+
* waiting for the old process can never reopen a room the user closed. */
|
|
243
|
+
private transitions;
|
|
244
|
+
/** Detached processes not yet proven exited. This is a process-safety fence,
|
|
245
|
+
* independent of any operation: close() and cancellation leave it in place,
|
|
246
|
+
* and no agent may start for the room until the process has really exited,
|
|
247
|
+
* so one external session never has two writers. */
|
|
248
|
+
private retiring;
|
|
249
|
+
private retiringProbes;
|
|
250
|
+
/** Bumped on every change to a room's process or mode, so a Host approval
|
|
251
|
+
* granted for one state cannot be applied to another. */
|
|
252
|
+
private revisions;
|
|
159
253
|
private modelTraces;
|
|
160
254
|
/** Rooms whose visible output is currently sourced from the external CLI's
|
|
161
255
|
* transcript tail. While present, the resident agent's stdout mirror is
|
|
@@ -198,6 +292,9 @@ export declare class RoomManager {
|
|
|
198
292
|
private readonly rootRealPath;
|
|
199
293
|
private readonly rootIdentity;
|
|
200
294
|
private readonly failedWrites;
|
|
295
|
+
private readonly preparingInputReceipts;
|
|
296
|
+
private readonly inputReceipts;
|
|
297
|
+
private hostTurnHooks;
|
|
201
298
|
/** Drop all pending AskUser entries for a room. Called when its agent goes
|
|
202
299
|
* away (exit/close) so a request that can no longer be answered doesn't leak
|
|
203
300
|
* in the map (small memory leak) and its approval card doesn't hang until the
|
|
@@ -219,6 +316,8 @@ export declare class RoomManager {
|
|
|
219
316
|
claudeSessionId?: string;
|
|
220
317
|
linkedSessionMode?: "observe-only";
|
|
221
318
|
}): RoomMeta;
|
|
319
|
+
private mintRoomId;
|
|
320
|
+
private createRoomWithId;
|
|
222
321
|
listRooms(): RoomMeta[];
|
|
223
322
|
getRoom(id: string): RoomMeta | undefined;
|
|
224
323
|
private nextSeq;
|
|
@@ -250,14 +349,114 @@ export declare class RoomManager {
|
|
|
250
349
|
/** Persist normalized transcript-tail messages through the same seq/history/
|
|
251
350
|
* broadcast path as resident-agent output. */
|
|
252
351
|
ingestTranscriptMessages(id: string, messages: Omit<RoomMessage, "seq" | "ts">[]): void;
|
|
352
|
+
/** Monotonic per-room state revision; see `revisions`. */
|
|
353
|
+
roomRevision(id: string): number;
|
|
354
|
+
private bumpRevision;
|
|
355
|
+
/** The room bound to an external session. Reuse must match BOTH id and kind:
|
|
356
|
+
* a codex thread id and a claude session id share the `claudeSessionId`
|
|
357
|
+
* field, so a bare id match could hand back the other CLI's room. */
|
|
358
|
+
findSessionRoom(sessionId: string, kind: RoomKind): RoomMeta | undefined;
|
|
359
|
+
/** Spawn-time mode of the room's live process, if one is running. */
|
|
360
|
+
liveMode(id: string): RoomPermissionMode | undefined;
|
|
361
|
+
/**
|
|
362
|
+
* Why the room cannot change its process now. A mode change stops the
|
|
363
|
+
* process, so it must not interrupt queued/active turns, a pending tool
|
|
364
|
+
* approval or AskUserQuestion, or a decision that already left the approval
|
|
365
|
+
* bridge but has not been consumed here yet (its entry stays in the pending
|
|
366
|
+
* maps until respondApproval consumes it).
|
|
367
|
+
*/
|
|
368
|
+
roomBusyReason(id: string): RoomBusyReason | undefined;
|
|
369
|
+
/**
|
|
370
|
+
* The room's own execution state, independent of any Host-held pending
|
|
371
|
+
* turns: an active or queued CLI input (Codex: a live turn child), a mode
|
|
372
|
+
* handoff, or an unanswered approval. A Host scheduler waits on this.
|
|
373
|
+
*/
|
|
374
|
+
private executionBusyReason;
|
|
375
|
+
/**
|
|
376
|
+
* True while a new input must not be handed to the room: it is executing
|
|
377
|
+
* or awaiting an approval, switching modes, or a retired process may still
|
|
378
|
+
* write. Host pending turns are deliberately not counted here.
|
|
379
|
+
*/
|
|
380
|
+
isExecutionBusy(id: string): boolean;
|
|
381
|
+
/** Mode a new input would run under: the live process mode, else the stored one. */
|
|
382
|
+
effectiveMode(id: string): RoomPermissionMode | undefined;
|
|
253
383
|
/**
|
|
254
|
-
*
|
|
255
|
-
*
|
|
384
|
+
* Host pending-turn integration, installed once by the Host composition.
|
|
385
|
+
* `hasPendingTurns` blocks every mode change; `beforeClose` runs before an
|
|
386
|
+
* explicit close signals the process, so pending turns pause first.
|
|
256
387
|
*/
|
|
257
|
-
|
|
388
|
+
setHostTurnHooks(hooks: {
|
|
389
|
+
hasPendingTurns?: (roomId: string) => boolean;
|
|
390
|
+
beforeClose?: (roomId: string) => void;
|
|
391
|
+
}): void;
|
|
392
|
+
/**
|
|
393
|
+
* Plan opening an external session's room under `mode` without changing
|
|
394
|
+
* anything. Throws for a cwd that differs from the room's bound directory,
|
|
395
|
+
* an occupied external session, or a busy room whose mode would change.
|
|
396
|
+
*/
|
|
397
|
+
planSessionOpen(input: {
|
|
398
|
+
sessionId: string;
|
|
399
|
+
cwd: string;
|
|
400
|
+
kind: RoomKind;
|
|
401
|
+
mode: RoomPermissionMode;
|
|
402
|
+
}): RoomSessionOpenPlan;
|
|
403
|
+
/**
|
|
404
|
+
* Apply a plan from planSessionOpen. `authorize` runs before anything is
|
|
405
|
+
* changed and again right before the replacement process is created; a
|
|
406
|
+
* throw leaves metadata untouched (or restores it) and never starts a CLI.
|
|
407
|
+
* `revalidate` re-checks asynchronous Host facts after every wait for a
|
|
408
|
+
* retired process, before that final `authorize`, while the room's handoff
|
|
409
|
+
* is still owned by this call.
|
|
410
|
+
*/
|
|
411
|
+
applySessionOpen(plan: RoomSessionOpenPlan, options?: {
|
|
412
|
+
authorize?: () => void;
|
|
413
|
+
revalidate?: () => Promise<void>;
|
|
414
|
+
}): Promise<RoomSessionOpenResult>;
|
|
415
|
+
/**
|
|
416
|
+
* Open (or create) the room bound to an external session id, deduping by
|
|
417
|
+
* that id so a given session maps to exactly one room. A different mode is
|
|
418
|
+
* applied by a guarded process handoff. Hosts that must authorize a mode
|
|
419
|
+
* use planSessionOpen/applySessionOpen directly.
|
|
420
|
+
*/
|
|
421
|
+
openForSession(claudeSessionId: string, cwd: string, mode: RoomPermissionMode, kind?: RoomKind): Promise<{
|
|
258
422
|
roomId: string;
|
|
259
423
|
status: RoomOpenStatus;
|
|
260
|
-
|
|
424
|
+
mode: RoomPermissionMode;
|
|
425
|
+
}>;
|
|
426
|
+
/**
|
|
427
|
+
* Re-probe a retired process of the session's room (see `retiring`), so a
|
|
428
|
+
* plan made right afterwards is refused only while that process is really
|
|
429
|
+
* still alive. Never starts anything.
|
|
430
|
+
*/
|
|
431
|
+
settleSessionRoom(sessionId: string, kind: RoomKind): Promise<void>;
|
|
432
|
+
/** Resolves true once no retired process of the room can still be alive. */
|
|
433
|
+
private settleRetiring;
|
|
434
|
+
private probeRetired;
|
|
435
|
+
/** Synchronous entry points cannot wait: refuse, and re-probe in the
|
|
436
|
+
* background so the next attempt succeeds once the process is gone. */
|
|
437
|
+
private assertNotRetiring;
|
|
438
|
+
/** Request an exit probe; the shared probe publishes a proven exit once.
|
|
439
|
+
* A handoff that still owns the room reports its own outcome. */
|
|
440
|
+
private watchRetired;
|
|
441
|
+
private notifyRoomState;
|
|
442
|
+
/**
|
|
443
|
+
* Replace a room's process with one in `toMode`. permissionMode is a
|
|
444
|
+
* spawn-time CLI argument (`--permission-mode` / codex sandbox flags), so a
|
|
445
|
+
* live process cannot change it. Order matters:
|
|
446
|
+
* 1. refuse a busy room, before touching anything;
|
|
447
|
+
* 2. detach the old process, so its late output/exit belongs to nobody, and
|
|
448
|
+
* wait (bounded) until it has really exited — never two writers;
|
|
449
|
+
* 3. re-authorize, then persist the mode and start the replacement;
|
|
450
|
+
* 4. on any failure restore the previous mode. The old process is already
|
|
451
|
+
* gone at that point, so the room reports not running instead of
|
|
452
|
+
* pretending the old state came back, and every view is told so.
|
|
453
|
+
* The detached process stays in `retiring` until its exit is proven: a
|
|
454
|
+
* timeout, close() or cancellation ends this operation but not the fence.
|
|
455
|
+
* Transcript followers are not ended: the external session continues.
|
|
456
|
+
*/
|
|
457
|
+
private transitionRoom;
|
|
458
|
+
/** Start (or reuse) the room's agent and wait until a spawned process exists. */
|
|
459
|
+
private startAgent;
|
|
261
460
|
/** Bind an external CLI transcript to a room for observation only.
|
|
262
461
|
*
|
|
263
462
|
* This method never creates or starts a resident agent. The host must first
|
|
@@ -275,12 +474,12 @@ export declare class RoomManager {
|
|
|
275
474
|
* This is the linked-session operation that may create/start an agent. The
|
|
276
475
|
* external identity is revalidated at takeover time so stale links cannot
|
|
277
476
|
* silently start a different session. */
|
|
278
|
-
takeOverLinkedSession(roomId: string, externalSessionId: string, cwd: string, kind: RoomKind): {
|
|
477
|
+
takeOverLinkedSession(roomId: string, externalSessionId: string, cwd: string, kind: RoomKind): Promise<{
|
|
279
478
|
roomId: string;
|
|
280
479
|
status: "running";
|
|
281
480
|
mode: RoomPermissionMode;
|
|
282
481
|
cwd: string;
|
|
283
|
-
}
|
|
482
|
+
}>;
|
|
284
483
|
private requireLinkedSessionTarget;
|
|
285
484
|
/**
|
|
286
485
|
* Forward a phone-side approval decision to the room's resident agent.
|
|
@@ -301,13 +500,21 @@ export declare class RoomManager {
|
|
|
301
500
|
open(id: string): {
|
|
302
501
|
status: RoomOpenStatus;
|
|
303
502
|
};
|
|
304
|
-
|
|
305
|
-
private retireAgentUnlessMode;
|
|
503
|
+
private openAgent;
|
|
306
504
|
private onAgentEvent;
|
|
505
|
+
/** Prepare cold receipts outside the synchronous send path; concurrent intents share one scan. */
|
|
506
|
+
prepareInputReceipts(id: string): Promise<void>;
|
|
507
|
+
private buildInputReceipts;
|
|
508
|
+
private currentInputReceipts;
|
|
509
|
+
/** A replay never opens a new CLI, reclaims an attachment, or reuses a changed intent. */
|
|
510
|
+
hasAcceptedInput(id: string, receipt: RoomInputReceipt): boolean;
|
|
307
511
|
/** Post a user message: persist it, ensure agent running, feed it. */
|
|
308
|
-
send(id: string, text: string, attachments?: InputAttachmentMeta[]): boolean;
|
|
512
|
+
send(id: string, text: string, attachments?: InputAttachmentMeta[], receipt?: RoomInputReceipt): boolean;
|
|
513
|
+
private recordAcceptedInput;
|
|
309
514
|
close(id: string): void;
|
|
310
515
|
closeAll(): void;
|
|
516
|
+
/** True while the room's process runs, or a retired one is not yet proven
|
|
517
|
+
* gone (a view must not be told the room stopped while it may still write). */
|
|
311
518
|
isOpen(id: string): boolean;
|
|
312
519
|
/**
|
|
313
520
|
* Delete rooms whose last activity is older than maxAgeMs (idle-based GC,
|