@pentoshi/clai 3.11.7 → 3.11.9
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 +15 -0
- package/dist/agent/confirm-port.d.ts +4 -0
- package/dist/agent/confirm-port.js +9 -1
- package/dist/agent/confirm-port.js.map +1 -1
- package/dist/agent/finalize-gate.d.ts +57 -0
- package/dist/agent/finalize-gate.js +146 -0
- package/dist/agent/finalize-gate.js.map +1 -0
- package/dist/agent/runner.js +71 -179
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/tool-call-parser.js +3 -0
- package/dist/agent/tool-call-parser.js.map +1 -1
- package/dist/app/adapters/current-interactive-sessions-adapter.d.ts +4 -0
- package/dist/app/adapters/current-interactive-sessions-adapter.js +12 -0
- package/dist/app/adapters/current-interactive-sessions-adapter.js.map +1 -0
- package/dist/app/controllers/cancel-all-result.d.ts +8 -0
- package/dist/app/controllers/cancel-all-result.js +32 -0
- package/dist/app/controllers/cancel-all-result.js.map +1 -0
- package/dist/app/controllers/session-controller.d.ts +3 -0
- package/dist/app/controllers/session-controller.js +22 -6
- package/dist/app/controllers/session-controller.js.map +1 -1
- package/dist/app/ports/interactive-sessions-port.d.ts +22 -0
- package/dist/app/ports/interactive-sessions-port.js +9 -0
- package/dist/app/ports/interactive-sessions-port.js.map +1 -0
- package/dist/index.js +24 -13
- package/dist/index.js.map +1 -1
- package/dist/interactive-session/artifact-writer.d.ts +54 -0
- package/dist/interactive-session/artifact-writer.js +161 -0
- package/dist/interactive-session/artifact-writer.js.map +1 -0
- package/dist/interactive-session/cleanup.d.ts +37 -0
- package/dist/interactive-session/cleanup.js +191 -0
- package/dist/interactive-session/cleanup.js.map +1 -0
- package/dist/interactive-session/config.d.ts +118 -0
- package/dist/interactive-session/config.js +141 -0
- package/dist/interactive-session/config.js.map +1 -0
- package/dist/interactive-session/input-policy.d.ts +26 -0
- package/dist/interactive-session/input-policy.js +70 -0
- package/dist/interactive-session/input-policy.js.map +1 -0
- package/dist/interactive-session/manager.d.ts +118 -0
- package/dist/interactive-session/manager.js +1214 -0
- package/dist/interactive-session/manager.js.map +1 -0
- package/dist/interactive-session/output-store.d.ts +81 -0
- package/dist/interactive-session/output-store.js +239 -0
- package/dist/interactive-session/output-store.js.map +1 -0
- package/dist/interactive-session/output-view.d.ts +20 -0
- package/dist/interactive-session/output-view.js +142 -0
- package/dist/interactive-session/output-view.js.map +1 -0
- package/dist/interactive-session/recovery-journal.d.ts +53 -0
- package/dist/interactive-session/recovery-journal.js +167 -0
- package/dist/interactive-session/recovery-journal.js.map +1 -0
- package/dist/interactive-session/registry.d.ts +59 -0
- package/dist/interactive-session/registry.js +170 -0
- package/dist/interactive-session/registry.js.map +1 -0
- package/dist/interactive-session/runtime.d.ts +107 -0
- package/dist/interactive-session/runtime.js +191 -0
- package/dist/interactive-session/runtime.js.map +1 -0
- package/dist/interactive-session/session-runtime.d.ts +53 -0
- package/dist/interactive-session/session-runtime.js +65 -0
- package/dist/interactive-session/session-runtime.js.map +1 -0
- package/dist/interactive-session/streaming-redactor.d.ts +53 -0
- package/dist/interactive-session/streaming-redactor.js +271 -0
- package/dist/interactive-session/streaming-redactor.js.map +1 -0
- package/dist/interactive-session/telemetry.d.ts +30 -0
- package/dist/interactive-session/telemetry.js +47 -0
- package/dist/interactive-session/telemetry.js.map +1 -0
- package/dist/interactive-session/transport-factory.d.ts +8 -0
- package/dist/interactive-session/transport-factory.js +16 -0
- package/dist/interactive-session/transport-factory.js.map +1 -0
- package/dist/interactive-session/transport-node-pty.d.ts +38 -0
- package/dist/interactive-session/transport-node-pty.js +390 -0
- package/dist/interactive-session/transport-node-pty.js.map +1 -0
- package/dist/interactive-session/transport-pipe.d.ts +9 -0
- package/dist/interactive-session/transport-pipe.js +267 -0
- package/dist/interactive-session/transport-pipe.js.map +1 -0
- package/dist/interactive-session/transport.d.ts +110 -0
- package/dist/interactive-session/transport.js +89 -0
- package/dist/interactive-session/transport.js.map +1 -0
- package/dist/interactive-session/types.d.ts +258 -0
- package/dist/interactive-session/types.js +191 -0
- package/dist/interactive-session/types.js.map +1 -0
- package/dist/os/process-identity.d.ts +46 -0
- package/dist/os/process-identity.js +147 -0
- package/dist/os/process-identity.js.map +1 -0
- package/dist/os/process-tree.d.ts +1 -0
- package/dist/os/process-tree.js +11 -0
- package/dist/os/process-tree.js.map +1 -1
- package/dist/prompts/embedded.js +1 -1
- package/dist/prompts/embedded.js.map +1 -1
- package/dist/prompts/index.d.ts +7 -0
- package/dist/prompts/index.js +23 -0
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/system.agent.md +3 -0
- package/dist/repl.js +16 -1
- package/dist/repl.js.map +1 -1
- package/dist/safety/classifier.d.ts +9 -0
- package/dist/safety/classifier.js +119 -18
- package/dist/safety/classifier.js.map +1 -1
- package/dist/safety/engagement-policy.d.ts +13 -0
- package/dist/safety/engagement-policy.js +129 -34
- package/dist/safety/engagement-policy.js.map +1 -1
- package/dist/tools/definitions.js +94 -0
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/interactive-session-tools.d.ts +11 -0
- package/dist/tools/interactive-session-tools.js +276 -0
- package/dist/tools/interactive-session-tools.js.map +1 -0
- package/dist/tools/registry.js +3 -0
- package/dist/tools/registry.js.map +1 -1
- package/dist/tui-v2/bootstrap/composition-root.d.ts +3 -0
- package/dist/tui-v2/bootstrap/composition-root.js +3 -0
- package/dist/tui-v2/bootstrap/composition-root.js.map +1 -1
- package/dist/tui-v2/bootstrap/start-tui-v2.js +8 -0
- package/dist/tui-v2/bootstrap/start-tui-v2.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/version.generated.d.ts +2 -2
- package/dist/version.generated.js +2 -2
- package/package.json +7 -6
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crash-recovery journal for live interactive sessions.
|
|
3
|
+
*
|
|
4
|
+
* Interactive sessions are never reattachable: this file exists only so a crash
|
|
5
|
+
* cannot leave an orphaned process tree behind. It therefore stores cleanup
|
|
6
|
+
* evidence and nothing else — no command, cwd, input, environment, or output.
|
|
7
|
+
*/
|
|
8
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
9
|
+
import { mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { getDataDir } from "../store/paths.js";
|
|
12
|
+
import { processIdentityTracker, } from "../os/process-identity.js";
|
|
13
|
+
import { terminateProcessTree, } from "../os/process-tree.js";
|
|
14
|
+
const JOURNAL_FILE = "registry-v1.json";
|
|
15
|
+
const JOURNAL_PREFIX = "registry-v1-";
|
|
16
|
+
const SCHEMA_VERSION = 1;
|
|
17
|
+
const DEFAULT_DIRECTORY = join(getDataDir(), "interactive-sessions");
|
|
18
|
+
export function hashOwner(ownerId) {
|
|
19
|
+
return createHash("sha256").update(ownerId).digest("hex").slice(0, 16);
|
|
20
|
+
}
|
|
21
|
+
const defaultProcessDeps = {
|
|
22
|
+
compareIdentity: (pid, identity) => processIdentityTracker.compare(pid, identity),
|
|
23
|
+
terminateTree: terminateProcessTree,
|
|
24
|
+
};
|
|
25
|
+
export class RecoveryJournal {
|
|
26
|
+
directory;
|
|
27
|
+
path;
|
|
28
|
+
process;
|
|
29
|
+
sharedDirectory;
|
|
30
|
+
manager;
|
|
31
|
+
records = new Map();
|
|
32
|
+
constructor(directory = DEFAULT_DIRECTORY, processDeps = {}) {
|
|
33
|
+
this.directory = directory;
|
|
34
|
+
this.sharedDirectory = directory === DEFAULT_DIRECTORY;
|
|
35
|
+
this.manager = this.sharedDirectory
|
|
36
|
+
? {
|
|
37
|
+
pid: process.pid,
|
|
38
|
+
identity: processIdentityTracker.capture(process.pid, {
|
|
39
|
+
refresh: true,
|
|
40
|
+
}),
|
|
41
|
+
}
|
|
42
|
+
: undefined;
|
|
43
|
+
this.path = this.sharedDirectory
|
|
44
|
+
? join(this.directory, `${JOURNAL_PREFIX}${process.pid}-${randomUUID()}.json`)
|
|
45
|
+
: join(this.directory, JOURNAL_FILE);
|
|
46
|
+
this.process = { ...defaultProcessDeps, ...processDeps };
|
|
47
|
+
}
|
|
48
|
+
upsert(record) {
|
|
49
|
+
this.records.set(record.id, record);
|
|
50
|
+
this.persist();
|
|
51
|
+
}
|
|
52
|
+
upsertDurable(record) {
|
|
53
|
+
this.records.set(record.id, record);
|
|
54
|
+
return this.persist();
|
|
55
|
+
}
|
|
56
|
+
remove(id) {
|
|
57
|
+
if (!this.records.delete(id))
|
|
58
|
+
return;
|
|
59
|
+
this.persist();
|
|
60
|
+
}
|
|
61
|
+
load() {
|
|
62
|
+
return this.readPersisted(this.path)?.sessions ?? [];
|
|
63
|
+
}
|
|
64
|
+
reconcile() {
|
|
65
|
+
const results = [];
|
|
66
|
+
for (const path of this.reconciliationPaths()) {
|
|
67
|
+
const persisted = this.readPersisted(path);
|
|
68
|
+
if (!persisted)
|
|
69
|
+
continue;
|
|
70
|
+
if (persisted.manager) {
|
|
71
|
+
const managerState = this.process.compareIdentity(persisted.manager.pid, persisted.manager.identity);
|
|
72
|
+
if (managerState === "match" || managerState === "unknown")
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const retained = [];
|
|
76
|
+
for (const record of persisted.sessions) {
|
|
77
|
+
const outcome = this.reconcileOne(record);
|
|
78
|
+
results.push({ id: record.id, outcome });
|
|
79
|
+
if (outcome === "unverified")
|
|
80
|
+
retained.push(record);
|
|
81
|
+
}
|
|
82
|
+
this.writePersisted(path, persisted.manager, retained);
|
|
83
|
+
}
|
|
84
|
+
this.records = new Map(this.load().map((record) => [record.id, record]));
|
|
85
|
+
return results;
|
|
86
|
+
}
|
|
87
|
+
reconciliationPaths() {
|
|
88
|
+
if (!this.sharedDirectory)
|
|
89
|
+
return [this.path];
|
|
90
|
+
try {
|
|
91
|
+
return readdirSync(this.directory, { withFileTypes: true })
|
|
92
|
+
.filter((entry) => entry.isFile() &&
|
|
93
|
+
(entry.name === JOURNAL_FILE ||
|
|
94
|
+
(entry.name.startsWith(JOURNAL_PREFIX) &&
|
|
95
|
+
entry.name.endsWith(".json"))))
|
|
96
|
+
.map((entry) => join(this.directory, entry.name));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
readPersisted(path) {
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
105
|
+
if (parsed?.schemaVersion !== SCHEMA_VERSION ||
|
|
106
|
+
!Array.isArray(parsed.sessions)) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
return parsed;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
reconcileOne(record) {
|
|
116
|
+
if (!record.launchConfirmed || !record.pid)
|
|
117
|
+
return "unverified";
|
|
118
|
+
const comparison = this.process.compareIdentity(record.pid, record.identity);
|
|
119
|
+
if (comparison === "gone")
|
|
120
|
+
return "gone";
|
|
121
|
+
if (comparison === "mismatch")
|
|
122
|
+
return "identity-mismatch";
|
|
123
|
+
if (comparison === "unknown")
|
|
124
|
+
return "unverified";
|
|
125
|
+
const outcome = this.process.terminateTree(record.pid, {
|
|
126
|
+
signal: "SIGKILL",
|
|
127
|
+
...(record.processGroupId !== undefined
|
|
128
|
+
? { processGroupId: record.processGroupId }
|
|
129
|
+
: {}),
|
|
130
|
+
});
|
|
131
|
+
if (outcome === "failed")
|
|
132
|
+
return "unverified";
|
|
133
|
+
return outcome === "gone" ? "gone" : "terminated";
|
|
134
|
+
}
|
|
135
|
+
persist() {
|
|
136
|
+
return this.writePersisted(this.path, this.manager, [...this.records.values()]);
|
|
137
|
+
}
|
|
138
|
+
writePersisted(path, manager, sessions) {
|
|
139
|
+
try {
|
|
140
|
+
if (sessions.length === 0) {
|
|
141
|
+
rmSync(path, { force: true });
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
mkdirSync(this.directory, { recursive: true, mode: 0o700 });
|
|
145
|
+
const payload = {
|
|
146
|
+
schemaVersion: SCHEMA_VERSION,
|
|
147
|
+
...(manager ? { manager } : {}),
|
|
148
|
+
sessions,
|
|
149
|
+
};
|
|
150
|
+
const temp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
151
|
+
writeFileSync(temp, `${JSON.stringify(payload)}\n`, { mode: 0o600 });
|
|
152
|
+
renameSync(temp, path);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
clear() {
|
|
160
|
+
this.records.clear();
|
|
161
|
+
try {
|
|
162
|
+
rmSync(this.path, { force: true });
|
|
163
|
+
}
|
|
164
|
+
catch { }
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=recovery-journal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery-journal.js","sourceRoot":"","sources":["../../src/interactive-session/recovery-journal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owner-scoped registry for interactive sessions.
|
|
3
|
+
*
|
|
4
|
+
* Records carry no command, input, environment value, prompt, or output — only
|
|
5
|
+
* opaque identity, state, cursors, and outcome metadata. Interactive records are
|
|
6
|
+
* intentionally separate from `JobManager` records: the two domains have
|
|
7
|
+
* different ownership, policy, and cleanup contracts.
|
|
8
|
+
*/
|
|
9
|
+
import { type InteractiveSessionRecord, type ProcessOutcome, type SessionState, type SessionSummary, type TerminationReason } from "./types.js";
|
|
10
|
+
export declare function canTransition(from: SessionState, to: SessionState): boolean;
|
|
11
|
+
export declare class SessionRegistry {
|
|
12
|
+
private readonly records;
|
|
13
|
+
private readonly byOwner;
|
|
14
|
+
/** Process-lifetime issued ids: never reused, even after removal. */
|
|
15
|
+
private readonly issuedIds;
|
|
16
|
+
private readonly ownerLocks;
|
|
17
|
+
private readonly fencedOwners;
|
|
18
|
+
/**
|
|
19
|
+
* Serialize id issuance and live-slot reservation for one owner so two
|
|
20
|
+
* concurrent starts cannot both pass the live limit.
|
|
21
|
+
*/
|
|
22
|
+
withOwnerLock<T>(ownerId: string, fn: () => Promise<T> | T): Promise<T>;
|
|
23
|
+
mintId(): string;
|
|
24
|
+
liveCount(ownerId: string): number;
|
|
25
|
+
insert(record: InteractiveSessionRecord): void;
|
|
26
|
+
/**
|
|
27
|
+
* Owner mismatch is indistinguishable from a missing id: callers must not be
|
|
28
|
+
* able to use this lookup as an existence oracle for another conversation.
|
|
29
|
+
*/
|
|
30
|
+
get(ownerId: string, id: string): InteractiveSessionRecord | undefined;
|
|
31
|
+
/** Unscoped access for cleanup/lifecycle paths that already own the record. */
|
|
32
|
+
getUnscoped(id: string): InteractiveSessionRecord | undefined;
|
|
33
|
+
liveRecords(ownerId: string): InteractiveSessionRecord[];
|
|
34
|
+
allLiveRecords(): InteractiveSessionRecord[];
|
|
35
|
+
owners(): string[];
|
|
36
|
+
/** Live first, then descending start time, capped for model consumption. */
|
|
37
|
+
list(ownerId: string): SessionSummary[];
|
|
38
|
+
/**
|
|
39
|
+
* Compare-and-transition. Returns false when the transition is not allowed,
|
|
40
|
+
* which is how a late exit observation loses to a committed terminal state.
|
|
41
|
+
*/
|
|
42
|
+
transition(record: InteractiveSessionRecord, to: SessionState, patch?: {
|
|
43
|
+
terminationReason?: TerminationReason | undefined;
|
|
44
|
+
processOutcome?: ProcessOutcome | undefined;
|
|
45
|
+
cleanupVerified?: boolean | undefined;
|
|
46
|
+
now?: number | undefined;
|
|
47
|
+
}): boolean;
|
|
48
|
+
/** Add outcome facts without replacing a chosen terminal state. */
|
|
49
|
+
enrich(record: InteractiveSessionRecord, patch: {
|
|
50
|
+
terminationReason?: TerminationReason | undefined;
|
|
51
|
+
processOutcome?: ProcessOutcome | undefined;
|
|
52
|
+
cleanupVerified?: boolean | undefined;
|
|
53
|
+
}): void;
|
|
54
|
+
/** Fence an owner before its id is rebound so no new operation can enter. */
|
|
55
|
+
fenceOwner(ownerId: string): void;
|
|
56
|
+
unfenceOwner(ownerId: string): void;
|
|
57
|
+
isFenced(ownerId: string): boolean;
|
|
58
|
+
private pruneTerminal;
|
|
59
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owner-scoped registry for interactive sessions.
|
|
3
|
+
*
|
|
4
|
+
* Records carry no command, input, environment value, prompt, or output — only
|
|
5
|
+
* opaque identity, state, cursors, and outcome metadata. Interactive records are
|
|
6
|
+
* intentionally separate from `JobManager` records: the two domains have
|
|
7
|
+
* different ownership, policy, and cleanup contracts.
|
|
8
|
+
*/
|
|
9
|
+
import { randomUUID } from "node:crypto";
|
|
10
|
+
import { MAX_LIST_SUMMARIES } from "./config.js";
|
|
11
|
+
import { AsyncMutex } from "./runtime.js";
|
|
12
|
+
import { isTerminalState, toSummary, } from "./types.js";
|
|
13
|
+
const SESSION_ID_PREFIX = "its_";
|
|
14
|
+
/** Terminal summaries retained per owner; live records are never evicted. */
|
|
15
|
+
const MAX_TERMINAL_RECORDS_PER_OWNER = MAX_LIST_SUMMARIES;
|
|
16
|
+
const ALLOWED_TRANSITIONS = {
|
|
17
|
+
starting: ["running", "exited", "failed"],
|
|
18
|
+
running: ["closing", "exited", "failed"],
|
|
19
|
+
closing: ["closed", "exited", "failed"],
|
|
20
|
+
exited: [],
|
|
21
|
+
failed: [],
|
|
22
|
+
closed: [],
|
|
23
|
+
};
|
|
24
|
+
export function canTransition(from, to) {
|
|
25
|
+
return ALLOWED_TRANSITIONS[from].includes(to);
|
|
26
|
+
}
|
|
27
|
+
export class SessionRegistry {
|
|
28
|
+
records = new Map();
|
|
29
|
+
byOwner = new Map();
|
|
30
|
+
/** Process-lifetime issued ids: never reused, even after removal. */
|
|
31
|
+
issuedIds = new Set();
|
|
32
|
+
ownerLocks = new Map();
|
|
33
|
+
fencedOwners = new Set();
|
|
34
|
+
/**
|
|
35
|
+
* Serialize id issuance and live-slot reservation for one owner so two
|
|
36
|
+
* concurrent starts cannot both pass the live limit.
|
|
37
|
+
*/
|
|
38
|
+
withOwnerLock(ownerId, fn) {
|
|
39
|
+
let lock = this.ownerLocks.get(ownerId);
|
|
40
|
+
if (!lock) {
|
|
41
|
+
lock = new AsyncMutex();
|
|
42
|
+
this.ownerLocks.set(ownerId, lock);
|
|
43
|
+
}
|
|
44
|
+
return lock.run(fn);
|
|
45
|
+
}
|
|
46
|
+
mintId() {
|
|
47
|
+
for (;;) {
|
|
48
|
+
const candidate = `${SESSION_ID_PREFIX}${randomUUID()}`;
|
|
49
|
+
if (this.issuedIds.has(candidate))
|
|
50
|
+
continue;
|
|
51
|
+
this.issuedIds.add(candidate);
|
|
52
|
+
return candidate;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
liveCount(ownerId) {
|
|
56
|
+
let count = 0;
|
|
57
|
+
for (const id of this.byOwner.get(ownerId) ?? []) {
|
|
58
|
+
const record = this.records.get(id);
|
|
59
|
+
if (record && !isTerminalState(record.state))
|
|
60
|
+
count += 1;
|
|
61
|
+
}
|
|
62
|
+
return count;
|
|
63
|
+
}
|
|
64
|
+
insert(record) {
|
|
65
|
+
this.records.set(record.id, record);
|
|
66
|
+
let owned = this.byOwner.get(record.ownerId);
|
|
67
|
+
if (!owned) {
|
|
68
|
+
owned = new Set();
|
|
69
|
+
this.byOwner.set(record.ownerId, owned);
|
|
70
|
+
}
|
|
71
|
+
owned.add(record.id);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Owner mismatch is indistinguishable from a missing id: callers must not be
|
|
75
|
+
* able to use this lookup as an existence oracle for another conversation.
|
|
76
|
+
*/
|
|
77
|
+
get(ownerId, id) {
|
|
78
|
+
const record = this.records.get(id);
|
|
79
|
+
return record && record.ownerId === ownerId ? record : undefined;
|
|
80
|
+
}
|
|
81
|
+
/** Unscoped access for cleanup/lifecycle paths that already own the record. */
|
|
82
|
+
getUnscoped(id) {
|
|
83
|
+
return this.records.get(id);
|
|
84
|
+
}
|
|
85
|
+
liveRecords(ownerId) {
|
|
86
|
+
return [...(this.byOwner.get(ownerId) ?? [])]
|
|
87
|
+
.map((id) => this.records.get(id))
|
|
88
|
+
.filter((record) => record !== undefined && !isTerminalState(record.state));
|
|
89
|
+
}
|
|
90
|
+
allLiveRecords() {
|
|
91
|
+
return [...this.records.values()].filter((record) => !isTerminalState(record.state));
|
|
92
|
+
}
|
|
93
|
+
owners() {
|
|
94
|
+
return [...this.byOwner.keys()];
|
|
95
|
+
}
|
|
96
|
+
/** Live first, then descending start time, capped for model consumption. */
|
|
97
|
+
list(ownerId) {
|
|
98
|
+
const records = [...(this.byOwner.get(ownerId) ?? [])]
|
|
99
|
+
.map((id) => this.records.get(id))
|
|
100
|
+
.filter((record) => record !== undefined);
|
|
101
|
+
records.sort((a, b) => {
|
|
102
|
+
const aLive = isTerminalState(a.state) ? 1 : 0;
|
|
103
|
+
const bLive = isTerminalState(b.state) ? 1 : 0;
|
|
104
|
+
if (aLive !== bLive)
|
|
105
|
+
return aLive - bLive;
|
|
106
|
+
return b.startedAt - a.startedAt;
|
|
107
|
+
});
|
|
108
|
+
return records.slice(0, MAX_LIST_SUMMARIES).map(toSummary);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Compare-and-transition. Returns false when the transition is not allowed,
|
|
112
|
+
* which is how a late exit observation loses to a committed terminal state.
|
|
113
|
+
*/
|
|
114
|
+
transition(record, to, patch = {}) {
|
|
115
|
+
if (!canTransition(record.state, to)) {
|
|
116
|
+
// A terminal record may still be enriched with late outcome metadata.
|
|
117
|
+
if (isTerminalState(record.state))
|
|
118
|
+
this.enrich(record, patch);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
record.state = to;
|
|
122
|
+
if (isTerminalState(to)) {
|
|
123
|
+
record.endedAt = patch.now ?? record.endedAt ?? Date.now();
|
|
124
|
+
if (patch.terminationReason)
|
|
125
|
+
record.terminationReason = patch.terminationReason;
|
|
126
|
+
if (patch.processOutcome)
|
|
127
|
+
record.processOutcome = patch.processOutcome;
|
|
128
|
+
if (patch.cleanupVerified !== undefined) {
|
|
129
|
+
record.cleanupVerified = patch.cleanupVerified;
|
|
130
|
+
}
|
|
131
|
+
this.pruneTerminal(record.ownerId);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
this.enrich(record, patch);
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
/** Add outcome facts without replacing a chosen terminal state. */
|
|
139
|
+
enrich(record, patch) {
|
|
140
|
+
record.terminationReason ??= patch.terminationReason;
|
|
141
|
+
record.processOutcome ??= patch.processOutcome;
|
|
142
|
+
if (record.cleanupVerified === undefined && patch.cleanupVerified !== undefined) {
|
|
143
|
+
record.cleanupVerified = patch.cleanupVerified;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** Fence an owner before its id is rebound so no new operation can enter. */
|
|
147
|
+
fenceOwner(ownerId) {
|
|
148
|
+
this.fencedOwners.add(ownerId);
|
|
149
|
+
}
|
|
150
|
+
unfenceOwner(ownerId) {
|
|
151
|
+
this.fencedOwners.delete(ownerId);
|
|
152
|
+
}
|
|
153
|
+
isFenced(ownerId) {
|
|
154
|
+
return this.fencedOwners.has(ownerId);
|
|
155
|
+
}
|
|
156
|
+
pruneTerminal(ownerId) {
|
|
157
|
+
const owned = this.byOwner.get(ownerId);
|
|
158
|
+
if (!owned)
|
|
159
|
+
return;
|
|
160
|
+
const terminal = [...owned]
|
|
161
|
+
.map((id) => this.records.get(id))
|
|
162
|
+
.filter((record) => record !== undefined && isTerminalState(record.state))
|
|
163
|
+
.sort((a, b) => (b.endedAt ?? b.startedAt) - (a.endedAt ?? a.startedAt));
|
|
164
|
+
for (const record of terminal.slice(MAX_TERMINAL_RECORDS_PER_OWNER)) {
|
|
165
|
+
this.records.delete(record.id);
|
|
166
|
+
owned.delete(record.id);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/interactive-session/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session concurrency primitives.
|
|
3
|
+
*
|
|
4
|
+
* The locking model is deliberately fine-grained: one owner-level mutex guards
|
|
5
|
+
* id issuance and live-slot reservation, one mutation mutex per session guards
|
|
6
|
+
* state/input/resize decisions, and output ingestion never takes either lock so
|
|
7
|
+
* a chatty child cannot block Close.
|
|
8
|
+
*/
|
|
9
|
+
/** Monotonic time source. Wall time is only used for receipts, never deadlines. */
|
|
10
|
+
export interface Clock {
|
|
11
|
+
now(): number;
|
|
12
|
+
setTimeout(handler: () => void, ms: number): TimerHandle;
|
|
13
|
+
clearTimeout(handle: TimerHandle): void;
|
|
14
|
+
}
|
|
15
|
+
export type TimerHandle = {
|
|
16
|
+
readonly id: unknown;
|
|
17
|
+
};
|
|
18
|
+
export declare const systemClock: Clock;
|
|
19
|
+
export declare class AsyncMutex {
|
|
20
|
+
private tail;
|
|
21
|
+
/** Serialize `fn`; rejections do not poison the queue. */
|
|
22
|
+
run<T>(fn: () => Promise<T> | T): Promise<T>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Single-winner terminal work. Every later caller joins the first promise, so
|
|
26
|
+
* cleanup executes exactly once no matter how many triggers race.
|
|
27
|
+
*/
|
|
28
|
+
export declare class FinalizeOnce<T> {
|
|
29
|
+
private promise;
|
|
30
|
+
private winnerReason;
|
|
31
|
+
get started(): boolean;
|
|
32
|
+
/** The reason that won the race, useful for choosing a terminal state. */
|
|
33
|
+
get reason(): string | undefined;
|
|
34
|
+
run(reason: string, fn: () => Promise<T>): Promise<T>;
|
|
35
|
+
join(): Promise<T> | undefined;
|
|
36
|
+
}
|
|
37
|
+
export type Unsubscribe = () => void;
|
|
38
|
+
/** Version-counter subscription used by blocking reads and send gathering. */
|
|
39
|
+
export declare class Notifier {
|
|
40
|
+
private version;
|
|
41
|
+
private readonly listeners;
|
|
42
|
+
get current(): number;
|
|
43
|
+
bump(): void;
|
|
44
|
+
subscribe(listener: (version: number) => void): Unsubscribe;
|
|
45
|
+
clear(): void;
|
|
46
|
+
}
|
|
47
|
+
export interface SessionTimers {
|
|
48
|
+
idle?: TimerHandle | undefined;
|
|
49
|
+
lifetime?: TimerHandle | undefined;
|
|
50
|
+
}
|
|
51
|
+
/** Idle resets on activity; lifetime is fixed from confirmed launch. */
|
|
52
|
+
export declare class TimerSet {
|
|
53
|
+
private readonly clock;
|
|
54
|
+
private readonly onExpire;
|
|
55
|
+
private readonly timers;
|
|
56
|
+
constructor(clock: Clock, onExpire: (reason: "idle-timeout" | "lifetime-timeout") => void);
|
|
57
|
+
armIdle(ms: number | undefined): void;
|
|
58
|
+
armLifetime(ms: number | undefined): void;
|
|
59
|
+
clearAll(): void;
|
|
60
|
+
}
|
|
61
|
+
/** Resolve/reject handle for one in-flight wait, removable on cancel. */
|
|
62
|
+
export interface Waiter {
|
|
63
|
+
cancel(): void;
|
|
64
|
+
}
|
|
65
|
+
export declare class WaiterSet {
|
|
66
|
+
private readonly waiters;
|
|
67
|
+
add(waiter: Waiter): Unsubscribe;
|
|
68
|
+
cancelAll(): void;
|
|
69
|
+
get size(): number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* One accepted input action. Immutable once queued: the payload is never
|
|
73
|
+
* rewritten, and delivery is attempted exactly once.
|
|
74
|
+
*/
|
|
75
|
+
export interface QueuedInputAction {
|
|
76
|
+
readonly sequence: number;
|
|
77
|
+
readonly queuedBytes: number;
|
|
78
|
+
/** Cursor captured when the action was accepted, used as the default page start. */
|
|
79
|
+
readonly cursorAtAcceptance: number;
|
|
80
|
+
readonly deliver: () => Promise<InputDeliveryOutcome>;
|
|
81
|
+
readonly settle: (outcome: InputDeliveryOutcome) => void;
|
|
82
|
+
}
|
|
83
|
+
export interface InputDeliveryOutcome {
|
|
84
|
+
readonly status: "delivered" | "not-delivered" | "unknown";
|
|
85
|
+
readonly deliveredBytes: number;
|
|
86
|
+
readonly cause?: unknown;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* FIFO writer lane. Accepted actions are written strictly in ascending sequence
|
|
90
|
+
* order by a single drain loop, so concurrent sends can never interleave bytes
|
|
91
|
+
* or reorder commands.
|
|
92
|
+
*/
|
|
93
|
+
export declare class OrderedInputQueue {
|
|
94
|
+
private readonly queue;
|
|
95
|
+
private draining;
|
|
96
|
+
private nextSequence;
|
|
97
|
+
private queuedBytes;
|
|
98
|
+
get depth(): number;
|
|
99
|
+
get pendingBytes(): number;
|
|
100
|
+
peekSequence(): number;
|
|
101
|
+
/** Reserve a sequence number and queued-byte budget for one action. */
|
|
102
|
+
reserve(bytes: number): number;
|
|
103
|
+
enqueue(action: QueuedInputAction): void;
|
|
104
|
+
private drain;
|
|
105
|
+
/** Reject every undelivered entry, e.g. after process exit or close. */
|
|
106
|
+
rejectAll(outcome: InputDeliveryOutcome): void;
|
|
107
|
+
}
|