@workerdeck/core 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.d.mts +89 -2
- package/build/index.mjs +182 -8
- package/build/index.mjs.map +1 -1
- package/package.json +3 -3
package/build/index.d.mts
CHANGED
|
@@ -242,6 +242,26 @@ interface Runner {
|
|
|
242
242
|
/** Resolve a pending permission request. Returns false if the id is unknown (e.g. timed out). */
|
|
243
243
|
resolvePermission(requestId: string, decision: PermissionDecision): boolean;
|
|
244
244
|
interrupt(): Promise<void>;
|
|
245
|
+
/**
|
|
246
|
+
* Reset the conversation in place: the session keeps its id, its watermarks
|
|
247
|
+
* and its place in the list, and the engine starts over with an empty
|
|
248
|
+
* context. Announced with a `conversation_reset` event, whose replay rules
|
|
249
|
+
* (see `transcriptContent` in `@workerdeck/protocol`) are what stop an
|
|
250
|
+
* attaching client from resurrecting the cleared rows.
|
|
251
|
+
*
|
|
252
|
+
* Optional, like every member added after `Runner` became public API: an
|
|
253
|
+
* out-of-tree runner that declines it declines the `clear_context` command
|
|
254
|
+
* with it, which is exactly what `EngineCapabilities.clearContext: false`
|
|
255
|
+
* tells clients to expect.
|
|
256
|
+
*
|
|
257
|
+
* **Queues behind in-flight work rather than racing it** — resolving when the
|
|
258
|
+
* clear has actually happened, not when it was accepted. A clear that landed
|
|
259
|
+
* in the middle of the turn it was clearing would be neither, and the engines
|
|
260
|
+
* differ in how they wait (claude hands `/clear` to a CLI that queues its own
|
|
261
|
+
* streamed input; codex and the provider put it on their turn chain), so the
|
|
262
|
+
* one thing callers may rely on is the resolution, not the mechanism.
|
|
263
|
+
*/
|
|
264
|
+
clearContext?(): Promise<void>;
|
|
245
265
|
setPermissionMode(mode: PermissionMode): Promise<void>;
|
|
246
266
|
/** Switch the model for subsequent responses; undefined = back to the default. */
|
|
247
267
|
setModel(model?: string): Promise<void>;
|
|
@@ -357,6 +377,25 @@ declare class SessionRunner implements Runner {
|
|
|
357
377
|
/** Resolve a pending permission request. Returns false if the id is unknown (e.g. timed out). */
|
|
358
378
|
resolvePermission(requestId: string, decision: PermissionDecision): boolean;
|
|
359
379
|
interrupt(): Promise<void>;
|
|
380
|
+
/**
|
|
381
|
+
* Reset the conversation by sending the `/clear` the CLI already honors.
|
|
382
|
+
*
|
|
383
|
+
* Deliberately not a second mechanism: this engine's reset arrives *from the
|
|
384
|
+
* SDK*, and `normalizeSdkMessage` turns the CLI's report of it into the
|
|
385
|
+
* `conversation_reset` event (adopting the new conversation id and re-polling
|
|
386
|
+
* context usage on the way through). Reimplementing the clear here would give
|
|
387
|
+
* one engine two ways to reach the same state, and only one of them would get
|
|
388
|
+
* the id adoption right. So the command and the composer's `/clear` are one
|
|
389
|
+
* behaviour, and this method is the thin end of it.
|
|
390
|
+
*
|
|
391
|
+
* The one place it differs from the other two engines: this resolves when the
|
|
392
|
+
* `/clear` has been **handed to the CLI**, not when the reset has happened —
|
|
393
|
+
* the CLI queues its own streamed input, so waiting is its job, and there is
|
|
394
|
+
* no chain here to ride. The observable contract is the same (a clear sent
|
|
395
|
+
* mid-turn queues rather than cutting the turn short); only the moment the
|
|
396
|
+
* promise settles is weaker, and no caller depends on it.
|
|
397
|
+
*/
|
|
398
|
+
clearContext(): Promise<void>;
|
|
360
399
|
setPermissionMode(mode: PermissionMode): Promise<void>;
|
|
361
400
|
/** Switch the model for subsequent responses; undefined = back to the default. */
|
|
362
401
|
setModel(model?: string): Promise<void>;
|
|
@@ -399,8 +438,12 @@ declare class SessionRunner implements Runner {
|
|
|
399
438
|
* 1. `afterSeq` — the caller already holds everything at or below it.
|
|
400
439
|
* 2. `resetSeq` — transcript *content* strictly below the latest
|
|
401
440
|
* `conversation_reset` is skipped, so a re-attach cannot resurrect a cleared
|
|
402
|
-
* conversation while state events still replay.
|
|
403
|
-
*
|
|
441
|
+
* conversation while state events still replay. **Every engine that can emit
|
|
442
|
+
* a reset must track it and pass it** — this was Claude's alone only for as
|
|
443
|
+
* long as Claude's was the only engine that could produce the event, and the
|
|
444
|
+
* failure when a runner forgets is quiet: the end state is right for a
|
|
445
|
+
* current reducer, so nothing looks broken while every attach re-sends the
|
|
446
|
+
* whole cleared conversation for the process's lifetime.
|
|
404
447
|
* 3. `coalesceReplay` — last-write-wins state readings superseded later in the
|
|
405
448
|
* same replay (`staleReplaySeqs`), plus everything `replayRetains` says the
|
|
406
449
|
* reducer reads and discards. Opt-in, and only sound for a consumer whose
|
|
@@ -627,6 +670,20 @@ declare class AiSdkRunner implements Runner {
|
|
|
627
670
|
* its tokens are never lost from the turn's accounting.
|
|
628
671
|
*/
|
|
629
672
|
generateDigest(prompt: string): Promise<string>;
|
|
673
|
+
/**
|
|
674
|
+
* Reset the conversation: drop the message array the next turn would have
|
|
675
|
+
* been built from. There is no engine round trip — this runner *is* where the
|
|
676
|
+
* transcript lives, so clearing it is the whole operation.
|
|
677
|
+
*
|
|
678
|
+
* Two things ride along, both already written elsewhere and both load-bearing
|
|
679
|
+
* here. `#emit`'s `conversation_reset` arm retires `#contextUsage` (the
|
|
680
|
+
* reading described a conversation that no longer exists), and the same arm
|
|
681
|
+
* in `restore` keeps a parked session that comes back after a clear from
|
|
682
|
+
* resurrecting it. Pending tool calls are NOT swept: a parked call is work a
|
|
683
|
+
* backend still owes an answer for, and a clear is not an interrupt — the
|
|
684
|
+
* refusal below is what keeps the two apart.
|
|
685
|
+
*/
|
|
686
|
+
clearContext(): Promise<void>;
|
|
630
687
|
interrupt(): Promise<void>;
|
|
631
688
|
setPermissionMode(mode: PermissionMode): Promise<void>;
|
|
632
689
|
setModel(model?: string): Promise<void>;
|
|
@@ -1717,6 +1774,36 @@ declare class CodexRunner implements Runner {
|
|
|
1717
1774
|
* timed out, or already settled by codex itself). */
|
|
1718
1775
|
resolvePermission(requestId: string, decision: PermissionDecision): boolean;
|
|
1719
1776
|
interrupt(): Promise<void>;
|
|
1777
|
+
/**
|
|
1778
|
+
* Reset the conversation: a **fresh thread on the same session**.
|
|
1779
|
+
*
|
|
1780
|
+
* Codex has no clear/reset RPC — `thread/compact/start` summarises and
|
|
1781
|
+
* continues, `thread/fork` makes a second thread, and neither is "same
|
|
1782
|
+
* session, empty context". So the analog is to stop resuming the old thread
|
|
1783
|
+
* and start a new one, which is the path a dead child already takes minus the
|
|
1784
|
+
* resume. The old thread is NOT deleted: it stays in CODEX_HOME and stays
|
|
1785
|
+
* resumable from `GET /sdk-sessions`.
|
|
1786
|
+
*
|
|
1787
|
+
* Two things it does on the way through, both mirroring the Claude engine's
|
|
1788
|
+
* SDK-driven reset (`engines/claude/runner.ts`):
|
|
1789
|
+
*
|
|
1790
|
+
* 1. **The new thread id is adopted before `conversation_reset` is emitted**,
|
|
1791
|
+
* whenever a child is already up — the eager `thread/start` costs no
|
|
1792
|
+
* tokens and no model call, and it is what keeps the dormant record from
|
|
1793
|
+
* ever naming the conversation that was just cleared. With no child there
|
|
1794
|
+
* is nothing to start against and the id is simply dropped; the parking
|
|
1795
|
+
* service treats a resumable session with no engine session id as one with
|
|
1796
|
+
* nothing to come back to, and forgets the stale record.
|
|
1797
|
+
* 2. **The context reading is retired**, in `#emit`'s `conversation_reset`
|
|
1798
|
+
* arm. Codex cannot re-poll it the way Claude does — the only source is
|
|
1799
|
+
* `thread/tokenUsage/updated`, which arrives *during* a turn — so there is
|
|
1800
|
+
* no reading at all until the next turn runs, and the protocol's rule
|
|
1801
|
+
* applies: render nothing rather than a stale ring or a 0%.
|
|
1802
|
+
*
|
|
1803
|
+
* The turn counter stays monotonic across this, on purpose (it is an unread
|
|
1804
|
+
* cursor, not an item count), and so does `activityCount` — `#emit` owns both.
|
|
1805
|
+
*/
|
|
1806
|
+
clearContext(): Promise<void>;
|
|
1720
1807
|
setPermissionMode(mode: PermissionMode): Promise<void>;
|
|
1721
1808
|
setModel(model?: string): Promise<void>;
|
|
1722
1809
|
fail(message: string): void;
|
package/build/index.mjs
CHANGED
|
@@ -607,8 +607,12 @@ function staleReplaySeqs(events, afterSeq) {
|
|
|
607
607
|
* 1. `afterSeq` — the caller already holds everything at or below it.
|
|
608
608
|
* 2. `resetSeq` — transcript *content* strictly below the latest
|
|
609
609
|
* `conversation_reset` is skipped, so a re-attach cannot resurrect a cleared
|
|
610
|
-
* conversation while state events still replay.
|
|
611
|
-
*
|
|
610
|
+
* conversation while state events still replay. **Every engine that can emit
|
|
611
|
+
* a reset must track it and pass it** — this was Claude's alone only for as
|
|
612
|
+
* long as Claude's was the only engine that could produce the event, and the
|
|
613
|
+
* failure when a runner forgets is quiet: the end state is right for a
|
|
614
|
+
* current reducer, so nothing looks broken while every attach re-sends the
|
|
615
|
+
* whole cleared conversation for the process's lifetime.
|
|
612
616
|
* 3. `coalesceReplay` — last-write-wins state readings superseded later in the
|
|
613
617
|
* same replay (`staleReplaySeqs`), plus everything `replayRetains` says the
|
|
614
618
|
* reducer reads and discards. Opt-in, and only sound for a consumer whose
|
|
@@ -1309,6 +1313,28 @@ var SessionRunner = class {
|
|
|
1309
1313
|
async interrupt() {
|
|
1310
1314
|
await this.#query?.interrupt();
|
|
1311
1315
|
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Reset the conversation by sending the `/clear` the CLI already honors.
|
|
1318
|
+
*
|
|
1319
|
+
* Deliberately not a second mechanism: this engine's reset arrives *from the
|
|
1320
|
+
* SDK*, and `normalizeSdkMessage` turns the CLI's report of it into the
|
|
1321
|
+
* `conversation_reset` event (adopting the new conversation id and re-polling
|
|
1322
|
+
* context usage on the way through). Reimplementing the clear here would give
|
|
1323
|
+
* one engine two ways to reach the same state, and only one of them would get
|
|
1324
|
+
* the id adoption right. So the command and the composer's `/clear` are one
|
|
1325
|
+
* behaviour, and this method is the thin end of it.
|
|
1326
|
+
*
|
|
1327
|
+
* The one place it differs from the other two engines: this resolves when the
|
|
1328
|
+
* `/clear` has been **handed to the CLI**, not when the reset has happened —
|
|
1329
|
+
* the CLI queues its own streamed input, so waiting is its job, and there is
|
|
1330
|
+
* no chain here to ride. The observable contract is the same (a clear sent
|
|
1331
|
+
* mid-turn queues rather than cutting the turn short); only the moment the
|
|
1332
|
+
* promise settles is weaker, and no caller depends on it.
|
|
1333
|
+
*/
|
|
1334
|
+
async clearContext() {
|
|
1335
|
+
if (this.#status === "closed" || this.#status === "failed") throw new Error("session is closed");
|
|
1336
|
+
this.sendMessage("/clear");
|
|
1337
|
+
}
|
|
1312
1338
|
async setPermissionMode(mode) {
|
|
1313
1339
|
await this.#query?.setPermissionMode(mode);
|
|
1314
1340
|
this.#permissionMode = mode;
|
|
@@ -1836,6 +1862,15 @@ var AiSdkRunner = class {
|
|
|
1836
1862
|
*/
|
|
1837
1863
|
#contextUsage;
|
|
1838
1864
|
#activityCount = 0;
|
|
1865
|
+
/**
|
|
1866
|
+
* Seq of the latest `conversation_reset` event, 0 when none. The log itself is
|
|
1867
|
+
* never truncated — it still carries the state-bearing events (`capabilities`,
|
|
1868
|
+
* `system_init`, …) a fresh attacher depends on and which are not re-emitted —
|
|
1869
|
+
* but `subscribe()` skips transcript *content* strictly below this mark, so a
|
|
1870
|
+
* replay does not resurrect a cleared conversation. A later reset supersedes
|
|
1871
|
+
* an earlier one by overwriting it.
|
|
1872
|
+
*/
|
|
1873
|
+
#resetSeq = 0;
|
|
1839
1874
|
#status = "starting";
|
|
1840
1875
|
#permissionMode;
|
|
1841
1876
|
#messages = [];
|
|
@@ -1887,8 +1922,10 @@ var AiSdkRunner = class {
|
|
|
1887
1922
|
this.#activityCount = 0;
|
|
1888
1923
|
for (const event of this.#events) {
|
|
1889
1924
|
this.#activityCount += transcriptActivity(event);
|
|
1890
|
-
if (event.type === "conversation_reset")
|
|
1891
|
-
|
|
1925
|
+
if (event.type === "conversation_reset") {
|
|
1926
|
+
this.#resetSeq = event.seq;
|
|
1927
|
+
this.#contextUsage = void 0;
|
|
1928
|
+
} else this.#contextUsage = contextReading(event) ?? this.#contextUsage;
|
|
1892
1929
|
}
|
|
1893
1930
|
this.#messages = [...state.messages];
|
|
1894
1931
|
for (const call of state.pendingToolCalls) this.#pendingToolCalls.set(call.toolCallId, call);
|
|
@@ -2158,6 +2195,30 @@ var AiSdkRunner = class {
|
|
|
2158
2195
|
}
|
|
2159
2196
|
return result.text;
|
|
2160
2197
|
}
|
|
2198
|
+
/**
|
|
2199
|
+
* Reset the conversation: drop the message array the next turn would have
|
|
2200
|
+
* been built from. There is no engine round trip — this runner *is* where the
|
|
2201
|
+
* transcript lives, so clearing it is the whole operation.
|
|
2202
|
+
*
|
|
2203
|
+
* Two things ride along, both already written elsewhere and both load-bearing
|
|
2204
|
+
* here. `#emit`'s `conversation_reset` arm retires `#contextUsage` (the
|
|
2205
|
+
* reading described a conversation that no longer exists), and the same arm
|
|
2206
|
+
* in `restore` keeps a parked session that comes back after a clear from
|
|
2207
|
+
* resurrecting it. Pending tool calls are NOT swept: a parked call is work a
|
|
2208
|
+
* backend still owes an answer for, and a clear is not an interrupt — the
|
|
2209
|
+
* refusal below is what keeps the two apart.
|
|
2210
|
+
*/
|
|
2211
|
+
async clearContext() {
|
|
2212
|
+
if (this.#status === "closed" || this.#status === "failed") throw new Error("session is closed");
|
|
2213
|
+
const run = this.#turnChain.then(() => {
|
|
2214
|
+
if (this.#closed) throw new Error("session is closed");
|
|
2215
|
+
if (this.#pendingToolCalls.size > 0) throw new Error("cannot clear context while tool calls are outstanding");
|
|
2216
|
+
this.#messages = [];
|
|
2217
|
+
this.#emit({ type: "conversation_reset" });
|
|
2218
|
+
});
|
|
2219
|
+
this.#turnChain = run.then(() => void 0, () => void 0);
|
|
2220
|
+
await run;
|
|
2221
|
+
}
|
|
2161
2222
|
async interrupt() {
|
|
2162
2223
|
if (this.#abort) this.#abort.abort();
|
|
2163
2224
|
else if (this.#pendingToolCalls.size > 0) {
|
|
@@ -2238,7 +2299,7 @@ var AiSdkRunner = class {
|
|
|
2238
2299
|
return this.#events.find((event) => event.seq === seq);
|
|
2239
2300
|
}
|
|
2240
2301
|
subscribe(listener, afterSeq = 0, options) {
|
|
2241
|
-
return this.#subscribers.subscribe(this.#events, listener, afterSeq, options);
|
|
2302
|
+
return this.#subscribers.subscribe(this.#events, listener, afterSeq, options, this.#resetSeq);
|
|
2242
2303
|
}
|
|
2243
2304
|
#scheduleTurn() {
|
|
2244
2305
|
this.#turnChain = this.#turnChain.then(() => this.#runTurn());
|
|
@@ -2628,7 +2689,10 @@ var AiSdkRunner = class {
|
|
|
2628
2689
|
this.#lastActivityAt = event.ts;
|
|
2629
2690
|
this.#activityCount += transcriptActivity(body);
|
|
2630
2691
|
this.#contextUsage = contextReading(body) ?? this.#contextUsage;
|
|
2631
|
-
if (body.type === "conversation_reset")
|
|
2692
|
+
if (body.type === "conversation_reset") {
|
|
2693
|
+
this.#resetSeq = event.seq;
|
|
2694
|
+
this.#contextUsage = void 0;
|
|
2695
|
+
}
|
|
2632
2696
|
this.#events.push(event);
|
|
2633
2697
|
this.#subscribers.emit(event);
|
|
2634
2698
|
}
|
|
@@ -4100,6 +4164,25 @@ var CodexAgentTracker = class {
|
|
|
4100
4164
|
sweep() {
|
|
4101
4165
|
for (const record of this.#byThread.values()) if (record.status === "running") this.#settle(record, "failed");
|
|
4102
4166
|
}
|
|
4167
|
+
/**
|
|
4168
|
+
* The conversation these agents belonged to is gone (a `conversation_reset`).
|
|
4169
|
+
*
|
|
4170
|
+
* Deliberately NOT {@link CodexAgentTracker.sweep}: that settles the running
|
|
4171
|
+
* ones as failed and keeps the rows, which is right when the *process* dies —
|
|
4172
|
+
* the transcript still holds the anchor `tool_use` each row points at, and a
|
|
4173
|
+
* row that vanished would leave that card unexplained. A clear is the other
|
|
4174
|
+
* way round. The anchors go with the transcript, so a surviving row would
|
|
4175
|
+
* publish a `toolUseId` that resolves to nothing — and clients key a
|
|
4176
|
+
* pressable, enterable agent line off exactly that id.
|
|
4177
|
+
*/
|
|
4178
|
+
forget() {
|
|
4179
|
+
this.#byThread.clear();
|
|
4180
|
+
}
|
|
4181
|
+
/** The thread ids currently tracked — what a clear remembers so a still-running
|
|
4182
|
+
* agent's later traffic can be dropped rather than re-anchored. */
|
|
4183
|
+
threadIds() {
|
|
4184
|
+
return Array.from(this.#byThread.keys());
|
|
4185
|
+
}
|
|
4103
4186
|
/** The rollup as `SessionInfo.subagents` serves it — spawn order, fresh
|
|
4104
4187
|
* objects, and `undefined` when there is nothing to say (absent and empty
|
|
4105
4188
|
* mean the same thing to a client, and bytes on a polled list are paid for). */
|
|
@@ -4943,6 +5026,15 @@ var CodexRunner = class {
|
|
|
4943
5026
|
*/
|
|
4944
5027
|
#contextUsage;
|
|
4945
5028
|
#activityCount = 0;
|
|
5029
|
+
/**
|
|
5030
|
+
* Seq of the latest `conversation_reset` event, 0 when none. The log itself is
|
|
5031
|
+
* never truncated — it still carries the state-bearing events (`capabilities`,
|
|
5032
|
+
* `system_init`, …) a fresh attacher depends on and which are not re-emitted —
|
|
5033
|
+
* but `subscribe()` skips transcript *content* strictly below this mark, so a
|
|
5034
|
+
* replay does not resurrect a cleared conversation. A later reset supersedes
|
|
5035
|
+
* an earlier one by overwriting it.
|
|
5036
|
+
*/
|
|
5037
|
+
#resetSeq = 0;
|
|
4946
5038
|
#status = "starting";
|
|
4947
5039
|
#sdkSessionId;
|
|
4948
5040
|
#model;
|
|
@@ -5005,6 +5097,15 @@ var CodexRunner = class {
|
|
|
5005
5097
|
* it, and only the child process dying (or the session closing) ends them
|
|
5006
5098
|
* all — see the module doc in `subagents.ts`. */
|
|
5007
5099
|
#agents = new CodexAgentTracker();
|
|
5100
|
+
/** Threads that belonged to a conversation this session has cleared — the
|
|
5101
|
+
* agents that were still running when it happened. Their notifications keep
|
|
5102
|
+
* arriving on the same connection (a clear does not interrupt them and does
|
|
5103
|
+
* not drop the child), and without this {@link CodexRunner.#agentFor} would
|
|
5104
|
+
* mint them a fresh anchor and stream the cleared conversation's agent work
|
|
5105
|
+
* into the new one. Never pruned: it is a handful of uuids for the session's
|
|
5106
|
+
* life, and a late report from a long-dead agent is exactly what it exists to
|
|
5107
|
+
* catch. */
|
|
5108
|
+
#clearedThreads = /* @__PURE__ */ new Set();
|
|
5008
5109
|
constructor(config, id = randomUUID()) {
|
|
5009
5110
|
const mode = config.permissionMode ?? "default";
|
|
5010
5111
|
if (!ENGINE_CAPABILITIES.codex.permissionModes.includes(mode)) throw new Error(`permission mode '${mode}' is not supported by the codex engine`);
|
|
@@ -5183,6 +5284,15 @@ var CodexRunner = class {
|
|
|
5183
5284
|
}
|
|
5184
5285
|
sendMessage(text, attachments) {
|
|
5185
5286
|
if (this.#closed) throw new Error("session is closed");
|
|
5287
|
+
if (text.trim() === "/clear" && !attachments?.length) {
|
|
5288
|
+
this.clearContext().catch((error) => {
|
|
5289
|
+
this.#emit({
|
|
5290
|
+
type: "session_error",
|
|
5291
|
+
message: `could not clear the conversation: ${error instanceof Error ? error.message : String(error)}`
|
|
5292
|
+
});
|
|
5293
|
+
});
|
|
5294
|
+
return;
|
|
5295
|
+
}
|
|
5186
5296
|
const input = this.#buildInput(text, attachments ?? []);
|
|
5187
5297
|
const echo = () => this.#emit({
|
|
5188
5298
|
type: "user_message",
|
|
@@ -5254,6 +5364,66 @@ var CodexRunner = class {
|
|
|
5254
5364
|
await this.#interruptTurn();
|
|
5255
5365
|
await this.#turnChain;
|
|
5256
5366
|
}
|
|
5367
|
+
/**
|
|
5368
|
+
* Reset the conversation: a **fresh thread on the same session**.
|
|
5369
|
+
*
|
|
5370
|
+
* Codex has no clear/reset RPC — `thread/compact/start` summarises and
|
|
5371
|
+
* continues, `thread/fork` makes a second thread, and neither is "same
|
|
5372
|
+
* session, empty context". So the analog is to stop resuming the old thread
|
|
5373
|
+
* and start a new one, which is the path a dead child already takes minus the
|
|
5374
|
+
* resume. The old thread is NOT deleted: it stays in CODEX_HOME and stays
|
|
5375
|
+
* resumable from `GET /sdk-sessions`.
|
|
5376
|
+
*
|
|
5377
|
+
* Two things it does on the way through, both mirroring the Claude engine's
|
|
5378
|
+
* SDK-driven reset (`engines/claude/runner.ts`):
|
|
5379
|
+
*
|
|
5380
|
+
* 1. **The new thread id is adopted before `conversation_reset` is emitted**,
|
|
5381
|
+
* whenever a child is already up — the eager `thread/start` costs no
|
|
5382
|
+
* tokens and no model call, and it is what keeps the dormant record from
|
|
5383
|
+
* ever naming the conversation that was just cleared. With no child there
|
|
5384
|
+
* is nothing to start against and the id is simply dropped; the parking
|
|
5385
|
+
* service treats a resumable session with no engine session id as one with
|
|
5386
|
+
* nothing to come back to, and forgets the stale record.
|
|
5387
|
+
* 2. **The context reading is retired**, in `#emit`'s `conversation_reset`
|
|
5388
|
+
* arm. Codex cannot re-poll it the way Claude does — the only source is
|
|
5389
|
+
* `thread/tokenUsage/updated`, which arrives *during* a turn — so there is
|
|
5390
|
+
* no reading at all until the next turn runs, and the protocol's rule
|
|
5391
|
+
* applies: render nothing rather than a stale ring or a 0%.
|
|
5392
|
+
*
|
|
5393
|
+
* The turn counter stays monotonic across this, on purpose (it is an unread
|
|
5394
|
+
* cursor, not an item count), and so does `activityCount` — `#emit` owns both.
|
|
5395
|
+
*/
|
|
5396
|
+
async clearContext() {
|
|
5397
|
+
if (this.#closed) throw new Error("session is closed");
|
|
5398
|
+
const run = this.#turnChain.then(() => this.#clearNow());
|
|
5399
|
+
this.#turnChain = run.then(() => void 0, () => void 0);
|
|
5400
|
+
await run;
|
|
5401
|
+
}
|
|
5402
|
+
/** The clear itself, only ever called as a turn-chain link. */
|
|
5403
|
+
async #clearNow() {
|
|
5404
|
+
if (this.#closed) throw new Error("session is closed");
|
|
5405
|
+
const previousThread = this.#sdkSessionId;
|
|
5406
|
+
this.#sdkSessionId = void 0;
|
|
5407
|
+
this.#threadLoaded = false;
|
|
5408
|
+
if (this.#connection) try {
|
|
5409
|
+
await this.#ensureThread();
|
|
5410
|
+
} catch (error) {
|
|
5411
|
+
this.#sdkSessionId = previousThread;
|
|
5412
|
+
this.#threadLoaded = false;
|
|
5413
|
+
throw error;
|
|
5414
|
+
}
|
|
5415
|
+
for (const agent of this.#agents.threadIds()) this.#clearedThreads.add(agent);
|
|
5416
|
+
this.#agents.forget();
|
|
5417
|
+
for (const [id, pending] of this.#approvals) this.#settleApproval(id, pending, {
|
|
5418
|
+
behavior: "deny",
|
|
5419
|
+
message: "the conversation was cleared"
|
|
5420
|
+
}, "policy");
|
|
5421
|
+
this.#resumedHistory = void 0;
|
|
5422
|
+
this.#emit({
|
|
5423
|
+
type: "conversation_reset",
|
|
5424
|
+
sdkSessionId: this.#sdkSessionId
|
|
5425
|
+
});
|
|
5426
|
+
}
|
|
5257
5427
|
/** Address the in-flight turn only (no approval sweep) — also the follow-up
|
|
5258
5428
|
* for a deny+interrupt whose wire decision couldn't carry the interrupt. */
|
|
5259
5429
|
async #interruptTurn() {
|
|
@@ -5331,7 +5501,7 @@ var CodexRunner = class {
|
|
|
5331
5501
|
return this.#events.find((event) => event.seq === seq);
|
|
5332
5502
|
}
|
|
5333
5503
|
subscribe(listener, afterSeq = 0, options) {
|
|
5334
|
-
return this.#subscribers.subscribe(this.#events, listener, afterSeq, options);
|
|
5504
|
+
return this.#subscribers.subscribe(this.#events, listener, afterSeq, options, this.#resetSeq);
|
|
5335
5505
|
}
|
|
5336
5506
|
#scheduleTurn() {
|
|
5337
5507
|
this.#turnChain = this.#turnChain.then(() => this.#runTurn());
|
|
@@ -5747,6 +5917,7 @@ var CodexRunner = class {
|
|
|
5747
5917
|
if (threadId === void 0 || threadId === this.#sdkSessionId) return void 0;
|
|
5748
5918
|
const known = this.#agents.get(threadId);
|
|
5749
5919
|
if (known) return known;
|
|
5920
|
+
if (this.#clearedThreads.has(threadId)) return void 0;
|
|
5750
5921
|
const nonce = this.#activeTurn?.nonce ?? "codex";
|
|
5751
5922
|
const record = this.#agents.open(threadId, `${nonce}:agent:${threadId}`, void 0, Date.now());
|
|
5752
5923
|
record.anchored = true;
|
|
@@ -6385,7 +6556,10 @@ var CodexRunner = class {
|
|
|
6385
6556
|
this.#lastActivityAt = event.ts;
|
|
6386
6557
|
this.#activityCount += transcriptActivity(body);
|
|
6387
6558
|
this.#contextUsage = contextReading(body) ?? this.#contextUsage;
|
|
6388
|
-
if (body.type === "conversation_reset")
|
|
6559
|
+
if (body.type === "conversation_reset") {
|
|
6560
|
+
this.#resetSeq = event.seq;
|
|
6561
|
+
this.#contextUsage = void 0;
|
|
6562
|
+
}
|
|
6389
6563
|
this.#events.push(event);
|
|
6390
6564
|
this.#subscribers.emit(event);
|
|
6391
6565
|
}
|