@parall/codex-agent 1.45.0 → 1.46.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/dist/dispatch.d.ts +58 -13
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +343 -67
- package/dist/index.js +10 -8
- package/dist/instructions-refresh.d.ts +136 -0
- package/dist/instructions-refresh.d.ts.map +1 -0
- package/dist/instructions-refresh.js +244 -0
- package/dist/jsonrpc-client.d.ts +49 -1
- package/dist/jsonrpc-client.d.ts.map +1 -1
- package/dist/jsonrpc-client.js +77 -5
- package/dist/server-requests.d.ts +10 -0
- package/dist/server-requests.d.ts.map +1 -0
- package/dist/server-requests.js +39 -0
- package/dist/session-manager.d.ts +12 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +53 -3
- package/dist/turn-sink.d.ts +3 -0
- package/dist/turn-sink.d.ts.map +1 -1
- package/dist/turn-sink.js +5 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +5 -6
- package/package.json +4 -4
- package/src/dispatch.ts +388 -83
- package/src/index.ts +10 -7
- package/src/instructions-refresh.ts +367 -0
- package/src/jsonrpc-client.ts +109 -7
- package/src/server-requests.ts +40 -0
- package/src/session-manager.ts +74 -6
- package/src/turn-sink.ts +6 -0
- package/src/workspace.ts +5 -6
package/src/session-manager.ts
CHANGED
|
@@ -5,6 +5,22 @@ import type { ForkSessionHandle } from '@parall/agent-core';
|
|
|
5
5
|
type PersistedThread = {
|
|
6
6
|
runtimeKey: string;
|
|
7
7
|
threadId: string;
|
|
8
|
+
/**
|
|
9
|
+
* sha256 (hex) of the platform instructions last known EFFECTIVE on the
|
|
10
|
+
* main thread — i.e. present in the thread's model-visible initial context:
|
|
11
|
+
* recorded when the thread is started (thread/start bakes them in) or after
|
|
12
|
+
* a compaction rebuilt the context from the resumed configuration. NOT
|
|
13
|
+
* recorded on resume alone — resume updates the canonical configuration
|
|
14
|
+
* but leaves the model-visible context untouched (see
|
|
15
|
+
* instructions-refresh.ts). ONE exception weakens the "proven visible"
|
|
16
|
+
* reading: a legacy state file (no sha) ADOPTS the current prompt as
|
|
17
|
+
* baseline without a compaction — there the sha means "assumed baseline,
|
|
18
|
+
* converges at the next change or organic compaction", not verified
|
|
19
|
+
* delivery. Absent on state files from before this tracking existed.
|
|
20
|
+
* Dropped whenever the thread id changes (an effectiveness fact is
|
|
21
|
+
* per-thread).
|
|
22
|
+
*/
|
|
23
|
+
effectiveInstructionsSha?: string;
|
|
8
24
|
};
|
|
9
25
|
|
|
10
26
|
type Logger = { warn(message: string): void };
|
|
@@ -17,6 +33,7 @@ type Logger = { warn(message: string): void };
|
|
|
17
33
|
*/
|
|
18
34
|
export class CodexSessionManager {
|
|
19
35
|
private readonly threadIds = new Map<string, string>();
|
|
36
|
+
private readonly effectiveInstructionsShas = new Map<string, string>();
|
|
20
37
|
|
|
21
38
|
constructor(
|
|
22
39
|
readonly mainSessionKey: string,
|
|
@@ -35,9 +52,47 @@ export class CodexSessionManager {
|
|
|
35
52
|
}
|
|
36
53
|
|
|
37
54
|
recordThreadId(sessionKey: string, threadId: string) {
|
|
55
|
+
// An effective-instructions sha is a fact about one specific thread.
|
|
56
|
+
// Recording a DIFFERENT thread id under the same session invalidates it;
|
|
57
|
+
// re-recording the same id (the resume path) must preserve it, or every
|
|
58
|
+
// bridge restart would trigger a redundant refresh.
|
|
59
|
+
if (this.threadIds.get(sessionKey) !== threadId) {
|
|
60
|
+
this.effectiveInstructionsShas.delete(sessionKey);
|
|
61
|
+
}
|
|
38
62
|
this.threadIds.set(sessionKey, threadId);
|
|
39
63
|
if (sessionKey === this.mainSessionKey) {
|
|
40
|
-
this.persist(
|
|
64
|
+
this.persist();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Record a freshly-STARTED thread together with the sha of the
|
|
70
|
+
* instructions baked into it — one atomic state-file write. Two separate
|
|
71
|
+
* writes (thread id, then sha) would leave a crash window whose survivor
|
|
72
|
+
* looks exactly like a pre-tracking legacy file, and the reconcile path
|
|
73
|
+
* deliberately does NOT compact legacy state — a stale thread would then
|
|
74
|
+
* be recorded as carrying instructions it never saw.
|
|
75
|
+
*/
|
|
76
|
+
recordStartedThread(sessionKey: string, threadId: string, effectiveSha: string | undefined) {
|
|
77
|
+
this.threadIds.set(sessionKey, threadId);
|
|
78
|
+
if (effectiveSha) {
|
|
79
|
+
this.effectiveInstructionsShas.set(sessionKey, effectiveSha);
|
|
80
|
+
} else {
|
|
81
|
+
this.effectiveInstructionsShas.delete(sessionKey);
|
|
82
|
+
}
|
|
83
|
+
if (sessionKey === this.mainSessionKey) {
|
|
84
|
+
this.persist();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getEffectiveInstructionsSha(sessionKey: string): string | undefined {
|
|
89
|
+
return this.effectiveInstructionsShas.get(sessionKey);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
recordEffectiveInstructionsSha(sessionKey: string, sha: string) {
|
|
93
|
+
this.effectiveInstructionsShas.set(sessionKey, sha);
|
|
94
|
+
if (sessionKey === this.mainSessionKey) {
|
|
95
|
+
this.persist();
|
|
41
96
|
}
|
|
42
97
|
}
|
|
43
98
|
|
|
@@ -50,11 +105,13 @@ export class CodexSessionManager {
|
|
|
50
105
|
|
|
51
106
|
cleanupFork(sessionKey: string) {
|
|
52
107
|
this.threadIds.delete(sessionKey);
|
|
108
|
+
this.effectiveInstructionsShas.delete(sessionKey);
|
|
53
109
|
}
|
|
54
110
|
|
|
55
111
|
/** Clear the main thread id when app-server rejects it (stale / deleted). */
|
|
56
112
|
clearMainThread() {
|
|
57
113
|
this.threadIds.delete(this.mainSessionKey);
|
|
114
|
+
this.effectiveInstructionsShas.delete(this.mainSessionKey);
|
|
58
115
|
try {
|
|
59
116
|
fs.rmSync(this.stateFilePath, { force: true });
|
|
60
117
|
} catch (error) {
|
|
@@ -74,6 +131,15 @@ export class CodexSessionManager {
|
|
|
74
131
|
parsed.threadId.trim()
|
|
75
132
|
) {
|
|
76
133
|
this.threadIds.set(this.mainSessionKey, parsed.threadId.trim());
|
|
134
|
+
if (
|
|
135
|
+
typeof parsed.effectiveInstructionsSha === 'string' &&
|
|
136
|
+
parsed.effectiveInstructionsSha.trim()
|
|
137
|
+
) {
|
|
138
|
+
this.effectiveInstructionsShas.set(
|
|
139
|
+
this.mainSessionKey,
|
|
140
|
+
parsed.effectiveInstructionsSha.trim(),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
77
143
|
}
|
|
78
144
|
} catch (error) {
|
|
79
145
|
if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') {
|
|
@@ -84,7 +150,9 @@ export class CodexSessionManager {
|
|
|
84
150
|
}
|
|
85
151
|
}
|
|
86
152
|
|
|
87
|
-
private persist(
|
|
153
|
+
private persist() {
|
|
154
|
+
const threadId = this.threadIds.get(this.mainSessionKey);
|
|
155
|
+
if (!threadId) return;
|
|
88
156
|
// Atomic write: truncate-in-place risks leaving a half-written / zero-byte
|
|
89
157
|
// state file if the process dies between `open(O_TRUNC)` and the final
|
|
90
158
|
// fsync. Since this file is the sole source of cross-restart `resume`
|
|
@@ -94,10 +162,10 @@ export class CodexSessionManager {
|
|
|
94
162
|
try {
|
|
95
163
|
fs.mkdirSync(path.dirname(this.stateFilePath), { recursive: true });
|
|
96
164
|
const tmpPath = `${this.stateFilePath}.tmp`;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
);
|
|
165
|
+
const state: PersistedThread = { runtimeKey: this.mainSessionKey, threadId };
|
|
166
|
+
const effectiveSha = this.effectiveInstructionsShas.get(this.mainSessionKey);
|
|
167
|
+
if (effectiveSha) state.effectiveInstructionsSha = effectiveSha;
|
|
168
|
+
fs.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
|
|
101
169
|
fs.renameSync(tmpPath, this.stateFilePath);
|
|
102
170
|
} catch (error) {
|
|
103
171
|
this.logger?.warn(
|
package/src/turn-sink.ts
CHANGED
|
@@ -45,4 +45,10 @@ export class TurnSink {
|
|
|
45
45
|
this.resolver = null;
|
|
46
46
|
r?.({ kind: 'turn_end' });
|
|
47
47
|
}
|
|
48
|
+
|
|
49
|
+
/** True once close() ran — the sink's terminal state (queued envelopes may
|
|
50
|
+
* still drain via next()). */
|
|
51
|
+
get isClosed(): boolean {
|
|
52
|
+
return this.closed;
|
|
53
|
+
}
|
|
48
54
|
}
|
package/src/workspace.ts
CHANGED
|
@@ -563,12 +563,11 @@ function systemPromptCopyPath(workspaceDir: string): string {
|
|
|
563
563
|
// guide, before skill references; empty/absent = no capability section.
|
|
564
564
|
// Split out from ensureCodexWorkspace so the bridge can rebuild the prompt on
|
|
565
565
|
// a platform-config heat-update and hand the new value to
|
|
566
|
-
// adapter.updateConfig
|
|
567
|
-
//
|
|
568
|
-
//
|
|
569
|
-
//
|
|
570
|
-
//
|
|
571
|
-
// docs/tech-debt/codex-persisted-thread-prompt-refresh.md.
|
|
566
|
+
// adapter.updateConfig. Delivery to threads is the adapter's job: baked into
|
|
567
|
+
// new threads at thread/start; converged onto the persisted main thread via
|
|
568
|
+
// the lazy restart's fresh-process thread/resume (canonical configuration)
|
|
569
|
+
// plus an explicit compaction (model-visible context) — see
|
|
570
|
+
// src/instructions-refresh.ts for the two-plane semantics.
|
|
572
571
|
// Throws on failure BY DESIGN — at boot the prompt is mandatory, so a write
|
|
573
572
|
// failure must fail startup loudly; the refresh hot path wraps this in
|
|
574
573
|
// try/catch and retries next refresh.
|