@parall/codex-agent 1.45.0 → 1.47.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.
@@ -13,10 +13,22 @@ export declare class CodexSessionManager {
13
13
  private readonly stateFilePath;
14
14
  private readonly logger?;
15
15
  private readonly threadIds;
16
+ private readonly effectiveInstructionsShas;
16
17
  constructor(mainSessionKey: string, stateFilePath: string, logger?: Logger | undefined);
17
18
  isMain(sessionKey: string): boolean;
18
19
  getThreadId(sessionKey: string): string | undefined;
19
20
  recordThreadId(sessionKey: string, threadId: string): void;
21
+ /**
22
+ * Record a freshly-STARTED thread together with the sha of the
23
+ * instructions baked into it — one atomic state-file write. Two separate
24
+ * writes (thread id, then sha) would leave a crash window whose survivor
25
+ * looks exactly like a pre-tracking legacy file, and the reconcile path
26
+ * deliberately does NOT compact legacy state — a stale thread would then
27
+ * be recorded as carrying instructions it never saw.
28
+ */
29
+ recordStartedThread(sessionKey: string, threadId: string, effectiveSha: string | undefined): void;
30
+ getEffectiveInstructionsSha(sessionKey: string): string | undefined;
31
+ recordEffectiveInstructionsSha(sessionKey: string, sha: string): void;
20
32
  createForkSessionKey(): ForkSessionHandle;
21
33
  cleanupFork(sessionKey: string): void;
22
34
  /** Clear the main thread id when app-server rejects it (stale / deleted). */
@@ -1 +1 @@
1
- {"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAO5D,KAAK,MAAM,GAAG;IAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAE9C;;;;;GAKG;AACH,qBAAa,mBAAmB;IAI5B,QAAQ,CAAC,cAAc,EAAE,MAAM;IAC/B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAL1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;gBAG5C,cAAc,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,MAAM,YAAA;IAKlC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAInC,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAInD,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAOnD,oBAAoB,IAAI,iBAAiB;IAOzC,WAAW,CAAC,UAAU,EAAE,MAAM;IAI9B,6EAA6E;IAC7E,eAAe;IAWf,OAAO,CAAC,OAAO;IAoBf,OAAO,CAAC,OAAO;CAqBhB"}
1
+ {"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAuB5D,KAAK,MAAM,GAAG;IAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAE9C;;;;;GAKG;AACH,qBAAa,mBAAmB;IAK5B,QAAQ,CAAC,cAAc,EAAE,MAAM;IAC/B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAN1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IACvD,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAA6B;gBAG5D,cAAc,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,MAAM,YAAA;IAKlC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAInC,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAInD,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAcnD;;;;;;;OAOG;IACH,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS;IAY1F,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAInE,8BAA8B,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;IAO9D,oBAAoB,IAAI,iBAAiB;IAOzC,WAAW,CAAC,UAAU,EAAE,MAAM;IAK9B,6EAA6E;IAC7E,eAAe;IAYf,OAAO,CAAC,OAAO;IA6Bf,OAAO,CAAC,OAAO;CAuBhB"}
@@ -11,6 +11,7 @@ export class CodexSessionManager {
11
11
  stateFilePath;
12
12
  logger;
13
13
  threadIds = new Map();
14
+ effectiveInstructionsShas = new Map();
14
15
  constructor(mainSessionKey, stateFilePath, logger) {
15
16
  this.mainSessionKey = mainSessionKey;
16
17
  this.stateFilePath = stateFilePath;
@@ -24,9 +25,45 @@ export class CodexSessionManager {
24
25
  return this.threadIds.get(sessionKey);
25
26
  }
26
27
  recordThreadId(sessionKey, threadId) {
28
+ // An effective-instructions sha is a fact about one specific thread.
29
+ // Recording a DIFFERENT thread id under the same session invalidates it;
30
+ // re-recording the same id (the resume path) must preserve it, or every
31
+ // bridge restart would trigger a redundant refresh.
32
+ if (this.threadIds.get(sessionKey) !== threadId) {
33
+ this.effectiveInstructionsShas.delete(sessionKey);
34
+ }
35
+ this.threadIds.set(sessionKey, threadId);
36
+ if (sessionKey === this.mainSessionKey) {
37
+ this.persist();
38
+ }
39
+ }
40
+ /**
41
+ * Record a freshly-STARTED thread together with the sha of the
42
+ * instructions baked into it — one atomic state-file write. Two separate
43
+ * writes (thread id, then sha) would leave a crash window whose survivor
44
+ * looks exactly like a pre-tracking legacy file, and the reconcile path
45
+ * deliberately does NOT compact legacy state — a stale thread would then
46
+ * be recorded as carrying instructions it never saw.
47
+ */
48
+ recordStartedThread(sessionKey, threadId, effectiveSha) {
27
49
  this.threadIds.set(sessionKey, threadId);
50
+ if (effectiveSha) {
51
+ this.effectiveInstructionsShas.set(sessionKey, effectiveSha);
52
+ }
53
+ else {
54
+ this.effectiveInstructionsShas.delete(sessionKey);
55
+ }
56
+ if (sessionKey === this.mainSessionKey) {
57
+ this.persist();
58
+ }
59
+ }
60
+ getEffectiveInstructionsSha(sessionKey) {
61
+ return this.effectiveInstructionsShas.get(sessionKey);
62
+ }
63
+ recordEffectiveInstructionsSha(sessionKey, sha) {
64
+ this.effectiveInstructionsShas.set(sessionKey, sha);
28
65
  if (sessionKey === this.mainSessionKey) {
29
- this.persist(threadId);
66
+ this.persist();
30
67
  }
31
68
  }
32
69
  createForkSessionKey() {
@@ -37,10 +74,12 @@ export class CodexSessionManager {
37
74
  }
38
75
  cleanupFork(sessionKey) {
39
76
  this.threadIds.delete(sessionKey);
77
+ this.effectiveInstructionsShas.delete(sessionKey);
40
78
  }
41
79
  /** Clear the main thread id when app-server rejects it (stale / deleted). */
42
80
  clearMainThread() {
43
81
  this.threadIds.delete(this.mainSessionKey);
82
+ this.effectiveInstructionsShas.delete(this.mainSessionKey);
44
83
  try {
45
84
  fs.rmSync(this.stateFilePath, { force: true });
46
85
  }
@@ -56,6 +95,10 @@ export class CodexSessionManager {
56
95
  typeof parsed.threadId === 'string' &&
57
96
  parsed.threadId.trim()) {
58
97
  this.threadIds.set(this.mainSessionKey, parsed.threadId.trim());
98
+ if (typeof parsed.effectiveInstructionsSha === 'string' &&
99
+ parsed.effectiveInstructionsSha.trim()) {
100
+ this.effectiveInstructionsShas.set(this.mainSessionKey, parsed.effectiveInstructionsSha.trim());
101
+ }
59
102
  }
60
103
  }
61
104
  catch (error) {
@@ -64,7 +107,10 @@ export class CodexSessionManager {
64
107
  }
65
108
  }
66
109
  }
67
- persist(threadId) {
110
+ persist() {
111
+ const threadId = this.threadIds.get(this.mainSessionKey);
112
+ if (!threadId)
113
+ return;
68
114
  // Atomic write: truncate-in-place risks leaving a half-written / zero-byte
69
115
  // state file if the process dies between `open(O_TRUNC)` and the final
70
116
  // fsync. Since this file is the sole source of cross-restart `resume`
@@ -74,7 +120,11 @@ export class CodexSessionManager {
74
120
  try {
75
121
  fs.mkdirSync(path.dirname(this.stateFilePath), { recursive: true });
76
122
  const tmpPath = `${this.stateFilePath}.tmp`;
77
- fs.writeFileSync(tmpPath, JSON.stringify({ runtimeKey: this.mainSessionKey, threadId }, null, 2));
123
+ const state = { runtimeKey: this.mainSessionKey, threadId };
124
+ const effectiveSha = this.effectiveInstructionsShas.get(this.mainSessionKey);
125
+ if (effectiveSha)
126
+ state.effectiveInstructionsSha = effectiveSha;
127
+ fs.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
78
128
  fs.renameSync(tmpPath, this.stateFilePath);
79
129
  }
80
130
  catch (error) {
@@ -19,5 +19,8 @@ export declare class TurnSink {
19
19
  push(envelope: TurnEventEnvelope): void;
20
20
  next(): Promise<TurnEventEnvelope>;
21
21
  close(): void;
22
+ /** True once close() ran — the sink's terminal state (queued envelopes may
23
+ * still drain via next()). */
24
+ get isClosed(): boolean;
22
25
  }
23
26
  //# sourceMappingURL=turn-sink.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"turn-sink.d.ts","sourceRoot":"","sources":["../src/turn-sink.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,mEAAmE;AACnE,qBAAa,QAAQ;IACnB,QAAQ,CAAC,MAAM,cAAqB;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2B;IACjD,OAAO,CAAC,QAAQ,CAAqD;IACrE,OAAO,CAAC,MAAM,CAAS;IAEvB,IAAI,CAAC,QAAQ,EAAE,iBAAiB;IAWhC,IAAI,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAelC,KAAK;CAMN"}
1
+ {"version":3,"file":"turn-sink.d.ts","sourceRoot":"","sources":["../src/turn-sink.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,mEAAmE;AACnE,qBAAa,QAAQ;IACnB,QAAQ,CAAC,MAAM,cAAqB;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2B;IACjD,OAAO,CAAC,QAAQ,CAAqD;IACrE,OAAO,CAAC,MAAM,CAAS;IAEvB,IAAI,CAAC,QAAQ,EAAE,iBAAiB;IAWhC,IAAI,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAelC,KAAK;IAOL;kCAC8B;IAC9B,IAAI,QAAQ,IAAI,OAAO,CAEtB;CACF"}
package/dist/turn-sink.js CHANGED
@@ -37,4 +37,9 @@ export class TurnSink {
37
37
  this.resolver = null;
38
38
  r?.({ kind: 'turn_end' });
39
39
  }
40
+ /** True once close() ran — the sink's terminal state (queued envelopes may
41
+ * still drain via next()). */
42
+ get isClosed() {
43
+ return this.closed;
44
+ }
40
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB;;;;CAI/B,CAAC;AAUF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GAAG,SAAS,EAChD,EAAE,EAAE,MAAM,IAAI,GACb,IAAI,CAkJN;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,EAAE;IACnC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B,CAAC;AAuJP;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAK/E;AAED;;;;;;;;;;;;;GAaG;AACH;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAerD;AAED,wEAAwE;AACxE,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA0BD,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,GAAG,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,EACrC,IAAI,CAAC,EAAE,yBAAyB,GAC/B,IAAI,CAEN;AAkFD,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,aAAa,CAAC,EAAE,aAAa,EAC7B,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAC7B,MAAM,CAwBR;AAED,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,MAAM,EACpB,GAAG,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,EACrC,aAAa,CAAC,EAAE,aAAa,EAC7B,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAC7B,MAAM,CA+BR"}
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB;;;;CAI/B,CAAC;AAUF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GAAG,SAAS,EAChD,EAAE,EAAE,MAAM,IAAI,GACb,IAAI,CAkJN;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,EAAE;IACnC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B,CAAC;AAuJP;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAK/E;AAED;;;;;;;;;;;;;GAaG;AACH;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAerD;AAED,wEAAwE;AACxE,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA0BD,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,GAAG,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,EACrC,IAAI,CAAC,EAAE,yBAAyB,GAC/B,IAAI,CAEN;AAiFD,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,aAAa,CAAC,EAAE,aAAa,EAC7B,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAC7B,MAAM,CAwBR;AAED,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,MAAM,EACpB,GAAG,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,EACrC,aAAa,CAAC,EAAE,aAAa,EAC7B,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAC7B,MAAM,CA+BR"}
package/dist/workspace.js CHANGED
@@ -547,12 +547,11 @@ function systemPromptCopyPath(workspaceDir) {
547
547
  // guide, before skill references; empty/absent = no capability section.
548
548
  // Split out from ensureCodexWorkspace so the bridge can rebuild the prompt on
549
549
  // a platform-config heat-update and hand the new value to
550
- // adapter.updateConfig, which carries it into the NEXT thread/start. A thread
551
- // that is already open keeps the instructions it was started with — codex
552
- // bakes them into the thread at start and neither thread/resume nor
553
- // thread/fork replaces them (verified against a live 0.144.1 app-server; the
554
- // retired workspace-config channel behaved identically). See
555
- // docs/tech-debt/codex-persisted-thread-prompt-refresh.md.
550
+ // adapter.updateConfig. Delivery to threads is the adapter's job: baked into
551
+ // new threads at thread/start; converged onto the persisted main thread via
552
+ // the lazy restart's fresh-process thread/resume (canonical configuration)
553
+ // plus an explicit compaction (model-visible context) see
554
+ // src/instructions-refresh.ts for the two-plane semantics.
556
555
  // Throws on failure BY DESIGN — at boot the prompt is mandatory, so a write
557
556
  // failure must fail startup loudly; the refresh hot path wraps this in
558
557
  // try/catch and retries next refresh.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/codex-agent",
3
- "version": "1.45.0",
3
+ "version": "1.47.0",
4
4
  "description": "Codex CLI bridge runtime for self-hosted Parall agents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -25,9 +25,9 @@
25
25
  "src"
26
26
  ],
27
27
  "dependencies": {
28
- "@parall/agent-core": "1.45.0",
29
- "@parall/cli": "1.45.0",
30
- "@parall/sdk": "1.45.0"
28
+ "@parall/agent-core": "1.47.0",
29
+ "@parall/sdk": "1.47.0",
30
+ "@parall/cli": "1.47.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^22.0.0",