@pentoshi/clai 3.9.3 → 3.9.4

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.
Files changed (94) hide show
  1. package/dist/agent/runner.d.ts +1 -1
  2. package/dist/agent/runner.js +9 -4
  3. package/dist/agent/runner.js.map +1 -1
  4. package/dist/app/adapters/current-interactive-sessions-adapter.d.ts +4 -0
  5. package/dist/app/adapters/current-interactive-sessions-adapter.js +11 -0
  6. package/dist/app/adapters/current-interactive-sessions-adapter.js.map +1 -0
  7. package/dist/app/controllers/cancel-all-result.d.ts +8 -0
  8. package/dist/app/controllers/cancel-all-result.js +32 -0
  9. package/dist/app/controllers/cancel-all-result.js.map +1 -0
  10. package/dist/app/controllers/session-controller.d.ts +8 -0
  11. package/dist/app/controllers/session-controller.js +20 -4
  12. package/dist/app/controllers/session-controller.js.map +1 -1
  13. package/dist/app/ports/interactive-sessions-port.d.ts +21 -0
  14. package/dist/app/ports/interactive-sessions-port.js +9 -0
  15. package/dist/app/ports/interactive-sessions-port.js.map +1 -0
  16. package/dist/index.js +21 -8
  17. package/dist/index.js.map +1 -1
  18. package/dist/interactive-session/artifact-writer.d.ts +54 -0
  19. package/dist/interactive-session/artifact-writer.js +161 -0
  20. package/dist/interactive-session/artifact-writer.js.map +1 -0
  21. package/dist/interactive-session/cleanup.d.ts +36 -0
  22. package/dist/interactive-session/cleanup.js +157 -0
  23. package/dist/interactive-session/cleanup.js.map +1 -0
  24. package/dist/interactive-session/config.d.ts +118 -0
  25. package/dist/interactive-session/config.js +141 -0
  26. package/dist/interactive-session/config.js.map +1 -0
  27. package/dist/interactive-session/input-policy.d.ts +41 -0
  28. package/dist/interactive-session/input-policy.js +163 -0
  29. package/dist/interactive-session/input-policy.js.map +1 -0
  30. package/dist/interactive-session/manager.d.ts +107 -0
  31. package/dist/interactive-session/manager.js +927 -0
  32. package/dist/interactive-session/manager.js.map +1 -0
  33. package/dist/interactive-session/output-store.d.ts +81 -0
  34. package/dist/interactive-session/output-store.js +239 -0
  35. package/dist/interactive-session/output-store.js.map +1 -0
  36. package/dist/interactive-session/output-view.d.ts +20 -0
  37. package/dist/interactive-session/output-view.js +142 -0
  38. package/dist/interactive-session/output-view.js.map +1 -0
  39. package/dist/interactive-session/recovery-journal.d.ts +55 -0
  40. package/dist/interactive-session/recovery-journal.js +115 -0
  41. package/dist/interactive-session/recovery-journal.js.map +1 -0
  42. package/dist/interactive-session/registry.d.ts +59 -0
  43. package/dist/interactive-session/registry.js +170 -0
  44. package/dist/interactive-session/registry.js.map +1 -0
  45. package/dist/interactive-session/runtime.d.ts +107 -0
  46. package/dist/interactive-session/runtime.js +191 -0
  47. package/dist/interactive-session/runtime.js.map +1 -0
  48. package/dist/interactive-session/session-runtime.d.ts +50 -0
  49. package/dist/interactive-session/session-runtime.js +63 -0
  50. package/dist/interactive-session/session-runtime.js.map +1 -0
  51. package/dist/interactive-session/streaming-redactor.d.ts +53 -0
  52. package/dist/interactive-session/streaming-redactor.js +263 -0
  53. package/dist/interactive-session/streaming-redactor.js.map +1 -0
  54. package/dist/interactive-session/telemetry.d.ts +30 -0
  55. package/dist/interactive-session/telemetry.js +47 -0
  56. package/dist/interactive-session/telemetry.js.map +1 -0
  57. package/dist/interactive-session/transport-factory.d.ts +8 -0
  58. package/dist/interactive-session/transport-factory.js +16 -0
  59. package/dist/interactive-session/transport-factory.js.map +1 -0
  60. package/dist/interactive-session/transport-node-pty.d.ts +49 -0
  61. package/dist/interactive-session/transport-node-pty.js +219 -0
  62. package/dist/interactive-session/transport-node-pty.js.map +1 -0
  63. package/dist/interactive-session/transport-pipe.d.ts +9 -0
  64. package/dist/interactive-session/transport-pipe.js +215 -0
  65. package/dist/interactive-session/transport-pipe.js.map +1 -0
  66. package/dist/interactive-session/transport.d.ts +104 -0
  67. package/dist/interactive-session/transport.js +89 -0
  68. package/dist/interactive-session/transport.js.map +1 -0
  69. package/dist/interactive-session/types.d.ts +254 -0
  70. package/dist/interactive-session/types.js +191 -0
  71. package/dist/interactive-session/types.js.map +1 -0
  72. package/dist/os/process-identity.d.ts +46 -0
  73. package/dist/os/process-identity.js +147 -0
  74. package/dist/os/process-identity.js.map +1 -0
  75. package/dist/repl.js +10 -0
  76. package/dist/repl.js.map +1 -1
  77. package/dist/tools/definitions.js +95 -0
  78. package/dist/tools/definitions.js.map +1 -1
  79. package/dist/tools/interactive-session-tools.d.ts +11 -0
  80. package/dist/tools/interactive-session-tools.js +261 -0
  81. package/dist/tools/interactive-session-tools.js.map +1 -0
  82. package/dist/tools/jobs.js +2 -70
  83. package/dist/tools/jobs.js.map +1 -1
  84. package/dist/tools/registry.js +4 -0
  85. package/dist/tools/registry.js.map +1 -1
  86. package/dist/tui-v2/bootstrap/composition-root.d.ts +3 -0
  87. package/dist/tui-v2/bootstrap/composition-root.js +3 -0
  88. package/dist/tui-v2/bootstrap/composition-root.js.map +1 -1
  89. package/dist/tui-v2/bootstrap/start-tui-v2.js +10 -0
  90. package/dist/tui-v2/bootstrap/start-tui-v2.js.map +1 -1
  91. package/dist/types.d.ts +5 -0
  92. package/dist/version.generated.d.ts +2 -2
  93. package/dist/version.generated.js +2 -2
  94. package/package.json +1 -1
