@akagilnc/pi-workflow-roles 0.1.5504 → 0.1.5575

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 (89) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/acp-host/production-host.js +5965 -5994
  4. package/dist/analyst-gate-cycles-read.js +23 -55
  5. package/dist/analyst-ledger.js +21 -35
  6. package/dist/analyst-metric-families/acceptance-success-rework.js +25 -1
  7. package/dist/analyst-metric-families/round-timeline.js +2 -5
  8. package/dist/archivist-record-pointer.js +38 -30
  9. package/dist/auditor-dossier-tool.js +4 -3
  10. package/dist/auditor-soul.js +3 -11
  11. package/dist/doctor-evidence.js +3 -3
  12. package/dist/engine-detour-usage.js +6 -14
  13. package/dist/headless-host/production-host.js +5316 -5324
  14. package/dist/host-descriptions.js +0 -4
  15. package/dist/host-session-record.js +266 -267
  16. package/dist/migrate-book-topology.js +1314 -696
  17. package/dist/notary-source-run.js +1 -1
  18. package/dist/packaged-role-registry.js +5 -44
  19. package/dist/pi/role-turn-host.js +53 -3
  20. package/dist/public-cli/auto-resume.js +8 -47
  21. package/dist/public-cli/instruction-seat-run.js +5 -3
  22. package/dist/public-cli/invocation.js +51 -56
  23. package/dist/public-cli/main.js +1916 -1748
  24. package/dist/public-cli/post-admission.js +96 -55
  25. package/dist/public-cli/run-lifecycle.js +27 -53
  26. package/dist/public-cli/settlement.js +157 -302
  27. package/dist/role-run-placement.js +1 -11
  28. package/dist/role-run-relocation.js +72 -50
  29. package/dist/run-dossier-files.js +30 -0
  30. package/dist/run-dossier.js +322 -0
  31. package/dist/run-terminal-artifacts.js +68 -197
  32. package/dist/run-ticket-number.js +16 -3
  33. package/dist/session-identity.js +49 -18
  34. package/dist/sitian-appender.js +13 -16
  35. package/dist/sitian-facade.js +11 -0
  36. package/dist/sitian-reader.js +44 -29
  37. package/dist/submission-gate.js +1 -1
  38. package/dist/ticket-provenance.js +22 -7
  39. package/package.json +1 -1
  40. package/scripts/build-package.mjs +2 -0
  41. package/src/acp-host/production-host.ts +1 -1
  42. package/src/acp-host/role-turn-host.ts +9 -2
  43. package/src/analyst-gate-cycles-read.ts +26 -60
  44. package/src/analyst-ledger.ts +27 -42
  45. package/src/analyst-metric-families/acceptance-success-rework.ts +24 -1
  46. package/src/analyst-metric-families/gate-cycles.ts +1 -1
  47. package/src/analyst-metric-families/round-timeline.ts +2 -5
  48. package/src/analyst-page.ts +3 -3
  49. package/src/archivist-record-entry.ts +2 -2
  50. package/src/archivist-record-pointer.ts +50 -36
  51. package/src/auditor-dossier-tool.ts +7 -5
  52. package/src/auditor-soul.ts +3 -9
  53. package/src/book-topology-migration-placement.ts +16 -3
  54. package/src/book-topology-record-class-migrators.ts +4 -2
  55. package/src/book-topology-runs-migrator.ts +6 -3
  56. package/src/doctor-contracts.ts +1 -1
  57. package/src/doctor-evidence.ts +2 -2
  58. package/src/engine-detour-tool.ts +1 -1
  59. package/src/engine-detour-usage.ts +6 -16
  60. package/src/external-host-turn-loop.ts +25 -0
  61. package/src/headless-host/production-host.ts +1 -4
  62. package/src/headless-host/role-turn-host.ts +54 -20
  63. package/src/host-descriptions.ts +0 -6
  64. package/src/host-session-record.ts +224 -140
  65. package/src/notary-source-run.ts +1 -1
  66. package/src/packaged-role-registry.ts +1 -50
  67. package/src/pi/adapter.ts +50 -7
  68. package/src/pi/role-turn-host.ts +51 -3
  69. package/src/public-cli/auto-resume.ts +7 -43
  70. package/src/public-cli/instruction-seat-run.ts +9 -2
  71. package/src/public-cli/invocation.ts +61 -87
  72. package/src/public-cli/post-admission.ts +111 -72
  73. package/src/public-cli/run-lifecycle.ts +27 -72
  74. package/src/public-cli/settlement.ts +193 -418
  75. package/src/public-cli/terminal.ts +1 -1
  76. package/src/role-run-placement.ts +2 -13
  77. package/src/role-run-relocation.ts +71 -68
  78. package/src/role-runtime.ts +6 -10
  79. package/src/run-dossier-files.ts +33 -0
  80. package/src/run-dossier.ts +357 -0
  81. package/src/run-terminal-artifacts.ts +83 -237
  82. package/src/run-ticket-number.ts +41 -11
  83. package/src/session-identity.ts +52 -20
  84. package/src/sitian-appender.ts +17 -18
  85. package/src/sitian-facade.ts +22 -0
  86. package/src/sitian-reader.ts +62 -32
  87. package/src/submission-gate.ts +1 -1
  88. package/src/ticket-provenance.ts +20 -7
  89. package/src/ticket-trajectory.ts +10 -10
