@spexcode/spec-cli 0.7.0-next.4 → 0.7.0-next.5

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.
@@ -46,6 +46,10 @@ export declare function prepareCodexGenerationClose(root: string, sessionId: str
46
46
  export declare function repinCodexGeneration(root: string, sessionId: string, threadId: string, generationId: string): void;
47
47
  export declare function resolveCodexGenerationForResume(root: string, sessionId: string, threadId: string, start: (endpoint: CodexGenerationEndpoint) => Promise<void>): Promise<CodexGenerationEndpoint | null>;
48
48
  export declare function resolveCodexGenerationForSession(root: string, sessionId: string, threadId: string): CodexGenerationEndpoint | null;
49
+ export declare function resolveCodexGenerationForClose(root: string, sessionId: string, threadId: string): {
50
+ endpoint: CodexGenerationEndpoint;
51
+ gone: boolean;
52
+ } | null;
49
53
  export declare function codexGenerationBindingForSession(root: string, sessionId: string): CodexGenerationBinding | null;
50
54
  export declare function currentCodexGeneration(root: string): CodexGenerationEndpoint | null;
51
55
  export declare function codexGenerationEndpoints(root: string): CodexGenerationEndpoint[];
@@ -765,6 +765,18 @@ export function resolveCodexGenerationForSession(root, sessionId, threadId) {
765
765
  const generation = ledger.generations[binding.generationId];
766
766
  return generation && generation.state !== 'reclaimed' ? generation.endpoint : null;
767
767
  }
768
+ // Close still needs the immutable endpoint record to recognize a positively retired generation. The
769
+ // endpoint is never used for native I/O in this state; it only identifies the empty control plane.
770
+ export function resolveCodexGenerationForClose(root, sessionId, threadId) {
771
+ const ledger = readCodexGenerationLedger(root);
772
+ const binding = ledger.bindings[sessionId];
773
+ if (!binding || binding.threadId !== threadId)
774
+ return null;
775
+ const generation = ledger.generations[binding.generationId];
776
+ if (!generation)
777
+ return null;
778
+ return { endpoint: generation.endpoint, gone: generation.state === 'reclaimed' };
779
+ }
768
780
  export function codexGenerationBindingForSession(root, sessionId) {
769
781
  return readCodexGenerationLedger(root).bindings[sessionId] ?? null;
770
782
  }
package/dist/harness.js CHANGED
@@ -18,7 +18,7 @@ import { runtimeRoot, mainCheckout, readConfig, sessionArtifactPath, spexcodeHom
18
18
  import { git } from '@spexcode/spec-core';
19
19
  import { shQuote } from './sh.js';
20
20
  import { detachedRuntimeGenerationToken, migrateLegacyDetachedRuntimeReceipt, processStartToken, verifyDetachedRuntime } from '@spexcode/spec-core';
21
- import { codexGenerationEndpoints, codexGenerationSocketPath, currentCodexGeneration, legacyCodexGenerationEndpoint, readCodexGenerationLedger, resolveCodexGenerationForSession } from './codex-runtime-generations.js';
21
+ import { codexGenerationEndpoints, codexGenerationSocketPath, currentCodexGeneration, legacyCodexGenerationEndpoint, readCodexGenerationLedger, prepareCodexGenerationClose, resolveCodexGenerationForClose, resolveCodexGenerationForSession } from './codex-runtime-generations.js';
22
22
  import { writeFileIfChanged } from './file-write.js';
23
23
  import { claudeTranscript, codexRolloutPath, codexTranscript, opencodeTranscript, piTranscript, unsupportedTranscript } from '@spexcode/transcript';
24
24
  import { harnessIdentity, HARNESS_IDENTITIES } from '@spexcode/spec-core';
@@ -220,12 +220,14 @@ function codexMutationGeneration(dir = runtimeRoot(), endpoint = legacyCodexGene
220
220
  return codexRuntimeGeneration(dir, endpoint);
221
221
  }
222
222
  const codexDescriptorKey = (endpoint) => endpoint.id === 'legacy' ? 'codex-app-server' : `codex-app-server:${endpoint.id}`;
223
- function codexEndpointForRecord(rec, dir = runtimeRoot()) {
223
+ function codexEndpointForRecord(rec, dir = runtimeRoot(), includeGone = false) {
224
224
  if (!rec.harnessSessionId)
225
225
  return null;
226
226
  const ledger = readCodexGenerationLedger(dir);
227
227
  if (ledger.revision === 0 && !ledger.current && !Object.keys(ledger.generations).length)
228
228
  return legacyCodexGenerationEndpoint(dir);
229
+ if (includeGone)
230
+ return resolveCodexGenerationForClose(dir, rec.session, rec.harnessSessionId)?.endpoint ?? null;
229
231
  return resolveCodexGenerationForSession(dir, rec.session, rec.harnessSessionId);
230
232
  }
231
233
  // the spex launcher (bin/spex.mjs), baked into the codex launch script (mirrors materialize.ts's SPEX) so
@@ -3028,7 +3030,7 @@ export const codexHarness = {
3028
3030
  interrupt: interruptCodexTurn,
3029
3031
  cleanupRuntime: async () => { },
3030
3032
  targetDescriptorKey: (rec) => {
3031
- const endpoint = codexEndpointForRecord(rec);
3033
+ const endpoint = codexEndpointForRecord(rec, runtimeRoot(), true);
3032
3034
  return endpoint ? codexDescriptorKey(endpoint) : null;
3033
3035
  },
3034
3036
  coldRetirementPreflight: async (rec) => {
@@ -3052,8 +3054,13 @@ export const codexHarness = {
3052
3054
  coldPreflight: async (rec) => {
3053
3055
  if (!rec.harnessSessionId)
3054
3056
  return { ok: false, reason: 'no exact Codex thread identity is registered' };
3055
- const endpoint = codexEndpointForRecord(rec);
3056
- return endpoint ? codexColdPreflight(rec.harnessSessionId, runtimeRoot(), undefined, endpoint)
3057
+ const dir = runtimeRoot();
3058
+ const binding = resolveCodexGenerationForClose(dir, rec.session, rec.harnessSessionId);
3059
+ if (binding?.gone) {
3060
+ return { ok: true, alreadyCold: true };
3061
+ }
3062
+ const endpoint = binding?.endpoint ?? codexEndpointForRecord(rec, dir);
3063
+ return endpoint ? codexColdPreflight(rec.harnessSessionId, dir, undefined, endpoint)
3057
3064
  : { ok: false, reason: 'no exact Codex generation binding is registered for this target' };
3058
3065
  },
3059
3066
  coldRuntime: async (rec, suppliedReceipt) => {
@@ -3061,7 +3068,12 @@ export const codexHarness = {
3061
3068
  return { ok: false, reason: 'no exact Codex thread identity is registered' };
3062
3069
  const threadId = rec.harnessSessionId;
3063
3070
  const dir = runtimeRoot();
3064
- const endpoint = codexEndpointForRecord(rec, dir);
3071
+ const binding = resolveCodexGenerationForClose(dir, rec.session, threadId);
3072
+ if (binding?.gone) {
3073
+ prepareCodexGenerationClose(dir, rec.session, threadId);
3074
+ return { ok: true };
3075
+ }
3076
+ const endpoint = binding?.endpoint ?? codexEndpointForRecord(rec, dir);
3065
3077
  if (!endpoint)
3066
3078
  return { ok: false, reason: 'no exact Codex generation binding is registered for this target' };
3067
3079
  const sock = endpoint.socketPath;
@@ -471,6 +471,17 @@ const sessionStopBlocker = async (id, harnessId, recs = rawRecords(), knownProbe
471
471
  const entryTargetThread = entry.recs.find((rec) => rec.session_id === id)?.harness_session_id;
472
472
  if (entryTargetThread && ownerCounts.get(entryTargetThread) !== 1)
473
473
  return `${descriptor.label} target thread ${entryTargetThread} has no one exact governed session owner`;
474
+ // A retired shared generation is positive death, not an unknown control plane. Its residency census is
475
+ // the only proof needed here; there is no native target left to signal or unload.
476
+ if (descriptor.residency && entryTargetThread) {
477
+ let residency = null;
478
+ try {
479
+ residency = await descriptor.residency();
480
+ }
481
+ catch { /* retain the normal fail-closed path */ }
482
+ if (residency?.healthy && residency.rootAbsent === true && residency.referenceIds.length === 0)
483
+ continue;
484
+ }
474
485
  if (!knownProbes && descriptor.mutationGuard) {
475
486
  if (!targetThread)
476
487
  return `${descriptor.label} target has no exact governed thread identity`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spexcode/spec-cli",
3
- "version": "0.7.0-next.4",
3
+ "version": "0.7.0-next.5",
4
4
  "type": "module",
5
5
  "description": "SpexCode CLI + server. The root spexcode package delegates to this compiled package; dashboard assets live in @spexcode/spec-dashboard.",
6
6
  "bin": {
@@ -35,12 +35,12 @@
35
35
  "test": "tsx --import ../scripts/test-home.mjs --test src/*.test.ts"
36
36
  },
37
37
  "dependencies": {
38
- "@spexcode/session-application": "0.7.0-next.4",
39
- "@spexcode/session-selflaunch": "0.7.0-next.4",
40
- "@spexcode/spec-core": "0.7.0-next.4",
41
- "@spexcode/spec-eval": "0.7.0-next.4",
42
- "@spexcode/spec-forge": "0.7.0-next.4",
43
- "@spexcode/transcript": "0.7.0-next.4",
38
+ "@spexcode/session-application": "0.7.0-next.5",
39
+ "@spexcode/session-selflaunch": "0.7.0-next.5",
40
+ "@spexcode/spec-core": "0.7.0-next.5",
41
+ "@spexcode/spec-eval": "0.7.0-next.5",
42
+ "@spexcode/spec-forge": "0.7.0-next.5",
43
+ "@spexcode/transcript": "0.7.0-next.5",
44
44
  "smol-toml": "^1.8.0"
45
45
  },
46
46
  "devDependencies": {