@@ -0,0 +1,115 @@
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 } from "node:crypto";
9
+ import { mkdirSync, readFileSync, 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 SCHEMA_VERSION = 1;
16
+ export function hashOwner(ownerId) {
17
+ return createHash("sha256").update(ownerId).digest("hex").slice(0, 16);
18
+ }
19
+ const defaultProcessDeps = {
20
+ compareIdentity: (pid, identity) => processIdentityTracker.compare(pid, identity),
21
+ terminateTree: terminateProcessTree,
22
+ };
23
+ export class RecoveryJournal {
24
+ directory;
25
+ path;
26
+ process;
27
+ records = new Map();
28
+ constructor(directory = join(getDataDir(), "interactive-sessions"), processDeps = {}) {
29
+ this.directory = directory;
30
+ this.path = join(this.directory, JOURNAL_FILE);
31
+ this.process = { ...defaultProcessDeps, ...processDeps };
32
+ }
33
+ upsert(record) {
34
+ this.records.set(record.id, record);
35
+ this.persist();
36
+ }
37
+ remove(id) {
38
+ if (!this.records.delete(id))
39
+ return;
40
+ this.persist();
41
+ }
42
+ /** Load prior records without trusting any of them as running. */
43
+ load() {
44
+ try {
45
+ const parsed = JSON.parse(readFileSync(this.path, "utf8"));
46
+ if (parsed?.schemaVersion !== SCHEMA_VERSION || !Array.isArray(parsed.sessions)) {
47
+ return [];
48
+ }
49
+ return parsed.sessions;
50
+ }
51
+ catch {
52
+ return [];
53
+ }
54
+ }
55
+ /**
56
+ * Reconcile prior records before tools are enabled. Only an identity match
57
+ * authorizes signalling; `unknown` identity is reported unverified and left
58
+ * alone so a recycled pid is never killed.
59
+ */
60
+ reconcile() {
61
+ const prior = this.load();
62
+ const results = [];
63
+ for (const record of prior) {
64
+ results.push({ id: record.id, outcome: this.reconcileOne(record) });
65
+ }
66
+ this.records.clear();
67
+ this.persist();
68
+ return results;
69
+ }
70
+ reconcileOne(record) {
71
+ if (!record.launchConfirmed || !record.pid)
72
+ return "launch-failed";
73
+ const comparison = this.process.compareIdentity(record.pid, record.identity);
74
+ if (comparison === "gone")
75
+ return "gone";
76
+ if (comparison === "mismatch")
77
+ return "identity-mismatch";
78
+ if (comparison === "unknown")
79
+ return "unverified";
80
+ this.process.terminateTree(record.pid, {
81
+ signal: "SIGKILL",
82
+ ...(record.processGroupId !== undefined
83
+ ? { processGroupId: record.processGroupId }
84
+ : {}),
85
+ });
86
+ return "terminated";
87
+ }
88
+ persist() {
89
+ try {
90
+ mkdirSync(this.directory, { recursive: true, mode: 0o700 });
91
+ const payload = {
92
+ schemaVersion: SCHEMA_VERSION,
93
+ sessions: [...this.records.values()],
94
+ };
95
+ const temp = `${this.path}.${process.pid}.tmp`;
96
+ writeFileSync(temp, `${JSON.stringify(payload)}\n`, { mode: 0o600 });
97
+ renameSync(temp, this.path);
98
+ }
99
+ catch {
100
+ // A journal write failure must never block a session operation; the worst
101
+ // case is an unreconciled orphan, which cleanup already reports.
102
+ }
103
+ }
104
+ /** Remove the journal file entirely; used when no live sessions remain. */
105
+ clear() {
106
+ this.records.clear();
107
+ try {
108
+ rmSync(this.path, { force: true });
109
+ }
110
+ catch {
111
+ // Best effort.
112
+ }
113
+ }
114
+ }
115
+ //# 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
+ }
@@ -0,0 +1,191 @@
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
+ export const systemClock = {
10
+ now: () => Date.now(),
11
+ setTimeout(handler, ms) {
12
+ const timer = setTimeout(handler, ms);
13
+ timer.unref?.();
14
+ return { id: timer };
15
+ },
16
+ clearTimeout(handle) {
17
+ clearTimeout(handle.id);
18
+ },
19
+ };
20
+ export class AsyncMutex {
21
+ tail = Promise.resolve();
22
+ /** Serialize `fn`; rejections do not poison the queue. */
23
+ run(fn) {
24
+ const result = this.tail.then(fn);
25
+ this.tail = result.then(() => undefined, () => undefined);
26
+ return result;
27
+ }
28
+ }
29
+ /**
30
+ * Single-winner terminal work. Every later caller joins the first promise, so
31
+ * cleanup executes exactly once no matter how many triggers race.
32
+ */
33
+ export class FinalizeOnce {
34
+ promise;
35
+ winnerReason;
36
+ get started() {
37
+ return this.promise !== undefined;
38
+ }
39
+ /** The reason that won the race, useful for choosing a terminal state. */
40
+ get reason() {
41
+ return this.winnerReason;
42
+ }
43
+ run(reason, fn) {
44
+ if (!this.promise) {
45
+ this.winnerReason = reason;
46
+ this.promise = fn();
47
+ }
48
+ return this.promise;
49
+ }
50
+ join() {
51
+ return this.promise;
52
+ }
53
+ }
54
+ /** Version-counter subscription used by blocking reads and send gathering. */
55
+ export class Notifier {
56
+ version = 0;
57
+ listeners = new Set();
58
+ get current() {
59
+ return this.version;
60
+ }
61
+ bump() {
62
+ this.version += 1;
63
+ for (const listener of [...this.listeners])
64
+ listener(this.version);
65
+ }
66
+ subscribe(listener) {
67
+ this.listeners.add(listener);
68
+ return () => {
69
+ this.listeners.delete(listener);
70
+ };
71
+ }
72
+ clear() {
73
+ this.listeners.clear();
74
+ }
75
+ }
76
+ /** Idle resets on activity; lifetime is fixed from confirmed launch. */
77
+ export class TimerSet {
78
+ clock;
79
+ onExpire;
80
+ timers = {};
81
+ constructor(clock, onExpire) {
82
+ this.clock = clock;
83
+ this.onExpire = onExpire;
84
+ }
85
+ armIdle(ms) {
86
+ if (this.timers.idle)
87
+ this.clock.clearTimeout(this.timers.idle);
88
+ this.timers.idle = undefined;
89
+ if (ms === undefined)
90
+ return;
91
+ this.timers.idle = this.clock.setTimeout(() => {
92
+ this.timers.idle = undefined;
93
+ this.onExpire("idle-timeout");
94
+ }, ms);
95
+ }
96
+ armLifetime(ms) {
97
+ if (this.timers.lifetime || ms === undefined)
98
+ return;
99
+ this.timers.lifetime = this.clock.setTimeout(() => {
100
+ this.timers.lifetime = undefined;
101
+ this.onExpire("lifetime-timeout");
102
+ }, ms);
103
+ }
104
+ clearAll() {
105
+ if (this.timers.idle)
106
+ this.clock.clearTimeout(this.timers.idle);
107
+ if (this.timers.lifetime)
108
+ this.clock.clearTimeout(this.timers.lifetime);
109
+ this.timers.idle = undefined;
110
+ this.timers.lifetime = undefined;
111
+ }
112
+ }
113
+ export class WaiterSet {
114
+ waiters = new Set();
115
+ add(waiter) {
116
+ this.waiters.add(waiter);
117
+ return () => {
118
+ this.waiters.delete(waiter);
119
+ };
120
+ }
121
+ cancelAll() {
122
+ for (const waiter of [...this.waiters])
123
+ waiter.cancel();
124
+ this.waiters.clear();
125
+ }
126
+ get size() {
127
+ return this.waiters.size;
128
+ }
129
+ }
130
+ /**
131
+ * FIFO writer lane. Accepted actions are written strictly in ascending sequence
132
+ * order by a single drain loop, so concurrent sends can never interleave bytes
133
+ * or reorder commands.
134
+ */
135
+ export class OrderedInputQueue {
136
+ queue = [];
137
+ draining = false;
138
+ nextSequence = 1;
139
+ queuedBytes = 0;
140
+ get depth() {
141
+ return this.queue.length;
142
+ }
143
+ get pendingBytes() {
144
+ return this.queuedBytes;
145
+ }
146
+ peekSequence() {
147
+ return this.nextSequence;
148
+ }
149
+ /** Reserve a sequence number and queued-byte budget for one action. */
150
+ reserve(bytes) {
151
+ const sequence = this.nextSequence;
152
+ this.nextSequence += 1;
153
+ this.queuedBytes += bytes;
154
+ return sequence;
155
+ }
156
+ enqueue(action) {
157
+ this.queue.push(action);
158
+ void this.drain();
159
+ }
160
+ async drain() {
161
+ if (this.draining)
162
+ return;
163
+ this.draining = true;
164
+ try {
165
+ while (this.queue.length > 0) {
166
+ const action = this.queue.shift();
167
+ let outcome;
168
+ try {
169
+ outcome = await action.deliver();
170
+ }
171
+ catch (error) {
172
+ outcome = { status: "unknown", deliveredBytes: 0, cause: error };
173
+ }
174
+ this.queuedBytes = Math.max(0, this.queuedBytes - action.queuedBytes);
175
+ action.settle(outcome);
176
+ }
177
+ }
178
+ finally {
179
+ this.draining = false;
180
+ }
181
+ }
182
+ /** Reject every undelivered entry, e.g. after process exit or close. */
183
+ rejectAll(outcome) {
184
+ while (this.queue.length > 0) {
185
+ const action = this.queue.shift();
186
+ this.queuedBytes = Math.max(0, this.queuedBytes - action.queuedBytes);
187
+ action.settle(outcome);
188
+ }
189
+ }
190
+ }
191
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/interactive-session/runtime.ts"],"names":[],"mappings":"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"}