@@ -26,7 +26,6 @@ export const HOST_DESCRIPTIONS = Object.freeze({
26
26
  }),
27
27
  modelPassing: "set_model",
28
28
  setModelId: "bare",
29
- sessionBindingFile: "grok-acp-session.json",
30
29
  }),
31
30
  /**
32
31
  * Operator home `~/.hermes`, native session/load resume, `acp` subcommand.
@@ -44,7 +43,6 @@ export const HOST_DESCRIPTIONS = Object.freeze({
44
43
  }),
45
44
  modelPassing: "set_model",
46
45
  setModelId: "provider:model",
47
- sessionBindingFile: "hermes-acp-session.json",
48
46
  seatProfileSoul: Object.freeze({
49
47
  flag: "-p",
50
48
  namePrefix: "ak-",
@@ -64,7 +62,6 @@ export const HEADLESS_HOST_DESCRIPTIONS = Object.freeze({
64
62
  "claude": Object.freeze({
65
63
  protocol: "claude-print",
66
64
  binaryFromHome: Object.freeze([".local", "bin", "claude"]),
67
- sessionBindingFile: "claude-headless-session.json",
68
65
  fixedArgs: Object.freeze([
69
66
  // Live NDJSON events → sitian host-session records (#811); last line is the result receipt.
70
67
  "--output-format", "stream-json",
@@ -89,7 +86,6 @@ export const HEADLESS_HOST_DESCRIPTIONS = Object.freeze({
89
86
  "codex": Object.freeze({
90
87
  protocol: "codex-exec",
91
88
  binaryFromHome: Object.freeze([".local", "bin", "codex"]),
92
- sessionBindingFile: "codex-headless-session.json",
93
89
  }),
94
90
  });
95
91
  export function lookupHostDescription(host) {
@@ -1,304 +1,303 @@
1
- /**
2
- * Host conversation dossiers are native file copies; sitian append-only (ADR 0086).
3
- * Replaces live event streaming (ADR 0077) with post-exit CLI original copy.
4
- * Writes native-session-pointer when session ID is obtained.
5
- * Writes native-session-copy (or native-session-warning on retry failure) after CLI exit.
6
- * Log line write failures declare to stderr and never abort the leg.
7
- */
1
+ import { randomBytes } from "node:crypto";
8
2
  import { spawnSync } from "node:child_process";
