@cydm/pie 2.5.0 → 2.5.1
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/builtin/extensions/ask-user/index.js +1 -1
- package/dist/builtin/extensions/computer-use/prebuilt/macos/arm64/pie-computer-use +0 -0
- package/dist/builtin/extensions/computer-use/prebuilt/macos/universal/pie-computer-use.app/Contents/MacOS/pie-computer-use +0 -0
- package/dist/builtin/extensions/computer-use/prebuilt/macos/x64/pie-computer-use +0 -0
- package/dist/builtin/extensions/herdr-agent-state/index.js +39 -3
- package/dist/builtin/extensions/mcp-adapter/index.js +52 -12
- package/dist/builtin/extensions/plan-mode/index.js +1 -1
- package/dist/builtin/extensions/subagent/index.js +2 -2
- package/dist/builtin/extensions/todo/index.js +1 -1
- package/dist/chunks/{chunk-PU7IIEKH.js → chunk-N2MK46AE.js} +19 -14
- package/dist/chunks/{chunk-JXY3YLDS.js → chunk-REP6QANY.js} +8 -3
- package/dist/cli.js +58 -9
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
createAskUserCapability
|
|
4
|
-
} from "../../../chunks/chunk-
|
|
4
|
+
} from "../../../chunks/chunk-REP6QANY.js";
|
|
5
5
|
import "../../../chunks/chunk-ZKHMNEXL.js";
|
|
6
6
|
import "../../../chunks/chunk-YRXXRLMQ.js";
|
|
7
7
|
import "../../../chunks/chunk-XXGYUYXI.js";
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -136,7 +136,42 @@ function spawnHerdrReport(binPath, args, timeoutMs = REPORT_TIMEOUT_MS) {
|
|
|
136
136
|
} catch {
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
-
|
|
139
|
+
var KILL_GRACE_MS = 1e3;
|
|
140
|
+
async function runHerdrReportBounded(binPath, args, timeoutMs = REPORT_TIMEOUT_MS) {
|
|
141
|
+
let child;
|
|
142
|
+
try {
|
|
143
|
+
child = spawn(binPath, args, { detached: true, stdio: "ignore" });
|
|
144
|
+
} catch {
|
|
145
|
+
return { outcome: "spawn-error", exitCode: null };
|
|
146
|
+
}
|
|
147
|
+
return await new Promise((resolve) => {
|
|
148
|
+
let killTimer;
|
|
149
|
+
let settled = false;
|
|
150
|
+
const finish = (outcome, exitCode) => {
|
|
151
|
+
if (settled) return;
|
|
152
|
+
settled = true;
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
if (killTimer) clearTimeout(killTimer);
|
|
155
|
+
resolve({ outcome, exitCode });
|
|
156
|
+
};
|
|
157
|
+
const timer = setTimeout(() => {
|
|
158
|
+
try {
|
|
159
|
+
child.kill("SIGTERM");
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
162
|
+
killTimer = setTimeout(() => {
|
|
163
|
+
try {
|
|
164
|
+
if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
killTimer = setTimeout(() => finish("escalated", child.exitCode), 250);
|
|
168
|
+
}, KILL_GRACE_MS);
|
|
169
|
+
}, timeoutMs);
|
|
170
|
+
child.on("error", () => finish("spawn-error", null));
|
|
171
|
+
child.on("exit", (code) => finish("exited", code));
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function attachHerdrReporter(host, herdr, transport = (args) => spawnHerdrReport(herdr.binPath, args), sequencer = createReportSequencer(), shutdownTransport = (args) => runHerdrReportBounded(herdr.binPath, args)) {
|
|
140
175
|
const nextSeq = sequencer;
|
|
141
176
|
let agentActive = false;
|
|
142
177
|
const blockingToolCalls = /* @__PURE__ */ new Map();
|
|
@@ -238,10 +273,10 @@ function attachHerdrReporter(host, herdr, transport = (args) => spawnHerdrReport
|
|
|
238
273
|
}
|
|
239
274
|
publishState();
|
|
240
275
|
});
|
|
241
|
-
host.registerCleanup?.((reason) => {
|
|
276
|
+
host.registerCleanup?.(async (reason) => {
|
|
242
277
|
if (reason !== "shutdown") return;
|
|
243
278
|
if (!heldPane) return;
|
|
244
|
-
|
|
279
|
+
await shutdownTransport(
|
|
245
280
|
buildReleaseReportArgs({
|
|
246
281
|
paneId: herdr.paneId,
|
|
247
282
|
source: SOURCE_ID,
|
|
@@ -267,6 +302,7 @@ export {
|
|
|
267
302
|
createReportSequencer,
|
|
268
303
|
herdrAgentStateExtension as default,
|
|
269
304
|
readHerdrEnvironment,
|
|
305
|
+
runHerdrReportBounded,
|
|
270
306
|
spawnHerdrReport,
|
|
271
307
|
summarizeAskUserQuestion
|
|
272
308
|
};
|
|
@@ -24724,11 +24724,13 @@ var McpServerManager = class {
|
|
|
24724
24724
|
};
|
|
24725
24725
|
for (const tools of Object.values(this.readCacheFile())) {
|
|
24726
24726
|
for (const tool of tools) {
|
|
24727
|
-
|
|
24727
|
+
const definition = this.definitions.get(tool.server);
|
|
24728
|
+
if (!definition || definition.disabled) continue;
|
|
24728
24729
|
claim2(tool, false);
|
|
24729
24730
|
}
|
|
24730
24731
|
}
|
|
24731
24732
|
for (const runtime of this.runtimes.values()) {
|
|
24733
|
+
if (runtime.definition.disabled) continue;
|
|
24732
24734
|
for (const tool of runtime.tools ?? []) claim2(tool, true);
|
|
24733
24735
|
}
|
|
24734
24736
|
for (const name of conflicts) {
|
|
@@ -24849,6 +24851,20 @@ ${tail}` : "")
|
|
|
24849
24851
|
error: `MCP call "${prefixedName}" was cancelled before the request was sent; nothing was executed.`
|
|
24850
24852
|
};
|
|
24851
24853
|
}
|
|
24854
|
+
const postConnectCatalog = this.loadCatalog();
|
|
24855
|
+
if (this.conflictedNames.has(prefixedName)) {
|
|
24856
|
+
return {
|
|
24857
|
+
delivery: "not_sent",
|
|
24858
|
+
error: `MCP tool name "${prefixedName}" became claimed by more than one server during connection. Rename one of the servers so every tool name is unambiguous; the call was NOT sent.`
|
|
24859
|
+
};
|
|
24860
|
+
}
|
|
24861
|
+
const postDescriptor = postConnectCatalog.find((tool) => tool.prefixedName === prefixedName);
|
|
24862
|
+
if (postDescriptor && (postDescriptor.server !== server || postDescriptor.tool !== toolName)) {
|
|
24863
|
+
return {
|
|
24864
|
+
delivery: "not_sent",
|
|
24865
|
+
error: `MCP tool "${prefixedName}" resolved to ${postDescriptor.server}/${postDescriptor.tool} after connecting; refusing to send the stale target ${server}/${toolName}. Retry the call.`
|
|
24866
|
+
};
|
|
24867
|
+
}
|
|
24852
24868
|
try {
|
|
24853
24869
|
const result = await withTimeout(
|
|
24854
24870
|
client.callTool({ name: toolName, arguments: args }, void 0, signal !== void 0 ? { signal } : void 0),
|
|
@@ -25266,19 +25282,26 @@ function mcpAdapter(ctx) {
|
|
|
25266
25282
|
ctx.log
|
|
25267
25283
|
);
|
|
25268
25284
|
async function reloadManager() {
|
|
25269
|
-
|
|
25285
|
+
let replacement;
|
|
25270
25286
|
try {
|
|
25271
|
-
|
|
25272
|
-
|
|
25273
|
-
|
|
25274
|
-
ctx.log
|
|
25275
|
-
|
|
25287
|
+
const resolved = resolveMcpAdapterConfig(ctx.cwd, configDir);
|
|
25288
|
+
if (resolved.problems.length > 0) {
|
|
25289
|
+
const message = `config layers failed to parse: ${resolved.problems.join("; ")}`;
|
|
25290
|
+
ctx.log(`mcp: config reload failed, keeping the previous manager: ${message}`);
|
|
25291
|
+
if (ctx.hasUI) ctx.ui.notify(`MCP adapter config error (previous servers kept): ${message}`, "error");
|
|
25292
|
+
return { ok: false, error: message };
|
|
25293
|
+
}
|
|
25294
|
+
replacement = new McpServerManager(resolved.servers, metadataCacheDir, ctx.log);
|
|
25276
25295
|
} catch (error2) {
|
|
25277
25296
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
25278
|
-
ctx.log(`mcp: config reload failed: ${message}`);
|
|
25279
|
-
if (ctx.hasUI) ctx.ui.notify(`MCP adapter config error: ${message}`, "error");
|
|
25280
|
-
|
|
25297
|
+
ctx.log(`mcp: config reload failed, keeping the previous manager: ${message}`);
|
|
25298
|
+
if (ctx.hasUI) ctx.ui.notify(`MCP adapter config error (previous servers kept): ${message}`, "error");
|
|
25299
|
+
return { ok: false, error: message };
|
|
25281
25300
|
}
|
|
25301
|
+
const previous = manager;
|
|
25302
|
+
manager = replacement;
|
|
25303
|
+
await previous.dispose().catch(() => void 0);
|
|
25304
|
+
return { ok: true };
|
|
25282
25305
|
}
|
|
25283
25306
|
ctx.registerCleanup?.(() => manager.dispose().catch(() => void 0));
|
|
25284
25307
|
function readProjectOverride() {
|
|
@@ -25359,6 +25382,7 @@ function mcpAdapter(ctx) {
|
|
|
25359
25382
|
}
|
|
25360
25383
|
const overrides = readProjectOverride().mcpServers;
|
|
25361
25384
|
const forceEnabled = overrides?.[definition.name]?.disabled === false;
|
|
25385
|
+
const previousOverride = overrides?.[definition.name]?.disabled;
|
|
25362
25386
|
const filePath = writeProjectOverride(
|
|
25363
25387
|
definition.name,
|
|
25364
25388
|
// enable must lift a source-level disabled:true with an explicit
|
|
@@ -25367,8 +25391,24 @@ function mcpAdapter(ctx) {
|
|
|
25367
25391
|
// existing force-enable on repeated enable commands too.
|
|
25368
25392
|
sub === "disable" ? true : definition.disabled || forceEnabled ? false : null
|
|
25369
25393
|
);
|
|
25370
|
-
await reloadManager();
|
|
25371
|
-
|
|
25394
|
+
const reload = await reloadManager();
|
|
25395
|
+
if (!reload.ok) {
|
|
25396
|
+
writeProjectOverride(definition.name, previousOverride === void 0 ? null : previousOverride);
|
|
25397
|
+
commandCtx.ui.notify(
|
|
25398
|
+
`MCP server "${definition.name}" NOT ${sub === "disable" ? "disabled" : "enabled"}: config reload failed (${reload.error}); previous servers kept and the override was rolled back.`,
|
|
25399
|
+
"error"
|
|
25400
|
+
);
|
|
25401
|
+
return;
|
|
25402
|
+
}
|
|
25403
|
+
const reloaded = manager.getDefinition(definition.name);
|
|
25404
|
+
if (!reloaded) {
|
|
25405
|
+
commandCtx.ui.notify(
|
|
25406
|
+
`MCP server "${definition.name}" disappeared from the reloaded config (wrote ${filePath}); check project/global config merges.`,
|
|
25407
|
+
"error"
|
|
25408
|
+
);
|
|
25409
|
+
return;
|
|
25410
|
+
}
|
|
25411
|
+
const effectiveDisabled = reloaded.disabled;
|
|
25372
25412
|
const stateLabel = effectiveDisabled ? "disabled" : sub === "disable" ? "still enabled" : "enabled";
|
|
25373
25413
|
commandCtx.ui.notify(
|
|
25374
25414
|
`MCP server "${definition.name}" ${stateLabel} (wrote ${filePath}). ` + (eagerRegistered.length > 0 ? "Restart or /reload to resync direct tools." : "Effective immediately."),
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
isPlanModeSafeCommand,
|
|
8
8
|
markCompletedPlanSteps,
|
|
9
9
|
restoreExecutionState
|
|
10
|
-
} from "../../../chunks/chunk-
|
|
10
|
+
} from "../../../chunks/chunk-REP6QANY.js";
|
|
11
11
|
import "../../../chunks/chunk-ZKHMNEXL.js";
|
|
12
12
|
import "../../../chunks/chunk-YRXXRLMQ.js";
|
|
13
13
|
import {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
createCliHostCapabilities
|
|
4
|
-
} from "../../../chunks/chunk-
|
|
4
|
+
} from "../../../chunks/chunk-N2MK46AE.js";
|
|
5
5
|
import {
|
|
6
6
|
createSharedFileSystemTools,
|
|
7
7
|
createSubagentCapability
|
|
8
|
-
} from "../../../chunks/chunk-
|
|
8
|
+
} from "../../../chunks/chunk-REP6QANY.js";
|
|
9
9
|
import "../../../chunks/chunk-ZKHMNEXL.js";
|
|
10
10
|
import "../../../chunks/chunk-YRXXRLMQ.js";
|
|
11
11
|
import "../../../chunks/chunk-5353DEEC.js";
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
executeManageTodoList,
|
|
9
9
|
executionStateToTodos,
|
|
10
10
|
restoreExecutionState
|
|
11
|
-
} from "../../../chunks/chunk-
|
|
11
|
+
} from "../../../chunks/chunk-REP6QANY.js";
|
|
12
12
|
import "../../../chunks/chunk-ZKHMNEXL.js";
|
|
13
13
|
import "../../../chunks/chunk-YRXXRLMQ.js";
|
|
14
14
|
import "../../../chunks/chunk-XXGYUYXI.js";
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
createSharedWebSearchTool,
|
|
9
9
|
interpretShellExit,
|
|
10
10
|
requestInteraction
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-REP6QANY.js";
|
|
12
12
|
import {
|
|
13
13
|
getBuiltinDir,
|
|
14
14
|
getConfigDir
|
|
@@ -543,9 +543,24 @@ function createBashTool(defaultCwd, options) {
|
|
|
543
543
|
totalNewlines += data.reduce((count, byte) => count + (byte === 10 ? 1 : 0), 0);
|
|
544
544
|
if (headBytes >= maxHeadBytes) return;
|
|
545
545
|
const prefix = data.subarray(0, maxHeadBytes - headBytes);
|
|
546
|
-
headChunks.push(prefix);
|
|
546
|
+
headChunks.push(Buffer.from(prefix));
|
|
547
547
|
headBytes += prefix.length;
|
|
548
548
|
};
|
|
549
|
+
const appendRollingChunk = (data) => {
|
|
550
|
+
const owned = Buffer.from(data);
|
|
551
|
+
chunks.push(owned);
|
|
552
|
+
chunksBytes += owned.length;
|
|
553
|
+
while (chunksBytes > maxChunksBytes && chunks.length > 1) {
|
|
554
|
+
const removed = chunks.shift();
|
|
555
|
+
chunksBytes -= removed.length;
|
|
556
|
+
}
|
|
557
|
+
if (chunks.length === 1 && chunksBytes > maxChunksBytes) {
|
|
558
|
+
const only = chunks[0];
|
|
559
|
+
const tail = only.subarray(only.length - maxChunksBytes);
|
|
560
|
+
chunks[0] = Buffer.from(tail);
|
|
561
|
+
chunksBytes = tail.length;
|
|
562
|
+
}
|
|
563
|
+
};
|
|
549
564
|
let timedOut = false;
|
|
550
565
|
let processExited = false;
|
|
551
566
|
let exitCode = null;
|
|
@@ -641,12 +656,7 @@ function createBashTool(defaultCwd, options) {
|
|
|
641
656
|
return;
|
|
642
657
|
}
|
|
643
658
|
}
|
|
644
|
-
|
|
645
|
-
chunksBytes += data.length;
|
|
646
|
-
while (chunksBytes > maxChunksBytes && chunks.length > 1) {
|
|
647
|
-
const removed = chunks.shift();
|
|
648
|
-
chunksBytes -= removed.length;
|
|
649
|
-
}
|
|
659
|
+
appendRollingChunk(data);
|
|
650
660
|
if (onUpdate) {
|
|
651
661
|
const fullBuffer = Buffer.concat(chunks);
|
|
652
662
|
const fullText = fullBuffer.toString("utf-8");
|
|
@@ -700,12 +710,7 @@ function createBashTool(defaultCwd, options) {
|
|
|
700
710
|
return;
|
|
701
711
|
}
|
|
702
712
|
}
|
|
703
|
-
|
|
704
|
-
chunksBytes += data.length;
|
|
705
|
-
while (chunksBytes > maxChunksBytes && chunks.length > 1) {
|
|
706
|
-
const removed = chunks.shift();
|
|
707
|
-
chunksBytes -= removed.length;
|
|
708
|
-
}
|
|
713
|
+
appendRollingChunk(data);
|
|
709
714
|
});
|
|
710
715
|
const finishExecution = async (code, sig, stdioIncomplete = false) => {
|
|
711
716
|
if (settled) return;
|
|
@@ -1742,8 +1742,7 @@ function normalizeTaskDefs(args) {
|
|
|
1742
1742
|
if (def.model !== void 0 && !modelOverride) {
|
|
1743
1743
|
return { error: `Invalid task #${index + 1}: "model" must be a non-empty model reference such as "provider/model-id".` };
|
|
1744
1744
|
}
|
|
1745
|
-
|
|
1746
|
-
if (def.thinkingLevel !== void 0 && (!thinkingLevel || !SUBAGENT_THINKING_LEVELS.includes(thinkingLevel))) {
|
|
1745
|
+
if (def.thinkingLevel !== void 0 && (typeof def.thinkingLevel !== "string" || !SUBAGENT_THINKING_LEVELS.includes(def.thinkingLevel))) {
|
|
1747
1746
|
return { error: `Invalid task #${index + 1}: "thinkingLevel" must be one of ${SUBAGENT_THINKING_LEVELS.map((level) => `"${level}"`).join(", ")}.` };
|
|
1748
1747
|
}
|
|
1749
1748
|
normalized.push({
|
|
@@ -1752,7 +1751,7 @@ function normalizeTaskDefs(args) {
|
|
|
1752
1751
|
priority,
|
|
1753
1752
|
timeoutSeconds,
|
|
1754
1753
|
modelOverride,
|
|
1755
|
-
thinkingLevel
|
|
1754
|
+
thinkingLevel: def.thinkingLevel
|
|
1756
1755
|
});
|
|
1757
1756
|
}
|
|
1758
1757
|
return normalized;
|
|
@@ -2968,6 +2967,9 @@ var FileSessionStore = class {
|
|
|
2968
2967
|
throw new Error(`Session directory is a symbolic link; ${operation} refused: ${dir}`);
|
|
2969
2968
|
}
|
|
2970
2969
|
headStatus(sessionId) {
|
|
2970
|
+
if (sessionDirIsSymlink(this.pathsFor(sessionId).dir)) {
|
|
2971
|
+
return "missing";
|
|
2972
|
+
}
|
|
2971
2973
|
const head = this.tryReadHeadFile(sessionId);
|
|
2972
2974
|
if (head === "corrupt") {
|
|
2973
2975
|
return "corrupt";
|
|
@@ -3023,6 +3025,9 @@ var FileSessionStore = class {
|
|
|
3023
3025
|
* never reinitialize a damaged session to sequence 0.
|
|
3024
3026
|
*/
|
|
3025
3027
|
tryReadHeadFile(sessionId) {
|
|
3028
|
+
if (sessionDirIsSymlink(this.pathsFor(sessionId).dir)) {
|
|
3029
|
+
return null;
|
|
3030
|
+
}
|
|
3026
3031
|
const headPath = this.pathsFor(sessionId).headPath;
|
|
3027
3032
|
const durable = readDurableFileSync(this.gateway, headPath, (content) => this.parseHeadContent(content));
|
|
3028
3033
|
if (durable !== null) {
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
createCliWebResearchHealthStore,
|
|
8
8
|
formatSize,
|
|
9
9
|
truncateHeadTail
|
|
10
|
-
} from "./chunks/chunk-
|
|
10
|
+
} from "./chunks/chunk-N2MK46AE.js";
|
|
11
11
|
import {
|
|
12
12
|
AGENTS_CONTEXT_FILE_NAME,
|
|
13
13
|
AgentSessionController,
|
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
setExtensionEnabledInSettings,
|
|
61
61
|
shouldPreserveExecutionStateForUserText,
|
|
62
62
|
supersedeExecutionState
|
|
63
|
-
} from "./chunks/chunk-
|
|
63
|
+
} from "./chunks/chunk-REP6QANY.js";
|
|
64
64
|
import "./chunks/chunk-ZKHMNEXL.js";
|
|
65
65
|
import {
|
|
66
66
|
Deref,
|
|
@@ -55209,11 +55209,44 @@ function canonicalSessionDir(sessionsDir, sessionId) {
|
|
|
55209
55209
|
function publishJournalPath(sessionsDir, safeTargetId) {
|
|
55210
55210
|
return path7.join(sessionsDir, PUBLISH_JOURNAL_DIR, `${safeTargetId}.json`);
|
|
55211
55211
|
}
|
|
55212
|
+
var FSYNC_UNSUPPORTED_CODES = /* @__PURE__ */ new Set(["EPERM", "EINVAL", "ENOTSUP", "EOPNOTSUPP", "EACCES", "EBADF"]);
|
|
55213
|
+
function fsyncDirectory(dirPath) {
|
|
55214
|
+
try {
|
|
55215
|
+
const fd = fs7.openSync(dirPath, "r");
|
|
55216
|
+
try {
|
|
55217
|
+
fs7.fsyncSync(fd);
|
|
55218
|
+
} finally {
|
|
55219
|
+
fs7.closeSync(fd);
|
|
55220
|
+
}
|
|
55221
|
+
} catch (error) {
|
|
55222
|
+
const code = error?.code;
|
|
55223
|
+
if (code !== void 0 && FSYNC_UNSUPPORTED_CODES.has(code)) return;
|
|
55224
|
+
throw error;
|
|
55225
|
+
}
|
|
55226
|
+
}
|
|
55212
55227
|
function writePublishJournal(journalPath, record) {
|
|
55213
|
-
|
|
55228
|
+
const journalDir = path7.dirname(journalPath);
|
|
55229
|
+
fs7.mkdirSync(journalDir, { recursive: true });
|
|
55214
55230
|
const tempPath = `${journalPath}.tmp`;
|
|
55215
55231
|
fs7.writeFileSync(tempPath, JSON.stringify(record, void 0, " "));
|
|
55232
|
+
const tempFd = fs7.openSync(tempPath, "r+");
|
|
55233
|
+
try {
|
|
55234
|
+
fs7.fsyncSync(tempFd);
|
|
55235
|
+
} finally {
|
|
55236
|
+
fs7.closeSync(tempFd);
|
|
55237
|
+
}
|
|
55216
55238
|
fs7.renameSync(tempPath, journalPath);
|
|
55239
|
+
fsyncDirectory(journalDir);
|
|
55240
|
+
fsyncDirectory(path7.dirname(journalDir));
|
|
55241
|
+
}
|
|
55242
|
+
function markPublishJournalFailed(journalPath) {
|
|
55243
|
+
if (!journalPath) return;
|
|
55244
|
+
try {
|
|
55245
|
+
const record = JSON.parse(fs7.readFileSync(journalPath, "utf8"));
|
|
55246
|
+
if (record?.v !== 1 || typeof record.failedAt === "number") return;
|
|
55247
|
+
writePublishJournal(journalPath, { ...record, failedAt: Date.now() });
|
|
55248
|
+
} catch {
|
|
55249
|
+
}
|
|
55217
55250
|
}
|
|
55218
55251
|
function clearPublishJournal(journalPath) {
|
|
55219
55252
|
if (!journalPath) return;
|
|
@@ -55266,6 +55299,7 @@ async function rewindStoredSession(params) {
|
|
|
55266
55299
|
}
|
|
55267
55300
|
async function forkStoredSession(params) {
|
|
55268
55301
|
let stagingId;
|
|
55302
|
+
let preserveStagingForRecovery = false;
|
|
55269
55303
|
try {
|
|
55270
55304
|
const sanitizedTargetId = params.targetSessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
55271
55305
|
if (sanitizedTargetId.startsWith(STAGING_SESSION_ID_PREFIX)) {
|
|
@@ -55396,6 +55430,7 @@ async function forkStoredSession(params) {
|
|
|
55396
55430
|
if (hadPreviousTarget) {
|
|
55397
55431
|
try {
|
|
55398
55432
|
params.store.moveSession(params.targetSessionId, backupId);
|
|
55433
|
+
if (params.sessionsDir) fsyncDirectory(params.sessionsDir);
|
|
55399
55434
|
} catch (error) {
|
|
55400
55435
|
clearPublishJournal(journalPath);
|
|
55401
55436
|
throw error;
|
|
@@ -55403,6 +55438,7 @@ async function forkStoredSession(params) {
|
|
|
55403
55438
|
}
|
|
55404
55439
|
try {
|
|
55405
55440
|
params.store.moveSession(stagingId, params.targetSessionId);
|
|
55441
|
+
if (params.sessionsDir) fsyncDirectory(params.sessionsDir);
|
|
55406
55442
|
} catch (error) {
|
|
55407
55443
|
if (hadPreviousTarget) {
|
|
55408
55444
|
try {
|
|
@@ -55410,8 +55446,11 @@ async function forkStoredSession(params) {
|
|
|
55410
55446
|
throw new Error("target path is occupied");
|
|
55411
55447
|
}
|
|
55412
55448
|
params.store.moveSession(backupId, params.targetSessionId);
|
|
55449
|
+
if (params.sessionsDir) fsyncDirectory(params.sessionsDir);
|
|
55413
55450
|
clearPublishJournal(journalPath);
|
|
55414
55451
|
} catch (restoreError) {
|
|
55452
|
+
markPublishJournalFailed(journalPath);
|
|
55453
|
+
preserveStagingForRecovery = true;
|
|
55415
55454
|
throw new SessionControlError(
|
|
55416
55455
|
"session_io_error",
|
|
55417
55456
|
`Fork publication failed; previous session is preserved as ${backupId}. Restore failed: ${String(restoreError)}`,
|
|
@@ -55446,7 +55485,7 @@ async function forkStoredSession(params) {
|
|
|
55446
55485
|
} catch (error) {
|
|
55447
55486
|
throw toIoError(error);
|
|
55448
55487
|
} finally {
|
|
55449
|
-
if (stagingId) {
|
|
55488
|
+
if (stagingId && !preserveStagingForRecovery) {
|
|
55450
55489
|
try {
|
|
55451
55490
|
params.store.delete(stagingId);
|
|
55452
55491
|
} catch {
|
|
@@ -55519,7 +55558,7 @@ function recoverOnePublishJournal(params) {
|
|
|
55519
55558
|
try {
|
|
55520
55559
|
const parsed = JSON.parse(fs7.readFileSync(params.journalPath, "utf8"));
|
|
55521
55560
|
const safeId = /^[a-zA-Z0-9_-]+$/;
|
|
55522
|
-
if (parsed?.v !== 1 || !safeId.test(parsed.targetId ?? "") || !safeId.test(parsed.stagingId ?? "") || !((parsed.backupDir ?? null) === null || safeId.test(parsed.backupDir))) {
|
|
55561
|
+
if (parsed?.v !== 1 || !safeId.test(parsed.targetId ?? "") || !safeId.test(parsed.stagingId ?? "") || !((parsed.backupDir ?? null) === null || safeId.test(parsed.backupDir)) || !((parsed.failedAt ?? void 0) === void 0 || typeof parsed.failedAt === "number")) {
|
|
55523
55562
|
throw new Error("journal record has unexpected shape");
|
|
55524
55563
|
}
|
|
55525
55564
|
record = parsed;
|
|
@@ -55545,7 +55584,7 @@ function recoverOnePublishJournal(params) {
|
|
|
55545
55584
|
const backupExists = record.backupDir ? dirExists(record.backupDir) : false;
|
|
55546
55585
|
const stagingExists = dirExists(record.stagingId);
|
|
55547
55586
|
try {
|
|
55548
|
-
if (targetExists && !stagingExists) {
|
|
55587
|
+
if (targetExists && !stagingExists && record.failedAt === void 0) {
|
|
55549
55588
|
if (backupExists && record.backupDir) removeSessionDir(record.backupDir);
|
|
55550
55589
|
removeJournal();
|
|
55551
55590
|
return { action: "publish-completed", targetId: record.targetId };
|
|
@@ -55555,22 +55594,32 @@ function recoverOnePublishJournal(params) {
|
|
|
55555
55594
|
path7.join(params.sessionsDir, record.backupDir),
|
|
55556
55595
|
path7.join(params.sessionsDir, record.targetId)
|
|
55557
55596
|
);
|
|
55558
|
-
|
|
55597
|
+
fsyncDirectory(params.sessionsDir);
|
|
55559
55598
|
if (stagingExists) {
|
|
55560
55599
|
try {
|
|
55561
55600
|
removeSessionDir(record.stagingId);
|
|
55562
55601
|
} catch (error) {
|
|
55563
55602
|
warn(`[session-control] Restored ${record.targetId} but could not remove its abandoned staging copy ${record.stagingId}: ${String(error)}`);
|
|
55603
|
+
return { action: "ambiguous-preserved", targetId: record.targetId, message: `Staging cleanup failed after restore; journal preserved: ${String(error)}` };
|
|
55564
55604
|
}
|
|
55565
55605
|
}
|
|
55606
|
+
removeJournal();
|
|
55566
55607
|
return { action: "restored-previous-target", targetId: record.targetId };
|
|
55567
55608
|
}
|
|
55568
55609
|
if (!targetExists && !backupExists) {
|
|
55569
|
-
if (stagingExists)
|
|
55610
|
+
if (stagingExists) {
|
|
55611
|
+
try {
|
|
55612
|
+
removeSessionDir(record.stagingId);
|
|
55613
|
+
} catch (error) {
|
|
55614
|
+
warn(`[session-control] Could not remove abandoned staging copy ${record.stagingId}: ${String(error)}`);
|
|
55615
|
+
return { action: "ambiguous-preserved", targetId: record.targetId, message: `Staging cleanup failed; journal preserved: ${String(error)}` };
|
|
55616
|
+
}
|
|
55617
|
+
}
|
|
55570
55618
|
removeJournal();
|
|
55571
55619
|
return stagingExists ? { action: "discarded-incomplete-fork", targetId: record.targetId } : { action: "nothing-to-recover", targetId: record.targetId };
|
|
55572
55620
|
}
|
|
55573
|
-
const
|
|
55621
|
+
const reason = record.failedAt !== void 0 ? "the publish failed in-process (restore failed) and the backup was preserved" : `target=${targetExists}, backup=${backupExists}, staging=${stagingExists}`;
|
|
55622
|
+
const message = `[session-control] Ambiguous interrupted publish for target ${record.targetId} (${reason}); nothing was deleted \u2014 inspect ${params.sessionsDir} and ${journalName} manually`;
|
|
55574
55623
|
warn(message);
|
|
55575
55624
|
return { action: "ambiguous-preserved", targetId: record.targetId, message };
|
|
55576
55625
|
} catch (error) {
|