9
- import { appendFileSync, copyFileSync, closeSync, existsSync, openSync, readFileSync, mkdirSync, readdirSync, rmSync, statSync, } from "node:fs";
3
+ import {
4
+ copyFileSync,
5
+ existsSync,
6
+ mkdirSync,
7
+ readdirSync,
8
+ renameSync,
9
+ rmSync,
10
+ statSync
11
+ } from "node:fs";
10
12
  import { homedir } from "node:os";
11
13
  import { dirname, join } from "node:path";
12
14
  import { sitianReport } from "./sitian-facade.js";
13
- import { errorText } from "./unknown-value.js";
14
- /** Volume category under `<run>/session/<kind>/records.jsonl`. */
15
- export const HOST_SESSION_RECORD_KIND = "host-session";
15
+ import { errorText, isEnoent } from "./unknown-value.js";
16
+ const HOST_SESSION_RECORD_KIND = "host-session";
16
17
  function declareHostSessionFailure(error) {
17
- const message = error instanceof Error ? (error.stack || error.message) : String(error);
18
- process.stderr.write(`[host-session] Dossier record failure: ${message}\n`);
18
+ const message = error instanceof Error ? error.stack || error.message : String(error);
19
+ process.stderr.write(`[host-session] Dossier record failure: ${message}
20
+ `);
19
21
  }
20
- /** Declare sitian record failure once to stderr without aborting the leg (ADR 0086). */
21
- export function sitianReportSafe(input) {
22
- try {
23
- sitianReport(input);
24
- }
25
- catch (error) {
26
- declareHostSessionFailure(error);
27
- }
22
+ function sitianReportSafe(input) {
23
+ try {
24
+ sitianReport(input);
25
+ } catch (error) {
26
+ declareHostSessionFailure(error);
27
+ }
28
28
  }
29
- /** Recursively scan for Codex rollout file matching sessionId under sessionsDir. */
30
29
  function findCodexRollout(sessionsDir, sessionId) {
31
- if (!existsSync(sessionsDir))
32
- return undefined;
33
- const matches = [];
34
- function scan(dir, depth = 0) {
35
- if (depth > 6)
36
- return;
37
- let entries;
30
+ const matches = [];
31
+ function scan(dir, depth = 0) {
32
+ if (depth > 6) return;
33
+ let entries;
34
+ try {
35
+ entries = readdirSync(dir, { withFileTypes: true });
36
+ } catch (error) {
37
+ if (isEnoent(error)) return;
38
+ throw error;
39
+ }
40
+ for (const entry of entries) {
41
+ const fullPath = join(dir, entry.name);
42
+ if (entry.isDirectory()) {
43
+ scan(fullPath, depth + 1);
44
+ } else if (entry.isFile() && entry.name.endsWith(".jsonl") && entry.name.includes(sessionId)) {
38
45
  try {
39
- entries = readdirSync(dir, { withFileTypes: true });
40
- }
41
- catch {
42
- return;
43
- }
44
- for (const entry of entries) {
45
- const fullPath = join(dir, entry.name);
46
- if (entry.isDirectory()) {
47
- scan(fullPath, depth + 1);
48
- }
49
- else if (entry.isFile() && entry.name.endsWith(".jsonl") && entry.name.includes(sessionId)) {
50
- try {
51
- const stat = statSync(fullPath);
52
- matches.push({ path: fullPath, mtime: stat.mtimeMs });
53
- }
54
- catch {
55
- matches.push({ path: fullPath, mtime: 0 });
56
- }
57
- }
46
+ const stat = statSync(fullPath);
47
+ matches.push({ path: fullPath, mtime: stat.mtimeMs });
48
+ } catch (error) {
49
+ if (isEnoent(error)) continue;
50
+ throw error;
58
51
  }
52
+ }
59
53
  }
54
+ }
55
+ try {
60
56
  scan(sessionsDir);
61
- if (matches.length === 0 || matches[0] === undefined)
62
- return undefined;
63
- matches.sort((a, b) => b.mtime - a.mtime);
64
- return matches[0]?.path;
57
+ } catch (error) {
58
+ if (isEnoent(error)) return void 0;
59
+ throw error;
60
+ }
61
+ if (matches.length === 0 || matches[0] === void 0) return void 0;
62
+ matches.sort((a, b) => b.mtime - a.mtime);
63
+ return matches[0]?.path;
65
64
  }
66
- /**
67
- * Sanitize working directory to match Claude Code's project directory name layout.
68
- * Claude Code replaces all non-alphanumeric characters (slashes, dots, underscores, spaces, etc.) with '-'.
69
- */
70
- export function sanitizeClaudeCwd(cwd) {
71
- return cwd.replace(/[^a-zA-Z0-9]/g, "-");
65
+ function sanitizeClaudeCwd(cwd) {
66
+ return cwd.replace(/[^a-zA-Z0-9]/g, "-");
72
67
  }
73
- /**
74
- * Resolve the CLI native session original path.
75
- * Codex: single file rollout from ~/.codex/sessions/** /rollout-*-${sessionId}.jsonl
76
- * Claude: single file transcript from ~/.claude/projects/<sanitized-cwd>/${sessionId}.jsonl
77
- * Grok: directory ~/.grok/sessions/<encoded-cwd>/${sessionId}
78
- * Pi / Hermes: undefined (Pi directly lands in session.jsonl; Hermes is state.db excluded from ADR 0086)
79
- */
80
- export function resolveNativeSessionPath(options) {
81
- const home = options.home || homedir();
82
- if (options.host === "claude") {
83
- const configDir = process.env.CLAUDE_CONFIG_DIR || join(home, ".claude");
84
- const sanitizedCwd = sanitizeClaudeCwd(options.cwd);
85
- return join(configDir, "projects", sanitizedCwd, `${options.sessionId}.jsonl`);
86
- }
87
- if (options.host === "codex") {
88
- return findCodexRollout(codexSessionsDirectory(home), options.sessionId);
89
- }
90
- if (options.host === "grok-build") {
91
- const grokHome = process.env.GROK_HOME || join(home, ".grok");
92
- return join(grokHome, "sessions", encodeURIComponent(options.cwd), options.sessionId);
93
- }
94
- return undefined;
68
+ function resolveNativeSessionPath(options) {
69
+ const home = options.home || homedir();
70
+ if (options.host === "claude") {
71
+ const configDir = process.env.CLAUDE_CONFIG_DIR || join(home, ".claude");
72
+ const sanitizedCwd = sanitizeClaudeCwd(options.cwd);
73
+ return join(configDir, "projects", sanitizedCwd, `${options.sessionId}.jsonl`);
74
+ }
75
+ if (options.host === "codex") {
76
+ return findCodexRollout(codexSessionsDirectory(home), options.sessionId);
77
+ }
78
+ if (options.host === "grok-build") {
79
+ const grokHome = process.env.GROK_HOME || join(home, ".grok");
80
+ return join(grokHome, "sessions", encodeURIComponent(options.cwd), options.sessionId);
81
+ }
82
+ return void 0;
95
83
  }
96
84
  function codexSessionsDirectory(home) {
97
- return join(process.env.CODEX_HOME || join(home || homedir(), ".codex"), "sessions");
85
+ return join(process.env.CODEX_HOME || join(home || homedir(), ".codex"), "sessions");
98
86
  }
99
- /** Count each host activation, including Pi turns without an external copy. */
100
- export function recordRunStart(runDirectory) {
101
- try {
102
- appendFileSync(join(runDirectory, ".run-starts"), "\n");
103
- }
104
- catch (error) {
105
- declareHostSessionFailure(error);
106
- }
87
+ function resolveHostDossierLandingPath(options) {
88
+ return options.host === "grok-build" ? join(options.sessionDirectory, options.host) : join(options.sessionDirectory, `${options.host}.jsonl`);
107
89
  }
108
- /**
109
- * Resolve the host dossier landing destination under `<run>/session/`.
110
- * Format: `<run>/session/<host>-<model>-<n>` (single file adds .jsonl; model slashes replaced with -).
111
- * Ordinal n is the start/resume count in the run (initial 1, each resume +1).
112
- */
113
- export function resolveHostDossierLandingPath(options) {
114
- const sanitizedModel = (options.model?.model ?? "default").replace(/[\/\\]+/g, "-");
115
- let maxOrdinal = 0;
116
- if (existsSync(options.sessionDirectory)) {
117
- try {
118
- const entries = readdirSync(options.sessionDirectory);
119
- for (const entry of entries) {
120
- const match = entry.match(/-(\d+)(?:\.jsonl|\.failed)?$/);
121
- const first = match?.[1];
122
- if (first !== undefined) {
123
- const val = parseInt(first, 10);
124
- if (!isNaN(val) && val > maxOrdinal) {
125
- maxOrdinal = val;
126
- }
127
- }
128
- }
129
- }
130
- catch {
131
- // Ignore directory read errors
132
- }
133
- }
134
- const startsPath = join(dirname(options.sessionDirectory), ".run-starts");
135
- let starts = 0;
136
- if (existsSync(startsPath))
137
- starts = readFileSync(startsPath, "utf8").length;
138
- const ordinal = Math.max(maxOrdinal + 1, starts, options.continuation.kind === "resume" ? 2 : 1);
139
- const baseName = `${options.host}-${sanitizedModel}-${ordinal}`;
140
- const landingPath = options.host === "grok-build"
141
- ? join(options.sessionDirectory, baseName)
142
- : join(options.sessionDirectory, `${baseName}.jsonl`);
143
- return { landingPath, ordinal, sanitizedModel };
144
- }
145
- /** Record native session pointer line to Sitian as soon as session ID is known. */
146
- export function recordNativeSessionPointer(options) {
147
- if (options.host === "pi" || options.host === "hermes") {
148
- return undefined;
90
+ function recordNativeSessionPointer(options) {
91
+ if (options.host === "pi" || options.host === "hermes") {
92
+ return void 0;
93
+ }
94
+ const nativePath = options.host === "codex" ? codexSessionsDirectory(options.home) : resolveNativeSessionPath(options);
95
+ if (nativePath === void 0) return void 0;
96
+ sitianReportSafe({
97
+ level: "event",
98
+ kind: HOST_SESSION_RECORD_KIND,
99
+ host: options.host,
100
+ cwd: options.cwd,
101
+ sessionParent: options.sessionParent,
102
+ source: `${options.host}-dossier`,
103
+ payload: {
104
+ type: "native-session-pointer",
105
+ nativePath,
106
+ sessionId: options.sessionId
149
107
  }
150
- const nativePath = options.host === "codex"
151
- ? codexSessionsDirectory(options.home)
152
- : resolveNativeSessionPath(options);
153
- if (nativePath === undefined)
154
- return undefined;
155
- sitianReportSafe({
156
- level: "event",
157
- kind: HOST_SESSION_RECORD_KIND,
158
- host: options.host,
159
- cwd: options.cwd,
160
- sessionParent: options.sessionParent,
161
- source: `${options.host}-dossier`,
162
- payload: {
163
- type: "native-session-pointer",
164
- nativePath,
165
- sessionId: options.sessionId,
166
- },
167
- });
168
- return nativePath;
108
+ });
109
+ return nativePath;
169
110
  }
170
- /** File clone via APFS `cp -c` with fallback to `copyFileSync`. */
171
111
  function copyFileCloneOrFallback(src, dest) {
172
- try {
173
- const res = spawnSync("cp", ["-c", src, dest]);
174
- if (res.status === 0)
175
- return;
176
- }
177
- catch {
178
- // cp execution failed, fall back
179
- }
180
- copyFileSync(src, dest);
112
+ try {
113
+ const res = spawnSync("cp", ["-c", src, dest]);
114
+ if (res.status === 0) return;
115
+ } catch {
116
+ }
117
+ copyFileSync(src, dest);
181
118
  }
182
- /** Copy both grok-build originals: chat_history.jsonl and usage.json into destDir. */
183
119
  function copyGrokDossier(srcDir, destDir) {
184
- const chatHistorySrc = join(srcDir, "chat_history.jsonl");
185
- if (!existsSync(chatHistorySrc)) {
186
- throw new Error(`Grok native chat_history.jsonl missing at ${chatHistorySrc}`);
187
- }
188
- mkdirSync(destDir, { recursive: true });
189
- const chatHistoryDest = join(destDir, "chat_history.jsonl");
190
- copyFileCloneOrFallback(chatHistorySrc, chatHistoryDest);
191
- const usageSrc = join(srcDir, "usage.json");
192
- if (!existsSync(usageSrc)) {
193
- throw new Error(`Grok native usage.json missing at ${usageSrc}`);
194
- }
195
- copyFileCloneOrFallback(usageSrc, join(destDir, "usage.json"));
120
+ mkdirSync(destDir, { recursive: true });
121
+ copyFileCloneOrFallback(join(srcDir, "chat_history.jsonl"), join(destDir, "chat_history.jsonl"));
122
+ copyFileCloneOrFallback(join(srcDir, "usage.json"), join(destDir, "usage.json"));
196
123
  }
197
- /**
198
- * Copy native host session original to run session dossier after child process exits.
199
- * If copy fails, retries once.
200
- * On success, appends native-session-copy line to Sitian.
201
- * On repeated failure, appends native-session-warning line to Sitian.
202
- * Does not throw; turn outcome is never altered by copy success or failure.
203
- */
204
- export function copyAndRecordHostDossier(options) {
205
- if (options.host === "pi" || options.host === "hermes") {
206
- return;
207
- }
208
- const nativePath = resolveNativeSessionPath({
124
+ function copyAndRecordHostDossier(options) {
125
+ if (options.host === "hermes") {
126
+ return;
127
+ }
128
+ const report = (payload) => sitianReportSafe({
129
+ level: "event",
130
+ kind: HOST_SESSION_RECORD_KIND,
131
+ host: options.host,
132
+ cwd: options.cwd,
133
+ sessionParent: options.sessionParent,
134
+ source: `${options.host}-dossier`,
135
+ payload
136
+ });
137
+ if (options.host === "pi") {
138
+ report({
139
+ type: "native-session-copy",
140
+ nativePath: options.sessionParent,
141
+ landingPath: options.sessionParent,
142
+ sessionId: options.sessionId
143
+ });
144
+ return;
145
+ }
146
+ const landingPath = resolveHostDossierLandingPath(options);
147
+ let lastError;
148
+ let copySuccess = false;
149
+ let stagingForCleanup;
150
+ let nativePath;
151
+ for (let attempt = 1; attempt <= 2; attempt++) {
152
+ const attemptToken = randomBytes(6).toString("hex");
153
+ const staging = `${landingPath}.copying.${attemptToken}`;
154
+ stagingForCleanup = staging;
155
+ try {
156
+ nativePath = resolveNativeSessionPath({
209
157
  host: options.host,
210
158
  sessionId: options.sessionId,
211
159
  cwd: options.cwd,
212
- ...(options.home !== undefined ? { home: options.home } : {}),
213
- });
214
- if (nativePath === undefined && options.host !== "codex")
215
- return;
216
- let destination;
217
- try {
218
- destination = resolveHostDossierLandingPath(options);
219
- }
220
- catch (error) {
221
- sitianReportSafe({
222
- level: "event",
223
- kind: HOST_SESSION_RECORD_KIND,
224
- host: options.host,
225
- cwd: options.cwd,
226
- sessionParent: options.sessionParent,
227
- source: `${options.host}-dossier`,
228
- payload: {
229
- type: "native-session-warning",
230
- sessionId: options.sessionId,
231
- error: errorText(error),
232
- },
233
- });
160
+ ...options.home !== void 0 ? { home: options.home } : {}
161
+ });
162
+ if (nativePath === void 0 && options.host !== "codex") {
163
+ stagingForCleanup = void 0;
234
164
  return;
235
- }
236
- const { landingPath, ordinal, sanitizedModel } = destination;
237
- let lastError;
238
- let copySuccess = false;
239
- for (let attempt = 1; attempt <= 2; attempt++) {
165
+ }
166
+ if (options.host === "grok-build") {
167
+ copyGrokDossier(nativePath, staging);
168
+ } else {
169
+ if (nativePath === void 0) {
170
+ throw new Error(`Native session file missing for ${options.host} session ${options.sessionId}`);
171
+ }
172
+ mkdirSync(dirname(landingPath), { recursive: true });
173
+ copyFileCloneOrFallback(nativePath, staging);
174
+ }
175
+ if (options.host === "grok-build") {
176
+ const previous = `${landingPath}.previous.${attemptToken}`;
177
+ const hadPrevious = existsSync(landingPath);
178
+ let parked = false;
179
+ let keepUniquePrevious = false;
180
+ let cleanupError;
181
+ let swapFailure;
182
+ let restoreError;
240
183
  try {
241
- if (options.host === "grok-build") {
242
- copyGrokDossier(nativePath, landingPath);
243
- }
244
- else {
245
- if (nativePath === undefined || !existsSync(nativePath)) {
246
- throw new Error(`Native session file missing for ${options.host} session ${options.sessionId}`);
184
+ if (hadPrevious) {
185
+ renameSync(landingPath, previous);
186
+ parked = true;
187
+ }
188
+ try {
189
+ renameSync(staging, landingPath);
190
+ } catch (swapError) {
191
+ if (parked && existsSync(previous) && !existsSync(landingPath)) {
192
+ try {
193
+ renameSync(previous, landingPath);
194
+ } catch (error) {
195
+ restoreError = error;
196
+ if (existsSync(previous) && !existsSync(landingPath)) {
197
+ keepUniquePrevious = true;
247
198
  }
248
- mkdirSync(dirname(landingPath), { recursive: true });
249
- copyFileCloneOrFallback(nativePath, landingPath);
199
+ }
200
+ }
201
+ swapFailure = swapError;
202
+ }
203
+ } finally {
204
+ if (parked && existsSync(previous) && !keepUniquePrevious) {
205
+ try {
206
+ rmSync(previous, { recursive: true, force: true });
207
+ } catch (error) {
208
+ cleanupError = error;
250
209
  }
251
- copySuccess = true;
210
+ }
211
+ }
212
+ if (swapFailure !== void 0) {
213
+ lastError = combineCopyFailureCauses(
214
+ swapFailure,
215
+ restoreError,
216
+ cleanupError,
217
+ cleanupError === void 0 ? void 0 : previous
218
+ );
219
+ if (restoreError !== void 0 || cleanupError !== void 0) {
220
+ break;
221
+ }
222
+ try {
223
+ rmSync(staging, { recursive: true, force: true });
224
+ stagingForCleanup = void 0;
225
+ } catch (stagingCleanupError) {
226
+ lastError = combineCopyFailureCauses(
227
+ swapFailure,
228
+ void 0,
229
+ stagingCleanupError,
230
+ staging
231
+ );
232
+ stagingForCleanup = void 0;
252
233
  break;
234
+ }
235
+ continue;
253
236
  }
254
- catch (error) {
255
- lastError = error;
237
+ if (cleanupError !== void 0) {
238
+ lastError = combineCopyFailureCauses(cleanupError, void 0, cleanupError, previous);
239
+ stagingForCleanup = void 0;
240
+ break;
256
241
  }
242
+ } else {
243
+ renameSync(staging, landingPath);
244
+ }
245
+ copySuccess = true;
246
+ stagingForCleanup = void 0;
247
+ break;
248
+ } catch (error) {
249
+ lastError = error;
250
+ try {
251
+ rmSync(staging, { recursive: true, force: true });
252
+ stagingForCleanup = void 0;
253
+ } catch (cleanupError) {
254
+ lastError = combineCopyFailureCauses(error, void 0, cleanupError, staging);
255
+ stagingForCleanup = void 0;
256
+ break;
257
+ }
257
258
  }
258
- if (copySuccess) {
259
- sitianReportSafe({
260
- level: "event",
261
- kind: HOST_SESSION_RECORD_KIND,
262
- host: options.host,
263
- cwd: options.cwd,
264
- sessionParent: options.sessionParent,
265
- source: `${options.host}-dossier`,
266
- payload: {
267
- type: "native-session-copy",
268
- nativePath,
269
- landingPath,
270
- ordinal,
271
- sessionId: options.sessionId,
272
- },
273
- });
274
- }
275
- else {
276
- // A partial first attempt must not masquerade as a complete native original.
277
- try {
278
- rmSync(landingPath, { recursive: options.host === "grok-build", force: true });
279
- }
280
- catch { /* warning below reports the original copy failure */ }
281
- // Reserve the failed ordinal outside the native dossier landing namespace.
282
- try {
283
- mkdirSync(options.sessionDirectory, { recursive: true });
284
- closeSync(openSync(join(options.sessionDirectory, `${options.host}-${sanitizedModel}-${ordinal}.failed`), "a"));
285
- }
286
- catch { /* the warning below retains the original copy failure */ }
287
- sitianReportSafe({
288
- level: "event",
289
- kind: HOST_SESSION_RECORD_KIND,
290
- host: options.host,
291
- cwd: options.cwd,
292
- sessionParent: options.sessionParent,
293
- source: `${options.host}-dossier`,
294
- payload: {
295
- type: "native-session-warning",
296
- nativePath,
297
- landingPath,
298
- ordinal,
299
- sessionId: options.sessionId,
300
- error: errorText(lastError),
301
- },
302
- });
259
+ }
260
+ if (copySuccess) {
261
+ report({ type: "native-session-copy", nativePath, landingPath, sessionId: options.sessionId });
262
+ } else {
263
+ if (stagingForCleanup !== void 0) {
264
+ try {
265
+ rmSync(stagingForCleanup, { recursive: true, force: true });
266
+ } catch (cleanupError) {
267
+ lastError = combineCopyFailureCauses(lastError, void 0, cleanupError, stagingForCleanup);
268
+ }
303
269
  }
270
+ report({
271
+ type: "native-session-warning",
272
+ nativePath,
273
+ landingPath,
274
+ sessionId: options.sessionId,
275
+ error: errorText(lastError)
276
+ });
277
+ }
278
+ }
279
+ function combineCopyFailureCauses(primary, restoreError, cleanupError, residualPath) {
280
+ const errors = [primary];
281
+ const parts = [errorText(primary)];
282
+ if (restoreError !== void 0) {
283
+ errors.push(restoreError);
284
+ parts.push(`restore failed: ${errorText(restoreError)}`);
285
+ }
286
+ if (cleanupError !== void 0) {
287
+ if (cleanupError !== primary) errors.push(cleanupError);
288
+ parts.push(
289
+ residualPath === void 0 ? `cleanup failed: ${errorText(cleanupError)}` : `cleanup failed: ${errorText(cleanupError)} (residual: ${residualPath})`
290
+ );
291
+ }
292
+ if (errors.length === 1 && parts.length === 1) return primary;
293
+ return new AggregateError(errors, parts.join("; "), { cause: primary });
304
294
  }
295
+ export {
296
+ HOST_SESSION_RECORD_KIND,
297
+ copyAndRecordHostDossier,
298
+ recordNativeSessionPointer,
299
+ resolveHostDossierLandingPath,
300
+ resolveNativeSessionPath,
301
+ sanitizeClaudeCwd,
302
+ sitianReportSafe
303
+ };