@marcoscale98/pi-extensible-workflows 5.14.1-fork.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/CHANGELOG.md +536 -0
- package/README.md +64 -0
- package/dist/src/agent-execution.d.ts +338 -0
- package/dist/src/agent-execution.js +1633 -0
- package/dist/src/background-widget.d.ts +74 -0
- package/dist/src/background-widget.js +1340 -0
- package/dist/src/budget.d.ts +38 -0
- package/dist/src/budget.js +188 -0
- package/dist/src/changelog.d.ts +6 -0
- package/dist/src/changelog.js +112 -0
- package/dist/src/decoders.d.ts +96 -0
- package/dist/src/decoders.js +629 -0
- package/dist/src/execution.d.ts +19 -0
- package/dist/src/execution.js +749 -0
- package/dist/src/herdr.d.ts +53 -0
- package/dist/src/herdr.js +270 -0
- package/dist/src/host-delivery.d.ts +95 -0
- package/dist/src/host-delivery.js +376 -0
- package/dist/src/host-navigator.d.ts +83 -0
- package/dist/src/host-navigator.js +1087 -0
- package/dist/src/host-phases.d.ts +89 -0
- package/dist/src/host-phases.js +320 -0
- package/dist/src/host-recovery.d.ts +141 -0
- package/dist/src/host-recovery.js +356 -0
- package/dist/src/host-runtime.d.ts +78 -0
- package/dist/src/host-runtime.js +378 -0
- package/dist/src/host-view.d.ts +116 -0
- package/dist/src/host-view.js +868 -0
- package/dist/src/host.d.ts +40 -0
- package/dist/src/host.js +1641 -0
- package/dist/src/index.d.ts +29 -0
- package/dist/src/index.js +13725 -0
- package/dist/src/index.js.map +6 -0
- package/dist/src/io.d.ts +14 -0
- package/dist/src/io.js +82 -0
- package/dist/src/package-identity.d.ts +4 -0
- package/dist/src/package-identity.js +6 -0
- package/dist/src/paths.d.ts +14 -0
- package/dist/src/paths.js +51 -0
- package/dist/src/persistence.d.ts +5 -0
- package/dist/src/persistence.js +5 -0
- package/dist/src/pi-runtime-adapter.d.ts +27 -0
- package/dist/src/pi-runtime-adapter.js +243 -0
- package/dist/src/pi-runtime-runner.d.ts +42 -0
- package/dist/src/pi-runtime-runner.js +505 -0
- package/dist/src/registry.d.ts +48 -0
- package/dist/src/registry.js +379 -0
- package/dist/src/retention.d.ts +17 -0
- package/dist/src/retention.js +195 -0
- package/dist/src/runtime/agent-runner.d.ts +143 -0
- package/dist/src/runtime/agent-runner.js +12 -0
- package/dist/src/runtime/index.d.ts +2 -0
- package/dist/src/runtime/index.js +2 -0
- package/dist/src/runtime/namespace.d.ts +14 -0
- package/dist/src/runtime/namespace.js +1 -0
- package/dist/src/runtime/workflow-result.d.ts +2 -0
- package/dist/src/runtime/workflow-result.js +6 -0
- package/dist/src/session-handoff.d.ts +2 -0
- package/dist/src/session-handoff.js +82 -0
- package/dist/src/session-lease.d.ts +12 -0
- package/dist/src/session-lease.js +180 -0
- package/dist/src/store.d.ts +95 -0
- package/dist/src/store.js +951 -0
- package/dist/src/subagent-manager-handle.d.ts +4 -0
- package/dist/src/subagent-manager-handle.js +6 -0
- package/dist/src/tool-timing.d.ts +12 -0
- package/dist/src/tool-timing.js +33 -0
- package/dist/src/trajectory-contracts.d.ts +8 -0
- package/dist/src/trajectory-contracts.js +9 -0
- package/dist/src/trajectory-host-handle.d.ts +12 -0
- package/dist/src/trajectory-host-handle.js +6 -0
- package/dist/src/trajectory.d.ts +128 -0
- package/dist/src/trajectory.js +594 -0
- package/dist/src/types.d.ts +960 -0
- package/dist/src/types.js +60 -0
- package/dist/src/utils.d.ts +57 -0
- package/dist/src/utils.js +225 -0
- package/dist/src/validation.d.ts +29 -0
- package/dist/src/validation.js +1100 -0
- package/dist/src/workflow-artifacts.d.ts +14 -0
- package/dist/src/workflow-artifacts.js +40 -0
- package/dist/starter/index.d.ts +1 -0
- package/dist/starter/index.js +103 -0
- package/dist/starter/index.js.map +6 -0
- package/dist/starter/review-loop.d.ts +72 -0
- package/dist/starter/review-loop.js +43 -0
- package/dist/starter/roles/developer.md +6 -0
- package/dist/starter/roles/oracle.md +18 -0
- package/dist/starter/roles/researcher.md +20 -0
- package/dist/starter/roles/reviewer.md +19 -0
- package/dist/starter/roles/scout.md +18 -0
- package/dist/subagents/index.d.ts +8 -0
- package/dist/subagents/index.js +8021 -0
- package/dist/subagents/index.js.map +6 -0
- package/dist/subagents/src/contracts.d.ts +173 -0
- package/dist/subagents/src/contracts.js +68 -0
- package/dist/subagents/src/decode.d.ts +15 -0
- package/dist/subagents/src/decode.js +182 -0
- package/dist/subagents/src/index.d.ts +12 -0
- package/dist/subagents/src/index.js +180 -0
- package/dist/subagents/src/manager.d.ts +3 -0
- package/dist/subagents/src/manager.js +1636 -0
- package/dist/subagents/src/navigator.d.ts +5 -0
- package/dist/subagents/src/navigator.js +627 -0
- package/dist/subagents/src/view.d.ts +70 -0
- package/dist/subagents/src/view.js +387 -0
- package/dist/subagents/src/worktree.d.ts +34 -0
- package/dist/subagents/src/worktree.js +68 -0
- package/dist/trajectory/assets/favicon.png +0 -0
- package/dist/trajectory/assets/index.html +772 -0
- package/dist/trajectory/assets/marked.min.js +6 -0
- package/dist/trajectory/assets/morphdom.min.js +1 -0
- package/dist/trajectory/index.d.ts +5 -0
- package/dist/trajectory/index.js +3459 -0
- package/dist/trajectory/index.js.map +6 -0
- package/dist/trajectory/src/assets/favicon.png +0 -0
- package/dist/trajectory/src/assets/index.html +772 -0
- package/dist/trajectory/src/assets/marked.min.js +6 -0
- package/dist/trajectory/src/assets/morphdom.min.js +1 -0
- package/dist/trajectory/src/export.d.ts +25 -0
- package/dist/trajectory/src/export.js +80 -0
- package/dist/trajectory/src/index.d.ts +26 -0
- package/dist/trajectory/src/index.js +732 -0
- package/dist/trajectory/src/server.d.ts +7 -0
- package/dist/trajectory/src/server.js +2534 -0
- package/dist/trajectory/src/server.js.map +6 -0
- package/examples/advanced-patterns/auto-model-router.mjs +78 -0
- package/examples/workflow-extension-template/README.md +37 -0
- package/examples/workflow-extension-template/extension.test.mjs +59 -0
- package/examples/workflow-extension-template/index.js +51 -0
- package/examples/workflow-extension-template/roles/reviewer.md +4 -0
- package/package.json +105 -0
- package/skills/pi-extensible-workflows/SKILL.md +160 -0
- package/src/agent-execution.ts +1533 -0
- package/src/background-widget.ts +1496 -0
- package/src/budget.ts +109 -0
- package/src/changelog.ts +114 -0
- package/src/decoders.ts +567 -0
- package/src/execution.ts +652 -0
- package/src/herdr.ts +240 -0
- package/src/host-delivery.ts +346 -0
- package/src/host-navigator.ts +871 -0
- package/src/host-phases.ts +264 -0
- package/src/host-recovery.ts +333 -0
- package/src/host-runtime.ts +373 -0
- package/src/host-view.ts +828 -0
- package/src/host.ts +1502 -0
- package/src/index.ts +29 -0
- package/src/io.ts +73 -0
- package/src/package-identity.ts +7 -0
- package/src/paths.ts +49 -0
- package/src/persistence.ts +11 -0
- package/src/pi-runtime-adapter.ts +247 -0
- package/src/pi-runtime-runner.ts +436 -0
- package/src/registry.ts +321 -0
- package/src/retention.ts +178 -0
- package/src/runtime/agent-runner.ts +158 -0
- package/src/runtime/index.ts +2 -0
- package/src/runtime/namespace.ts +16 -0
- package/src/runtime/workflow-result.ts +8 -0
- package/src/session-handoff.ts +78 -0
- package/src/session-lease.ts +125 -0
- package/src/store.ts +832 -0
- package/src/subagent-manager-handle.ts +8 -0
- package/src/tool-timing.ts +47 -0
- package/src/trajectory-contracts.ts +13 -0
- package/src/trajectory-host-handle.ts +16 -0
- package/src/trajectory.ts +543 -0
- package/src/types.ts +374 -0
- package/src/utils.ts +192 -0
- package/src/validation.ts +910 -0
- package/src/workflow-artifacts.ts +35 -0
- package/starter/index.ts +35 -0
- package/starter/prompts/council.md +16 -0
- package/starter/prompts/deep-research.md +16 -0
- package/starter/prompts/oracle.md +14 -0
- package/starter/prompts/parallel-review.md +22 -0
- package/starter/prompts/parallel-scout.md +16 -0
- package/starter/prompts/review-loop.md +14 -0
- package/starter/prompts/review.md +14 -0
- package/starter/prompts/scout.md +14 -0
- package/starter/review-loop.ts +73 -0
- package/starter/roles/developer.md +6 -0
- package/starter/roles/oracle.md +18 -0
- package/starter/roles/researcher.md +20 -0
- package/starter/roles/reviewer.md +19 -0
- package/starter/roles/scout.md +18 -0
- package/subagents/README.md +145 -0
- package/subagents/index.ts +8 -0
- package/subagents/src/contracts.ts +176 -0
- package/subagents/src/decode.ts +158 -0
- package/subagents/src/index.ts +194 -0
- package/subagents/src/manager.ts +1560 -0
- package/subagents/src/navigator.ts +488 -0
- package/subagents/src/view.ts +376 -0
- package/subagents/src/worktree.ts +99 -0
- package/trajectory/index.ts +5 -0
- package/trajectory/src/assets/favicon.png +0 -0
- package/trajectory/src/assets/index.html +772 -0
- package/trajectory/src/assets/marked.min.js +6 -0
- package/trajectory/src/assets/morphdom.min.js +1 -0
- package/trajectory/src/export.ts +85 -0
- package/trajectory/src/index.ts +604 -0
- package/trajectory/src/server.ts +447 -0
|
@@ -0,0 +1,3459 @@
|
|
|
1
|
+
// trajectory/src/index.ts
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
4
|
+
import { mkdir as mkdir3, open as open3, readFile as readFile6, rm as rm5 } from "node:fs/promises";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { dirname as dirname5, join as join8 } from "node:path";
|
|
7
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
8
|
+
import { getAgentDir as getAgentDir3 } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
|
|
10
|
+
// src/trajectory-host-handle.ts
|
|
11
|
+
var TRAJECTORY_HOST_KEY = /* @__PURE__ */ Symbol.for("pi-extensible-workflows.trajectory-host");
|
|
12
|
+
var globalTrajectoryHosts = globalThis;
|
|
13
|
+
function setTrajectoryHost(host) {
|
|
14
|
+
globalTrajectoryHosts[TRAJECTORY_HOST_KEY] = host;
|
|
15
|
+
}
|
|
16
|
+
function clearTrajectoryHost(host) {
|
|
17
|
+
if (globalTrajectoryHosts[TRAJECTORY_HOST_KEY] === host) globalTrajectoryHosts[TRAJECTORY_HOST_KEY] = void 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/session-lease.ts
|
|
21
|
+
import { link, mkdir, open, readFile as readFile2, readdir, rename as rename2, rm as rm2, stat } from "node:fs/promises";
|
|
22
|
+
import { homedir as homedir2 } from "node:os";
|
|
23
|
+
import { join as join2 } from "node:path";
|
|
24
|
+
|
|
25
|
+
// src/types.ts
|
|
26
|
+
var RUN_STATES = ["queued", "running", "pausing", "paused", "awaiting_input", "completed", "failed", "stopped", "interrupted", "budget_exhausted"];
|
|
27
|
+
var AGENT_STATES = ["queued", "running", "waiting_for_child", "paused", "retrying", "completed", "failed", "cancelled"];
|
|
28
|
+
var HARD_TERMINAL_RUN_STATES = /* @__PURE__ */ new Set(["completed", "failed", "stopped"]);
|
|
29
|
+
var ERROR_CODES = [
|
|
30
|
+
"CONFIG_ERROR",
|
|
31
|
+
"INVALID_SETTINGS",
|
|
32
|
+
"INVALID_SYNTAX",
|
|
33
|
+
"INVALID_METADATA",
|
|
34
|
+
"DUPLICATE_NAME",
|
|
35
|
+
"INVALID_SCHEMA",
|
|
36
|
+
"UNKNOWN_MODEL",
|
|
37
|
+
"UNKNOWN_TOOL",
|
|
38
|
+
"UNKNOWN_AGENT_TYPE",
|
|
39
|
+
"RUN_OWNED",
|
|
40
|
+
"RUN_NOT_FOUND",
|
|
41
|
+
"REGISTRY_FROZEN",
|
|
42
|
+
"GLOBAL_COLLISION",
|
|
43
|
+
"MISSING_WORKFLOW",
|
|
44
|
+
"RPC_LIMIT_EXCEEDED",
|
|
45
|
+
"SHELL_FAILED",
|
|
46
|
+
"AGENT_TIMEOUT",
|
|
47
|
+
"AGENT_FAILED",
|
|
48
|
+
"AGENT_RESULT_COLLECTED",
|
|
49
|
+
"RESULT_INVALID",
|
|
50
|
+
"CANCELLED",
|
|
51
|
+
"WORKER_UNRESPONSIVE",
|
|
52
|
+
"WORKTREE_FAILED",
|
|
53
|
+
"RESUME_INCOMPATIBLE",
|
|
54
|
+
"BUDGET_EXHAUSTED",
|
|
55
|
+
"INTERNAL_ERROR"
|
|
56
|
+
];
|
|
57
|
+
var THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
58
|
+
var BUDGET_DIMENSIONS = ["tokens", "costUsd", "durationMs", "agentLaunches"];
|
|
59
|
+
var BUDGET_EVENT_TYPES = ["soft_crossed", "hard_overrun", "hard_exhausted", "adjustment_requested", "adjustment_approved", "adjustment_rejected"];
|
|
60
|
+
var CONTEXT_FILE_SCOPES = ["global", "project", "cwd"];
|
|
61
|
+
function isContextFileScope(value) {
|
|
62
|
+
return CONTEXT_FILE_SCOPES.some((scope) => scope === value);
|
|
63
|
+
}
|
|
64
|
+
var WORKFLOW_AGENT_STALL_THRESHOLD_MS = 10 * 60 * 1e3;
|
|
65
|
+
var WORKFLOW_ERROR_BRAND = /* @__PURE__ */ Symbol.for("pi-extensible-workflows.workflow-error");
|
|
66
|
+
var WorkflowError = class extends Error {
|
|
67
|
+
constructor(code, message) {
|
|
68
|
+
super(message);
|
|
69
|
+
this.code = code;
|
|
70
|
+
this.name = "WorkflowError";
|
|
71
|
+
this[WORKFLOW_ERROR_BRAND] = true;
|
|
72
|
+
}
|
|
73
|
+
code;
|
|
74
|
+
static [Symbol.hasInstance](value) {
|
|
75
|
+
if (Function.prototype[Symbol.hasInstance].call(this, value)) return true;
|
|
76
|
+
return Object.hasOwn(this, WORKFLOW_ERROR_BRAND) && typeof value === "object" && value !== null && WORKFLOW_ERROR_BRAND in value;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
Object.defineProperty(WorkflowError, WORKFLOW_ERROR_BRAND, { value: true });
|
|
80
|
+
|
|
81
|
+
// src/utils.ts
|
|
82
|
+
import { Minimatch } from "minimatch";
|
|
83
|
+
var SerialLane = class {
|
|
84
|
+
#tail = Promise.resolve();
|
|
85
|
+
run(task) {
|
|
86
|
+
const next = this.#tail.then(task, task);
|
|
87
|
+
this.#tail = next.then(() => void 0, () => void 0);
|
|
88
|
+
return next;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
function object(value) {
|
|
92
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
93
|
+
}
|
|
94
|
+
var ESCAPE_CHARACTER = String.fromCharCode(27);
|
|
95
|
+
var BELL_CHARACTER = String.fromCharCode(7);
|
|
96
|
+
var ANSI_ESCAPE_SEQUENCE = new RegExp(`${ESCAPE_CHARACTER}(?:\\[[0-?]*[ -/]*[@-~]|\\][^${BELL_CHARACTER}]*(?:${BELL_CHARACTER}|${ESCAPE_CHARACTER}\\\\))`, "g");
|
|
97
|
+
var TERMINAL_CONTROL_CHARACTER = new RegExp(`[${String.fromCharCode(0)}-${String.fromCharCode(31)}${String.fromCharCode(127)}-${String.fromCharCode(159)}]`, "g");
|
|
98
|
+
function isStringKey(key) {
|
|
99
|
+
return typeof key === "string";
|
|
100
|
+
}
|
|
101
|
+
function stringKeyValue(value, key) {
|
|
102
|
+
return object(value) ? value[key] : void 0;
|
|
103
|
+
}
|
|
104
|
+
function jsonValue(value, seen = /* @__PURE__ */ new Set()) {
|
|
105
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") return true;
|
|
106
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
107
|
+
if (typeof value !== "object" || seen.has(value)) return false;
|
|
108
|
+
if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return false;
|
|
109
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
110
|
+
const keys = ownKeys.filter(isStringKey);
|
|
111
|
+
if (keys.length !== ownKeys.length) return false;
|
|
112
|
+
seen.add(value);
|
|
113
|
+
const valid = (Array.isArray(value) ? Array.from(value) : keys.map((key) => stringKeyValue(value, key))).every((item) => jsonValue(item, seen));
|
|
114
|
+
seen.delete(value);
|
|
115
|
+
return valid;
|
|
116
|
+
}
|
|
117
|
+
function positiveInteger(value) {
|
|
118
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
119
|
+
}
|
|
120
|
+
function finiteNumber(value) {
|
|
121
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
122
|
+
}
|
|
123
|
+
function deepFreeze(value) {
|
|
124
|
+
if (typeof value === "object" && value !== null && !Object.isFrozen(value)) {
|
|
125
|
+
Object.freeze(value);
|
|
126
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
function isNodeError(error, code) {
|
|
131
|
+
return object(error) && error.code === code;
|
|
132
|
+
}
|
|
133
|
+
function isWorkflowErrorCode(value) {
|
|
134
|
+
return ERROR_CODES.some((candidate) => candidate === value);
|
|
135
|
+
}
|
|
136
|
+
function errorText(error) {
|
|
137
|
+
return object(error) && typeof error.message === "string" ? error.message : error instanceof Error ? error.message : String(error);
|
|
138
|
+
}
|
|
139
|
+
function coerceWorkflowError(code, error) {
|
|
140
|
+
return error instanceof WorkflowError && error.code === code ? error : new WorkflowError(code, errorText(error));
|
|
141
|
+
}
|
|
142
|
+
function errorCode(error) {
|
|
143
|
+
if (error instanceof WorkflowError) return isWorkflowErrorCode(error.code) ? error.code : void 0;
|
|
144
|
+
if (!object(error)) return void 0;
|
|
145
|
+
return isWorkflowErrorCode(error.code) ? error.code : void 0;
|
|
146
|
+
}
|
|
147
|
+
function fail(code, message) {
|
|
148
|
+
throw new WorkflowError(code, message);
|
|
149
|
+
}
|
|
150
|
+
function byPriorityThenName(left, right) {
|
|
151
|
+
return left.priority - right.priority || (left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
152
|
+
}
|
|
153
|
+
function isThinkingLevel(value) {
|
|
154
|
+
return THINKING_LEVELS.some((level) => level === value);
|
|
155
|
+
}
|
|
156
|
+
var MODEL_ALIAS_NAME = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
157
|
+
var WORKFLOW_EXTENSION_NAMESPACE = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
158
|
+
function validWorkflowExtensionNamespace(value) {
|
|
159
|
+
return WORKFLOW_EXTENSION_NAMESPACE.test(value) && value !== "__proto__" && value !== "constructor" && value !== "prototype";
|
|
160
|
+
}
|
|
161
|
+
function parseModelReference(value) {
|
|
162
|
+
const match = /^([^/:\s]+)\/([^:\s]+)(?::([^:\s]+))?$/.exec(value);
|
|
163
|
+
if (!match?.[1] || !match[2]) fail("UNKNOWN_MODEL", `Invalid model spec: ${value}`);
|
|
164
|
+
const thinking = match[3];
|
|
165
|
+
if (thinking !== void 0 && !isThinkingLevel(thinking)) fail("UNKNOWN_MODEL", `Invalid thinking level: ${thinking}`);
|
|
166
|
+
return { provider: match[1], model: match[2], ...thinking !== void 0 ? { thinking } : {} };
|
|
167
|
+
}
|
|
168
|
+
var MODEL_ALIAS_ERROR_NAME = /* @__PURE__ */ Symbol("modelAliasErrorName");
|
|
169
|
+
function aliasError(message, settingsPath, name) {
|
|
170
|
+
const error = new WorkflowError("CONFIG_ERROR", `${message} (settings: ${settingsPath})`);
|
|
171
|
+
if (name) Object.defineProperty(error, MODEL_ALIAS_ERROR_NAME, { value: name, configurable: true });
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
function modelAliasName(value, aliases) {
|
|
175
|
+
const name = /^([^/:\s]+)(?::[^:\s]+)?$/.exec(value)?.[1];
|
|
176
|
+
return name && Object.prototype.hasOwnProperty.call(aliases, name) ? name : void 0;
|
|
177
|
+
}
|
|
178
|
+
function validateModelAliases(value, settingsPath = "workflow settings") {
|
|
179
|
+
if (!object(value)) aliasError("modelAliases must be an object", settingsPath);
|
|
180
|
+
const aliases = {};
|
|
181
|
+
for (const [name, target] of Object.entries(value)) {
|
|
182
|
+
if (!MODEL_ALIAS_NAME.test(name)) aliasError(`Invalid model alias name: ${name}`, settingsPath, name);
|
|
183
|
+
if (typeof target !== "string" || !target.trim()) aliasError(`Invalid model alias target for ${name}`, settingsPath, name);
|
|
184
|
+
aliases[name] = target;
|
|
185
|
+
}
|
|
186
|
+
for (const name of Object.keys(aliases)) {
|
|
187
|
+
try {
|
|
188
|
+
resolveModelReference(name, aliases);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
aliasError(`Invalid model alias target for ${name}: ${errorText(error)}`, settingsPath, name);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return Object.freeze(aliases);
|
|
194
|
+
}
|
|
195
|
+
function unknownModel(value, target, settingsPath) {
|
|
196
|
+
const resolved = target ? ` resolved to ${target}` : "";
|
|
197
|
+
const path = settingsPath ? ` (settings: ${settingsPath})` : "";
|
|
198
|
+
fail("UNKNOWN_MODEL", `Unknown model${target ? " alias" : ""} ${value}${resolved}${path}`);
|
|
199
|
+
}
|
|
200
|
+
function resolveModelReference(value, aliases = {}, knownModels, settingsPath) {
|
|
201
|
+
const resolveReference = (reference, chain) => {
|
|
202
|
+
if (reference.includes("/")) return parseModelReference(reference);
|
|
203
|
+
const match = /^([^:\s]+)(?::([^:\s]+))?$/.exec(reference);
|
|
204
|
+
const thinking = match?.[2];
|
|
205
|
+
if (!match?.[1] || thinking !== void 0 && !isThinkingLevel(thinking)) unknownModel(reference, void 0, settingsPath);
|
|
206
|
+
const alias = modelAliasName(reference, aliases);
|
|
207
|
+
if (alias) {
|
|
208
|
+
if (chain.includes(alias)) fail("UNKNOWN_MODEL", `Circular model alias: ${[...chain, alias].join(" -> ")}${settingsPath ? ` (settings: ${settingsPath})` : ""}`);
|
|
209
|
+
const { [alias]: target } = aliases;
|
|
210
|
+
if (typeof target !== "string") unknownModel(reference, void 0, settingsPath);
|
|
211
|
+
const parsed = resolveReference(target, [...chain, alias]);
|
|
212
|
+
return thinking !== void 0 ? { ...parsed, thinking } : parsed;
|
|
213
|
+
}
|
|
214
|
+
const candidates = [...knownModels ?? []].filter((model) => model.slice(model.indexOf("/") + 1) === match[1]);
|
|
215
|
+
if (candidates.length === 1) {
|
|
216
|
+
const [candidate] = candidates;
|
|
217
|
+
if (candidate !== void 0) {
|
|
218
|
+
const parsed = parseModelReference(candidate);
|
|
219
|
+
return thinking !== void 0 ? { ...parsed, thinking } : parsed;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
unknownModel(reference, void 0, settingsPath);
|
|
223
|
+
};
|
|
224
|
+
return resolveReference(value, []);
|
|
225
|
+
}
|
|
226
|
+
var RESOURCE_PATTERN_OPTIONS = { dot: true, nonegate: true, nocomment: true };
|
|
227
|
+
function resourcePatternBody(pattern) {
|
|
228
|
+
return pattern.startsWith("!") ? pattern.slice(1) : pattern;
|
|
229
|
+
}
|
|
230
|
+
function resourcePatternPath(value) {
|
|
231
|
+
return value.replaceAll("\\", "/");
|
|
232
|
+
}
|
|
233
|
+
function validateResourcePattern(pattern) {
|
|
234
|
+
const body = resourcePatternBody(pattern);
|
|
235
|
+
if (!body) throw new Error(`Empty minimatch pattern ${JSON.stringify(pattern)}`);
|
|
236
|
+
const matcher = new Minimatch(resourcePatternPath(body), RESOURCE_PATTERN_OPTIONS);
|
|
237
|
+
if (matcher.makeRe() === false) throw new Error(`Invalid minimatch pattern ${JSON.stringify(pattern)}`);
|
|
238
|
+
}
|
|
239
|
+
function resourcePatternMatches(resource, pattern) {
|
|
240
|
+
const body = resourcePatternBody(pattern);
|
|
241
|
+
if (body === "*") return true;
|
|
242
|
+
return new Minimatch(resourcePatternPath(body), RESOURCE_PATTERN_OPTIONS).match(resourcePatternPath(resource));
|
|
243
|
+
}
|
|
244
|
+
function selectResourcesByLayers(layers, resources) {
|
|
245
|
+
const enabled = new Set(resources);
|
|
246
|
+
for (const layer of layers) {
|
|
247
|
+
if (layer === void 0) continue;
|
|
248
|
+
for (const resource of resources) {
|
|
249
|
+
for (const pattern of layer) if (resourcePatternMatches(resource, pattern)) {
|
|
250
|
+
if (pattern.startsWith("!")) enabled.delete(resource);
|
|
251
|
+
else enabled.add(resource);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return resources.filter((resource) => enabled.has(resource));
|
|
256
|
+
}
|
|
257
|
+
function resourcePatternHasMagic(pattern) {
|
|
258
|
+
return /[*?\x5b\x5d{}()]/.test(resourcePatternBody(pattern));
|
|
259
|
+
}
|
|
260
|
+
function loadLaunchSnapshot(input) {
|
|
261
|
+
return deepFreeze(structuredClone(input));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/decoders.ts
|
|
265
|
+
var INVALID_PERSISTED_VALUE = /* @__PURE__ */ Symbol("invalid persisted value");
|
|
266
|
+
function integer(value) {
|
|
267
|
+
return finiteNumber(value) && Number.isInteger(value);
|
|
268
|
+
}
|
|
269
|
+
function safePositiveInteger(value) {
|
|
270
|
+
return integer(value) && Number.isSafeInteger(value) && value > 0;
|
|
271
|
+
}
|
|
272
|
+
function isLaunchMode(value) {
|
|
273
|
+
return value === "foreground" || value === "background";
|
|
274
|
+
}
|
|
275
|
+
function isRunState(value) {
|
|
276
|
+
return RUN_STATES.some((candidate) => candidate === value);
|
|
277
|
+
}
|
|
278
|
+
function isAgentState(value) {
|
|
279
|
+
return AGENT_STATES.some((candidate) => candidate === value);
|
|
280
|
+
}
|
|
281
|
+
function isAgentContinuity(value) {
|
|
282
|
+
return value === "fresh" || value === "continued";
|
|
283
|
+
}
|
|
284
|
+
function isBudgetDimension(value) {
|
|
285
|
+
return BUDGET_DIMENSIONS.some((candidate) => candidate === value);
|
|
286
|
+
}
|
|
287
|
+
function isBudgetEventType(value) {
|
|
288
|
+
return BUDGET_EVENT_TYPES.some((candidate) => candidate === value);
|
|
289
|
+
}
|
|
290
|
+
function optionalString(value) {
|
|
291
|
+
return value === void 0 || typeof value === "string" ? value : INVALID_PERSISTED_VALUE;
|
|
292
|
+
}
|
|
293
|
+
function optionalNumber(value) {
|
|
294
|
+
return value === void 0 || finiteNumber(value) ? value : INVALID_PERSISTED_VALUE;
|
|
295
|
+
}
|
|
296
|
+
function optionalBoolean(value) {
|
|
297
|
+
return value === void 0 || typeof value === "boolean" ? value : INVALID_PERSISTED_VALUE;
|
|
298
|
+
}
|
|
299
|
+
function decodeArray(value, decoder) {
|
|
300
|
+
if (!Array.isArray(value)) return void 0;
|
|
301
|
+
const decoded = [];
|
|
302
|
+
for (const entry of value) {
|
|
303
|
+
const result = decoder(entry);
|
|
304
|
+
if (result === void 0) return void 0;
|
|
305
|
+
decoded.push(result);
|
|
306
|
+
}
|
|
307
|
+
return decoded;
|
|
308
|
+
}
|
|
309
|
+
function decodeStringArray(value) {
|
|
310
|
+
return decodeArray(value, (entry) => typeof entry === "string" ? entry : void 0);
|
|
311
|
+
}
|
|
312
|
+
function decodeWorktreePostCreateCommand(value) {
|
|
313
|
+
const command = decodeStringArray(value);
|
|
314
|
+
return command && command.length > 0 && command.every((entry) => Boolean(entry.trim())) ? command : void 0;
|
|
315
|
+
}
|
|
316
|
+
function decodeJsonValue(value) {
|
|
317
|
+
return jsonValue(value) ? value : void 0;
|
|
318
|
+
}
|
|
319
|
+
function decodeJsonObject(value) {
|
|
320
|
+
return object(value) && jsonValue(value) ? { ...value } : void 0;
|
|
321
|
+
}
|
|
322
|
+
function decodeStringMap(value) {
|
|
323
|
+
return decodeRecord(value, (entry) => typeof entry === "string" ? entry : void 0);
|
|
324
|
+
}
|
|
325
|
+
function decodeRecord(value, decoder) {
|
|
326
|
+
if (!object(value)) return void 0;
|
|
327
|
+
const entries = [];
|
|
328
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
329
|
+
const decoded = decoder(entry);
|
|
330
|
+
if (decoded === void 0) return void 0;
|
|
331
|
+
entries.push([key, decoded]);
|
|
332
|
+
}
|
|
333
|
+
return Object.fromEntries(entries);
|
|
334
|
+
}
|
|
335
|
+
function decodeModelSpec(value) {
|
|
336
|
+
if (!object(value) || typeof value.provider !== "string" || typeof value.model !== "string") return void 0;
|
|
337
|
+
const thinking = value.thinking;
|
|
338
|
+
if (thinking !== void 0 && !isThinkingLevel(thinking)) return void 0;
|
|
339
|
+
return { provider: value.provider, model: value.model, ...isThinkingLevel(thinking) ? { thinking } : {} };
|
|
340
|
+
}
|
|
341
|
+
function decodeAgentResourceSelectors(value) {
|
|
342
|
+
if (!object(value)) return void 0;
|
|
343
|
+
const skills = value.skills === void 0 ? void 0 : decodeStringArray(value.skills);
|
|
344
|
+
const extensions = value.extensions === void 0 ? void 0 : decodeStringArray(value.extensions);
|
|
345
|
+
const tools = value.tools === void 0 ? void 0 : decodeStringArray(value.tools);
|
|
346
|
+
if (value.skills !== void 0 && !skills || value.extensions !== void 0 && !extensions || value.tools !== void 0 && !tools) return void 0;
|
|
347
|
+
return { ...skills === void 0 ? {} : { skills }, ...extensions === void 0 ? {} : { extensions }, ...tools === void 0 ? {} : { tools } };
|
|
348
|
+
}
|
|
349
|
+
function decodeContextFileScopes(value) {
|
|
350
|
+
return decodeArray(value, (scope) => isContextFileScope(scope) ? scope : void 0);
|
|
351
|
+
}
|
|
352
|
+
function decodeAgentDefinition(value) {
|
|
353
|
+
if (!object(value)) return void 0;
|
|
354
|
+
const prompt = optionalString(value.prompt);
|
|
355
|
+
const description = optionalString(value.description);
|
|
356
|
+
const model = optionalString(value.model);
|
|
357
|
+
const thinking = value.thinking;
|
|
358
|
+
const tools = value.tools === void 0 ? void 0 : decodeStringArray(value.tools);
|
|
359
|
+
const skills = value.skills === void 0 ? void 0 : decodeStringArray(value.skills);
|
|
360
|
+
const extensions = value.extensions === void 0 ? void 0 : decodeStringArray(value.extensions);
|
|
361
|
+
const overrideSystemPrompt = optionalBoolean(value.overrideSystemPrompt);
|
|
362
|
+
const contextFiles = value.contextFiles === void 0 ? void 0 : decodeContextFileScopes(value.contextFiles);
|
|
363
|
+
const extensionSettings = value.extensionSettings === void 0 ? void 0 : decodeWorkflowExtensions(value.extensionSettings);
|
|
364
|
+
if (prompt === INVALID_PERSISTED_VALUE || description === INVALID_PERSISTED_VALUE || model === INVALID_PERSISTED_VALUE || overrideSystemPrompt === INVALID_PERSISTED_VALUE) return void 0;
|
|
365
|
+
if (thinking !== void 0 && !isThinkingLevel(thinking) || value.tools !== void 0 && !tools || value.skills !== void 0 && !skills || value.extensions !== void 0 && !extensions || value.contextFiles !== void 0 && !contextFiles || value.extensionSettings !== void 0 && !extensionSettings) return void 0;
|
|
366
|
+
const foldedModel = typeof model === "string" && thinking !== void 0 && !model.includes(":") ? `${model}:${thinking}` : model;
|
|
367
|
+
return {
|
|
368
|
+
...prompt === void 0 ? {} : { prompt },
|
|
369
|
+
...description === void 0 ? {} : { description },
|
|
370
|
+
...foldedModel === void 0 ? {} : { model: foldedModel },
|
|
371
|
+
...tools === void 0 ? {} : { tools },
|
|
372
|
+
...skills === void 0 ? {} : { skills },
|
|
373
|
+
...extensions === void 0 ? {} : { extensions },
|
|
374
|
+
...overrideSystemPrompt === void 0 ? {} : { overrideSystemPrompt },
|
|
375
|
+
...contextFiles === void 0 ? {} : { contextFiles },
|
|
376
|
+
...extensionSettings === void 0 ? {} : { extensionSettings }
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function decodeWorkflowMetadata(value) {
|
|
380
|
+
if (!object(value) || typeof value.name !== "string") return void 0;
|
|
381
|
+
const description = optionalString(value.description);
|
|
382
|
+
if (description === INVALID_PERSISTED_VALUE) return void 0;
|
|
383
|
+
return { name: value.name, ...description === void 0 ? {} : { description } };
|
|
384
|
+
}
|
|
385
|
+
function decodeWorkflowExtensions(value) {
|
|
386
|
+
if (!object(value) || !jsonValue(value)) return void 0;
|
|
387
|
+
const normalized = {};
|
|
388
|
+
for (const [namespace, raw] of Object.entries(value)) {
|
|
389
|
+
if (!validWorkflowExtensionNamespace(namespace)) return void 0;
|
|
390
|
+
if (namespace === "herdr") {
|
|
391
|
+
if (!object(raw) || Object.keys(raw).some((key) => key !== "enableFullyInspectableMode") || raw.enableFullyInspectableMode !== void 0 && typeof raw.enableFullyInspectableMode !== "boolean") return void 0;
|
|
392
|
+
normalized.herdr = { ...raw.enableFullyInspectableMode === void 0 ? {} : { enableFullyInspectableMode: raw.enableFullyInspectableMode } };
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (namespace === "trajectory") {
|
|
396
|
+
if (!object(raw) || Object.keys(raw).some((key) => key !== "port" && key !== "themes") || raw.port !== void 0 && (typeof raw.port !== "number" || !Number.isSafeInteger(raw.port) || raw.port < 1 || raw.port > 65535) || raw.themes !== void 0 && typeof raw.themes !== "boolean") return void 0;
|
|
397
|
+
normalized.trajectory = { ...raw.port === void 0 ? {} : { port: raw.port }, ...raw.themes === void 0 ? {} : { themes: raw.themes } };
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (!jsonValue(raw)) return void 0;
|
|
401
|
+
normalized[namespace] = structuredClone(raw);
|
|
402
|
+
}
|
|
403
|
+
return normalized;
|
|
404
|
+
}
|
|
405
|
+
function decodeBudgetLimits(value) {
|
|
406
|
+
if (!object(value)) return void 0;
|
|
407
|
+
const soft = optionalNumber(value.soft);
|
|
408
|
+
const hard = optionalNumber(value.hard);
|
|
409
|
+
if (soft === INVALID_PERSISTED_VALUE || hard === INVALID_PERSISTED_VALUE) return void 0;
|
|
410
|
+
return { ...soft === void 0 ? {} : { soft }, ...hard === void 0 ? {} : { hard } };
|
|
411
|
+
}
|
|
412
|
+
function decodeBudget(value) {
|
|
413
|
+
if (!object(value)) return void 0;
|
|
414
|
+
const budget = {};
|
|
415
|
+
for (const dimension of BUDGET_DIMENSIONS) {
|
|
416
|
+
const raw = value[dimension];
|
|
417
|
+
if (raw === void 0) continue;
|
|
418
|
+
const limits = decodeBudgetLimits(raw);
|
|
419
|
+
if (limits === void 0) return void 0;
|
|
420
|
+
budget[dimension] = limits;
|
|
421
|
+
}
|
|
422
|
+
return budget;
|
|
423
|
+
}
|
|
424
|
+
function decodeRetention(value) {
|
|
425
|
+
if (!object(value) || Object.keys(value).some((key) => key !== "olderThanDays" && key !== "maxTerminalRuns")) return INVALID_PERSISTED_VALUE;
|
|
426
|
+
const olderThanDays = value.olderThanDays;
|
|
427
|
+
const maxTerminalRuns = value.maxTerminalRuns;
|
|
428
|
+
if (olderThanDays !== void 0 && !safePositiveInteger(olderThanDays) || maxTerminalRuns !== void 0 && !safePositiveInteger(maxTerminalRuns)) return INVALID_PERSISTED_VALUE;
|
|
429
|
+
return Object.freeze({ ...olderThanDays === void 0 ? {} : { olderThanDays }, ...maxTerminalRuns === void 0 ? {} : { maxTerminalRuns } });
|
|
430
|
+
}
|
|
431
|
+
function decodeWorkflowSettings(value) {
|
|
432
|
+
if (!object(value) || !positiveInteger(value.concurrency)) return void 0;
|
|
433
|
+
const backgroundWidget = optionalBoolean(value.backgroundWidget);
|
|
434
|
+
const modelAliases = value.modelAliases === void 0 ? void 0 : decodeStringMap(value.modelAliases);
|
|
435
|
+
const skills = value.skills === void 0 ? void 0 : decodeStringArray(value.skills);
|
|
436
|
+
const tools = value.tools === void 0 ? void 0 : decodeStringArray(value.tools);
|
|
437
|
+
const legacyExtensionSettings = value.extensions !== void 0 && object(value.extensions) ? decodeWorkflowExtensions(value.extensions) : void 0;
|
|
438
|
+
const extensions = value.extensions === void 0 || object(value.extensions) ? void 0 : decodeStringArray(value.extensions);
|
|
439
|
+
const extensionSettings = value.extensionSettings === void 0 ? void 0 : decodeWorkflowExtensions(value.extensionSettings);
|
|
440
|
+
const effectiveExtensionSettings = extensionSettings ?? legacyExtensionSettings;
|
|
441
|
+
const retention = value.retention === void 0 ? void 0 : decodeRetention(value.retention);
|
|
442
|
+
const worktreePostCreateCommand = value.worktreePostCreateCommand === void 0 ? void 0 : decodeWorktreePostCreateCommand(value.worktreePostCreateCommand);
|
|
443
|
+
if (backgroundWidget === INVALID_PERSISTED_VALUE || value.modelAliases !== void 0 && !modelAliases || value.skills !== void 0 && !skills || value.tools !== void 0 && !tools || value.extensions !== void 0 && !extensions && !legacyExtensionSettings || value.extensionSettings !== void 0 && !extensionSettings || retention === INVALID_PERSISTED_VALUE || value.worktreePostCreateCommand !== void 0 && !worktreePostCreateCommand) return void 0;
|
|
444
|
+
return {
|
|
445
|
+
concurrency: value.concurrency,
|
|
446
|
+
...backgroundWidget === void 0 ? {} : { backgroundWidget },
|
|
447
|
+
...modelAliases === void 0 ? {} : { modelAliases },
|
|
448
|
+
...skills === void 0 ? {} : { skills },
|
|
449
|
+
...tools === void 0 ? {} : { tools },
|
|
450
|
+
...extensions === void 0 ? {} : { extensions },
|
|
451
|
+
...effectiveExtensionSettings === void 0 ? {} : { extensionSettings: effectiveExtensionSettings },
|
|
452
|
+
...retention === void 0 ? {} : { retention },
|
|
453
|
+
...worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand }
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function decodeWorkflowSettingsSources(value) {
|
|
457
|
+
if (!object(value) || typeof value.concurrency !== "string" || typeof value.modelAliases !== "string") return void 0;
|
|
458
|
+
const skills = value.skills === void 0 ? void 0 : typeof value.skills === "string" ? value.skills : void 0;
|
|
459
|
+
const extensions = value.extensions === void 0 ? void 0 : typeof value.extensions === "string" ? value.extensions : void 0;
|
|
460
|
+
const tools = value.tools === void 0 ? void 0 : typeof value.tools === "string" ? value.tools : void 0;
|
|
461
|
+
const extensionSettings = value.extensionSettings === void 0 ? void 0 : typeof value.extensionSettings === "string" ? value.extensionSettings : void 0;
|
|
462
|
+
const retention = value.retention === void 0 ? void 0 : typeof value.retention === "string" ? value.retention : void 0;
|
|
463
|
+
const worktreePostCreateCommand = value.worktreePostCreateCommand === void 0 ? void 0 : typeof value.worktreePostCreateCommand === "string" ? value.worktreePostCreateCommand : void 0;
|
|
464
|
+
if (value.skills !== void 0 && skills === void 0 || value.extensions !== void 0 && extensions === void 0 || value.tools !== void 0 && tools === void 0 || value.extensionSettings !== void 0 && extensionSettings === void 0 || value.retention !== void 0 && retention === void 0 || value.worktreePostCreateCommand !== void 0 && worktreePostCreateCommand === void 0) return void 0;
|
|
465
|
+
return { concurrency: value.concurrency, modelAliases: value.modelAliases, ...skills === void 0 ? {} : { skills }, ...extensions === void 0 ? {} : { extensions }, ...tools === void 0 ? {} : { tools }, ...extensionSettings === void 0 ? {} : { extensionSettings }, ...retention === void 0 ? {} : { retention }, ...worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand } };
|
|
466
|
+
}
|
|
467
|
+
function decodeIdentity(value) {
|
|
468
|
+
if (!object(value) || typeof value.callSite !== "string" || !positiveInteger(value.occurrence)) return void 0;
|
|
469
|
+
const structuralPath2 = decodeStringArray(value.structuralPath);
|
|
470
|
+
const parentBreadcrumb = optionalString(value.parentBreadcrumb);
|
|
471
|
+
const worktreeOwner = optionalString(value.worktreeOwner);
|
|
472
|
+
const handle = optionalString(value.handle);
|
|
473
|
+
if (!structuralPath2 || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || handle === INVALID_PERSISTED_VALUE || handle !== void 0 && !positiveInteger(value.turn)) return void 0;
|
|
474
|
+
return { structuralPath: structuralPath2, callSite: value.callSite, occurrence: value.occurrence, ...parentBreadcrumb === void 0 ? {} : { parentBreadcrumb }, ...worktreeOwner === void 0 ? {} : { worktreeOwner }, ...handle === void 0 ? {} : { handle, turn: value.turn } };
|
|
475
|
+
}
|
|
476
|
+
function decodeScheduledAgentOptions(value) {
|
|
477
|
+
if (!object(value) || typeof value.label !== "string" || typeof value.cwd !== "string") return void 0;
|
|
478
|
+
const tools = decodeStringArray(value.tools);
|
|
479
|
+
const skills = value.skills === void 0 ? void 0 : decodeStringArray(value.skills);
|
|
480
|
+
const extensions = value.extensions === void 0 ? void 0 : decodeStringArray(value.extensions);
|
|
481
|
+
if (!tools) return void 0;
|
|
482
|
+
const requestedLabel = optionalString(value.requestedLabel);
|
|
483
|
+
const parentBreadcrumb = optionalString(value.parentBreadcrumb);
|
|
484
|
+
const worktreeOwner = optionalString(value.worktreeOwner);
|
|
485
|
+
const model = optionalString(value.model);
|
|
486
|
+
const role = typeof value.role === "string" ? value.role : void 0;
|
|
487
|
+
const contextFiles = value.contextFiles === void 0 ? void 0 : decodeContextFileScopes(value.contextFiles);
|
|
488
|
+
const schema = value.schema === void 0 ? void 0 : decodeJsonObject(value.schema);
|
|
489
|
+
const retries = optionalNumber(value.retries);
|
|
490
|
+
const timeoutMs = value.timeoutMs;
|
|
491
|
+
const agentOptions = value.agentOptions === void 0 ? void 0 : decodeJsonObject(value.agentOptions);
|
|
492
|
+
const agentIdentity = value.agentIdentity === void 0 ? void 0 : decodeIdentity(value.agentIdentity);
|
|
493
|
+
const sessionPath = optionalString(value.sessionPath);
|
|
494
|
+
const continuity = value.continuity === void 0 ? void 0 : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
|
|
495
|
+
const extensionSettings = value.extensionSettings === void 0 ? void 0 : decodeWorkflowExtensions(value.extensionSettings);
|
|
496
|
+
if (sessionPath === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || requestedLabel === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || model === INVALID_PERSISTED_VALUE || value.skills !== void 0 && !skills || value.extensions !== void 0 && !extensions || value.role !== void 0 && typeof value.role !== "string" || value.contextFiles !== void 0 && !contextFiles || value.schema !== void 0 && !schema || retries === INVALID_PERSISTED_VALUE || retries !== void 0 && !integer(retries) || timeoutMs !== void 0 && timeoutMs !== null && !finiteNumber(timeoutMs) || value.agentOptions !== void 0 && !agentOptions || value.agentIdentity !== void 0 && !agentIdentity || value.extensionSettings !== void 0 && !extensionSettings) return void 0;
|
|
497
|
+
return {
|
|
498
|
+
label: value.label,
|
|
499
|
+
...requestedLabel === void 0 ? {} : { requestedLabel },
|
|
500
|
+
...parentBreadcrumb === void 0 ? {} : { parentBreadcrumb },
|
|
501
|
+
cwd: value.cwd,
|
|
502
|
+
tools,
|
|
503
|
+
...skills === void 0 ? {} : { skills },
|
|
504
|
+
...extensions === void 0 ? {} : { extensions },
|
|
505
|
+
...worktreeOwner === void 0 ? {} : { worktreeOwner },
|
|
506
|
+
...model === void 0 ? {} : { model },
|
|
507
|
+
...role === void 0 ? {} : { role },
|
|
508
|
+
...contextFiles === void 0 ? {} : { contextFiles },
|
|
509
|
+
...schema === void 0 ? {} : { schema },
|
|
510
|
+
...retries === void 0 ? {} : { retries },
|
|
511
|
+
...timeoutMs === void 0 ? {} : { timeoutMs },
|
|
512
|
+
...sessionPath === void 0 ? {} : { sessionPath },
|
|
513
|
+
...continuity === void 0 ? {} : { continuity },
|
|
514
|
+
...agentOptions === void 0 ? {} : { agentOptions },
|
|
515
|
+
...agentIdentity === void 0 ? {} : { agentIdentity },
|
|
516
|
+
...extensionSettings === void 0 ? {} : { extensionSettings }
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function decodeAgentSession(value) {
|
|
520
|
+
if (!object(value) || typeof value.transport !== "string" || typeof value.sessionId !== "string") return void 0;
|
|
521
|
+
const locator = value.locator === void 0 ? void 0 : decodeJsonValue(value.locator);
|
|
522
|
+
if (value.locator !== void 0 && locator === void 0) return void 0;
|
|
523
|
+
return { transport: value.transport, sessionId: value.sessionId, ...locator === void 0 ? {} : { locator } };
|
|
524
|
+
}
|
|
525
|
+
function decodeAgentAccounting(value) {
|
|
526
|
+
if (!object(value) || !finiteNumber(value.input) || !finiteNumber(value.output) || !finiteNumber(value.cacheRead) || !finiteNumber(value.cacheWrite) || !finiteNumber(value.cost)) return void 0;
|
|
527
|
+
return { input: value.input, output: value.output, cacheRead: value.cacheRead, cacheWrite: value.cacheWrite, cost: value.cost };
|
|
528
|
+
}
|
|
529
|
+
function decodeAgentActivity(value) {
|
|
530
|
+
if (!object(value) || typeof value.text !== "string") return void 0;
|
|
531
|
+
const kind = value.kind;
|
|
532
|
+
if (kind !== "reasoning" && kind !== "tool" && kind !== "text") return void 0;
|
|
533
|
+
return { kind, text: value.text };
|
|
534
|
+
}
|
|
535
|
+
function decodeAgentToolCall(value) {
|
|
536
|
+
if (!object(value) || typeof value.id !== "string" || typeof value.name !== "string") return void 0;
|
|
537
|
+
const state = value.state;
|
|
538
|
+
if (state !== "running" && state !== "completed" && state !== "failed") return void 0;
|
|
539
|
+
return { id: value.id, name: value.name, state };
|
|
540
|
+
}
|
|
541
|
+
function decodeAgentResourceInspection(value) {
|
|
542
|
+
if (!object(value)) return void 0;
|
|
543
|
+
const selectors = decodeAgentResourceSelectors(value.selectors);
|
|
544
|
+
const skills = decodeStringArray(value.skills);
|
|
545
|
+
const extensions = decodeStringArray(value.extensions);
|
|
546
|
+
const tools = decodeStringArray(value.tools);
|
|
547
|
+
const unmatchedSkills = decodeStringArray(value.unmatchedSkills);
|
|
548
|
+
const unmatchedExtensions = decodeStringArray(value.unmatchedExtensions);
|
|
549
|
+
const unmatchedTools = decodeStringArray(value.unmatchedTools);
|
|
550
|
+
const rawSources = value.selectorSources;
|
|
551
|
+
const sourceRecord = object(rawSources) ? rawSources : void 0;
|
|
552
|
+
const sources = sourceRecord === void 0 ? void 0 : {
|
|
553
|
+
global: decodeAgentResourceSelectors(sourceRecord.global),
|
|
554
|
+
project: decodeAgentResourceSelectors(sourceRecord.project),
|
|
555
|
+
...sourceRecord.role === void 0 ? {} : { role: decodeAgentResourceSelectors(sourceRecord.role) },
|
|
556
|
+
...sourceRecord.call === void 0 ? {} : { call: decodeAgentResourceSelectors(sourceRecord.call) }
|
|
557
|
+
};
|
|
558
|
+
if (!selectors || !skills || !extensions || !tools || !unmatchedSkills || !unmatchedExtensions || !unmatchedTools || rawSources !== void 0 && (!sources || !sources.global || !sources.project || sources.role === void 0 && sourceRecord?.role !== void 0 || sources.call === void 0 && sourceRecord?.call !== void 0)) return void 0;
|
|
559
|
+
return { selectors: { skills: [...selectors.skills ?? []], extensions: [...selectors.extensions ?? []], tools: [...selectors.tools ?? []] }, skills, extensions, tools, unmatchedSkills, unmatchedExtensions, unmatchedTools, ...sources === void 0 ? {} : { selectorSources: { global: sources.global ?? {}, project: sources.project ?? {}, ...sources.role === void 0 ? {} : { role: sources.role }, ...sources.call === void 0 ? {} : { call: sources.call } } } };
|
|
560
|
+
}
|
|
561
|
+
function decodeAgentSetupSummary(value) {
|
|
562
|
+
if (!object(value) || typeof value.cwd !== "string") return void 0;
|
|
563
|
+
const hookNames = decodeStringArray(value.hookNames);
|
|
564
|
+
const tools = decodeStringArray(value.tools);
|
|
565
|
+
const model = decodeModelSpec(value.model);
|
|
566
|
+
const resourceSelectors = value.resourceSelectors === void 0 ? void 0 : decodeAgentResourceInspection(value.resourceSelectors);
|
|
567
|
+
if (!hookNames || !tools || !model || value.resourceSelectors !== void 0 && !resourceSelectors) return void 0;
|
|
568
|
+
return { hookNames, model, tools, cwd: value.cwd, ...resourceSelectors === void 0 ? {} : { resourceSelectors } };
|
|
569
|
+
}
|
|
570
|
+
function decodeAttemptError(value) {
|
|
571
|
+
if (!object(value) || typeof value.code !== "string" || typeof value.message !== "string") return void 0;
|
|
572
|
+
return { code: value.code, message: value.message };
|
|
573
|
+
}
|
|
574
|
+
function decodeAgentAttempt(value) {
|
|
575
|
+
if (!object(value) || !positiveInteger(value.attempt) || typeof value.transport !== "string") return void 0;
|
|
576
|
+
const session = value.session === void 0 ? void 0 : decodeAgentSession(value.session);
|
|
577
|
+
const setup = decodeAgentSetupSummary(value.setup);
|
|
578
|
+
const error = value.error === void 0 ? void 0 : decodeAttemptError(value.error);
|
|
579
|
+
const accounting = decodeAgentAccounting(value.accounting);
|
|
580
|
+
if (!setup || !accounting || value.session !== void 0 && !session || value.error !== void 0 && !error) return void 0;
|
|
581
|
+
return { attempt: value.attempt, transport: value.transport, setup, accounting, ...session === void 0 ? {} : { session }, ...error === void 0 ? {} : { error } };
|
|
582
|
+
}
|
|
583
|
+
function decodeAgent(value) {
|
|
584
|
+
if (!object(value) || typeof value.id !== "string" || typeof value.name !== "string" || typeof value.path !== "string" || !isAgentState(value.state) || !integer(value.attempts)) return void 0;
|
|
585
|
+
const model = decodeModelSpec(value.model);
|
|
586
|
+
const tools = decodeStringArray(value.tools);
|
|
587
|
+
const systemPrompt = optionalString(value.systemPrompt);
|
|
588
|
+
const prompt = optionalString(value.prompt);
|
|
589
|
+
const label = optionalString(value.label);
|
|
590
|
+
const parentId = optionalString(value.parentId);
|
|
591
|
+
const structuralPath2 = value.structuralPath === void 0 ? void 0 : decodeStringArray(value.structuralPath);
|
|
592
|
+
const resultPath = optionalString(value.resultPath);
|
|
593
|
+
const parentBreadcrumb = optionalString(value.parentBreadcrumb);
|
|
594
|
+
const worktreeOwner = optionalString(value.worktreeOwner);
|
|
595
|
+
const handle = optionalString(value.handle);
|
|
596
|
+
const continuity = value.continuity === void 0 ? void 0 : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
|
|
597
|
+
const role = optionalString(value.role);
|
|
598
|
+
const requestedModel = optionalString(value.requestedModel);
|
|
599
|
+
const startedAt = optionalNumber(value.startedAt);
|
|
600
|
+
const durationMs = optionalNumber(value.durationMs);
|
|
601
|
+
const attemptDetails = value.attemptDetails === void 0 ? void 0 : decodeArray(value.attemptDetails, decodeAgentAttempt);
|
|
602
|
+
const accounting = value.accounting === void 0 ? void 0 : decodeAgentAccounting(value.accounting);
|
|
603
|
+
const toolCalls = value.toolCalls === void 0 ? void 0 : decodeArray(value.toolCalls, decodeAgentToolCall);
|
|
604
|
+
const activity = value.activity === void 0 ? void 0 : decodeAgentActivity(value.activity);
|
|
605
|
+
const lastEventAt = optionalNumber(value.lastEventAt);
|
|
606
|
+
if (systemPrompt === INVALID_PERSISTED_VALUE || prompt === INVALID_PERSISTED_VALUE || label === INVALID_PERSISTED_VALUE || parentId === INVALID_PERSISTED_VALUE || resultPath === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || role === INVALID_PERSISTED_VALUE || requestedModel === INVALID_PERSISTED_VALUE || startedAt === INVALID_PERSISTED_VALUE || durationMs === INVALID_PERSISTED_VALUE || lastEventAt === INVALID_PERSISTED_VALUE || handle === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || handle !== void 0 && !positiveInteger(value.turn)) return void 0;
|
|
607
|
+
if (!model || !tools || value.structuralPath !== void 0 && !structuralPath2 || value.attemptDetails !== void 0 && !attemptDetails || value.accounting !== void 0 && !accounting || value.toolCalls !== void 0 && !toolCalls || value.activity !== void 0 && !activity) return void 0;
|
|
608
|
+
return {
|
|
609
|
+
...systemPrompt === void 0 ? {} : { systemPrompt },
|
|
610
|
+
...prompt === void 0 ? {} : { prompt },
|
|
611
|
+
id: value.id,
|
|
612
|
+
name: value.name,
|
|
613
|
+
...label === void 0 ? {} : { label },
|
|
614
|
+
path: value.path,
|
|
615
|
+
state: value.state,
|
|
616
|
+
...parentId === void 0 ? {} : { parentId },
|
|
617
|
+
...structuralPath2 === void 0 ? {} : { structuralPath: structuralPath2 },
|
|
618
|
+
...resultPath === void 0 ? {} : { resultPath },
|
|
619
|
+
...parentBreadcrumb === void 0 ? {} : { parentBreadcrumb },
|
|
620
|
+
...worktreeOwner === void 0 ? {} : { worktreeOwner },
|
|
621
|
+
...handle === void 0 ? {} : { handle, turn: value.turn },
|
|
622
|
+
...continuity === void 0 ? {} : { continuity },
|
|
623
|
+
...role === void 0 ? {} : { role },
|
|
624
|
+
...requestedModel === void 0 ? {} : { requestedModel },
|
|
625
|
+
model,
|
|
626
|
+
tools,
|
|
627
|
+
attempts: value.attempts,
|
|
628
|
+
...startedAt === void 0 ? {} : { startedAt },
|
|
629
|
+
...durationMs === void 0 ? {} : { durationMs },
|
|
630
|
+
...attemptDetails === void 0 ? {} : { attemptDetails },
|
|
631
|
+
...accounting === void 0 ? {} : { accounting },
|
|
632
|
+
...toolCalls === void 0 ? {} : { toolCalls },
|
|
633
|
+
...activity === void 0 ? {} : { activity },
|
|
634
|
+
...lastEventAt === void 0 ? {} : { lastEventAt }
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function decodeRetry(value) {
|
|
638
|
+
if (!object(value) || typeof value.sourceRunId !== "string" || typeof value.lineageRootRunId !== "string") return void 0;
|
|
639
|
+
const completedPaths = decodeStringArray(value.completedPaths);
|
|
640
|
+
const incompletePaths = decodeStringArray(value.incompletePaths);
|
|
641
|
+
const namedWorktrees = decodeStringArray(value.namedWorktrees);
|
|
642
|
+
if (!completedPaths || !incompletePaths || !namedWorktrees) return void 0;
|
|
643
|
+
return { sourceRunId: value.sourceRunId, lineageRootRunId: value.lineageRootRunId, completedPaths, incompletePaths, namedWorktrees };
|
|
644
|
+
}
|
|
645
|
+
function decodeWorkflowError(value) {
|
|
646
|
+
if (!object(value) || typeof value.code !== "string" || typeof value.message !== "string") return void 0;
|
|
647
|
+
const code = isWorkflowErrorCode(value.code) ? value.code : "INTERNAL_ERROR";
|
|
648
|
+
const failedAt = optionalString(value.failedAt);
|
|
649
|
+
if (failedAt === INVALID_PERSISTED_VALUE) return void 0;
|
|
650
|
+
return { code, message: value.message, ...failedAt === void 0 ? {} : { failedAt } };
|
|
651
|
+
}
|
|
652
|
+
function decodeUsage(value) {
|
|
653
|
+
if (!object(value) || !finiteNumber(value.tokens) || !finiteNumber(value.costUsd) || !finiteNumber(value.durationMs) || !finiteNumber(value.agentLaunches)) return void 0;
|
|
654
|
+
return { tokens: value.tokens, costUsd: value.costUsd, durationMs: value.durationMs, agentLaunches: value.agentLaunches };
|
|
655
|
+
}
|
|
656
|
+
function decodeBudgetEvent(value) {
|
|
657
|
+
if (!object(value) || !isBudgetEventType(value.type) || !integer(value.budgetVersion) || !finiteNumber(value.at)) return void 0;
|
|
658
|
+
const dimensions = decodeArray(value.dimensions, (entry) => isBudgetDimension(entry) ? entry : void 0);
|
|
659
|
+
const usage = decodeUsage(value.usage);
|
|
660
|
+
const limits = decodeBudget(value.limits);
|
|
661
|
+
const proposalId = optionalString(value.proposalId);
|
|
662
|
+
const previous = value.previous === void 0 ? void 0 : decodeBudget(value.previous);
|
|
663
|
+
const proposed = value.proposed === void 0 ? void 0 : decodeBudget(value.proposed);
|
|
664
|
+
if (!dimensions || !usage || !limits || proposalId === INVALID_PERSISTED_VALUE || value.previous !== void 0 && !previous || value.proposed !== void 0 && !proposed) return void 0;
|
|
665
|
+
return { type: value.type, budgetVersion: value.budgetVersion, dimensions, usage, limits, at: value.at, ...proposalId === void 0 ? {} : { proposalId }, ...previous === void 0 ? {} : { previous }, ...proposed === void 0 ? {} : { proposed } };
|
|
666
|
+
}
|
|
667
|
+
function decodePhaseRecord(value) {
|
|
668
|
+
if (!object(value) || typeof value.phase !== "string" || !integer(value.afterAgent)) return void 0;
|
|
669
|
+
return { phase: value.phase, afterAgent: value.afterAgent };
|
|
670
|
+
}
|
|
671
|
+
function decodeShellActivity(value) {
|
|
672
|
+
if (!object(value) || !integer(value.phaseIndex) || !integer(value.active) || !finiteNumber(value.startedAt)) return void 0;
|
|
673
|
+
return { phaseIndex: value.phaseIndex, active: value.active, startedAt: value.startedAt };
|
|
674
|
+
}
|
|
675
|
+
function decodeRunEvent(value) {
|
|
676
|
+
if (!object(value) || typeof value.type !== "string" || typeof value.message !== "string") return void 0;
|
|
677
|
+
const timestamp = optionalNumber(value.timestamp);
|
|
678
|
+
if (timestamp === INVALID_PERSISTED_VALUE) return void 0;
|
|
679
|
+
return { type: value.type, message: value.message, ...timestamp === void 0 ? {} : { timestamp } };
|
|
680
|
+
}
|
|
681
|
+
function decodeDelivery(value) {
|
|
682
|
+
if (!object(value)) return void 0;
|
|
683
|
+
const mode = value.mode;
|
|
684
|
+
const state = value.state;
|
|
685
|
+
if (mode !== "foreground" && mode !== "background" || state !== "attached" && state !== "pending" && state !== "delivered") return void 0;
|
|
686
|
+
const toolCallId = optionalString(value.toolCallId);
|
|
687
|
+
if (toolCallId === INVALID_PERSISTED_VALUE) return void 0;
|
|
688
|
+
return { mode, state, ...toolCallId === void 0 ? {} : { toolCallId } };
|
|
689
|
+
}
|
|
690
|
+
function decodePersistedRun(value, allowLegacyAgentSessions = false) {
|
|
691
|
+
if (!object(value) || typeof value.id !== "string" || typeof value.workflowName !== "string" || typeof value.cwd !== "string" || typeof value.sessionId !== "string" || !isRunState(value.state)) return void 0;
|
|
692
|
+
const agentSessions = value.agentSessions === void 0 && allowLegacyAgentSessions ? [] : decodeArray(value.agentSessions, decodeAgentSession);
|
|
693
|
+
const agents = decodeArray(value.agents, decodeAgent);
|
|
694
|
+
const parentRunId = optionalString(value.parentRunId);
|
|
695
|
+
const retry = value.retry === void 0 ? void 0 : decodeRetry(value.retry);
|
|
696
|
+
const phase = optionalString(value.phase);
|
|
697
|
+
const phaseHistory = value.phaseHistory === void 0 ? void 0 : decodeArray(value.phaseHistory, decodePhaseRecord);
|
|
698
|
+
const phaseHistoryIndex = optionalNumber(value.phaseHistoryIndex);
|
|
699
|
+
const activeShells = optionalNumber(value.activeShells);
|
|
700
|
+
const activeShellStartedAt = optionalNumber(value.activeShellStartedAt);
|
|
701
|
+
const activeShellsByPhase = value.activeShellsByPhase === void 0 ? void 0 : decodeArray(value.activeShellsByPhase, decodeShellActivity);
|
|
702
|
+
const error = value.error === void 0 ? void 0 : decodeWorkflowError(value.error);
|
|
703
|
+
const failedAt = optionalString(value.failedAt);
|
|
704
|
+
const budget = value.budget === void 0 ? void 0 : decodeBudget(value.budget);
|
|
705
|
+
const budgetVersion = optionalNumber(value.budgetVersion);
|
|
706
|
+
const usage = value.usage === void 0 ? void 0 : decodeUsage(value.usage);
|
|
707
|
+
const budgetEvents = value.budgetEvents === void 0 ? void 0 : decodeArray(value.budgetEvents, decodeBudgetEvent);
|
|
708
|
+
const events = value.events === void 0 ? void 0 : decodeArray(value.events, decodeRunEvent);
|
|
709
|
+
const delivery = value.delivery === void 0 ? void 0 : decodeDelivery(value.delivery);
|
|
710
|
+
if (!agentSessions || !agents || parentRunId === INVALID_PERSISTED_VALUE || value.retry !== void 0 && !retry || phase === INVALID_PERSISTED_VALUE || value.phaseHistory !== void 0 && !phaseHistory || phaseHistoryIndex === INVALID_PERSISTED_VALUE || activeShells === INVALID_PERSISTED_VALUE || activeShellStartedAt === INVALID_PERSISTED_VALUE || value.activeShellsByPhase !== void 0 && !activeShellsByPhase || value.error !== void 0 && !error || failedAt === INVALID_PERSISTED_VALUE || value.budget !== void 0 && !budget || budgetVersion === INVALID_PERSISTED_VALUE || value.budgetVersion !== void 0 && !integer(budgetVersion) || value.usage !== void 0 && !usage || value.budgetEvents !== void 0 && !budgetEvents || value.events !== void 0 && !events || value.delivery !== void 0 && !delivery) return void 0;
|
|
711
|
+
if (value.agentSessions === void 0 && !allowLegacyAgentSessions) return void 0;
|
|
712
|
+
return {
|
|
713
|
+
id: value.id,
|
|
714
|
+
workflowName: value.workflowName,
|
|
715
|
+
cwd: value.cwd,
|
|
716
|
+
sessionId: value.sessionId,
|
|
717
|
+
state: value.state,
|
|
718
|
+
agentSessions,
|
|
719
|
+
...parentRunId === void 0 ? {} : { parentRunId },
|
|
720
|
+
...retry === void 0 ? {} : { retry },
|
|
721
|
+
...phase === void 0 ? {} : { phase },
|
|
722
|
+
...phaseHistory === void 0 ? {} : { phaseHistory },
|
|
723
|
+
...phaseHistoryIndex === void 0 ? {} : { phaseHistoryIndex },
|
|
724
|
+
agents,
|
|
725
|
+
...activeShells === void 0 ? {} : { activeShells },
|
|
726
|
+
...activeShellStartedAt === void 0 ? {} : { activeShellStartedAt },
|
|
727
|
+
...activeShellsByPhase === void 0 ? {} : { activeShellsByPhase },
|
|
728
|
+
...error === void 0 ? {} : { error },
|
|
729
|
+
...failedAt === void 0 ? {} : { failedAt },
|
|
730
|
+
...budget === void 0 ? {} : { budget },
|
|
731
|
+
...budgetVersion === void 0 ? {} : { budgetVersion },
|
|
732
|
+
...usage === void 0 ? {} : { usage },
|
|
733
|
+
...budgetEvents === void 0 ? {} : { budgetEvents },
|
|
734
|
+
...events === void 0 ? {} : { events },
|
|
735
|
+
...delivery === void 0 ? {} : { delivery }
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function decodeLaunchSnapshot(value) {
|
|
739
|
+
if (!object(value) || typeof value.script !== "string") return void 0;
|
|
740
|
+
const identityVersion = optionalNumber(value.identityVersion);
|
|
741
|
+
const launchMode = value.launchMode;
|
|
742
|
+
const args = decodeJsonValue(value.args);
|
|
743
|
+
const metadata = decodeWorkflowMetadata(value.metadata);
|
|
744
|
+
const settings = decodeWorkflowSettings(value.settings);
|
|
745
|
+
const settingsSources = value.settingsSources === void 0 ? void 0 : decodeWorkflowSettingsSources(value.settingsSources);
|
|
746
|
+
const budget = value.budget === void 0 ? void 0 : decodeBudget(value.budget);
|
|
747
|
+
const settingsPath = optionalString(value.settingsPath);
|
|
748
|
+
const modelAliases = value.modelAliases === void 0 ? void 0 : decodeStringMap(value.modelAliases);
|
|
749
|
+
const phases = value.phases === void 0 ? void 0 : decodeStringArray(value.phases);
|
|
750
|
+
const models = decodeStringArray(value.models);
|
|
751
|
+
const tools = decodeStringArray(value.tools);
|
|
752
|
+
const agentTypes = decodeStringArray(value.agentTypes);
|
|
753
|
+
const roles = value.roles === void 0 ? void 0 : decodeRecord(value.roles, decodeAgentDefinition);
|
|
754
|
+
const projectRoles = value.projectRoles === void 0 ? void 0 : decodeStringArray(value.projectRoles);
|
|
755
|
+
const schemas = decodeArray(value.schemas, decodeJsonObject);
|
|
756
|
+
if (identityVersion === INVALID_PERSISTED_VALUE || value.identityVersion !== void 0 && !integer(identityVersion) || launchMode !== void 0 && !isLaunchMode(launchMode) || args === void 0 || !metadata || !settings || value.settingsSources !== void 0 && !settingsSources || value.budget !== void 0 && !budget || settingsPath === INVALID_PERSISTED_VALUE || value.modelAliases !== void 0 && !modelAliases || value.phases !== void 0 && !phases || !models || !tools || !agentTypes || value.roles !== void 0 && !roles || value.projectRoles !== void 0 && !projectRoles || !schemas) return void 0;
|
|
757
|
+
return {
|
|
758
|
+
...identityVersion === void 0 ? {} : { identityVersion },
|
|
759
|
+
...launchMode === void 0 ? {} : { launchMode },
|
|
760
|
+
script: value.script,
|
|
761
|
+
args,
|
|
762
|
+
metadata,
|
|
763
|
+
settings,
|
|
764
|
+
...settingsSources === void 0 ? {} : { settingsSources },
|
|
765
|
+
...budget === void 0 ? {} : { budget },
|
|
766
|
+
...settingsPath === void 0 ? {} : { settingsPath },
|
|
767
|
+
...modelAliases === void 0 ? {} : { modelAliases },
|
|
768
|
+
...phases === void 0 ? {} : { phases },
|
|
769
|
+
models,
|
|
770
|
+
tools,
|
|
771
|
+
agentTypes,
|
|
772
|
+
...roles === void 0 ? {} : { roles },
|
|
773
|
+
...projectRoles === void 0 ? {} : { projectRoles },
|
|
774
|
+
schemas
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
function decodeOwnershipRecord(value) {
|
|
778
|
+
if (!object(value) || typeof value.id !== "string" || typeof value.label !== "string" || !isAgentState(value.state)) return void 0;
|
|
779
|
+
const parentId = optionalString(value.parentId);
|
|
780
|
+
const prompt = optionalString(value.prompt);
|
|
781
|
+
const options = decodeScheduledAgentOptions(value.options);
|
|
782
|
+
if (parentId === INVALID_PERSISTED_VALUE || prompt === INVALID_PERSISTED_VALUE || !options) return void 0;
|
|
783
|
+
return { id: value.id, label: value.label, state: value.state, options, ...parentId === void 0 ? {} : { parentId }, ...prompt === void 0 ? {} : { prompt } };
|
|
784
|
+
}
|
|
785
|
+
function decodeOwnershipRecords(value) {
|
|
786
|
+
return decodeArray(value, decodeOwnershipRecord);
|
|
787
|
+
}
|
|
788
|
+
function decodeWorktreeReference(value) {
|
|
789
|
+
if (!object(value) || typeof value.owner !== "string" || typeof value.path !== "string" || typeof value.branch !== "string" || typeof value.cwd !== "string" || typeof value.base !== "string") return void 0;
|
|
790
|
+
return { owner: value.owner, path: value.path, branch: value.branch, cwd: value.cwd, base: value.base };
|
|
791
|
+
}
|
|
792
|
+
function decodeWorktreeReferences(value) {
|
|
793
|
+
return decodeArray(value, decodeWorktreeReference);
|
|
794
|
+
}
|
|
795
|
+
function decodeBorrowedWorktreeBinding(value) {
|
|
796
|
+
if (!object(value) || typeof value.name !== "string" || typeof value.sourceRunId !== "string" || typeof value.owner !== "string") return void 0;
|
|
797
|
+
return { name: value.name, sourceRunId: value.sourceRunId, owner: value.owner };
|
|
798
|
+
}
|
|
799
|
+
function decodeBorrowedWorktreeBindings(value) {
|
|
800
|
+
return decodeArray(value, decodeBorrowedWorktreeBinding);
|
|
801
|
+
}
|
|
802
|
+
function decodeCompletedOperation(value) {
|
|
803
|
+
if (!object(value) || typeof value.path !== "string") return void 0;
|
|
804
|
+
const decodedValue = decodeJsonValue(value.value);
|
|
805
|
+
if (decodedValue === void 0) return void 0;
|
|
806
|
+
return { path: value.path, value: decodedValue };
|
|
807
|
+
}
|
|
808
|
+
function decodeAwaitingCheckpoint(value) {
|
|
809
|
+
if (!object(value) || typeof value.path !== "string" || typeof value.name !== "string" || typeof value.prompt !== "string") return void 0;
|
|
810
|
+
const context = decodeJsonValue(value.context);
|
|
811
|
+
if (context === void 0) return void 0;
|
|
812
|
+
return { path: value.path, name: value.name, prompt: value.prompt, context };
|
|
813
|
+
}
|
|
814
|
+
function decodePendingWorkflowDecision(value) {
|
|
815
|
+
if (!object(value) || value.kind !== "budget" || typeof value.proposalId !== "string" || typeof value.runId !== "string" || !integer(value.budgetVersion)) return void 0;
|
|
816
|
+
const consumed = decodeUsage(value.consumed);
|
|
817
|
+
const previous = decodeBudget(value.previous);
|
|
818
|
+
const proposed = decodeBudget(value.proposed);
|
|
819
|
+
const foreground = optionalBoolean(value.foreground);
|
|
820
|
+
if (!consumed || !previous || !proposed || foreground === INVALID_PERSISTED_VALUE) return void 0;
|
|
821
|
+
return { kind: "budget", proposalId: value.proposalId, runId: value.runId, consumed, previous, proposed, budgetVersion: value.budgetVersion, ...foreground === void 0 ? {} : { foreground } };
|
|
822
|
+
}
|
|
823
|
+
function decodeJournal(value) {
|
|
824
|
+
if (!object(value)) return void 0;
|
|
825
|
+
const completed = decodeRecord(value.completed, decodeCompletedOperation);
|
|
826
|
+
const awaiting = value.awaiting === void 0 ? void 0 : decodeRecord(value.awaiting, decodeAwaitingCheckpoint);
|
|
827
|
+
const decisions = value.decisions === void 0 ? void 0 : decodeRecord(value.decisions, decodePendingWorkflowDecision);
|
|
828
|
+
if (!completed || value.awaiting !== void 0 && !awaiting || value.decisions !== void 0 && !decisions) return void 0;
|
|
829
|
+
return { completed, ...awaiting === void 0 ? {} : { awaiting }, ...decisions === void 0 ? {} : { decisions } };
|
|
830
|
+
}
|
|
831
|
+
function decodeBooleanCheckpointResult(value) {
|
|
832
|
+
return typeof value === "boolean" ? value : void 0;
|
|
833
|
+
}
|
|
834
|
+
function decodeSummaryProjection(value) {
|
|
835
|
+
if (!object(value)) return void 0;
|
|
836
|
+
const createdAt = optionalString(value.createdAt);
|
|
837
|
+
const updatedAt = optionalString(value.updatedAt);
|
|
838
|
+
const terminalAt = optionalString(value.terminalAt);
|
|
839
|
+
if (createdAt === INVALID_PERSISTED_VALUE || updatedAt === INVALID_PERSISTED_VALUE || terminalAt === INVALID_PERSISTED_VALUE) return void 0;
|
|
840
|
+
return { ...createdAt === void 0 ? {} : { createdAt }, ...updatedAt === void 0 ? {} : { updatedAt }, ...terminalAt === void 0 ? {} : { terminalAt } };
|
|
841
|
+
}
|
|
842
|
+
function decodeEffectiveSystemPrompt(value) {
|
|
843
|
+
if (!object(value) || typeof value.sessionId !== "string" || !positiveInteger(value.attempt) || !positiveInteger(value.turn) || typeof value.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(value.sha256) || typeof value.prompt !== "string") return void 0;
|
|
844
|
+
return { sessionId: value.sessionId, attempt: value.attempt, turn: value.turn, sha256: value.sha256, prompt: value.prompt };
|
|
845
|
+
}
|
|
846
|
+
function decodeSystemPromptArtifact(value) {
|
|
847
|
+
if (!object(value) || !finiteNumber(value.version)) return void 0;
|
|
848
|
+
const entries = value.entries === void 0 ? void 0 : decodeArray(value.entries, decodeEffectiveSystemPrompt);
|
|
849
|
+
if (value.entries !== void 0 && !entries) return void 0;
|
|
850
|
+
return { version: value.version, ...value.format === void 0 ? {} : { format: value.format }, ...value.storage === void 0 ? {} : { storage: value.storage }, ...entries === void 0 ? {} : { entries } };
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// src/io.ts
|
|
854
|
+
import { execFile } from "node:child_process";
|
|
855
|
+
import { randomUUID } from "node:crypto";
|
|
856
|
+
import { renameSync, rmSync, writeFileSync } from "node:fs";
|
|
857
|
+
import { readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
858
|
+
import { promisify } from "node:util";
|
|
859
|
+
var execute = promisify(execFile);
|
|
860
|
+
var RENAME_RETRY_DELAYS_MS = [10, 25, 50, 100, 200, 400, 800];
|
|
861
|
+
var syncSleepBuffer = new Int32Array(new SharedArrayBuffer(4));
|
|
862
|
+
function retryableRenameError(error) {
|
|
863
|
+
return isNodeError(error, "EPERM") || isNodeError(error, "EBUSY") || isNodeError(error, "EACCES");
|
|
864
|
+
}
|
|
865
|
+
async function renameWithRetry(from, to) {
|
|
866
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
867
|
+
try {
|
|
868
|
+
await rename(from, to);
|
|
869
|
+
return;
|
|
870
|
+
} catch (error) {
|
|
871
|
+
const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
|
|
872
|
+
if (!retryableRenameError(error) || delayMs === void 0) throw error;
|
|
873
|
+
await new Promise((resolve5) => setTimeout(resolve5, delayMs));
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
function renameSyncWithRetry(from, to) {
|
|
878
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
879
|
+
try {
|
|
880
|
+
renameSync(from, to);
|
|
881
|
+
return;
|
|
882
|
+
} catch (error) {
|
|
883
|
+
const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
|
|
884
|
+
if (!retryableRenameError(error) || delayMs === void 0) throw error;
|
|
885
|
+
Atomics.wait(syncSleepBuffer, 0, 0, delayMs);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
var gitIdentity = {
|
|
890
|
+
GIT_AUTHOR_NAME: "pi-extensible-workflows",
|
|
891
|
+
GIT_AUTHOR_EMAIL: "pi-extensible-workflows@localhost",
|
|
892
|
+
GIT_COMMITTER_NAME: "pi-extensible-workflows",
|
|
893
|
+
GIT_COMMITTER_EMAIL: "pi-extensible-workflows@localhost",
|
|
894
|
+
GIT_AUTHOR_DATE: "2000-01-01T00:00:00Z",
|
|
895
|
+
GIT_COMMITTER_DATE: "2000-01-01T00:00:00Z"
|
|
896
|
+
};
|
|
897
|
+
function atomicWriteFile(path, content, sync = false) {
|
|
898
|
+
const temporary = `${path}.${String(process.pid)}.${randomUUID()}.tmp`;
|
|
899
|
+
if (sync) {
|
|
900
|
+
try {
|
|
901
|
+
writeFileSync(temporary, content, { encoding: "utf8", mode: 384 });
|
|
902
|
+
renameSyncWithRetry(temporary, path);
|
|
903
|
+
} catch (error) {
|
|
904
|
+
try {
|
|
905
|
+
rmSync(temporary, { force: true });
|
|
906
|
+
} catch {
|
|
907
|
+
}
|
|
908
|
+
throw error;
|
|
909
|
+
}
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
return writeFile(temporary, content, { encoding: "utf8", mode: 384 }).then(() => renameWithRetry(temporary, path)).catch(async (error) => {
|
|
913
|
+
try {
|
|
914
|
+
await rm(temporary, { force: true });
|
|
915
|
+
} catch {
|
|
916
|
+
}
|
|
917
|
+
throw error;
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
async function atomicJson(path, value) {
|
|
921
|
+
const serialized = JSON.stringify(value);
|
|
922
|
+
if (typeof serialized !== "string") throw new WorkflowError("INTERNAL_ERROR", `Cannot serialize JSON for ${path}`);
|
|
923
|
+
await atomicWriteFile(path, `${serialized}
|
|
924
|
+
`);
|
|
925
|
+
}
|
|
926
|
+
async function atomicPrettyJson(path, value) {
|
|
927
|
+
await atomicWriteFile(path, `${JSON.stringify(value, null, 2)}
|
|
928
|
+
`);
|
|
929
|
+
}
|
|
930
|
+
async function json(path) {
|
|
931
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
932
|
+
}
|
|
933
|
+
async function git(cwd, args, extraEnv = {}) {
|
|
934
|
+
const { stdout } = await execute("git", ["-c", "core.hooksPath=/dev/null", "-c", "commit.gpgSign=false", ...args], { cwd, env: { ...process.env, ...extraEnv }, encoding: "utf8", maxBuffer: 10 * 1024 * 1024 });
|
|
935
|
+
return stdout;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// src/paths.ts
|
|
939
|
+
import { createHash } from "node:crypto";
|
|
940
|
+
import { realpathSync } from "node:fs";
|
|
941
|
+
import { homedir } from "node:os";
|
|
942
|
+
import { basename, join, resolve } from "node:path";
|
|
943
|
+
function safePart(value) {
|
|
944
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
945
|
+
}
|
|
946
|
+
function canonicalPath(path) {
|
|
947
|
+
const absolute = resolve(path);
|
|
948
|
+
try {
|
|
949
|
+
return realpathSync(absolute);
|
|
950
|
+
} catch {
|
|
951
|
+
return absolute;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
function sameFilesystemPath(left, right) {
|
|
955
|
+
return canonicalPath(left) === canonicalPath(right);
|
|
956
|
+
}
|
|
957
|
+
function workflowProjectSettingsPath(cwd) {
|
|
958
|
+
return join(cwd, ".pi", "pi-extensible-workflows", "settings.json");
|
|
959
|
+
}
|
|
960
|
+
function projectStorageKey(cwd) {
|
|
961
|
+
const exact = resolve(cwd);
|
|
962
|
+
const slug = safePart(basename(exact)) || "root";
|
|
963
|
+
return `${slug}-${createHash("sha256").update(exact).digest("hex").slice(0, 12)}`;
|
|
964
|
+
}
|
|
965
|
+
function projectSessionsDirectory(cwd, home = homedir()) {
|
|
966
|
+
return join(home, ".pi", "workflows", "projects", projectStorageKey(cwd), "sessions");
|
|
967
|
+
}
|
|
968
|
+
function runsDirectory(cwd, sessionId, home = homedir()) {
|
|
969
|
+
return join(projectSessionsDirectory(cwd, home), safePart(sessionId), "runs");
|
|
970
|
+
}
|
|
971
|
+
function structuralPath(...names) {
|
|
972
|
+
if (names.length === 0 || names.some((name) => name.trim() === "")) throw new WorkflowError("INVALID_METADATA", "Structural paths require non-empty explicit names");
|
|
973
|
+
return names.map((name) => encodeURIComponent(name)).join("/");
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// src/session-lease.ts
|
|
977
|
+
var RUN_CREATE_TEMP = /^\.([a-zA-Z0-9._-]+)\.(\d+)\.[0-9a-f-]+\.tmp$/;
|
|
978
|
+
async function processAlive(pid, startedAt) {
|
|
979
|
+
try {
|
|
980
|
+
process.kill(pid, 0);
|
|
981
|
+
} catch (error) {
|
|
982
|
+
return !isNodeError(error, "ESRCH");
|
|
983
|
+
}
|
|
984
|
+
if (startedAt !== void 0 && process.platform === "linux") {
|
|
985
|
+
try {
|
|
986
|
+
if ((await stat(`/proc/${String(pid)}`)).ctimeMs > startedAt) return false;
|
|
987
|
+
} catch (error) {
|
|
988
|
+
if (isNodeError(error, "ENOENT")) return false;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
async function cleanupRunTemps(directory, entries) {
|
|
994
|
+
await Promise.all(entries.map(async (entry) => {
|
|
995
|
+
const match = entry.isDirectory() ? RUN_CREATE_TEMP.exec(entry.name) : void 0;
|
|
996
|
+
const pid = match?.[2] ? Number(match[2]) : void 0;
|
|
997
|
+
if (pid && !await processAlive(pid)) await rm2(join2(directory, entry.name), { recursive: true, force: true });
|
|
998
|
+
}));
|
|
999
|
+
}
|
|
1000
|
+
async function listRunIds(cwd, sessionId, home = homedir2(), cleanTemps = true) {
|
|
1001
|
+
const directory = runsDirectory(cwd, sessionId, home);
|
|
1002
|
+
try {
|
|
1003
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
1004
|
+
if (cleanTemps) await cleanupRunTemps(directory, entries);
|
|
1005
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map(({ name }) => name);
|
|
1006
|
+
} catch (error) {
|
|
1007
|
+
if (isNodeError(error, "ENOENT")) return [];
|
|
1008
|
+
throw error;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// src/trajectory.ts
|
|
1013
|
+
import { open as open2, readFile as readFile4, readdir as readdir3, stat as stat3 } from "node:fs/promises";
|
|
1014
|
+
import { homedir as homedir5 } from "node:os";
|
|
1015
|
+
import { basename as basename4, dirname as dirname4, join as join6, resolve as resolve4 } from "node:path";
|
|
1016
|
+
import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DefaultPackageManager, getAgentDir as getAgentDir2, SettingsManager, DEFAULT_MAX_BYTES } from "@earendil-works/pi-coding-agent";
|
|
1017
|
+
|
|
1018
|
+
// src/store.ts
|
|
1019
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
1020
|
+
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
1021
|
+
import { access, mkdir as mkdir2, readdir as readdir2, readFile as readFile3, realpath, rename as rename3, rm as rm3, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1022
|
+
import { homedir as homedir3 } from "node:os";
|
|
1023
|
+
import { dirname, join as join3, relative, resolve as resolve2, sep } from "node:path";
|
|
1024
|
+
import { promisify as promisify2 } from "node:util";
|
|
1025
|
+
|
|
1026
|
+
// src/budget.ts
|
|
1027
|
+
function budgetUsage(value) {
|
|
1028
|
+
return { tokens: value?.tokens ?? 0, costUsd: value?.costUsd ?? 0, durationMs: value?.durationMs ?? 0, agentLaunches: value?.agentLaunches ?? 0 };
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// src/store.ts
|
|
1032
|
+
var executeFile = promisify2(execFile2);
|
|
1033
|
+
var WORKTREE_POST_CREATE_TIMEOUT_MS = 6e4;
|
|
1034
|
+
var WORKTREE_POST_CREATE_OUTPUT_LIMIT = 4096;
|
|
1035
|
+
var SYSTEM_PROMPT_STORAGE = ".system-prompts";
|
|
1036
|
+
var SYSTEM_PROMPT_RECORDS = "records";
|
|
1037
|
+
var SYSTEM_PROMPT_BODIES = "bodies";
|
|
1038
|
+
var SYSTEM_PROMPT_SEQUENCE = "sequence";
|
|
1039
|
+
var SYSTEM_PROMPT_ARTIFACT = { version: 2, format: "append-only", storage: SYSTEM_PROMPT_STORAGE };
|
|
1040
|
+
function summaryArtifacts(directory) {
|
|
1041
|
+
return { runDirectory: directory, statePath: join3(directory, "state.json"), journalPath: join3(directory, "journal.json"), snapshotPath: join3(directory, "snapshot.json"), workflowPath: join3(directory, "workflow.js"), resultPath: join3(directory, "result.json"), summaryPath: join3(directory, "summary.json") };
|
|
1042
|
+
}
|
|
1043
|
+
function summaryFromRun(run, directory, journal, previous, fallbackCreatedAt, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1044
|
+
const createdAt = typeof previous?.createdAt === "string" ? previous.createdAt : fallbackCreatedAt;
|
|
1045
|
+
const failedAt = run.failedAt ?? run.error?.failedAt;
|
|
1046
|
+
const replayablePaths = [.../* @__PURE__ */ new Set([...run.retry?.completedPaths ?? [], ...Object.keys(journal.completed)])];
|
|
1047
|
+
const incompletePaths = [.../* @__PURE__ */ new Set([...run.retry?.incompletePaths ?? [], ...failedAt ? [failedAt] : []])];
|
|
1048
|
+
return { schemaVersion: 1, runId: run.id, sessionId: run.sessionId, workflowName: run.workflowName, state: run.state, createdAt, updatedAt: now, ...previous?.terminalAt || HARD_TERMINAL_RUN_STATES.has(run.state) ? { terminalAt: previous?.terminalAt ?? now } : {}, usage: budgetUsage(run.usage), agents: run.agents.map(({ id, name, label, state, role, attempts }) => ({ id, name, ...label ? { label } : {}, state, ...role ? { role } : {}, attempts })), ...run.error ? { error: run.error } : {}, ...failedAt ? { failedAt } : {}, replayablePaths, incompletePaths, artifacts: summaryArtifacts(directory) };
|
|
1049
|
+
}
|
|
1050
|
+
function systemPromptStoragePath(directory) {
|
|
1051
|
+
return join3(directory, SYSTEM_PROMPT_STORAGE);
|
|
1052
|
+
}
|
|
1053
|
+
function systemPromptRecordsPath(directory) {
|
|
1054
|
+
return join3(systemPromptStoragePath(directory), SYSTEM_PROMPT_RECORDS);
|
|
1055
|
+
}
|
|
1056
|
+
function systemPromptBodiesPath(directory) {
|
|
1057
|
+
return join3(systemPromptStoragePath(directory), SYSTEM_PROMPT_BODIES);
|
|
1058
|
+
}
|
|
1059
|
+
function systemPromptSequencePath(directory) {
|
|
1060
|
+
return join3(systemPromptStoragePath(directory), SYSTEM_PROMPT_SEQUENCE);
|
|
1061
|
+
}
|
|
1062
|
+
function systemPromptRecordName(sequence) {
|
|
1063
|
+
return `${String(sequence).padStart(20, "0")}.json`;
|
|
1064
|
+
}
|
|
1065
|
+
async function createSystemPromptStorage(directory, writeArtifact) {
|
|
1066
|
+
const storage = systemPromptStoragePath(directory);
|
|
1067
|
+
await mkdir2(join3(storage, SYSTEM_PROMPT_RECORDS), { recursive: true, mode: 448 });
|
|
1068
|
+
await mkdir2(join3(storage, SYSTEM_PROMPT_BODIES), { recursive: true, mode: 448 });
|
|
1069
|
+
await atomicWriteFile(systemPromptSequencePath(directory), "0\n");
|
|
1070
|
+
if (writeArtifact) await atomicJson(join3(directory, "system-prompts.json"), SYSTEM_PROMPT_ARTIFACT);
|
|
1071
|
+
}
|
|
1072
|
+
var RunStore = class _RunStore {
|
|
1073
|
+
constructor(cwd, sessionId, runId, home = homedir3()) {
|
|
1074
|
+
this.cwd = cwd;
|
|
1075
|
+
this.sessionId = sessionId;
|
|
1076
|
+
this.runId = runId;
|
|
1077
|
+
this.home = home;
|
|
1078
|
+
this.cwd = resolve2(cwd);
|
|
1079
|
+
this.directory = join3(runsDirectory(this.cwd, sessionId, home), safePart(runId));
|
|
1080
|
+
}
|
|
1081
|
+
cwd;
|
|
1082
|
+
sessionId;
|
|
1083
|
+
runId;
|
|
1084
|
+
home;
|
|
1085
|
+
directory;
|
|
1086
|
+
journalLane = new SerialLane();
|
|
1087
|
+
// ponytail: serializes one RunStore instance; cross-process run sharing remains unsupported.
|
|
1088
|
+
stateLane = new SerialLane();
|
|
1089
|
+
summaryLane = new SerialLane();
|
|
1090
|
+
worktreeLane = new SerialLane();
|
|
1091
|
+
borrowedWorktreeLane = new SerialLane();
|
|
1092
|
+
snapshotLane = new SerialLane();
|
|
1093
|
+
launchSnapshotLane = new SerialLane();
|
|
1094
|
+
// ponytail: the session lease prevents concurrent RunStore writers for one run.
|
|
1095
|
+
systemPromptLane = new SerialLane();
|
|
1096
|
+
#assertRunIdentity(run, code, message) {
|
|
1097
|
+
if (resolve2(run.cwd) !== this.cwd || run.sessionId !== this.sessionId || run.id !== this.runId) throw new WorkflowError(code, message);
|
|
1098
|
+
}
|
|
1099
|
+
async create(run, snapshot) {
|
|
1100
|
+
this.#assertRunIdentity(run, "INTERNAL_ERROR", "Run identity does not match its session-scoped store");
|
|
1101
|
+
const temporary = join3(dirname(this.directory), `.${safePart(this.runId)}.${String(process.pid)}.${randomUUID2()}.tmp`);
|
|
1102
|
+
await mkdir2(dirname(this.directory), { recursive: true, mode: 448 });
|
|
1103
|
+
await mkdir2(temporary, { mode: 448 });
|
|
1104
|
+
try {
|
|
1105
|
+
await writeFile2(join3(temporary, "workflow.js"), snapshot.script, { encoding: "utf8", mode: 384 });
|
|
1106
|
+
await atomicJson(join3(temporary, "snapshot.json"), snapshot);
|
|
1107
|
+
await atomicJson(join3(temporary, "journal.json"), { completed: {}, awaiting: {}, decisions: {} });
|
|
1108
|
+
await atomicJson(join3(temporary, "ownership.json"), []);
|
|
1109
|
+
await atomicJson(join3(temporary, "worktrees.json"), []);
|
|
1110
|
+
await atomicJson(join3(temporary, "borrowed-worktrees.json"), []);
|
|
1111
|
+
await atomicJson(join3(temporary, "state.json"), run);
|
|
1112
|
+
await createSystemPromptStorage(temporary, true);
|
|
1113
|
+
await atomicJson(join3(temporary, "summary.json"), summaryFromRun(run, this.directory, { completed: {} }, void 0, (/* @__PURE__ */ new Date()).toISOString()));
|
|
1114
|
+
await rename3(temporary, this.directory);
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
await rm3(temporary, { recursive: true, force: true });
|
|
1117
|
+
throw error;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
async refreshSummary() {
|
|
1121
|
+
const write = this.summaryLane.run(async () => {
|
|
1122
|
+
const run = decodePersistedRun(await json(join3(this.directory, "state.json")), true);
|
|
1123
|
+
const journal = decodeJournal(await json(join3(this.directory, "journal.json")));
|
|
1124
|
+
const previous = await json(join3(this.directory, "summary.json")).then(decodeSummaryProjection).catch(() => void 0);
|
|
1125
|
+
if (!run || !journal) throw new Error("Persisted run or journal is invalid");
|
|
1126
|
+
const fallbackCreatedAt = await stat2(join3(this.directory, "state.json")).then((value) => new Date(value.mtimeMs).toISOString());
|
|
1127
|
+
await atomicJson(join3(this.directory, "summary.json"), summaryFromRun(run, this.directory, journal, previous, fallbackCreatedAt));
|
|
1128
|
+
});
|
|
1129
|
+
await write;
|
|
1130
|
+
}
|
|
1131
|
+
//NOTE: summary.json is an optional derived cache (see OPTIONAL_RUN_FILES); a missed refresh self-heals because every reader (loadSummary, CLI inspector) recomputes from state/journal. Keep this best-effort so a cache hiccup never fails the primary write.
|
|
1132
|
+
refreshSummaryBestEffort() {
|
|
1133
|
+
void this.refreshSummary().catch(() => void 0);
|
|
1134
|
+
}
|
|
1135
|
+
async isComplete() {
|
|
1136
|
+
try {
|
|
1137
|
+
await Promise.all([access(join3(this.directory, "snapshot.json")), access(join3(this.directory, "journal.json")), access(join3(this.directory, "ownership.json")), access(join3(this.directory, "state.json"))]);
|
|
1138
|
+
return true;
|
|
1139
|
+
} catch {
|
|
1140
|
+
return false;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
async load() {
|
|
1144
|
+
await this.stateLane.run(async () => void 0);
|
|
1145
|
+
const rawRun = await json(join3(this.directory, "state.json"));
|
|
1146
|
+
const run = decodePersistedRun(rawRun, true);
|
|
1147
|
+
if (!run) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted run is invalid");
|
|
1148
|
+
this.#assertRunIdentity(run, "RESUME_INCOMPATIBLE", "Persisted run belongs to another cwd or Pi session");
|
|
1149
|
+
if (!object(rawRun) || !Array.isArray(rawRun.agentSessions) || Object.hasOwn(rawRun, "nativeSessions")) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted run uses an unsupported agent session format");
|
|
1150
|
+
const snapshot = decodeLaunchSnapshot(await json(join3(this.directory, "snapshot.json")));
|
|
1151
|
+
if (!snapshot) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted launch snapshot is invalid");
|
|
1152
|
+
return { run, snapshot: loadLaunchSnapshot(snapshot) };
|
|
1153
|
+
}
|
|
1154
|
+
async loadStatus() {
|
|
1155
|
+
await this.stateLane.run(async () => void 0);
|
|
1156
|
+
const run = decodePersistedRun(await json(join3(this.directory, "state.json")), true);
|
|
1157
|
+
if (!run) throw new WorkflowError("RUN_NOT_FOUND", "Persisted run is invalid");
|
|
1158
|
+
this.#assertRunIdentity(run, "RUN_NOT_FOUND", "Persisted run does not belong to this project");
|
|
1159
|
+
return run;
|
|
1160
|
+
}
|
|
1161
|
+
async loadSummary() {
|
|
1162
|
+
await this.stateLane.run(async () => void 0);
|
|
1163
|
+
await this.journalLane.run(async () => void 0);
|
|
1164
|
+
await this.summaryLane.run(async () => void 0);
|
|
1165
|
+
const run = decodePersistedRun(await json(join3(this.directory, "state.json")), true);
|
|
1166
|
+
const journal = decodeJournal(await json(join3(this.directory, "journal.json")));
|
|
1167
|
+
const previous = await json(join3(this.directory, "summary.json")).then(decodeSummaryProjection).catch(() => void 0);
|
|
1168
|
+
if (!run || !journal) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted run or journal is invalid");
|
|
1169
|
+
const [stateStat, journalStat] = await Promise.all([stat2(join3(this.directory, "state.json")), stat2(join3(this.directory, "journal.json"))]);
|
|
1170
|
+
const fallbackCreatedAt = new Date(stateStat.mtimeMs).toISOString();
|
|
1171
|
+
const previousUpdatedAt = previous?.updatedAt === void 0 ? Number.NaN : Date.parse(previous.updatedAt);
|
|
1172
|
+
const updatedAt = new Date(Math.max(stateStat.mtimeMs, journalStat.mtimeMs, Number.isNaN(previousUpdatedAt) ? 0 : previousUpdatedAt)).toISOString();
|
|
1173
|
+
return summaryFromRun(run, this.directory, journal, previous, fallbackCreatedAt, updatedAt);
|
|
1174
|
+
}
|
|
1175
|
+
async saveState(run) {
|
|
1176
|
+
const write = this.stateLane.run(async () => {
|
|
1177
|
+
this.#assertRunIdentity(run, "INTERNAL_ERROR", "Run identity does not match its session-scoped store");
|
|
1178
|
+
await atomicJson(join3(this.directory, "state.json"), run);
|
|
1179
|
+
this.refreshSummaryBestEffort();
|
|
1180
|
+
});
|
|
1181
|
+
await write;
|
|
1182
|
+
}
|
|
1183
|
+
async updateState(update) {
|
|
1184
|
+
const write = this.stateLane.run(async () => {
|
|
1185
|
+
const current = decodePersistedRun(await json(join3(this.directory, "state.json")));
|
|
1186
|
+
if (!current) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted run is invalid");
|
|
1187
|
+
this.#assertRunIdentity(current, "RESUME_INCOMPATIBLE", "Persisted run belongs to another cwd or Pi session");
|
|
1188
|
+
const result = await update(current);
|
|
1189
|
+
this.#assertRunIdentity(result, "INTERNAL_ERROR", "Run identity does not match its session-scoped store");
|
|
1190
|
+
await atomicJson(join3(this.directory, "state.json"), result);
|
|
1191
|
+
this.refreshSummaryBestEffort();
|
|
1192
|
+
return result;
|
|
1193
|
+
});
|
|
1194
|
+
return write;
|
|
1195
|
+
}
|
|
1196
|
+
async saveSnapshot(snapshot) {
|
|
1197
|
+
const write = this.launchSnapshotLane.run(() => atomicJson(join3(this.directory, "snapshot.json"), snapshot));
|
|
1198
|
+
await write;
|
|
1199
|
+
}
|
|
1200
|
+
async appendEvent(event) {
|
|
1201
|
+
await this.updateState((run) => ({ ...run, events: [...run.events ?? [], ...event.type !== "log" && run.events?.some((current) => current.type === event.type && current.message === event.message) ? [] : [event]] }));
|
|
1202
|
+
}
|
|
1203
|
+
async saveOwnership(nodes) {
|
|
1204
|
+
await atomicJson(join3(this.directory, "ownership.json"), nodes);
|
|
1205
|
+
}
|
|
1206
|
+
async loadOwnership() {
|
|
1207
|
+
const nodes = decodeOwnershipRecords(await json(join3(this.directory, "ownership.json")));
|
|
1208
|
+
if (!nodes) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted ownership records are invalid");
|
|
1209
|
+
return nodes;
|
|
1210
|
+
}
|
|
1211
|
+
systemPromptPath() {
|
|
1212
|
+
return join3(this.directory, "system-prompts.json");
|
|
1213
|
+
}
|
|
1214
|
+
async readSystemPromptArtifact() {
|
|
1215
|
+
try {
|
|
1216
|
+
const artifact = decodeSystemPromptArtifact(await json(this.systemPromptPath()));
|
|
1217
|
+
if (!artifact) throw new Error("Persisted system prompts are invalid");
|
|
1218
|
+
return artifact;
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
if (isNodeError(error, "ENOENT")) return void 0;
|
|
1221
|
+
throw error;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
async appendSystemPromptV2(entry) {
|
|
1225
|
+
const sha256 = createHash2("sha256").update(entry.prompt).digest("hex");
|
|
1226
|
+
const bodyPath = join3(systemPromptBodiesPath(this.directory), sha256);
|
|
1227
|
+
try {
|
|
1228
|
+
await access(bodyPath);
|
|
1229
|
+
} catch (error) {
|
|
1230
|
+
if (!isNodeError(error, "ENOENT")) throw error;
|
|
1231
|
+
await atomicWriteFile(bodyPath, entry.prompt);
|
|
1232
|
+
}
|
|
1233
|
+
const previous = Number(await readFile3(systemPromptSequencePath(this.directory), "utf8"));
|
|
1234
|
+
if (!Number.isSafeInteger(previous) || previous < 0 || previous >= Number.MAX_SAFE_INTEGER) throw new Error("Persisted system-prompt sequence is invalid");
|
|
1235
|
+
const sequence = previous + 1;
|
|
1236
|
+
await atomicWriteFile(systemPromptSequencePath(this.directory), `${String(sequence)}
|
|
1237
|
+
`);
|
|
1238
|
+
await atomicJson(join3(systemPromptRecordsPath(this.directory), systemPromptRecordName(sequence)), { sessionId: entry.sessionId, attempt: entry.attempt, turn: entry.turn, sha256 });
|
|
1239
|
+
}
|
|
1240
|
+
async migrateSystemPrompts(legacy) {
|
|
1241
|
+
await rm3(systemPromptStoragePath(this.directory), { recursive: true, force: true });
|
|
1242
|
+
await createSystemPromptStorage(this.directory, false);
|
|
1243
|
+
for (const entry of legacy.entries ?? []) await this.appendSystemPromptV2(entry);
|
|
1244
|
+
await atomicJson(this.systemPromptPath(), SYSTEM_PROMPT_ARTIFACT);
|
|
1245
|
+
}
|
|
1246
|
+
async prepareSystemPromptStorage() {
|
|
1247
|
+
const artifact = await this.readSystemPromptArtifact();
|
|
1248
|
+
if (artifact === void 0) {
|
|
1249
|
+
await rm3(systemPromptStoragePath(this.directory), { recursive: true, force: true });
|
|
1250
|
+
await createSystemPromptStorage(this.directory, true);
|
|
1251
|
+
} else if (artifact.version === 1) {
|
|
1252
|
+
if (!Array.isArray(artifact.entries)) throw new Error("Persisted system prompts are invalid");
|
|
1253
|
+
await this.migrateSystemPrompts(artifact);
|
|
1254
|
+
} else if (artifact.version !== SYSTEM_PROMPT_ARTIFACT.version || artifact.format !== SYSTEM_PROMPT_ARTIFACT.format || artifact.storage !== SYSTEM_PROMPT_ARTIFACT.storage) throw new Error("Persisted system prompts are invalid");
|
|
1255
|
+
}
|
|
1256
|
+
async readSystemPromptsV2() {
|
|
1257
|
+
const artifact = await this.readSystemPromptArtifact();
|
|
1258
|
+
if (!artifact || artifact.version !== SYSTEM_PROMPT_ARTIFACT.version || artifact.format !== SYSTEM_PROMPT_ARTIFACT.format || artifact.storage !== SYSTEM_PROMPT_ARTIFACT.storage) throw new Error("Persisted system prompts are invalid");
|
|
1259
|
+
const sequence = Number(await readFile3(systemPromptSequencePath(this.directory), "utf8"));
|
|
1260
|
+
if (!Number.isSafeInteger(sequence) || sequence < 0) throw new Error("Persisted system-prompt sequence is invalid");
|
|
1261
|
+
const names = (await readdir2(systemPromptRecordsPath(this.directory))).filter((name) => !name.endsWith(".tmp")).sort();
|
|
1262
|
+
const entries = [];
|
|
1263
|
+
let highest = 0;
|
|
1264
|
+
for (const name of names) {
|
|
1265
|
+
const match = /^(\d{20})\.json$/.exec(name);
|
|
1266
|
+
if (!match) throw new Error("Persisted system-prompt records are invalid");
|
|
1267
|
+
const recordSequence = Number(match[1]);
|
|
1268
|
+
if (!Number.isSafeInteger(recordSequence) || recordSequence < 1) throw new Error("Persisted system-prompt records are invalid");
|
|
1269
|
+
highest = Math.max(highest, recordSequence);
|
|
1270
|
+
const record = await json(join3(systemPromptRecordsPath(this.directory), name));
|
|
1271
|
+
if (!object(record)) throw new Error("Persisted system-prompt record is invalid");
|
|
1272
|
+
const sessionId = record.sessionId;
|
|
1273
|
+
const attempt = record.attempt;
|
|
1274
|
+
const turn = record.turn;
|
|
1275
|
+
const sha256 = record.sha256;
|
|
1276
|
+
if (typeof sessionId !== "string" || !sessionId || !positiveInteger(attempt) || !positiveInteger(turn) || typeof sha256 !== "string" || !/^[0-9a-f]{64}$/.test(sha256)) throw new Error("Persisted system-prompt record is invalid");
|
|
1277
|
+
const prompt = await readFile3(join3(systemPromptBodiesPath(this.directory), sha256), "utf8");
|
|
1278
|
+
if (createHash2("sha256").update(prompt).digest("hex") !== sha256) throw new Error("Persisted system-prompt body is invalid");
|
|
1279
|
+
entries.push({ sessionId, attempt, turn, sha256, prompt });
|
|
1280
|
+
}
|
|
1281
|
+
if (sequence < highest) throw new Error("Persisted system-prompt sequence is invalid");
|
|
1282
|
+
return entries;
|
|
1283
|
+
}
|
|
1284
|
+
async recordSystemPrompt(entry) {
|
|
1285
|
+
const write = this.systemPromptLane.run(async () => {
|
|
1286
|
+
await this.prepareSystemPromptStorage();
|
|
1287
|
+
await this.appendSystemPromptV2(entry);
|
|
1288
|
+
});
|
|
1289
|
+
await write;
|
|
1290
|
+
}
|
|
1291
|
+
async systemPrompts() {
|
|
1292
|
+
await this.systemPromptLane.run(async () => void 0);
|
|
1293
|
+
const artifact = await this.readSystemPromptArtifact();
|
|
1294
|
+
if (artifact === void 0) return [];
|
|
1295
|
+
if (artifact.version === 1) return artifact.entries ?? [];
|
|
1296
|
+
if (artifact.version === SYSTEM_PROMPT_ARTIFACT.version) return this.readSystemPromptsV2();
|
|
1297
|
+
throw new Error("Persisted system prompts are invalid");
|
|
1298
|
+
}
|
|
1299
|
+
async #decodeJournal() {
|
|
1300
|
+
const journal = decodeJournal(await json(join3(this.directory, "journal.json")));
|
|
1301
|
+
if (!journal) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted journal is invalid");
|
|
1302
|
+
return journal;
|
|
1303
|
+
}
|
|
1304
|
+
/** Reads the journal after every queued journal write has settled. */
|
|
1305
|
+
async #readJournal() {
|
|
1306
|
+
await this.journalLane.run(async () => void 0);
|
|
1307
|
+
return this.#decodeJournal();
|
|
1308
|
+
}
|
|
1309
|
+
async updateJournal(update) {
|
|
1310
|
+
const write = this.journalLane.run(async () => {
|
|
1311
|
+
const journal = await this.#decodeJournal();
|
|
1312
|
+
journal.awaiting ??= {};
|
|
1313
|
+
const result = await update(journal);
|
|
1314
|
+
await atomicJson(join3(this.directory, "journal.json"), journal);
|
|
1315
|
+
this.refreshSummaryBestEffort();
|
|
1316
|
+
return result;
|
|
1317
|
+
});
|
|
1318
|
+
return write;
|
|
1319
|
+
}
|
|
1320
|
+
async complete(path, value) {
|
|
1321
|
+
await this.updateJournal((journal) => {
|
|
1322
|
+
if (journal.completed[path]) throw new WorkflowError("DUPLICATE_NAME", `Completed structural path already exists: ${path}`);
|
|
1323
|
+
journal.completed[path] = { path, value };
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
async replay(path) {
|
|
1327
|
+
const [operation] = await this.replayableOperationsFrom(/* @__PURE__ */ new Set(), path);
|
|
1328
|
+
return operation;
|
|
1329
|
+
}
|
|
1330
|
+
async replayableOperations() {
|
|
1331
|
+
return this.replayableOperationsFrom(/* @__PURE__ */ new Set());
|
|
1332
|
+
}
|
|
1333
|
+
/** Every journaled agent operation of the retry lineage, mapped to its session file when one was recorded. */
|
|
1334
|
+
async agentSessionFiles() {
|
|
1335
|
+
return this.agentSessionFilesFrom(/* @__PURE__ */ new Set());
|
|
1336
|
+
}
|
|
1337
|
+
async agentSessionFilesFrom(seen) {
|
|
1338
|
+
if (seen.has(this.runId)) throw new WorkflowError("RESUME_INCOMPATIBLE", "Retry provenance contains a cycle");
|
|
1339
|
+
const nextSeen = new Set(seen);
|
|
1340
|
+
nextSeen.add(this.runId);
|
|
1341
|
+
const loaded = await this.load();
|
|
1342
|
+
const files = /* @__PURE__ */ new Map();
|
|
1343
|
+
if (loaded.run.retry?.sourceRunId) {
|
|
1344
|
+
const source = await this.sourceRun(loaded.run.retry.sourceRunId);
|
|
1345
|
+
for (const [path, file] of await source.agentSessionFilesFrom(nextSeen)) files.set(path, file);
|
|
1346
|
+
}
|
|
1347
|
+
const journal = await this.#readJournal();
|
|
1348
|
+
for (const agent of loaded.run.agents) {
|
|
1349
|
+
if (!agent.resultPath || !journal.completed[agent.resultPath]) continue;
|
|
1350
|
+
const locator = agent.attemptDetails?.filter((detail) => !detail.error).at(-1)?.session?.locator;
|
|
1351
|
+
files.set(agent.resultPath, object(locator) && typeof locator.sessionFile === "string" ? locator.sessionFile : void 0);
|
|
1352
|
+
}
|
|
1353
|
+
return files;
|
|
1354
|
+
}
|
|
1355
|
+
/** Source lineage first, own journal last (it overrides); `path` narrows to one operation without cloning the rest. */
|
|
1356
|
+
async replayableOperationsFrom(seen, path) {
|
|
1357
|
+
if (seen.has(this.runId)) throw new WorkflowError("RESUME_INCOMPATIBLE", "Retry provenance contains a cycle");
|
|
1358
|
+
const nextSeen = new Set(seen);
|
|
1359
|
+
nextSeen.add(this.runId);
|
|
1360
|
+
const loaded = await this.load();
|
|
1361
|
+
const operations = /* @__PURE__ */ new Map();
|
|
1362
|
+
if (loaded.run.retry?.sourceRunId) {
|
|
1363
|
+
const source = await this.sourceRun(loaded.run.retry.sourceRunId);
|
|
1364
|
+
for (const operation of await source.replayableOperationsFrom(nextSeen, path)) operations.set(operation.path, operation);
|
|
1365
|
+
}
|
|
1366
|
+
const journal = await this.#readJournal();
|
|
1367
|
+
for (const operation of Object.values(journal.completed)) if (path === void 0 || operation.path === path) operations.set(operation.path, operation);
|
|
1368
|
+
return [...operations.values()].map((operation) => structuredClone(operation));
|
|
1369
|
+
}
|
|
1370
|
+
async awaitCheckpoint(checkpoint) {
|
|
1371
|
+
const replayed = await this.replay(checkpoint.path);
|
|
1372
|
+
if (replayed) {
|
|
1373
|
+
const result = decodeBooleanCheckpointResult(replayed.value);
|
|
1374
|
+
if (result === void 0) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted checkpoint result is invalid");
|
|
1375
|
+
return result;
|
|
1376
|
+
}
|
|
1377
|
+
return this.updateJournal((journal) => {
|
|
1378
|
+
const completed = journal.completed[checkpoint.path];
|
|
1379
|
+
if (completed) {
|
|
1380
|
+
const result = decodeBooleanCheckpointResult(completed.value);
|
|
1381
|
+
if (result === void 0) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted checkpoint result is invalid");
|
|
1382
|
+
return result;
|
|
1383
|
+
}
|
|
1384
|
+
const awaiting = journal.awaiting ?? (journal.awaiting = {});
|
|
1385
|
+
awaiting[checkpoint.path] = checkpoint;
|
|
1386
|
+
return void 0;
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
async awaitingCheckpoints() {
|
|
1390
|
+
return Object.values((await this.#readJournal()).awaiting ?? {});
|
|
1391
|
+
}
|
|
1392
|
+
async requestWorkflowDecision(request) {
|
|
1393
|
+
await this.updateJournal((journal) => {
|
|
1394
|
+
journal.decisions ??= {};
|
|
1395
|
+
journal.decisions[request.proposalId] = request;
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
async pendingWorkflowDecisions() {
|
|
1399
|
+
return Object.values((await this.#readJournal()).decisions ?? {});
|
|
1400
|
+
}
|
|
1401
|
+
async answerWorkflowDecision(proposalId, approved) {
|
|
1402
|
+
return this.updateJournal((journal) => {
|
|
1403
|
+
const request = journal.decisions?.[proposalId];
|
|
1404
|
+
if (!request) return void 0;
|
|
1405
|
+
journal.completed[`decision/${proposalId}`] = { path: `decision/${proposalId}`, value: approved };
|
|
1406
|
+
delete journal.decisions?.[proposalId];
|
|
1407
|
+
return request;
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
async answerCheckpoint(name, approved) {
|
|
1411
|
+
return this.updateJournal((journal) => {
|
|
1412
|
+
const checkpoint = Object.values(journal.awaiting ?? {}).find((item) => item.name === name);
|
|
1413
|
+
if (!checkpoint || journal.completed[checkpoint.path]) return void 0;
|
|
1414
|
+
journal.completed[checkpoint.path] = { path: checkpoint.path, value: approved };
|
|
1415
|
+
journal.awaiting = Object.fromEntries(Object.entries(journal.awaiting ?? {}).filter(([path]) => path !== checkpoint.path));
|
|
1416
|
+
return checkpoint;
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
#worktreeKey(owner) {
|
|
1420
|
+
return createHash2("sha256").update(`${this.sessionId}\0${this.runId}\0${owner}`).digest("hex").slice(0, 16);
|
|
1421
|
+
}
|
|
1422
|
+
expectedWorktree(owner) {
|
|
1423
|
+
const key = this.#worktreeKey(owner);
|
|
1424
|
+
return { path: join3(this.directory, "worktrees", key), branch: `pi-extensible-workflows/${safePart(this.runId)}/${key}` };
|
|
1425
|
+
}
|
|
1426
|
+
markerPath(owner) {
|
|
1427
|
+
return join3(this.directory, `worktree-${this.#worktreeKey(owner)}.creating`);
|
|
1428
|
+
}
|
|
1429
|
+
namedWorktreeOwner(name) {
|
|
1430
|
+
if (!name.trim()) throw new WorkflowError("WORKTREE_FAILED", "Named worktree names must be non-empty");
|
|
1431
|
+
return structuralPath("worktree", "named", name.trim());
|
|
1432
|
+
}
|
|
1433
|
+
worktreeName(owner) {
|
|
1434
|
+
const prefix = `${structuralPath("worktree", "named")}/`;
|
|
1435
|
+
if (!owner.startsWith(prefix)) return void 0;
|
|
1436
|
+
const encoded = owner.slice(prefix.length);
|
|
1437
|
+
if (!encoded || encoded.includes("/")) return void 0;
|
|
1438
|
+
try {
|
|
1439
|
+
const name = decodeURIComponent(encoded);
|
|
1440
|
+
return name.trim() ? name : void 0;
|
|
1441
|
+
} catch {
|
|
1442
|
+
return void 0;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
async runWorktreePostCreateCommand(command, cwd, source) {
|
|
1446
|
+
const executable = command[0];
|
|
1447
|
+
if (!executable) return;
|
|
1448
|
+
const display = command.map((argument) => JSON.stringify(argument)).join(" ");
|
|
1449
|
+
try {
|
|
1450
|
+
await executeFile(executable, [...command.slice(1)], { cwd, env: process.env, encoding: "utf8", timeout: WORKTREE_POST_CREATE_TIMEOUT_MS, maxBuffer: 1024 * 1024 });
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
const record = typeof error === "object" && error !== null ? error : {};
|
|
1453
|
+
const code = record.code;
|
|
1454
|
+
const signal = typeof record.signal === "string" ? record.signal : void 0;
|
|
1455
|
+
const stderr = typeof record.stderr === "string" ? record.stderr.trim() : "";
|
|
1456
|
+
const stdout = typeof record.stdout === "string" ? record.stdout.trim() : "";
|
|
1457
|
+
const output = [stderr, stdout].filter(Boolean).join("\n").slice(0, WORKTREE_POST_CREATE_OUTPUT_LIMIT);
|
|
1458
|
+
const provenance = source !== void 0 && sameFilesystemPath(source, workflowProjectSettingsPath(this.cwd)) ? "trusted project settings" : "global settings";
|
|
1459
|
+
let category;
|
|
1460
|
+
if (code === "ENOENT") category = `executable ${JSON.stringify(executable)} was not found`;
|
|
1461
|
+
else if (code === "ETIMEDOUT" || record.killed === true && signal === "SIGTERM") category = `timed out after ${String(WORKTREE_POST_CREATE_TIMEOUT_MS / 1e3)} seconds`;
|
|
1462
|
+
else if (typeof code === "number") category = `exited with status ${String(code)}`;
|
|
1463
|
+
else if (signal) category = `was terminated by ${signal}`;
|
|
1464
|
+
else category = `could not be started${typeof code === "string" ? ` (${code})` : ""}`;
|
|
1465
|
+
throw new Error(`worktree post-create command ${display} from ${provenance} failed in ${cwd}: ${category}${output ? `; output: ${output}` : ""}`, { cause: error });
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
structuralWorktree(owner, record) {
|
|
1469
|
+
const expected = this.expectedWorktree(owner);
|
|
1470
|
+
const relativePath = relative(this.directory, record.path);
|
|
1471
|
+
const relativeCwd = relative(record.path, record.cwd);
|
|
1472
|
+
if (record.owner !== owner || resolve2(record.path) !== expected.path || record.branch !== expected.branch || relativePath === ".." || relativePath.startsWith(`..${sep}`) || relativeCwd === ".." || relativeCwd.startsWith(`..${sep}`)) throw new Error(`Invalid worktree record for ${owner}`);
|
|
1473
|
+
return record;
|
|
1474
|
+
}
|
|
1475
|
+
async #loadWorktreeRecords(missingOk = true) {
|
|
1476
|
+
const rawRecords = await json(join3(this.directory, "worktrees.json")).catch((error) => {
|
|
1477
|
+
if (missingOk && isNodeError(error, "ENOENT")) return [];
|
|
1478
|
+
throw error;
|
|
1479
|
+
});
|
|
1480
|
+
const records = decodeWorktreeReferences(rawRecords);
|
|
1481
|
+
if (!records) throw new WorkflowError("WORKTREE_FAILED", "Worktree records are invalid");
|
|
1482
|
+
return records;
|
|
1483
|
+
}
|
|
1484
|
+
async borrowedWorktreeRecords(wait = true) {
|
|
1485
|
+
if (wait) await this.borrowedWorktreeLane.run(async () => void 0);
|
|
1486
|
+
const rawRecords = await json(join3(this.directory, "borrowed-worktrees.json")).catch((error) => {
|
|
1487
|
+
if (isNodeError(error, "ENOENT")) return [];
|
|
1488
|
+
throw error;
|
|
1489
|
+
});
|
|
1490
|
+
const records = decodeBorrowedWorktreeBindings(rawRecords);
|
|
1491
|
+
if (!records) throw new WorkflowError("WORKTREE_FAILED", "Borrowed worktree bindings are invalid");
|
|
1492
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1493
|
+
return records.map((candidate) => {
|
|
1494
|
+
if (!candidate.name.trim() || candidate.name !== candidate.name.trim() || !candidate.sourceRunId || candidate.owner !== this.namedWorktreeOwner(candidate.name)) throw new WorkflowError("WORKTREE_FAILED", "Borrowed worktree binding is invalid");
|
|
1495
|
+
if (seen.has(candidate.name)) throw new WorkflowError("WORKTREE_FAILED", `Duplicate borrowed worktree binding for ${candidate.name}`);
|
|
1496
|
+
seen.add(candidate.name);
|
|
1497
|
+
return candidate;
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
async borrowedWorktrees() {
|
|
1501
|
+
return this.borrowedWorktreeRecords();
|
|
1502
|
+
}
|
|
1503
|
+
async borrowedWorktree(name) {
|
|
1504
|
+
return (await this.borrowedWorktreeRecords()).find((binding) => binding.name === name);
|
|
1505
|
+
}
|
|
1506
|
+
async sourceRun(sourceRunId) {
|
|
1507
|
+
if (!sourceRunId || sourceRunId === this.runId) throw new WorkflowError("WORKTREE_FAILED", "Borrowed worktree source run is invalid");
|
|
1508
|
+
const source = new _RunStore(this.cwd, this.sessionId, sourceRunId, this.home);
|
|
1509
|
+
try {
|
|
1510
|
+
const loaded = await source.load();
|
|
1511
|
+
if (!HARD_TERMINAL_RUN_STATES.has(loaded.run.state)) throw new Error(`Source run ${sourceRunId} is not terminal`);
|
|
1512
|
+
return source;
|
|
1513
|
+
} catch (error) {
|
|
1514
|
+
throw coerceWorkflowError("WORKTREE_FAILED", error);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
async validateParentRun(parentRunId) {
|
|
1518
|
+
await this.sourceRun(parentRunId);
|
|
1519
|
+
}
|
|
1520
|
+
async validateRetrySource() {
|
|
1521
|
+
const validate = async (current, seen) => {
|
|
1522
|
+
if (seen.has(current.runId)) throw new WorkflowError("RESUME_INCOMPATIBLE", "Retry provenance contains a cycle");
|
|
1523
|
+
const nextSeen = new Set(seen);
|
|
1524
|
+
nextSeen.add(current.runId);
|
|
1525
|
+
const loaded = await current.load();
|
|
1526
|
+
const retry = loaded.run.retry;
|
|
1527
|
+
if (!retry) return;
|
|
1528
|
+
if (!retry.sourceRunId || retry.sourceRunId === current.runId || !retry.lineageRootRunId) throw new WorkflowError("RESUME_INCOMPATIBLE", "Retry provenance is incomplete");
|
|
1529
|
+
const source = await current.sourceRun(retry.sourceRunId);
|
|
1530
|
+
const sourceRun = (await source.load()).run;
|
|
1531
|
+
if (loaded.run.parentRunId !== retry.sourceRunId) throw new WorkflowError("RESUME_INCOMPATIBLE", "Retry parent run does not match its source run");
|
|
1532
|
+
if (sourceRun.state !== "failed") throw new WorkflowError("RESUME_INCOMPATIBLE", `Retry source run ${retry.sourceRunId} is not failed`);
|
|
1533
|
+
const expectedLineageRoot = sourceRun.retry?.lineageRootRunId ?? sourceRun.id;
|
|
1534
|
+
if (retry.lineageRootRunId !== expectedLineageRoot) throw new WorkflowError("RESUME_INCOMPATIBLE", "Retry lineage root does not match its source run");
|
|
1535
|
+
await validate(source, nextSeen);
|
|
1536
|
+
};
|
|
1537
|
+
try {
|
|
1538
|
+
await validate(this, /* @__PURE__ */ new Set());
|
|
1539
|
+
} catch (error) {
|
|
1540
|
+
throw coerceWorkflowError("RESUME_INCOMPATIBLE", error);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
async ownedWorktree(owner, cwd) {
|
|
1544
|
+
const records = await this.#loadWorktreeRecords(false);
|
|
1545
|
+
const matches = records.filter((candidate) => candidate.owner === owner);
|
|
1546
|
+
if (matches.length !== 1) throw new Error(`Missing or duplicate worktree record for ${owner}`);
|
|
1547
|
+
const record = matches[0];
|
|
1548
|
+
if (!record) throw new Error(`Missing or duplicate worktree record for ${owner}`);
|
|
1549
|
+
const validated = this.structuralWorktree(owner, record);
|
|
1550
|
+
if (cwd !== void 0 && resolve2(cwd) !== resolve2(validated.cwd)) throw new Error(`Invalid worktree record for ${owner}`);
|
|
1551
|
+
await access(validated.cwd);
|
|
1552
|
+
return validated;
|
|
1553
|
+
}
|
|
1554
|
+
async resolveBorrowedWorktree(binding, seen) {
|
|
1555
|
+
try {
|
|
1556
|
+
const source = await this.sourceRun(binding.sourceRunId);
|
|
1557
|
+
const resolved = await source.findNamedWorktree(binding.name, seen);
|
|
1558
|
+
if (!resolved) throw new Error(`Missing named worktree ${binding.name} in source run ${binding.sourceRunId}`);
|
|
1559
|
+
if (resolved.owner !== binding.owner) throw new Error(`Borrowed worktree binding does not match source owner for ${binding.name}`);
|
|
1560
|
+
return resolved;
|
|
1561
|
+
} catch (error) {
|
|
1562
|
+
throw coerceWorkflowError("WORKTREE_FAILED", error);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
async findNamedWorktree(name, seen = /* @__PURE__ */ new Set()) {
|
|
1566
|
+
const owner = this.namedWorktreeOwner(name);
|
|
1567
|
+
if (seen.has(this.runId)) throw new WorkflowError("WORKTREE_FAILED", "Borrowed worktree bindings contain a cycle");
|
|
1568
|
+
const nextSeen = new Set(seen);
|
|
1569
|
+
nextSeen.add(this.runId);
|
|
1570
|
+
const binding = await this.borrowedWorktree(name);
|
|
1571
|
+
if (binding) {
|
|
1572
|
+
const loaded = await this.load();
|
|
1573
|
+
if (loaded.run.parentRunId === void 0) throw new WorkflowError("WORKTREE_FAILED", `Borrowed worktree ${name} has no parent run`);
|
|
1574
|
+
const parent = await this.sourceRun(loaded.run.parentRunId);
|
|
1575
|
+
const resolved = await parent.findNamedWorktree(name, nextSeen);
|
|
1576
|
+
if (!resolved || resolved.sourceRunId !== binding.sourceRunId || resolved.owner !== binding.owner) throw new WorkflowError("WORKTREE_FAILED", `Borrowed worktree binding for ${name} is not inherited from its parent run`);
|
|
1577
|
+
return resolved;
|
|
1578
|
+
}
|
|
1579
|
+
const records = await this.#loadWorktreeRecords(false);
|
|
1580
|
+
const matches = records.filter((candidate) => candidate.owner === owner);
|
|
1581
|
+
if (matches.length === 0) {
|
|
1582
|
+
const loaded = await this.load();
|
|
1583
|
+
if (loaded.run.parentRunId === void 0) return void 0;
|
|
1584
|
+
const parent = await this.sourceRun(loaded.run.parentRunId);
|
|
1585
|
+
return parent.findNamedWorktree(name, nextSeen);
|
|
1586
|
+
}
|
|
1587
|
+
try {
|
|
1588
|
+
const reference = await this.ownedWorktree(owner);
|
|
1589
|
+
return { reference, sourceRunId: this.runId, owner };
|
|
1590
|
+
} catch (error) {
|
|
1591
|
+
throw coerceWorkflowError("WORKTREE_FAILED", error);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
async resolveNamedWorktree(name, seen = /* @__PURE__ */ new Set()) {
|
|
1595
|
+
const resolved = await this.findNamedWorktree(name, seen);
|
|
1596
|
+
if (!resolved) throw new WorkflowError("WORKTREE_FAILED", `Missing named worktree ${name}`);
|
|
1597
|
+
return resolved;
|
|
1598
|
+
}
|
|
1599
|
+
async validateDeletionWorktrees() {
|
|
1600
|
+
try {
|
|
1601
|
+
const records = await this.#loadWorktreeRecords(false);
|
|
1602
|
+
const owners = /* @__PURE__ */ new Set();
|
|
1603
|
+
const paths = /* @__PURE__ */ new Set();
|
|
1604
|
+
for (const record of records) {
|
|
1605
|
+
const owner = record.owner;
|
|
1606
|
+
if (owners.has(owner)) throw new Error(`Duplicate worktree record for ${owner}`);
|
|
1607
|
+
owners.add(owner);
|
|
1608
|
+
const reference = this.structuralWorktree(owner, record);
|
|
1609
|
+
paths.add(resolve2(reference.path));
|
|
1610
|
+
}
|
|
1611
|
+
const entries = await readdir2(join3(this.directory, "worktrees"), { withFileTypes: true }).catch((error) => {
|
|
1612
|
+
if (isNodeError(error, "ENOENT")) return [];
|
|
1613
|
+
throw error;
|
|
1614
|
+
});
|
|
1615
|
+
for (const entry of entries) if (!entry.isDirectory() || entry.isSymbolicLink() || !paths.has(resolve2(join3(this.directory, "worktrees", entry.name)))) throw new Error(`Unrecorded worktree artifact: ${join3(this.directory, "worktrees", entry.name)}`);
|
|
1616
|
+
} catch (error) {
|
|
1617
|
+
throw coerceWorkflowError("WORKTREE_FAILED", error);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
async validateBorrowedWorktrees() {
|
|
1621
|
+
try {
|
|
1622
|
+
const loaded = await this.load();
|
|
1623
|
+
if (loaded.run.parentRunId !== void 0) await this.validateParentRun(loaded.run.parentRunId);
|
|
1624
|
+
for (const binding of await this.borrowedWorktreeRecords()) await this.resolveBorrowedWorktree(binding, /* @__PURE__ */ new Set([this.runId]));
|
|
1625
|
+
} catch (error) {
|
|
1626
|
+
throw coerceWorkflowError("WORKTREE_FAILED", error);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
async validateNamedWorktrees() {
|
|
1630
|
+
try {
|
|
1631
|
+
const records = await this.#loadWorktreeRecords(false);
|
|
1632
|
+
for (const record of records) {
|
|
1633
|
+
const owner = record.owner;
|
|
1634
|
+
if (this.worktreeName(owner)) await this.validateWorktree(owner);
|
|
1635
|
+
}
|
|
1636
|
+
} catch (error) {
|
|
1637
|
+
throw coerceWorkflowError("WORKTREE_FAILED", error);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
async ownsWorktree(owner) {
|
|
1641
|
+
const records = await this.#loadWorktreeRecords(false);
|
|
1642
|
+
return records.filter((candidate) => candidate.owner === owner).length === 1;
|
|
1643
|
+
}
|
|
1644
|
+
async cleanupMarker(markerPath) {
|
|
1645
|
+
let marker;
|
|
1646
|
+
try {
|
|
1647
|
+
const parsed = await json(markerPath);
|
|
1648
|
+
if (!object(parsed)) return;
|
|
1649
|
+
marker = parsed;
|
|
1650
|
+
} catch {
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
if (typeof marker.owner !== "string" || typeof marker.base !== "string") return;
|
|
1654
|
+
const expected = this.expectedWorktree(marker.owner);
|
|
1655
|
+
if (marker.path !== expected.path || marker.branch !== expected.branch) return;
|
|
1656
|
+
const root = await git(this.cwd, ["rev-parse", "--show-toplevel"]).then((value) => value.trim()).catch(() => "");
|
|
1657
|
+
if (!root) return;
|
|
1658
|
+
const branchBase = await git(root, ["rev-parse", "--verify", `${expected.branch}^{commit}`]).then((value) => value.trim()).catch(() => "");
|
|
1659
|
+
if (branchBase !== marker.base) return;
|
|
1660
|
+
await git(root, ["worktree", "remove", "--force", expected.path]).catch(() => void 0);
|
|
1661
|
+
await git(root, ["branch", "-D", expected.branch]).catch(() => void 0);
|
|
1662
|
+
await rm3(expected.path, { recursive: true, force: true });
|
|
1663
|
+
await rm3(markerPath, { force: true });
|
|
1664
|
+
}
|
|
1665
|
+
async cleanupOrphanWorktrees() {
|
|
1666
|
+
const entries = await readdir2(this.directory).catch(() => []);
|
|
1667
|
+
for (const entry of entries.filter((name) => name.endsWith(".creating"))) await this.cleanupMarker(join3(this.directory, entry));
|
|
1668
|
+
}
|
|
1669
|
+
async validateWorktree(owner, cwd) {
|
|
1670
|
+
try {
|
|
1671
|
+
await this.load();
|
|
1672
|
+
const name = this.worktreeName(owner);
|
|
1673
|
+
const binding = name ? await this.borrowedWorktree(name) : void 0;
|
|
1674
|
+
if (binding) {
|
|
1675
|
+
const resolved = await this.resolveBorrowedWorktree(binding, /* @__PURE__ */ new Set([this.runId]));
|
|
1676
|
+
if (cwd !== void 0 && resolve2(cwd) !== resolve2(resolved.reference.cwd)) throw new Error(`Invalid worktree record for ${owner}`);
|
|
1677
|
+
return resolved.reference;
|
|
1678
|
+
}
|
|
1679
|
+
return await this.ownedWorktree(owner, cwd);
|
|
1680
|
+
} catch (error) {
|
|
1681
|
+
throw coerceWorkflowError("WORKTREE_FAILED", error);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
async worktree(owner) {
|
|
1685
|
+
const write = this.worktreeLane.run(async () => {
|
|
1686
|
+
const loaded = await this.load();
|
|
1687
|
+
const recordsPath = resolve2(this.directory, "worktrees.json");
|
|
1688
|
+
let records = await this.#loadWorktreeRecords();
|
|
1689
|
+
const name = this.worktreeName(owner);
|
|
1690
|
+
const binding = name ? await this.borrowedWorktree(name) : void 0;
|
|
1691
|
+
if (binding) return (await this.resolveBorrowedWorktree(binding, /* @__PURE__ */ new Set([this.runId]))).reference;
|
|
1692
|
+
if (name && loaded.run.parentRunId !== void 0) {
|
|
1693
|
+
const resolved = await this.resolveNamedWorktreeFromParent(name, loaded.run.parentRunId);
|
|
1694
|
+
if (resolved) {
|
|
1695
|
+
await this.bindBorrowedWorktree({ name, sourceRunId: resolved.sourceRunId, owner: resolved.owner });
|
|
1696
|
+
return resolved.reference;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
if (name && Array.isArray(loaded.run.retry?.namedWorktrees) && loaded.run.retry.namedWorktrees.includes(name)) throw new WorkflowError("WORKTREE_FAILED", `Missing inherited named worktree ${name}`);
|
|
1700
|
+
const existing = records.find((record) => record.owner === owner);
|
|
1701
|
+
if (existing) return this.validateWorktree(owner);
|
|
1702
|
+
const { path, branch } = this.expectedWorktree(owner);
|
|
1703
|
+
const markerPath = this.markerPath(owner);
|
|
1704
|
+
let branchCreated = false;
|
|
1705
|
+
let worktreeCreated = false;
|
|
1706
|
+
let markerWritten = false;
|
|
1707
|
+
try {
|
|
1708
|
+
const root = (await git(this.cwd, ["rev-parse", "--show-toplevel"])).trim();
|
|
1709
|
+
const [canonicalRoot, canonicalCwd] = await Promise.all([realpath(root), realpath(this.cwd)]);
|
|
1710
|
+
const launchRelative = relative(canonicalRoot, canonicalCwd);
|
|
1711
|
+
if (launchRelative === ".." || launchRelative.startsWith(`..${sep}`)) throw new Error("launch cwd is outside the repository");
|
|
1712
|
+
await this.cleanupMarker(markerPath);
|
|
1713
|
+
const dirty = (await git(root, ["status", "--porcelain", "--untracked-files=normal"])).trim();
|
|
1714
|
+
if (dirty) {
|
|
1715
|
+
const dirtyPaths = dirty.split("\n").filter(Boolean).map((entry) => entry.slice(3));
|
|
1716
|
+
const shownPaths = dirtyPaths.slice(0, 10);
|
|
1717
|
+
const more = dirtyPaths.length > shownPaths.length ? `, and ${String(dirtyPaths.length - shownPaths.length)} more` : "";
|
|
1718
|
+
throw new Error(`repository ${root} has uncommitted changes at worktree creation; commit or stash them first: ${shownPaths.join(", ")}${more}`);
|
|
1719
|
+
}
|
|
1720
|
+
let commit;
|
|
1721
|
+
try {
|
|
1722
|
+
commit = (await git(root, ["rev-parse", "--verify", "HEAD^{commit}"])).trim();
|
|
1723
|
+
} catch {
|
|
1724
|
+
throw new Error("repository has no commits");
|
|
1725
|
+
}
|
|
1726
|
+
await mkdir2(dirname(path), { recursive: true, mode: 448 });
|
|
1727
|
+
const record = { owner, path, branch, cwd: join3(path, launchRelative), base: commit };
|
|
1728
|
+
await atomicJson(markerPath, { owner, path, branch, base: commit });
|
|
1729
|
+
markerWritten = true;
|
|
1730
|
+
await git(root, ["branch", branch, commit]);
|
|
1731
|
+
branchCreated = true;
|
|
1732
|
+
await git(root, ["worktree", "add", "--no-checkout", path, branch]);
|
|
1733
|
+
worktreeCreated = true;
|
|
1734
|
+
await git(path, ["checkout", "--force", branch]);
|
|
1735
|
+
if (loaded.snapshot.settings.worktreePostCreateCommand) await this.runWorktreePostCreateCommand(loaded.snapshot.settings.worktreePostCreateCommand, record.cwd, loaded.snapshot.settingsSources?.worktreePostCreateCommand);
|
|
1736
|
+
await atomicJson(recordsPath, [...records, record]);
|
|
1737
|
+
await rm3(markerPath, { force: true });
|
|
1738
|
+
return record;
|
|
1739
|
+
} catch (error) {
|
|
1740
|
+
if (worktreeCreated) await git(this.cwd, ["worktree", "remove", "--force", path]).catch(() => void 0);
|
|
1741
|
+
if (branchCreated) await git(this.cwd, ["branch", "-D", branch]).catch(() => void 0);
|
|
1742
|
+
if (markerWritten) await rm3(markerPath, { force: true });
|
|
1743
|
+
try {
|
|
1744
|
+
const persisted = await this.#loadWorktreeRecords();
|
|
1745
|
+
const match = persisted.filter((candidate2) => candidate2.owner === owner);
|
|
1746
|
+
const candidate = match.length === 1 ? match[0] : void 0;
|
|
1747
|
+
if (candidate) {
|
|
1748
|
+
this.structuralWorktree(owner, candidate);
|
|
1749
|
+
records = persisted.filter((current) => current !== candidate);
|
|
1750
|
+
await atomicJson(recordsPath, records);
|
|
1751
|
+
}
|
|
1752
|
+
} catch {
|
|
1753
|
+
}
|
|
1754
|
+
throw new WorkflowError("WORKTREE_FAILED", errorText(error));
|
|
1755
|
+
}
|
|
1756
|
+
});
|
|
1757
|
+
return write;
|
|
1758
|
+
}
|
|
1759
|
+
async resolveNamedWorktreeFromParent(name, parentRunId) {
|
|
1760
|
+
const source = await this.sourceRun(parentRunId);
|
|
1761
|
+
return source.findNamedWorktree(name, /* @__PURE__ */ new Set([this.runId]));
|
|
1762
|
+
}
|
|
1763
|
+
async bindBorrowedWorktree(binding) {
|
|
1764
|
+
const write = this.borrowedWorktreeLane.run(async () => {
|
|
1765
|
+
const records = [...await this.borrowedWorktreeRecords(false)];
|
|
1766
|
+
const existing = records.find((candidate) => candidate.name === binding.name);
|
|
1767
|
+
if (existing) {
|
|
1768
|
+
if (JSON.stringify(existing) !== JSON.stringify(binding)) throw new WorkflowError("WORKTREE_FAILED", `Borrowed worktree binding for ${binding.name} changed`);
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
records.push(binding);
|
|
1772
|
+
await atomicJson(join3(this.directory, "borrowed-worktrees.json"), records);
|
|
1773
|
+
});
|
|
1774
|
+
await write;
|
|
1775
|
+
}
|
|
1776
|
+
async snapshotWorktree(owner) {
|
|
1777
|
+
try {
|
|
1778
|
+
const write = this.snapshotLane.run(async () => {
|
|
1779
|
+
const record = await this.worktree(owner);
|
|
1780
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
1781
|
+
await git(record.path, ["add", "-A"]);
|
|
1782
|
+
if (!(await git(record.path, ["status", "--porcelain"])).trim()) break;
|
|
1783
|
+
try {
|
|
1784
|
+
await git(record.path, ["commit", "-m", "pi-extensible-workflows runtime snapshot"], gitIdentity);
|
|
1785
|
+
break;
|
|
1786
|
+
} catch (error) {
|
|
1787
|
+
if (attempt === 2) throw error;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
return (await git(record.path, ["rev-parse", "HEAD"])).trim();
|
|
1791
|
+
});
|
|
1792
|
+
return await write;
|
|
1793
|
+
} catch (error) {
|
|
1794
|
+
throw coerceWorkflowError("WORKTREE_FAILED", error);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
async worktrees() {
|
|
1798
|
+
const records = await this.#loadWorktreeRecords();
|
|
1799
|
+
const bindings = await this.borrowedWorktreeRecords();
|
|
1800
|
+
const boundOwners = new Set(bindings.map((binding) => binding.owner));
|
|
1801
|
+
const owned = await Promise.all(records.filter((record) => !boundOwners.has(record.owner)).map(async (record) => {
|
|
1802
|
+
try {
|
|
1803
|
+
return await this.validateWorktree(record.owner);
|
|
1804
|
+
} catch {
|
|
1805
|
+
return void 0;
|
|
1806
|
+
}
|
|
1807
|
+
}));
|
|
1808
|
+
const borrowed = await Promise.all(bindings.map(async (binding) => (await this.resolveBorrowedWorktree(binding, /* @__PURE__ */ new Set([this.runId]))).reference));
|
|
1809
|
+
return [...owned.filter((record) => record !== void 0), ...borrowed];
|
|
1810
|
+
}
|
|
1811
|
+
async validNamedWorktrees() {
|
|
1812
|
+
const names = /* @__PURE__ */ new Set();
|
|
1813
|
+
const records = await this.#loadWorktreeRecords();
|
|
1814
|
+
let bindings;
|
|
1815
|
+
try {
|
|
1816
|
+
bindings = await this.borrowedWorktreeRecords();
|
|
1817
|
+
} catch (error) {
|
|
1818
|
+
if (error instanceof WorkflowError && error.code === "WORKTREE_FAILED") return [];
|
|
1819
|
+
throw error;
|
|
1820
|
+
}
|
|
1821
|
+
const boundOwners = new Set(bindings.map((binding) => binding.owner));
|
|
1822
|
+
for (const record of records) {
|
|
1823
|
+
const owner = record.owner;
|
|
1824
|
+
const name = this.worktreeName(owner);
|
|
1825
|
+
if (!name || owner !== this.namedWorktreeOwner(name) || boundOwners.has(owner)) continue;
|
|
1826
|
+
try {
|
|
1827
|
+
await this.ownedWorktree(owner);
|
|
1828
|
+
names.add(name);
|
|
1829
|
+
} catch {
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
for (const binding of bindings) {
|
|
1833
|
+
try {
|
|
1834
|
+
await this.resolveBorrowedWorktree(binding, /* @__PURE__ */ new Set([this.runId]));
|
|
1835
|
+
names.add(binding.name);
|
|
1836
|
+
} catch {
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
return [...names];
|
|
1840
|
+
}
|
|
1841
|
+
async changedWorktrees() {
|
|
1842
|
+
const changed = [];
|
|
1843
|
+
for (const valid of await this.worktrees()) {
|
|
1844
|
+
try {
|
|
1845
|
+
await git(valid.path, ["diff", "--quiet", valid.base, "HEAD"]);
|
|
1846
|
+
} catch {
|
|
1847
|
+
changed.push(valid);
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
return changed;
|
|
1851
|
+
}
|
|
1852
|
+
async saveResult(value) {
|
|
1853
|
+
const path = join3(this.directory, "result.json");
|
|
1854
|
+
await atomicPrettyJson(path, value);
|
|
1855
|
+
return path;
|
|
1856
|
+
}
|
|
1857
|
+
async resultBytes() {
|
|
1858
|
+
return (await stat2(join3(this.directory, "result.json"))).size;
|
|
1859
|
+
}
|
|
1860
|
+
async delete(confirmed) {
|
|
1861
|
+
if (!confirmed) throw new WorkflowError("CANCELLED", "Run deletion requires confirmation");
|
|
1862
|
+
const records = await this.#loadWorktreeRecords();
|
|
1863
|
+
const validated = records.map((record) => {
|
|
1864
|
+
try {
|
|
1865
|
+
return this.structuralWorktree(record.owner, record);
|
|
1866
|
+
} catch (error) {
|
|
1867
|
+
throw new WorkflowError("WORKTREE_FAILED", errorText(error));
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1870
|
+
await this.cleanupOrphanWorktrees();
|
|
1871
|
+
for (const record of validated) {
|
|
1872
|
+
await git(this.cwd, ["worktree", "remove", "--force", record.path]).catch(() => void 0);
|
|
1873
|
+
await git(this.cwd, ["branch", "-D", record.branch]).catch(() => void 0);
|
|
1874
|
+
}
|
|
1875
|
+
await rm3(this.directory, { recursive: true, force: true });
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
|
|
1879
|
+
// src/host-view.ts
|
|
1880
|
+
import { keyHint, truncateToVisualLines } from "@earendil-works/pi-coding-agent";
|
|
1881
|
+
import { truncateToWidth as truncateToWidth2 } from "@earendil-works/pi-tui";
|
|
1882
|
+
|
|
1883
|
+
// src/background-widget.ts
|
|
1884
|
+
import { isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
1885
|
+
var QUIET_MS = 3 * 60 * 1e3;
|
|
1886
|
+
|
|
1887
|
+
// src/host-view.ts
|
|
1888
|
+
var ANSI_SGR_SOURCE = `${String.fromCharCode(27)}\\[[0-9;]*m`;
|
|
1889
|
+
var ANSI_SGR = new RegExp(ANSI_SGR_SOURCE);
|
|
1890
|
+
|
|
1891
|
+
// subagents/src/contracts.ts
|
|
1892
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
1893
|
+
|
|
1894
|
+
// src/validation.ts
|
|
1895
|
+
import { mkdirSync, readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
|
|
1896
|
+
import { homedir as homedir4 } from "node:os";
|
|
1897
|
+
import { basename as basename3, dirname as dirname3, extname, join as join5, resolve as resolve3 } from "node:path";
|
|
1898
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1899
|
+
import * as acorn from "acorn";
|
|
1900
|
+
import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
1901
|
+
|
|
1902
|
+
// src/registry.ts
|
|
1903
|
+
import { readFileSync } from "node:fs";
|
|
1904
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join4 } from "node:path";
|
|
1905
|
+
import { fileURLToPath } from "node:url";
|
|
1906
|
+
import { Value } from "typebox/value";
|
|
1907
|
+
|
|
1908
|
+
// src/package-identity.ts
|
|
1909
|
+
var CORE_PACKAGE_NAME = "@marcoscale98/pi-extensible-workflows";
|
|
1910
|
+
var CORE_PACKAGE_COMPATIBILITY_NAME = "pi-extensible-workflows";
|
|
1911
|
+
var CORE_PACKAGE_NAMES = [CORE_PACKAGE_NAME, CORE_PACKAGE_COMPATIBILITY_NAME];
|
|
1912
|
+
function isCorePackageName(value) {
|
|
1913
|
+
return typeof value === "string" && CORE_PACKAGE_NAMES.includes(value);
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
// src/registry.ts
|
|
1917
|
+
var RESERVED_GLOBALS = /* @__PURE__ */ new Set(["agent", "shell", "prompt", "checkpoint", "parallel", "pipeline", "phase", "withWorktree", "log", "args", "Promise", "JSON", "Math", "Date", "eval", "Function", "WebAssembly", "process", "require", "module", "exports", "console", "fetch", "XMLHttpRequest", "WebSocket", "performance", "crypto", "setTimeout", "setInterval", "setImmediate", "queueMicrotask", "Intl", "SharedArrayBuffer", "Atomics", "globalThis", "global", "undefined", "NaN", "Infinity", "extensions", "workflow_catalog"]);
|
|
1918
|
+
var IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
1919
|
+
var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
1920
|
+
var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
1921
|
+
function normalizeRoleDirectory(value) {
|
|
1922
|
+
try {
|
|
1923
|
+
if (value instanceof URL) {
|
|
1924
|
+
if (value.protocol !== "file:") fail("INVALID_METADATA", "Workflow role directories require file URLs or absolute filesystem paths");
|
|
1925
|
+
return fileURLToPath(value);
|
|
1926
|
+
}
|
|
1927
|
+
if (typeof value === "string") {
|
|
1928
|
+
if (value.startsWith("file:")) return fileURLToPath(value);
|
|
1929
|
+
if (isAbsolute(value)) return value;
|
|
1930
|
+
}
|
|
1931
|
+
} catch (error) {
|
|
1932
|
+
fail("INVALID_METADATA", `Invalid workflow role directory: ${errorText(error)}`);
|
|
1933
|
+
}
|
|
1934
|
+
fail("INVALID_METADATA", "Workflow role directories require file URLs or absolute filesystem paths");
|
|
1935
|
+
}
|
|
1936
|
+
function isBuiltinRoleDirectory(path) {
|
|
1937
|
+
const canonical = canonicalPath(path);
|
|
1938
|
+
const starterDirectory = dirname2(canonical);
|
|
1939
|
+
if (basename2(canonical) !== "roles" || basename2(starterDirectory) !== "starter") return false;
|
|
1940
|
+
const distributionDirectory = dirname2(starterDirectory);
|
|
1941
|
+
const packageRoot = basename2(distributionDirectory) === "dist" ? dirname2(distributionDirectory) : distributionDirectory;
|
|
1942
|
+
try {
|
|
1943
|
+
const metadata = JSON.parse(readFileSync(join4(packageRoot, "package.json"), "utf8"));
|
|
1944
|
+
return object(metadata) && isCorePackageName(metadata.name);
|
|
1945
|
+
} catch {
|
|
1946
|
+
return false;
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
function catalogSchema(schema, at) {
|
|
1950
|
+
validateSchema(schema, at);
|
|
1951
|
+
return structuredClone(schema);
|
|
1952
|
+
}
|
|
1953
|
+
var WorkflowRegistry = class {
|
|
1954
|
+
#extensions = /* @__PURE__ */ new Set();
|
|
1955
|
+
#functionSources = /* @__PURE__ */ new Map();
|
|
1956
|
+
#globals = /* @__PURE__ */ new Map();
|
|
1957
|
+
#hooks = /* @__PURE__ */ new Map();
|
|
1958
|
+
#agentAttemptActions = /* @__PURE__ */ new Map();
|
|
1959
|
+
#roleDirectories = /* @__PURE__ */ new Map();
|
|
1960
|
+
#modelAliases = /* @__PURE__ */ new Map();
|
|
1961
|
+
#settingsValidators = /* @__PURE__ */ new Set();
|
|
1962
|
+
#frozen = false;
|
|
1963
|
+
#subagentStatusObserver;
|
|
1964
|
+
get frozen() {
|
|
1965
|
+
return this.#frozen;
|
|
1966
|
+
}
|
|
1967
|
+
freeze() {
|
|
1968
|
+
this.#frozen = true;
|
|
1969
|
+
}
|
|
1970
|
+
setSubagentStatusObserver(observer) {
|
|
1971
|
+
this.#subagentStatusObserver = observer;
|
|
1972
|
+
}
|
|
1973
|
+
observeSubagentStatus(status, request) {
|
|
1974
|
+
this.#subagentStatusObserver?.(status, request);
|
|
1975
|
+
}
|
|
1976
|
+
register(extension2) {
|
|
1977
|
+
if (this.#frozen) fail("REGISTRY_FROZEN", "Workflow extension registration is closed after session_start");
|
|
1978
|
+
if (object(extension2) && Object.prototype.hasOwnProperty.call(extension2, "workflows")) fail("INVALID_METADATA", "Separate registered workflow definitions were removed; register a function with input and output schemas instead");
|
|
1979
|
+
if (!object(extension2) || Object.keys(extension2).some((key) => !["version", "headline", "description", "source", "dependencies", "validateSettings", "functions", "modelAliases", "agentSetupHooks", "agentAttemptActions", "roleDirectories"].includes(key)) || typeof extension2.version !== "string" || !SEMVER.test(extension2.version) || typeof extension2.headline !== "string" || !extension2.headline.trim()) fail("INVALID_METADATA", "Workflow extensions require a semantic version and non-empty headline");
|
|
1980
|
+
const functions = extension2.functions ?? {};
|
|
1981
|
+
const modelAliases = extension2.modelAliases ?? {};
|
|
1982
|
+
const agentSetupHooks = extension2.agentSetupHooks ?? {};
|
|
1983
|
+
const agentAttemptActions = extension2.agentAttemptActions ?? {};
|
|
1984
|
+
const source = extension2.source;
|
|
1985
|
+
const dependencyValues = extension2.dependencies;
|
|
1986
|
+
const dependencies = dependencyValues ?? [];
|
|
1987
|
+
const validateSettings = extension2.validateSettings;
|
|
1988
|
+
if (validateSettings !== void 0 && typeof validateSettings !== "function") fail("INVALID_METADATA", "Workflow extension validateSettings must be a function");
|
|
1989
|
+
if (source !== void 0 && (typeof source !== "string" || !source.trim())) fail("INVALID_METADATA", "Workflow extension source must be a non-empty module URL");
|
|
1990
|
+
if (dependencyValues !== void 0 && (!Array.isArray(dependencyValues) || dependencyValues.some((dependency) => typeof dependency !== "string" || dependency.trim() !== dependency || !PACKAGE_NAME.test(dependency)) || new Set(dependencyValues).size !== dependencyValues.length)) fail("INVALID_METADATA", "Workflow extension dependencies must be unique, non-empty package names");
|
|
1991
|
+
const roleDirectoryValues = extension2.roleDirectories === void 0 ? [] : extension2.roleDirectories;
|
|
1992
|
+
if (!Array.isArray(roleDirectoryValues)) fail("INVALID_METADATA", "Workflow extension roleDirectories must be an array");
|
|
1993
|
+
const roleDirectories = [...new Set(Array.from(roleDirectoryValues, (value) => normalizeRoleDirectory(value)))];
|
|
1994
|
+
if (!object(functions) || !object(modelAliases) || !object(agentSetupHooks) || !object(agentAttemptActions) || Object.keys(functions).length === 0 && Object.keys(modelAliases).length === 0 && Object.keys(agentSetupHooks).length === 0 && Object.keys(agentAttemptActions).length === 0 && roleDirectories.length === 0 && validateSettings === void 0) fail("INVALID_METADATA", "Workflow extensions require functions, model aliases, agent setup hooks, agent attempt actions, role directories, or a settings validator");
|
|
1995
|
+
const names = Object.keys(functions);
|
|
1996
|
+
if (new Set(names).size !== names.length) fail("GLOBAL_COLLISION", "Global name collision inside extension");
|
|
1997
|
+
for (const name of names) {
|
|
1998
|
+
if (!IDENTIFIER.test(name) || name.startsWith("__pi_extensible_workflows_")) fail("INVALID_METADATA", `Invalid global name: ${name}`);
|
|
1999
|
+
if (RESERVED_GLOBALS.has(name)) fail("GLOBAL_COLLISION", `Global name is reserved: ${name}`);
|
|
2000
|
+
if (this.#globals.has(name)) fail("GLOBAL_COLLISION", `Global name is already registered: ${name}`);
|
|
2001
|
+
}
|
|
2002
|
+
for (const [name, fn] of Object.entries(functions)) {
|
|
2003
|
+
if (!object(fn) || Object.keys(fn).some((key) => !["description", "input", "output", "run"].includes(key)) || typeof fn.description !== "string" || !fn.description.trim() || typeof fn.run !== "function") fail("INVALID_METADATA", `Invalid workflow function: ${name}`);
|
|
2004
|
+
validateSchema(fn.input, `${name} input`);
|
|
2005
|
+
validateSchema(fn.output, `${name} output`);
|
|
2006
|
+
if (fn.input.type !== "object") fail("INVALID_SCHEMA", `${name} input must describe one object`);
|
|
2007
|
+
}
|
|
2008
|
+
for (const [name, alias] of Object.entries(modelAliases)) {
|
|
2009
|
+
if (!MODEL_ALIAS_NAME.test(name)) fail("INVALID_METADATA", `Invalid model alias name: ${name}`);
|
|
2010
|
+
if (!object(alias) || Object.keys(alias).some((key) => key !== "resolve") || typeof alias.resolve !== "function") fail("INVALID_METADATA", `Invalid model alias resolver: ${name}`);
|
|
2011
|
+
if (this.#modelAliases.has(name)) fail("DUPLICATE_NAME", `Model alias already registered: ${name}`);
|
|
2012
|
+
}
|
|
2013
|
+
for (const [name, hook] of Object.entries(agentSetupHooks)) {
|
|
2014
|
+
if (!IDENTIFIER.test(name) || !object(hook) || Object.keys(hook).some((key) => !["priority", "setup"].includes(key)) || typeof hook.setup !== "function" || hook.priority !== void 0 && (typeof hook.priority !== "number" || !Number.isFinite(hook.priority))) fail("INVALID_METADATA", `Invalid agent setup hook: ${name}`);
|
|
2015
|
+
if (this.#hooks.has(name)) fail("DUPLICATE_NAME", `Agent setup hook already registered: ${name}`);
|
|
2016
|
+
}
|
|
2017
|
+
for (const [name, action] of Object.entries(agentAttemptActions)) {
|
|
2018
|
+
if (!IDENTIFIER.test(name) || !object(action) || Object.keys(action).some((key) => !["label", "visible", "run", "visibleStandalone", "runStandalone"].includes(key)) || typeof action.label !== "string" || !action.label.trim() || typeof action.visible !== "function" || typeof action.run !== "function" || action.visibleStandalone !== void 0 && typeof action.visibleStandalone !== "function" || action.runStandalone !== void 0 && typeof action.runStandalone !== "function") fail("INVALID_METADATA", `Invalid agent attempt action: ${name}`);
|
|
2019
|
+
if (action.visibleStandalone !== void 0 !== (action.runStandalone !== void 0)) fail("INVALID_METADATA", `Standalone agent attempt actions require visibleStandalone and runStandalone: ${name}`);
|
|
2020
|
+
if (this.#agentAttemptActions.has(name)) fail("DUPLICATE_NAME", `Agent attempt action already registered: ${name}`);
|
|
2021
|
+
}
|
|
2022
|
+
const stored = deepFreeze({ ...extension2, functions, modelAliases, agentSetupHooks, agentAttemptActions, ...roleDirectories.length ? { roleDirectories } : {} });
|
|
2023
|
+
if (source !== void 0) for (const name of names) this.#functionSources.set(name, Object.freeze({ module: source, export: "default", dependencies: Object.freeze([...dependencies]) }));
|
|
2024
|
+
this.#extensions.add(stored);
|
|
2025
|
+
for (const directory of roleDirectories) if (!this.#roleDirectories.has(directory)) this.#roleDirectories.set(directory, deepFreeze({ path: directory, extension: { version: extension2.version, headline: extension2.headline }, ...isBuiltinRoleDirectory(directory) ? { builtin: true } : {} }));
|
|
2026
|
+
for (const name of names) this.#globals.set(name, name);
|
|
2027
|
+
for (const [name, alias] of Object.entries(modelAliases)) this.#modelAliases.set(name, { name, version: extension2.version, headline: extension2.headline, resolve: alias.resolve });
|
|
2028
|
+
for (const [name, hook] of Object.entries(agentSetupHooks)) this.#hooks.set(name, { name, priority: hook.priority ?? 10, setup: hook.setup });
|
|
2029
|
+
for (const [name, action] of Object.entries(agentAttemptActions)) this.#agentAttemptActions.set(name, action);
|
|
2030
|
+
if (validateSettings !== void 0) this.#settingsValidators.add({ headline: extension2.headline, validate: validateSettings });
|
|
2031
|
+
}
|
|
2032
|
+
function(name) {
|
|
2033
|
+
if (!IDENTIFIER.test(name)) fail("MISSING_WORKFLOW", `Registered functions require an unqualified name: ${name}`);
|
|
2034
|
+
const fn = [...this.#extensions].find((extension2) => extension2.functions?.[name])?.functions?.[name];
|
|
2035
|
+
if (!fn) fail("MISSING_WORKFLOW", `Registered function is unavailable: ${name}; the separate registered-workflow format was removed`);
|
|
2036
|
+
return fn;
|
|
2037
|
+
}
|
|
2038
|
+
functions() {
|
|
2039
|
+
return Object.freeze(Object.fromEntries([...this.#extensions].flatMap((extension2) => Object.entries(extension2.functions ?? {}))));
|
|
2040
|
+
}
|
|
2041
|
+
functionSources() {
|
|
2042
|
+
return Object.freeze(Object.fromEntries(this.#functionSources));
|
|
2043
|
+
}
|
|
2044
|
+
catalog(context) {
|
|
2045
|
+
const functions = [];
|
|
2046
|
+
for (const extension2 of this.#extensions) {
|
|
2047
|
+
for (const [name, fn] of Object.entries(extension2.functions ?? {})) functions.push({ name, version: extension2.version, headline: extension2.headline, description: fn.description, input: catalogSchema(fn.input, `${name} input`), output: catalogSchema(fn.output, `${name} output`) });
|
|
2048
|
+
}
|
|
2049
|
+
let aliases;
|
|
2050
|
+
let settings;
|
|
2051
|
+
let source = "global settings";
|
|
2052
|
+
try {
|
|
2053
|
+
const resolved = context ? resolveWorkflowSettings(context.cwd, context.projectTrusted, context.globalSettingsPath) : void 0;
|
|
2054
|
+
aliases = resolved?.effective.modelAliases ?? loadSettings().modelAliases;
|
|
2055
|
+
if (resolved) {
|
|
2056
|
+
source = resolved.projectTrusted && [resolved.sources.modelAliases, resolved.sources.skills, resolved.sources.extensions, resolved.sources.tools].some((path) => path === resolved.projectSettingsPath) ? "trusted project settings" : "global settings";
|
|
2057
|
+
settings = { concurrency: resolved.effective.concurrency, backgroundWidget: resolved.effective.backgroundWidget ?? true, modelAliases: resolved.effective.modelAliases ?? {}, skills: resolved.effective.skills ?? [], extensions: Array.isArray(resolved.effective.extensions) ? resolved.effective.extensions : [], tools: resolved.effective.tools ?? [], ...resolved.effective.extensionSettings === void 0 ? {} : { extensionSettings: resolved.effective.extensionSettings }, ...resolved.effective.worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand: resolved.effective.worktreePostCreateCommand }, globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted: resolved.projectTrusted, sources: resolved.sources };
|
|
2058
|
+
}
|
|
2059
|
+
} catch {
|
|
2060
|
+
aliases = void 0;
|
|
2061
|
+
}
|
|
2062
|
+
const staticEntries = Object.keys(aliases ?? {}).map((name) => ({ name, kind: "static", provenance: source }));
|
|
2063
|
+
const dynamicEntries = [...this.#modelAliases.values()].map(({ name, version, headline }) => ({ name, kind: "dynamic", provenance: `extension: ${headline}`, version, headline }));
|
|
2064
|
+
const modelAliasEntries = [...staticEntries, ...dynamicEntries].sort((left, right) => left.name.localeCompare(right.name) || left.kind.localeCompare(right.kind) || left.provenance.localeCompare(right.provenance));
|
|
2065
|
+
const sort = (left, right) => left.name.localeCompare(right.name);
|
|
2066
|
+
const catalog = { functions: functions.sort(sort), ...modelAliasEntries.length ? { modelAliasEntries } : {} };
|
|
2067
|
+
if (aliases && Object.keys(aliases).length) Object.defineProperty(catalog, "modelAliases", { value: Object.freeze(structuredClone(aliases)), enumerable: false });
|
|
2068
|
+
if (settings) {
|
|
2069
|
+
const publicSettings = { ...settings, modelAliases: Object.keys(aliases ?? {}).length ? {} : settings.modelAliases };
|
|
2070
|
+
Object.defineProperty(catalog, "settings", { value: deepFreeze(publicSettings), enumerable: true });
|
|
2071
|
+
}
|
|
2072
|
+
return deepFreeze(catalog);
|
|
2073
|
+
}
|
|
2074
|
+
catalogIndex(context) {
|
|
2075
|
+
const catalog = this.catalog(context);
|
|
2076
|
+
let modelAliasEntries;
|
|
2077
|
+
if (catalog.modelAliasEntries !== void 0) {
|
|
2078
|
+
const winningEntries = /* @__PURE__ */ new Map();
|
|
2079
|
+
for (const entry of catalog.modelAliasEntries) {
|
|
2080
|
+
if (!winningEntries.has(entry.name) || entry.kind === "static") winningEntries.set(entry.name, entry);
|
|
2081
|
+
}
|
|
2082
|
+
modelAliasEntries = [...winningEntries.values()];
|
|
2083
|
+
}
|
|
2084
|
+
const index = {
|
|
2085
|
+
functions: catalog.functions.map(({ name, description, input }) => ({ name, description, input: structuredClone(input) })),
|
|
2086
|
+
...modelAliasEntries ? { modelAliasEntries: structuredClone(modelAliasEntries) } : {}
|
|
2087
|
+
};
|
|
2088
|
+
if (catalog.modelAliases) Object.defineProperty(index, "modelAliases", { value: Object.freeze(structuredClone(catalog.modelAliases)), enumerable: false });
|
|
2089
|
+
if (catalog.settings) Object.defineProperty(index, "settings", { value: deepFreeze(structuredClone(catalog.settings)), enumerable: true });
|
|
2090
|
+
return deepFreeze(index);
|
|
2091
|
+
}
|
|
2092
|
+
catalogDetail(name, context) {
|
|
2093
|
+
const catalog = this.catalog(context);
|
|
2094
|
+
const entry = catalog.functions.find((candidate) => candidate.name === name);
|
|
2095
|
+
if (entry) return entry;
|
|
2096
|
+
const alias = catalog.modelAliasEntries?.find((candidate) => candidate.name === name && candidate.kind === "static") ?? catalog.modelAliasEntries?.find((candidate) => candidate.name === name);
|
|
2097
|
+
if (alias) return alias;
|
|
2098
|
+
return deepFreeze({ error: { code: "NOT_FOUND", name, message: `No registered workflow function is available: ${name}` } });
|
|
2099
|
+
}
|
|
2100
|
+
globals() {
|
|
2101
|
+
return Object.freeze(Object.fromEntries([...this.#extensions].flatMap((extension2) => Object.keys(extension2.functions ?? {}).map((name) => [name, { name }]))));
|
|
2102
|
+
}
|
|
2103
|
+
validateExtensionSettings(settings, context) {
|
|
2104
|
+
const effectiveSettings = settings ?? Object.freeze({});
|
|
2105
|
+
if (!jsonValue(effectiveSettings) || !object(effectiveSettings)) fail("INVALID_SETTINGS", "Workflow extension settings must be a JSON object");
|
|
2106
|
+
const unsafeNamespace = Object.keys(effectiveSettings).find((namespace) => !validWorkflowExtensionNamespace(namespace));
|
|
2107
|
+
if (unsafeNamespace !== void 0) fail("INVALID_SETTINGS", `Workflow extension settings contains an invalid namespace: ${unsafeNamespace}`);
|
|
2108
|
+
const frozenSettings = deepFreeze(structuredClone(effectiveSettings));
|
|
2109
|
+
for (const { headline, validate } of this.#settingsValidators) {
|
|
2110
|
+
try {
|
|
2111
|
+
validate(frozenSettings, Object.freeze({ ...context }));
|
|
2112
|
+
} catch (error) {
|
|
2113
|
+
const location = context.source === "role" && context.role ? `role ${context.role}` : context.source;
|
|
2114
|
+
fail("INVALID_SETTINGS", `Invalid extension settings (${headline}; ${location}): ${errorText(error)}`);
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
async invokeFunction(name, input, context, path, journal) {
|
|
2119
|
+
const fn = this.function(name);
|
|
2120
|
+
if (!object(input) || !jsonValue(input) || !Value.Check(fn.input, input)) fail("RESULT_INVALID", `Invalid input for ${name}`);
|
|
2121
|
+
const replayed = journal.get(path);
|
|
2122
|
+
if (replayed !== void 0) {
|
|
2123
|
+
if (!jsonValue(replayed) || !Value.Check(fn.output, replayed)) fail("RESULT_INVALID", `Invalid replay for ${name}`);
|
|
2124
|
+
return structuredClone(replayed);
|
|
2125
|
+
}
|
|
2126
|
+
const result = await fn.run(deepFreeze(structuredClone(input)), Object.freeze({ run: context.run, settings: deepFreeze(structuredClone(context.settings)), invoke: context.invoke, agent: context.agent, shell: context.shell, prompt: context.prompt, parallel: context.parallel, pipeline: context.pipeline, withWorktree: context.withWorktree, checkpoint: context.checkpoint, phase: context.phase, log: context.log }));
|
|
2127
|
+
if (!jsonValue(result) || !Value.Check(fn.output, result)) fail("RESULT_INVALID", `Invalid output from ${name}`);
|
|
2128
|
+
const stored = structuredClone(result);
|
|
2129
|
+
journal.put(path, stored);
|
|
2130
|
+
return structuredClone(stored);
|
|
2131
|
+
}
|
|
2132
|
+
agentSetupHooks() {
|
|
2133
|
+
return [...this.#hooks.values()].sort(byPriorityThenName);
|
|
2134
|
+
}
|
|
2135
|
+
agentAttemptActions() {
|
|
2136
|
+
return Object.freeze(Object.fromEntries(this.#agentAttemptActions.entries()));
|
|
2137
|
+
}
|
|
2138
|
+
roleDirectories() {
|
|
2139
|
+
return [...this.#roleDirectories.keys()];
|
|
2140
|
+
}
|
|
2141
|
+
roleDirectoryRegistrations() {
|
|
2142
|
+
return [...this.#roleDirectories.values()];
|
|
2143
|
+
}
|
|
2144
|
+
modelAliases() {
|
|
2145
|
+
return [...this.#modelAliases.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
2146
|
+
}
|
|
2147
|
+
async resolveModelAliases(context, shadowed = /* @__PURE__ */ new Set()) {
|
|
2148
|
+
const resolved = {};
|
|
2149
|
+
const isAborted = () => context.signal.aborted;
|
|
2150
|
+
for (const alias of this.modelAliases()) {
|
|
2151
|
+
if (shadowed.has(alias.name)) continue;
|
|
2152
|
+
if (isAborted()) fail("CANCELLED", `Model alias resolver cancelled: ${alias.name} (${alias.headline})`);
|
|
2153
|
+
let target;
|
|
2154
|
+
try {
|
|
2155
|
+
target = await alias.resolve(Object.freeze({ cwd: context.cwd, projectTrusted: context.projectTrusted, rootModel: Object.freeze({ ...context.rootModel }), knownModels: new Set(context.knownModels), availableModels: new Set(context.availableModels), signal: context.signal }));
|
|
2156
|
+
} catch (error) {
|
|
2157
|
+
if (isAborted() || error instanceof Error && error.name === "AbortError" || errorCode(error) === "CANCELLED") fail("CANCELLED", `Model alias resolver cancelled: ${alias.name} (${alias.headline})`);
|
|
2158
|
+
fail("CONFIG_ERROR", `Model alias resolver failed for ${alias.name} (${alias.headline}): ${errorText(error)}`);
|
|
2159
|
+
}
|
|
2160
|
+
if (isAborted()) fail("CANCELLED", `Model alias resolver cancelled: ${alias.name} (${alias.headline})`);
|
|
2161
|
+
if (typeof target !== "string" || !target.trim()) fail("CONFIG_ERROR", `Model alias resolver returned an invalid target for ${alias.name} (${alias.headline})`);
|
|
2162
|
+
resolved[alias.name] = target.trim();
|
|
2163
|
+
}
|
|
2164
|
+
return Object.freeze(resolved);
|
|
2165
|
+
}
|
|
2166
|
+
};
|
|
2167
|
+
var WORKFLOW_REGISTRY_KEY = /* @__PURE__ */ Symbol.for("pi-extensible-workflows.workflow-registry");
|
|
2168
|
+
var globalRegistry = globalThis;
|
|
2169
|
+
function createWorkflowRegistryApi(registry) {
|
|
2170
|
+
return {
|
|
2171
|
+
get frozen() {
|
|
2172
|
+
return registry.frozen;
|
|
2173
|
+
},
|
|
2174
|
+
freeze: () => {
|
|
2175
|
+
registry.freeze();
|
|
2176
|
+
},
|
|
2177
|
+
setSubagentStatusObserver: (observer) => {
|
|
2178
|
+
registry.setSubagentStatusObserver(observer);
|
|
2179
|
+
},
|
|
2180
|
+
observeSubagentStatus: (status, request) => {
|
|
2181
|
+
registry.observeSubagentStatus(status, request);
|
|
2182
|
+
},
|
|
2183
|
+
register: (extension2) => {
|
|
2184
|
+
registry.register(extension2);
|
|
2185
|
+
},
|
|
2186
|
+
function: (name) => registry.function(name),
|
|
2187
|
+
functions: () => registry.functions(),
|
|
2188
|
+
functionSources: () => registry.functionSources(),
|
|
2189
|
+
catalog: (context) => registry.catalog(context),
|
|
2190
|
+
catalogIndex: (context) => registry.catalogIndex(context),
|
|
2191
|
+
catalogDetail: (name, context) => registry.catalogDetail(name, context),
|
|
2192
|
+
globals: () => registry.globals(),
|
|
2193
|
+
invokeFunction: (...args) => registry.invokeFunction(...args),
|
|
2194
|
+
validateExtensionSettings: (...args) => {
|
|
2195
|
+
registry.validateExtensionSettings(...args);
|
|
2196
|
+
},
|
|
2197
|
+
modelAliases: () => registry.modelAliases(),
|
|
2198
|
+
resolveModelAliases: (...args) => registry.resolveModelAliases(...args),
|
|
2199
|
+
roleDirectories: () => registry.roleDirectories(),
|
|
2200
|
+
roleDirectoryRegistrations: () => registry.roleDirectoryRegistrations(),
|
|
2201
|
+
agentSetupHooks: () => registry.agentSetupHooks(),
|
|
2202
|
+
agentAttemptActions: () => registry.agentAttemptActions()
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
function workflowRegistryHost() {
|
|
2206
|
+
return globalRegistry[WORKFLOW_REGISTRY_KEY] ??= { api: createWorkflowRegistryApi(new WorkflowRegistry()), activeHosts: 0 };
|
|
2207
|
+
}
|
|
2208
|
+
function resetWorkflowRegistry() {
|
|
2209
|
+
workflowRegistryHost().api = createWorkflowRegistryApi(new WorkflowRegistry());
|
|
2210
|
+
}
|
|
2211
|
+
function beginWorkflowExtensionLoading() {
|
|
2212
|
+
const host = workflowRegistryHost();
|
|
2213
|
+
if (host.api.frozen && host.activeHosts === 0) resetWorkflowRegistry();
|
|
2214
|
+
}
|
|
2215
|
+
beginWorkflowExtensionLoading();
|
|
2216
|
+
|
|
2217
|
+
// src/validation.ts
|
|
2218
|
+
var DEFAULT_SETTINGS = Object.freeze({ concurrency: 8, backgroundWidget: true });
|
|
2219
|
+
function workflowSettingsPath(agentDir = getAgentDir()) {
|
|
2220
|
+
return join5(agentDir, ROLE_DIRECTORY, "settings.json");
|
|
2221
|
+
}
|
|
2222
|
+
function normalizedResourcePath(value, settingsPath) {
|
|
2223
|
+
if (value === "*") return value;
|
|
2224
|
+
let expanded = value === "~" ? homedir4() : value.startsWith("~/") || value.startsWith("~\\") ? join5(homedir4(), value.slice(2)) : value;
|
|
2225
|
+
if (expanded.startsWith("file://")) expanded = fileURLToPath2(expanded);
|
|
2226
|
+
const resolved = resolve3(dirname3(settingsPath), expanded);
|
|
2227
|
+
if (expanded === "**" || expanded.startsWith("**/") || expanded.startsWith("**\\")) return expanded;
|
|
2228
|
+
if (resourcePatternHasMagic(expanded)) {
|
|
2229
|
+
const magicIndex = resolved.search(/[*?\x5b\x5d{}()]/);
|
|
2230
|
+
const separatorIndex = Math.max(resolved.lastIndexOf("/", magicIndex), resolved.lastIndexOf("\\", magicIndex));
|
|
2231
|
+
const rootBoundary = separatorIndex === 0 || separatorIndex === 2 && /^[A-Za-z]:[\\/]/.test(resolved);
|
|
2232
|
+
const prefix = rootBoundary ? resolved.slice(0, separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(0, separatorIndex) : resolved;
|
|
2233
|
+
const suffix = rootBoundary ? resolved.slice(separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(separatorIndex) : "";
|
|
2234
|
+
return `${canonicalPath(prefix)}${suffix}`;
|
|
2235
|
+
}
|
|
2236
|
+
return canonicalPath(resolved);
|
|
2237
|
+
}
|
|
2238
|
+
function validateSelectorList(value, path, kind, errorCode2 = "INVALID_SETTINGS", normalizeExtensions = true) {
|
|
2239
|
+
if (value === void 0) return void 0;
|
|
2240
|
+
if (!Array.isArray(value)) fail(errorCode2, `${path}.${kind} must be an array`);
|
|
2241
|
+
const normalized = [];
|
|
2242
|
+
for (const [index, entry] of value.entries()) {
|
|
2243
|
+
if (typeof entry !== "string" || !entry.trim()) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a non-empty string`);
|
|
2244
|
+
let selector = entry.trim();
|
|
2245
|
+
if (kind === "extensions" && normalizeExtensions) {
|
|
2246
|
+
const negated = selector.startsWith("!");
|
|
2247
|
+
const body = negated ? selector.slice(1) : selector;
|
|
2248
|
+
if (!body) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: Empty minimatch pattern ${JSON.stringify(selector)}`);
|
|
2249
|
+
try {
|
|
2250
|
+
selector = `${negated ? "!" : ""}${normalizedResourcePath(body, path)}`;
|
|
2251
|
+
} catch (error) {
|
|
2252
|
+
fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid path: ${errorText(error)}`);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
try {
|
|
2256
|
+
validateResourcePattern(selector);
|
|
2257
|
+
} catch (error) {
|
|
2258
|
+
fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: ${errorText(error)}`);
|
|
2259
|
+
}
|
|
2260
|
+
normalized.push(selector);
|
|
2261
|
+
}
|
|
2262
|
+
return Object.freeze(normalized);
|
|
2263
|
+
}
|
|
2264
|
+
function validateWorktreePostCreateCommand(value, settingsPath) {
|
|
2265
|
+
if (value === void 0) return void 0;
|
|
2266
|
+
if (!Array.isArray(value) || value.length === 0) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand must be a non-empty array`);
|
|
2267
|
+
return Object.freeze(value.map((entry, index) => {
|
|
2268
|
+
if (typeof entry !== "string" || !entry.trim()) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand[${String(index)}] must be a non-empty string`);
|
|
2269
|
+
return entry;
|
|
2270
|
+
}));
|
|
2271
|
+
}
|
|
2272
|
+
function selectorsFromSettings(settings) {
|
|
2273
|
+
return {
|
|
2274
|
+
...settings.skills === void 0 ? {} : { skills: settings.skills },
|
|
2275
|
+
...settings.extensions === void 0 ? {} : { extensions: settings.extensions },
|
|
2276
|
+
...settings.tools === void 0 ? {} : { tools: settings.tools }
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
function selectorSet(value) {
|
|
2280
|
+
return { skills: [...value?.skills ?? []], extensions: [...value?.extensions ?? []], ...value?.tools === void 0 ? {} : { tools: [...value.tools] } };
|
|
2281
|
+
}
|
|
2282
|
+
function validateWorkflowExtensionSettings(value, settingsPath, errorCode2 = "INVALID_SETTINGS", applyDefaults = true) {
|
|
2283
|
+
if (value === void 0) return void 0;
|
|
2284
|
+
const base = `${settingsPath}.extensionSettings`;
|
|
2285
|
+
if (!object(value)) fail(errorCode2, `${base} must be an object`);
|
|
2286
|
+
const normalized = {};
|
|
2287
|
+
for (const [namespace, raw] of Object.entries(value)) {
|
|
2288
|
+
if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode2, `${base} contains an invalid namespace: ${namespace}`);
|
|
2289
|
+
if (namespace === "herdr") {
|
|
2290
|
+
if (!object(raw)) fail(errorCode2, `${base}.herdr must be an object`);
|
|
2291
|
+
if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode")) fail(errorCode2, `${base}.herdr contains an unsupported setting`);
|
|
2292
|
+
if (raw.enableFullyInspectableMode !== void 0 && typeof raw.enableFullyInspectableMode !== "boolean") fail(errorCode2, `${base}.herdr.enableFullyInspectableMode must be a boolean`);
|
|
2293
|
+
normalized.herdr = Object.freeze({ ...raw.enableFullyInspectableMode === void 0 ? {} : { enableFullyInspectableMode: raw.enableFullyInspectableMode } });
|
|
2294
|
+
continue;
|
|
2295
|
+
}
|
|
2296
|
+
if (namespace === "trajectory") {
|
|
2297
|
+
if (!object(raw)) fail(errorCode2, `${base}.trajectory must be an object`);
|
|
2298
|
+
if (Object.keys(raw).some((key) => key !== "port" && key !== "themes")) fail(errorCode2, `${base}.trajectory contains an unsupported setting`);
|
|
2299
|
+
if (raw.port !== void 0 && (!positiveInteger(raw.port) || raw.port > 65535)) fail(errorCode2, `${base}.trajectory.port must be an integer from 1 to 65535`);
|
|
2300
|
+
if (raw.themes !== void 0 && typeof raw.themes !== "boolean") fail(errorCode2, `${base}.trajectory.themes must be a boolean`);
|
|
2301
|
+
normalized.trajectory = Object.freeze({ ...raw.port === void 0 ? {} : { port: raw.port }, ...raw.themes === void 0 ? applyDefaults ? { themes: false } : {} : { themes: raw.themes } });
|
|
2302
|
+
continue;
|
|
2303
|
+
}
|
|
2304
|
+
if (!jsonValue(raw)) fail(errorCode2, `${base}.${namespace} must be JSON-compatible`);
|
|
2305
|
+
normalized[namespace] = structuredClone(raw);
|
|
2306
|
+
}
|
|
2307
|
+
return deepFreeze(normalized);
|
|
2308
|
+
}
|
|
2309
|
+
function positiveRetentionInteger(value) {
|
|
2310
|
+
return positiveInteger(value) && Number.isSafeInteger(value);
|
|
2311
|
+
}
|
|
2312
|
+
function validateRetention(value, settingsPath) {
|
|
2313
|
+
if (value === void 0) return void 0;
|
|
2314
|
+
const base = `${settingsPath}.retention`;
|
|
2315
|
+
if (!object(value)) fail("INVALID_SETTINGS", `${base} must be an object`);
|
|
2316
|
+
const unknown = Object.keys(value).find((key) => key !== "olderThanDays" && key !== "maxTerminalRuns");
|
|
2317
|
+
if (unknown) fail("INVALID_SETTINGS", `Unknown retention setting at ${base}: ${unknown}`);
|
|
2318
|
+
if (value.olderThanDays !== void 0 && !positiveRetentionInteger(value.olderThanDays)) fail("INVALID_SETTINGS", `${base}.olderThanDays must be a positive integer`);
|
|
2319
|
+
if (value.maxTerminalRuns !== void 0 && !positiveRetentionInteger(value.maxTerminalRuns)) fail("INVALID_SETTINGS", `${base}.maxTerminalRuns must be a positive integer`);
|
|
2320
|
+
return Object.freeze({ ...value.olderThanDays === void 0 ? {} : { olderThanDays: value.olderThanDays }, ...value.maxTerminalRuns === void 0 ? {} : { maxTerminalRuns: value.maxTerminalRuns } });
|
|
2321
|
+
}
|
|
2322
|
+
function parseSettings(path, partial) {
|
|
2323
|
+
let parsed;
|
|
2324
|
+
try {
|
|
2325
|
+
parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
2326
|
+
} catch (error) {
|
|
2327
|
+
if (isNodeError(error, "ENOENT")) return partial ? Object.freeze({}) : DEFAULT_SETTINGS;
|
|
2328
|
+
fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
|
|
2329
|
+
}
|
|
2330
|
+
if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
|
|
2331
|
+
const allowed = /* @__PURE__ */ new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...partial ? [] : ["backgroundWidget"]]);
|
|
2332
|
+
const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
|
|
2333
|
+
if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources")) fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
|
|
2334
|
+
if (unknown) fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
|
|
2335
|
+
const concurrency = parsed.concurrency === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.concurrency : parsed.concurrency;
|
|
2336
|
+
if (concurrency !== void 0 && (!positiveInteger(concurrency) || concurrency > 16)) fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
|
|
2337
|
+
const backgroundWidget = parsed.backgroundWidget === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.backgroundWidget : parsed.backgroundWidget;
|
|
2338
|
+
if (backgroundWidget !== void 0 && typeof backgroundWidget !== "boolean") fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
|
|
2339
|
+
const modelAliases = parsed.modelAliases === void 0 ? void 0 : validateModelAliases(parsed.modelAliases, path);
|
|
2340
|
+
const skills = validateSelectorList(parsed.skills, path, "skills");
|
|
2341
|
+
const tools = validateSelectorList(parsed.tools, path, "tools");
|
|
2342
|
+
const extensions = validateSelectorList(parsed.extensions, path, "extensions");
|
|
2343
|
+
const extensionSettings = parsed.extensionSettings === void 0 ? void 0 : validateWorkflowExtensionSettings(parsed.extensionSettings, path, "INVALID_SETTINGS", !partial);
|
|
2344
|
+
const retention = validateRetention(parsed.retention, path);
|
|
2345
|
+
const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
|
|
2346
|
+
return Object.freeze({
|
|
2347
|
+
...concurrency === void 0 ? {} : { concurrency },
|
|
2348
|
+
...backgroundWidget === void 0 ? {} : { backgroundWidget },
|
|
2349
|
+
...modelAliases === void 0 ? {} : { modelAliases },
|
|
2350
|
+
...skills === void 0 ? {} : { skills },
|
|
2351
|
+
...extensions === void 0 ? {} : { extensions },
|
|
2352
|
+
...extensionSettings === void 0 ? {} : { extensionSettings },
|
|
2353
|
+
...tools === void 0 ? {} : { tools },
|
|
2354
|
+
...retention === void 0 ? {} : { retention },
|
|
2355
|
+
...worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand }
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
function loadSettings(path = workflowSettingsPath()) {
|
|
2359
|
+
return parseSettings(path, false);
|
|
2360
|
+
}
|
|
2361
|
+
function loadSettingsOverrides(path) {
|
|
2362
|
+
return parseSettings(path, true);
|
|
2363
|
+
}
|
|
2364
|
+
function resolveWorkflowSettings(cwd, projectTrusted, globalSettingsPath = workflowSettingsPath()) {
|
|
2365
|
+
const projectSettingsPath = workflowProjectSettingsPath(cwd);
|
|
2366
|
+
const global = loadSettings(globalSettingsPath);
|
|
2367
|
+
const project = projectTrusted ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
|
|
2368
|
+
const projectHas = (key) => Object.prototype.hasOwnProperty.call(project, key);
|
|
2369
|
+
const sourceFor = (key) => projectHas(key) ? projectSettingsPath : globalSettingsPath;
|
|
2370
|
+
const globalSelectors = selectorsFromSettings(global);
|
|
2371
|
+
const projectSelectors = selectorsFromSettings(project);
|
|
2372
|
+
const effectiveSelectors = selectorSet({
|
|
2373
|
+
skills: [...globalSelectors.skills ?? [], ...projectSelectors.skills ?? []],
|
|
2374
|
+
extensions: [...globalSelectors.extensions ?? [], ...projectSelectors.extensions ?? []],
|
|
2375
|
+
...globalSelectors.tools === void 0 && projectSelectors.tools === void 0 ? {} : { tools: [...globalSelectors.tools ?? [], ...projectSelectors.tools ?? []] }
|
|
2376
|
+
});
|
|
2377
|
+
const hasExtensionSelectors = global.extensions !== void 0 || project.extensions !== void 0;
|
|
2378
|
+
const extensionSettings = projectHas("extensionSettings") ? project.extensionSettings : global.extensionSettings;
|
|
2379
|
+
const sources = {
|
|
2380
|
+
concurrency: projectHas("concurrency") ? projectSettingsPath : globalSettingsPath,
|
|
2381
|
+
modelAliases: projectHas("modelAliases") ? projectSettingsPath : globalSettingsPath,
|
|
2382
|
+
skills: sourceFor("skills"),
|
|
2383
|
+
extensions: sourceFor("extensions"),
|
|
2384
|
+
tools: sourceFor("tools"),
|
|
2385
|
+
...extensionSettings === void 0 ? {} : { extensionSettings: projectHas("extensionSettings") ? projectSettingsPath : globalSettingsPath },
|
|
2386
|
+
...project.retention === void 0 && global.retention === void 0 ? {} : { retention: project.retention === void 0 ? globalSettingsPath : projectSettingsPath },
|
|
2387
|
+
...projectHas("worktreePostCreateCommand") || global.worktreePostCreateCommand !== void 0 ? { worktreePostCreateCommand: projectHas("worktreePostCreateCommand") ? projectSettingsPath : globalSettingsPath } : {}
|
|
2388
|
+
};
|
|
2389
|
+
const effective = Object.freeze({
|
|
2390
|
+
concurrency: project.concurrency ?? global.concurrency,
|
|
2391
|
+
backgroundWidget: global.backgroundWidget ?? true,
|
|
2392
|
+
...projectHas("modelAliases") ? { modelAliases: project.modelAliases } : global.modelAliases === void 0 ? {} : { modelAliases: global.modelAliases },
|
|
2393
|
+
...effectiveSelectors.skills.length ? { skills: effectiveSelectors.skills } : global.skills !== void 0 || project.skills !== void 0 ? { skills: effectiveSelectors.skills } : {},
|
|
2394
|
+
...hasExtensionSelectors ? { extensions: effectiveSelectors.extensions } : {},
|
|
2395
|
+
...extensionSettings === void 0 ? {} : { extensionSettings },
|
|
2396
|
+
...effectiveSelectors.tools?.length ? { tools: effectiveSelectors.tools } : global.tools !== void 0 || project.tools !== void 0 ? { tools: effectiveSelectors.tools } : {},
|
|
2397
|
+
...(project.retention ?? global.retention) === void 0 ? {} : { retention: project.retention ?? global.retention },
|
|
2398
|
+
...projectHas("worktreePostCreateCommand") ? { worktreePostCreateCommand: project.worktreePostCreateCommand } : global.worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand: global.worktreePostCreateCommand }
|
|
2399
|
+
});
|
|
2400
|
+
return { globalSettingsPath, projectSettingsPath, projectTrusted, global, project, effective, sources };
|
|
2401
|
+
}
|
|
2402
|
+
var ROLE_DIRECTORY = "pi-extensible-workflows";
|
|
2403
|
+
function validateSchema(schema, at = "schema") {
|
|
2404
|
+
if (!object(schema) || Object.getPrototypeOf(schema) !== Object.prototype || !jsonValue(schema)) fail("INVALID_SCHEMA", `${at} must be a plain JSON-compatible Schema object`);
|
|
2405
|
+
if (typeof schema.type !== "string" && !Array.isArray(schema.type) && schema.$ref === void 0 && schema.anyOf === void 0 && schema.oneOf === void 0 && schema.allOf === void 0 && schema.const === void 0 && schema.enum === void 0) fail("INVALID_SCHEMA", `${at} has no JSON Schema shape`);
|
|
2406
|
+
if (schema.required !== void 0 && (!Array.isArray(schema.required) || schema.required.some((key) => typeof key !== "string"))) fail("INVALID_SCHEMA", `${at}.required must be an array of strings`);
|
|
2407
|
+
if (schema.properties !== void 0 && !object(schema.properties)) fail("INVALID_SCHEMA", `${at}.properties must be an object`);
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
// subagents/src/contracts.ts
|
|
2411
|
+
import { Value as Value2 } from "typebox/value";
|
|
2412
|
+
var SUBAGENT_MAX_RETRIES = 255;
|
|
2413
|
+
var SUBAGENT_SYSTEM_PROMPT_LIMIT = 64 * 1024;
|
|
2414
|
+
var SUBAGENTS_MODE = Type.Union([
|
|
2415
|
+
Type.Literal("background"),
|
|
2416
|
+
Type.Literal("foreground")
|
|
2417
|
+
], { description: "Launch mode; background is the default and foreground waits for the terminal envelope" });
|
|
2418
|
+
var SUBAGENTS_RUN_PARAMETERS = Type.Object({
|
|
2419
|
+
prompt: Type.String({ description: "Task for the subagent" }),
|
|
2420
|
+
mode: Type.Optional(SUBAGENTS_MODE),
|
|
2421
|
+
label: Type.Optional(Type.String({ description: "Optional display label for the subagent" })),
|
|
2422
|
+
model: Type.Optional(Type.String({ description: "Optional model as provider/model:thinking or alias[:thinking]" })),
|
|
2423
|
+
tools: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered tool selectors; candidates start enabled and !* restricts the set" })),
|
|
2424
|
+
skills: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered skill selectors; candidates start enabled and !* restricts the set" })),
|
|
2425
|
+
extensions: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered extension selectors; candidates start enabled and !* restricts the set" })),
|
|
2426
|
+
contextFiles: Type.Optional(Type.Array(Type.String(), { description: "Optional context-file scopes: global, project, cwd" })),
|
|
2427
|
+
role: Type.Optional(Type.String({ description: "Workflow role name" })),
|
|
2428
|
+
worktree: Type.Optional(Type.String({ description: "Optional named worktree; requires a clean working tree" })),
|
|
2429
|
+
outputSchema: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Optional JSON schema for the result" })),
|
|
2430
|
+
retries: Type.Optional(Type.Integer({ minimum: 0, maximum: SUBAGENT_MAX_RETRIES, description: "Optional retry count; at most 255 retries" })),
|
|
2431
|
+
timeoutMs: Type.Optional(Type.Union([Type.Integer({ minimum: 1, description: "Optional execution timeout in milliseconds" }), Type.Null()]))
|
|
2432
|
+
}, { additionalProperties: false });
|
|
2433
|
+
var SUBAGENTS_INSPECT_PARAMETERS = Type.Object({
|
|
2434
|
+
id: Type.Optional(Type.String({ description: "Subagent ID; omit to list ordered run summaries" }))
|
|
2435
|
+
}, { additionalProperties: false });
|
|
2436
|
+
var SUBAGENTS_ID_PARAMETERS = Type.Object({
|
|
2437
|
+
id: Type.String({ description: "Subagent ID" })
|
|
2438
|
+
}, { additionalProperties: false });
|
|
2439
|
+
var SUBAGENTS_STEER_PARAMETERS = Type.Object({
|
|
2440
|
+
id: Type.String({ description: "Subagent ID" }),
|
|
2441
|
+
message: Type.String({ description: "Message to send to the running subagent" })
|
|
2442
|
+
}, { additionalProperties: false });
|
|
2443
|
+
var SUBAGENTS_STOP_PARAMETERS = Type.Object({
|
|
2444
|
+
id: Type.String({ description: "Subagent ID" })
|
|
2445
|
+
}, { additionalProperties: false });
|
|
2446
|
+
var SUBAGENTS_RETRY_PARAMETERS = Type.Object({
|
|
2447
|
+
id: Type.String({ description: "Failed or stopped subagent ID to retry" })
|
|
2448
|
+
}, { additionalProperties: false });
|
|
2449
|
+
|
|
2450
|
+
// src/trajectory-contracts.ts
|
|
2451
|
+
var TRAJECTORY_ACTIONS = ["checkpoint-approve", "checkpoint-reject", "pause", "resume", "steer", "stop", "retry", "share"];
|
|
2452
|
+
function isTrajectoryAction(value) {
|
|
2453
|
+
return typeof value === "string" && TRAJECTORY_ACTIONS.includes(value);
|
|
2454
|
+
}
|
|
2455
|
+
function isTrajectoryTarget(value) {
|
|
2456
|
+
const target = value;
|
|
2457
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && (target.kind === "run" || target.kind === "subagent") && typeof target.id === "string" && target.id.length >= 1 && target.id.length <= 200;
|
|
2458
|
+
}
|
|
2459
|
+
function trajectoryActionError(action, target) {
|
|
2460
|
+
return target.kind === "subagent" && (action === "checkpoint-approve" || action === "checkpoint-reject" || action === "pause" || action === "resume" || action === "share") ? `Trajectory action ${action} is not supported for subagent targets` : target.kind === "run" && action === "steer" ? "Trajectory action steer is not supported for run targets" : void 0;
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
// src/trajectory.ts
|
|
2464
|
+
var TRAJECTORY_MAX_TRANSCRIPT_BYTES = 2 * 1024 * 1024;
|
|
2465
|
+
var TRAJECTORY_MAX_NON_TIMING_ENTRIES = 400;
|
|
2466
|
+
function sessionFile(reference) {
|
|
2467
|
+
const locator = reference?.locator;
|
|
2468
|
+
if (!object(locator) || typeof locator.sessionFile !== "string" || !locator.sessionFile) return void 0;
|
|
2469
|
+
return locator.sessionFile;
|
|
2470
|
+
}
|
|
2471
|
+
function transcriptToolCallId(value) {
|
|
2472
|
+
if (!object(value)) return void 0;
|
|
2473
|
+
if (typeof value.toolCallId === "string") return value.toolCallId;
|
|
2474
|
+
const message = object(value.message) ? value.message : void 0;
|
|
2475
|
+
if (message && typeof message.toolCallId === "string") return message.toolCallId;
|
|
2476
|
+
if (!message || !Array.isArray(message.content)) return void 0;
|
|
2477
|
+
for (const part of message.content) if (object(part) && typeof part.id === "string") return part.id;
|
|
2478
|
+
return void 0;
|
|
2479
|
+
}
|
|
2480
|
+
function isTimingTranscriptEntry(value) {
|
|
2481
|
+
return object(value) && value.type === "custom" && value.customType === "pi-workflows:tool-timing";
|
|
2482
|
+
}
|
|
2483
|
+
function timingToolCallId(value) {
|
|
2484
|
+
if (!object(value) || !isTimingTranscriptEntry(value) || !object(value.data)) return void 0;
|
|
2485
|
+
return typeof value.data.toolCallId === "string" ? value.data.toolCallId : void 0;
|
|
2486
|
+
}
|
|
2487
|
+
var OversizedTranscriptError = class extends Error {
|
|
2488
|
+
};
|
|
2489
|
+
async function readTranscript(path, throwOnError = false) {
|
|
2490
|
+
let handle;
|
|
2491
|
+
try {
|
|
2492
|
+
const info = await stat3(path);
|
|
2493
|
+
if (!info.isFile()) return [];
|
|
2494
|
+
const startOffset = Math.max(0, info.size - TRAJECTORY_MAX_TRANSCRIPT_BYTES);
|
|
2495
|
+
const length = info.size - startOffset;
|
|
2496
|
+
handle = await open2(path, "r");
|
|
2497
|
+
const buffer = Buffer.alloc(length);
|
|
2498
|
+
let bytesRead = 0;
|
|
2499
|
+
while (bytesRead < length) {
|
|
2500
|
+
const result = await handle.read(buffer, bytesRead, length - bytesRead, startOffset + bytesRead);
|
|
2501
|
+
if (result.bytesRead === 0) break;
|
|
2502
|
+
bytesRead += result.bytesRead;
|
|
2503
|
+
}
|
|
2504
|
+
let content = buffer.subarray(0, bytesRead).toString("utf8");
|
|
2505
|
+
if (startOffset > 0) {
|
|
2506
|
+
const boundary = Buffer.alloc(1);
|
|
2507
|
+
const boundaryRead = await handle.read(boundary, 0, 1, startOffset - 1);
|
|
2508
|
+
const startsAtRecordBoundary = boundaryRead.bytesRead === 1 && boundary[0] === 10;
|
|
2509
|
+
const firstLine = content.indexOf("\n");
|
|
2510
|
+
if (firstLine < 0) {
|
|
2511
|
+
if (!startsAtRecordBoundary && throwOnError) throw new OversizedTranscriptError("Transcript is too large");
|
|
2512
|
+
if (!startsAtRecordBoundary) return [];
|
|
2513
|
+
} else if (firstLine === content.length - 1 && content.slice(0, firstLine).trim() && !startsAtRecordBoundary) {
|
|
2514
|
+
if (throwOnError) throw new OversizedTranscriptError("Transcript is too large");
|
|
2515
|
+
return [];
|
|
2516
|
+
} else content = content.slice(firstLine + 1);
|
|
2517
|
+
}
|
|
2518
|
+
const entries = [];
|
|
2519
|
+
for (const line of content.split("\n")) {
|
|
2520
|
+
if (!line.trim()) continue;
|
|
2521
|
+
try {
|
|
2522
|
+
const value = JSON.parse(line);
|
|
2523
|
+
if (object(value)) entries.push(value);
|
|
2524
|
+
} catch {
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
const retainedEntries = entries.filter((entry) => !isTimingTranscriptEntry(entry)).slice(-TRAJECTORY_MAX_NON_TIMING_ENTRIES);
|
|
2528
|
+
const retainedIds = new Set(retainedEntries.map(transcriptToolCallId).filter((id) => id !== void 0));
|
|
2529
|
+
const retainedTiming = entries.filter((entry) => {
|
|
2530
|
+
const id = timingToolCallId(entry);
|
|
2531
|
+
return id !== void 0 && retainedIds.has(id);
|
|
2532
|
+
});
|
|
2533
|
+
const retained = /* @__PURE__ */ new Set([...retainedEntries, ...retainedTiming]);
|
|
2534
|
+
return entries.filter((entry) => retained.has(entry));
|
|
2535
|
+
} catch (error) {
|
|
2536
|
+
if (throwOnError) throw error;
|
|
2537
|
+
return [];
|
|
2538
|
+
} finally {
|
|
2539
|
+
await handle?.close();
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
function transcriptPaths(run) {
|
|
2543
|
+
const paths = /* @__PURE__ */ new Map();
|
|
2544
|
+
for (const agent of run.agents) {
|
|
2545
|
+
const path = sessionFile(agent.attemptDetails?.at(-1)?.session);
|
|
2546
|
+
if (path) paths.set(agent.id, path);
|
|
2547
|
+
}
|
|
2548
|
+
return paths;
|
|
2549
|
+
}
|
|
2550
|
+
async function runTranscripts(run) {
|
|
2551
|
+
const result = {};
|
|
2552
|
+
for (const [agentId, path] of transcriptPaths(run)) result[agentId] = await readTranscript(path);
|
|
2553
|
+
return result;
|
|
2554
|
+
}
|
|
2555
|
+
function applySystemPrompts(run, prompts) {
|
|
2556
|
+
if (!prompts.length) return run;
|
|
2557
|
+
const bySession = /* @__PURE__ */ new Map();
|
|
2558
|
+
for (const entry of prompts) bySession.set(entry.sessionId, entry.prompt);
|
|
2559
|
+
const agents = run.agents.map((agent) => {
|
|
2560
|
+
if (agent.systemPrompt) return agent;
|
|
2561
|
+
const sessionId = agent.attemptDetails?.at(-1)?.session?.sessionId;
|
|
2562
|
+
const prompt = typeof sessionId === "string" ? bySession.get(sessionId) : void 0;
|
|
2563
|
+
if (prompt === void 0) return agent;
|
|
2564
|
+
return { ...agent, systemPrompt: prompt };
|
|
2565
|
+
});
|
|
2566
|
+
if (agents.every((agent, index) => agent === run.agents[index])) return run;
|
|
2567
|
+
return { ...run, agents };
|
|
2568
|
+
}
|
|
2569
|
+
function toolDefinitionsFor(tools, catalog) {
|
|
2570
|
+
return tools.flatMap((name) => {
|
|
2571
|
+
const description = catalog.get(name);
|
|
2572
|
+
return description ? [{ name, description }] : [];
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
function applyToolDescriptions(run, catalog) {
|
|
2576
|
+
const agents = run.agents.map((agent) => {
|
|
2577
|
+
if (agent.toolDefinitions?.length) return agent;
|
|
2578
|
+
const toolDefinitions = toolDefinitionsFor(agent.tools, catalog);
|
|
2579
|
+
if (!toolDefinitions.length) return agent;
|
|
2580
|
+
return { ...agent, toolDefinitions };
|
|
2581
|
+
});
|
|
2582
|
+
if (agents.every((agent, index) => agent === run.agents[index])) return run;
|
|
2583
|
+
return { ...run, agents };
|
|
2584
|
+
}
|
|
2585
|
+
function piToolCatalog(cwd = process.cwd()) {
|
|
2586
|
+
return new Map([
|
|
2587
|
+
createReadToolDefinition(cwd),
|
|
2588
|
+
createBashToolDefinition(cwd),
|
|
2589
|
+
createGrepToolDefinition(cwd),
|
|
2590
|
+
createFindToolDefinition(cwd),
|
|
2591
|
+
createLsToolDefinition(cwd),
|
|
2592
|
+
createEditToolDefinition(cwd),
|
|
2593
|
+
createWriteToolDefinition(cwd)
|
|
2594
|
+
].map((tool) => [tool.name, tool.description]));
|
|
2595
|
+
}
|
|
2596
|
+
function withPiToolDescriptions(run) {
|
|
2597
|
+
return applyToolDescriptions(run, piToolCatalog());
|
|
2598
|
+
}
|
|
2599
|
+
function canonicalExtensionSelector(selector, base) {
|
|
2600
|
+
const negated = selector.startsWith("!");
|
|
2601
|
+
const body = negated ? selector.slice(1) : selector;
|
|
2602
|
+
if (body === "*" || body === "**" || body.startsWith("**/")) return selector;
|
|
2603
|
+
const resolved = resolve4(base, body);
|
|
2604
|
+
if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
|
|
2605
|
+
return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
|
|
2606
|
+
}
|
|
2607
|
+
function skillNameFromPath(path) {
|
|
2608
|
+
const file = basename4(path);
|
|
2609
|
+
return file.toLowerCase() === "skill.md" ? basename4(dirname4(path)) : file;
|
|
2610
|
+
}
|
|
2611
|
+
var discoveredResourcesByCwd = /* @__PURE__ */ new Map();
|
|
2612
|
+
async function discoveredResources(cwd) {
|
|
2613
|
+
const cached = discoveredResourcesByCwd.get(cwd);
|
|
2614
|
+
if (cached) return cached;
|
|
2615
|
+
const pending = (async () => {
|
|
2616
|
+
const agentDir = getAgentDir2();
|
|
2617
|
+
const settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted: false });
|
|
2618
|
+
const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });
|
|
2619
|
+
const resolved = await packageManager.resolve();
|
|
2620
|
+
return {
|
|
2621
|
+
extensions: [...new Set(resolved.extensions.filter((entry) => entry.enabled).map((entry) => canonicalPath(entry.path)))],
|
|
2622
|
+
skills: [...new Set(resolved.skills.filter((entry) => entry.enabled).map((entry) => skillNameFromPath(entry.path)))]
|
|
2623
|
+
};
|
|
2624
|
+
})();
|
|
2625
|
+
discoveredResourcesByCwd.set(cwd, pending);
|
|
2626
|
+
pending.catch(() => discoveredResourcesByCwd.delete(cwd));
|
|
2627
|
+
return pending;
|
|
2628
|
+
}
|
|
2629
|
+
async function resolveResourceInspection(inspection, cwd, selectorCwd = cwd) {
|
|
2630
|
+
if (!inspection || inspection.skills.length && inspection.extensions.length) return inspection;
|
|
2631
|
+
let discovered;
|
|
2632
|
+
try {
|
|
2633
|
+
discovered = await discoveredResources(cwd);
|
|
2634
|
+
} catch {
|
|
2635
|
+
return inspection;
|
|
2636
|
+
}
|
|
2637
|
+
const sources = inspection.selectorSources ?? { global: inspection.selectors, project: {} };
|
|
2638
|
+
const skills = inspection.skills.length ? inspection.skills : selectResourcesByLayers([sources.global.skills, sources.project.skills, sources.role?.skills, sources.call?.skills], discovered.skills);
|
|
2639
|
+
const extensions = inspection.extensions.length ? inspection.extensions : selectResourcesByLayers([sources.global.extensions, sources.project.extensions, sources.role?.extensions, sources.call?.extensions].map((layer) => layer?.map((selector) => canonicalExtensionSelector(selector, selectorCwd))), discovered.extensions);
|
|
2640
|
+
return skills === inspection.skills && extensions === inspection.extensions ? inspection : { ...inspection, skills, extensions };
|
|
2641
|
+
}
|
|
2642
|
+
async function withResolvedResources(run, cwd) {
|
|
2643
|
+
const agents = await Promise.all(run.agents.map(async (agent) => {
|
|
2644
|
+
const details = agent.attemptDetails;
|
|
2645
|
+
const last = details?.at(-1);
|
|
2646
|
+
if (!details || !last) return agent;
|
|
2647
|
+
const resourceSelectors = await resolveResourceInspection(last.setup.resourceSelectors, cwd, last.setup.cwd || cwd);
|
|
2648
|
+
if (resourceSelectors === last.setup.resourceSelectors) return agent;
|
|
2649
|
+
return { ...agent, attemptDetails: [...details.slice(0, -1), { ...last, setup: { ...last.setup, ...resourceSelectors === void 0 ? {} : { resourceSelectors } } }] };
|
|
2650
|
+
}));
|
|
2651
|
+
if (agents.every((agent, index) => agent === run.agents[index])) return run;
|
|
2652
|
+
return { ...run, agents };
|
|
2653
|
+
}
|
|
2654
|
+
function withoutAgentActivities(run) {
|
|
2655
|
+
const agents = run.agents.map((agent) => {
|
|
2656
|
+
if (agent.activity === void 0) return agent;
|
|
2657
|
+
const next = { ...agent };
|
|
2658
|
+
delete next.activity;
|
|
2659
|
+
return next;
|
|
2660
|
+
});
|
|
2661
|
+
return agents.every((agent, index) => agent === run.agents[index]) ? run : { ...run, agents };
|
|
2662
|
+
}
|
|
2663
|
+
async function loadTrajectoryRun(store, includeTranscripts = true) {
|
|
2664
|
+
const value = await store.load();
|
|
2665
|
+
const summary = await store.loadSummary().catch(() => void 0);
|
|
2666
|
+
const prompts = await store.systemPrompts().catch(() => []);
|
|
2667
|
+
const run = withoutAgentActivities(await withResolvedResources(withPiToolDescriptions(applySystemPrompts(value.run, prompts)), store.cwd));
|
|
2668
|
+
return { run, snapshot: value.snapshot, awaiting: await store.awaitingCheckpoints(), ...summary?.createdAt === void 0 ? {} : { createdAt: summary.createdAt }, transcripts: includeTranscripts ? await runTranscripts(run) : {} };
|
|
2669
|
+
}
|
|
2670
|
+
async function loadTrajectoryRuns(cwd, sessionId, home = homedir5(), overlay) {
|
|
2671
|
+
const loaded = [];
|
|
2672
|
+
for (const runId of await listRunIds(cwd, sessionId, home, false)) {
|
|
2673
|
+
try {
|
|
2674
|
+
const value = await loadTrajectoryRun(new RunStore(cwd, sessionId, runId, home));
|
|
2675
|
+
loaded.push(overlay ? { ...value, run: overlay(value.run) } : value);
|
|
2676
|
+
} catch {
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
return loaded;
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
// trajectory/src/export.ts
|
|
2683
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
2684
|
+
import { mkdtemp, readFile as readFile5, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
|
|
2685
|
+
import { homedir as homedir6, tmpdir } from "node:os";
|
|
2686
|
+
import { join as join7 } from "node:path";
|
|
2687
|
+
var DEFAULT_SHARE_VIEWER_URL = "https://vekexasia.github.io/pi-extensible-workflows/run.html";
|
|
2688
|
+
function inlineAsset(html, tag, replacement) {
|
|
2689
|
+
if (!html.includes(tag)) throw new Error(`Trajectory UI is missing the expected ${tag} reference`);
|
|
2690
|
+
return html.replace(tag, () => replacement);
|
|
2691
|
+
}
|
|
2692
|
+
async function exportTrajectoryRunHtml(options) {
|
|
2693
|
+
const home = options.home ?? homedir6();
|
|
2694
|
+
const runs = await loadTrajectoryRuns(options.cwd, options.sessionId, home);
|
|
2695
|
+
const run = runs.find((candidate) => candidate.run.id === options.runId);
|
|
2696
|
+
if (!run) throw new Error(`Workflow run ${options.runId} was not found for session ${options.sessionId}`);
|
|
2697
|
+
const assets = new URL("./assets/", import.meta.url);
|
|
2698
|
+
const [html, marked, morphdom, favicon] = await Promise.all([
|
|
2699
|
+
readFile5(new URL("index.html", assets), "utf8"),
|
|
2700
|
+
readFile5(new URL("marked.min.js", assets)),
|
|
2701
|
+
readFile5(new URL("morphdom.min.js", assets)),
|
|
2702
|
+
readFile5(new URL("favicon.png", assets))
|
|
2703
|
+
]);
|
|
2704
|
+
const state = {
|
|
2705
|
+
type: "state",
|
|
2706
|
+
publishers: [{ id: `export-${options.runId}`, title: `run ${options.runId.slice(0, 8)}`, cwd: options.cwd, sessionId: options.sessionId, themes: true, connected: true, runs: [run], subagents: [] }],
|
|
2707
|
+
updatedAt: Date.now()
|
|
2708
|
+
};
|
|
2709
|
+
const script = (source) => `<script src="data:text/javascript;base64,${source.toString("base64")}"></script>`;
|
|
2710
|
+
let output = html;
|
|
2711
|
+
output = inlineAsset(output, '<link rel="icon" type="image/png" href="./favicon.png" />', `<link rel="icon" type="image/png" href="data:image/png;base64,${favicon.toString("base64")}" />`);
|
|
2712
|
+
output = inlineAsset(output, '<script src="./marked.min.js"></script>', script(marked));
|
|
2713
|
+
output = inlineAsset(output, '<script src="./morphdom.min.js"></script>', script(morphdom));
|
|
2714
|
+
output = inlineAsset(output, '<body data-view="run">', `<body data-view="run">
|
|
2715
|
+
<script>window.__PIEWF_STATIC__ = ${JSON.stringify(state).replace(/</g, "\\u003c")};</script>`);
|
|
2716
|
+
return output;
|
|
2717
|
+
}
|
|
2718
|
+
function runGh(ghPath, args) {
|
|
2719
|
+
return new Promise((resolve5, reject) => {
|
|
2720
|
+
execFile3(ghPath, [...args], { encoding: "utf8", maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
|
|
2721
|
+
if (error) {
|
|
2722
|
+
const notFound = error.code === "ENOENT";
|
|
2723
|
+
reject(new Error(notFound ? "GitHub CLI (gh) is not installed. Install it from https://cli.github.com/ and run `gh auth login`." : stderr.trim() || error.message));
|
|
2724
|
+
return;
|
|
2725
|
+
}
|
|
2726
|
+
resolve5(stdout);
|
|
2727
|
+
});
|
|
2728
|
+
});
|
|
2729
|
+
}
|
|
2730
|
+
async function shareTrajectoryRun(options) {
|
|
2731
|
+
const html = await exportTrajectoryRunHtml(options);
|
|
2732
|
+
const directory = await mkdtemp(join7(tmpdir(), "piewf-share-"));
|
|
2733
|
+
try {
|
|
2734
|
+
const file = join7(directory, "trajectory.html");
|
|
2735
|
+
await writeFile3(file, html);
|
|
2736
|
+
const stdout = await runGh(options.ghPath ?? "gh", ["gist", "create", "--public=false", file]);
|
|
2737
|
+
const gistUrl = stdout.trim().split("\n").pop() ?? "";
|
|
2738
|
+
const gistId = gistUrl.split("/").pop() ?? "";
|
|
2739
|
+
if (!/^[0-9a-f]+$/i.test(gistId)) throw new Error(`Could not parse the gist ID from gh output: ${stdout.trim()}`);
|
|
2740
|
+
const base = process.env.PIEWF_SHARE_VIEWER_URL || DEFAULT_SHARE_VIEWER_URL;
|
|
2741
|
+
return { gistUrl, shareUrl: `${base}#${gistId}` };
|
|
2742
|
+
} finally {
|
|
2743
|
+
await rm4(directory, { recursive: true, force: true });
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
// trajectory/src/index.ts
|
|
2748
|
+
var DEFAULT_TRAJECTORY_PORT = 7432;
|
|
2749
|
+
var TRAJECTORY_IDLE_EXIT_MS = 5 * 60 * 1e3;
|
|
2750
|
+
var TRAJECTORY_LOCK_NAME = "trajectory.lock";
|
|
2751
|
+
function trajectoryLockPath(agentDir) {
|
|
2752
|
+
return join8(agentDir, "pi-extensible-workflows", TRAJECTORY_LOCK_NAME);
|
|
2753
|
+
}
|
|
2754
|
+
function trajectoryServerPath(moduleDirectory = dirname5(fileURLToPath3(import.meta.url))) {
|
|
2755
|
+
const candidates = [join8(moduleDirectory, "server.js"), join8(moduleDirectory, "../../dist/trajectory/src/server.js")];
|
|
2756
|
+
const path = candidates.find((candidate) => existsSync(candidate));
|
|
2757
|
+
if (!path) throw new Error("Trajectory server implementation is unavailable");
|
|
2758
|
+
return path;
|
|
2759
|
+
}
|
|
2760
|
+
async function trajectoryFingerprint(serverPath) {
|
|
2761
|
+
const [serverBytes, htmlBytes] = await Promise.all([readFile6(serverPath), readFile6(join8(dirname5(serverPath), "assets/index.html"))]);
|
|
2762
|
+
return `${createHash3("sha256").update(serverBytes).digest("hex")}:${createHash3("sha256").update(htmlBytes).digest("hex")}`;
|
|
2763
|
+
}
|
|
2764
|
+
function publisherId(cwd, sessionId) {
|
|
2765
|
+
return createHash3("sha256").update(`${cwd}
|
|
2766
|
+
${sessionId}`).digest("hex").slice(0, 16);
|
|
2767
|
+
}
|
|
2768
|
+
function trajectoryPort(value) {
|
|
2769
|
+
return positiveInteger(value) && value <= 65535 ? value : DEFAULT_TRAJECTORY_PORT;
|
|
2770
|
+
}
|
|
2771
|
+
function delay(ms) {
|
|
2772
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
2773
|
+
}
|
|
2774
|
+
async function serverHealthy(port) {
|
|
2775
|
+
try {
|
|
2776
|
+
const response = await fetch(`http://127.0.0.1:${String(port)}/health`, { signal: AbortSignal.timeout(300) });
|
|
2777
|
+
return response.ok;
|
|
2778
|
+
} catch {
|
|
2779
|
+
return false;
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
function signalProcess(pid, signal) {
|
|
2783
|
+
try {
|
|
2784
|
+
process.kill(pid, signal);
|
|
2785
|
+
} catch (error) {
|
|
2786
|
+
if (!isNodeError(error, "ESRCH")) throw error;
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
async function stopStaleServer(lock) {
|
|
2790
|
+
if (lock.pid === process.pid) return;
|
|
2791
|
+
if (!await processAlive(lock.pid, lock.startedAt)) return;
|
|
2792
|
+
signalProcess(lock.pid, "SIGTERM");
|
|
2793
|
+
const deadline = Date.now() + 3e3;
|
|
2794
|
+
while (Date.now() < deadline) {
|
|
2795
|
+
if (!await serverHealthy(lock.port)) break;
|
|
2796
|
+
const remaining = deadline - Date.now();
|
|
2797
|
+
if (remaining <= 0) break;
|
|
2798
|
+
await delay(Math.min(50, remaining));
|
|
2799
|
+
}
|
|
2800
|
+
if (await processAlive(lock.pid, lock.startedAt)) signalProcess(lock.pid, "SIGKILL");
|
|
2801
|
+
}
|
|
2802
|
+
async function readLock(path) {
|
|
2803
|
+
try {
|
|
2804
|
+
const parsed = JSON.parse(await readFile6(path, "utf8"));
|
|
2805
|
+
if (!object(parsed) || !positiveInteger(parsed.pid) || !positiveInteger(parsed.port) || parsed.port > 65535) return void 0;
|
|
2806
|
+
const fingerprint = typeof parsed.fingerprint === "string" ? parsed.fingerprint : void 0;
|
|
2807
|
+
const startedAt = positiveInteger(parsed.startedAt) ? parsed.startedAt : void 0;
|
|
2808
|
+
return { pid: parsed.pid, port: parsed.port, ...fingerprint === void 0 ? {} : { fingerprint }, ...startedAt === void 0 ? {} : { startedAt } };
|
|
2809
|
+
} catch (error) {
|
|
2810
|
+
if (isNodeError(error, "ENOENT")) return void 0;
|
|
2811
|
+
return void 0;
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
async function waitForServer(port) {
|
|
2815
|
+
for (let attempt = 0; attempt < 60; attempt += 1) {
|
|
2816
|
+
if (await serverHealthy(port)) return;
|
|
2817
|
+
await delay(50);
|
|
2818
|
+
}
|
|
2819
|
+
throw new Error(`Trajectory server did not start on port ${String(port)}`);
|
|
2820
|
+
}
|
|
2821
|
+
async function resolveExistingServer(lockPath, existing, fingerprint) {
|
|
2822
|
+
if (await serverHealthy(existing.port)) {
|
|
2823
|
+
if (existing.fingerprint === fingerprint) return existing;
|
|
2824
|
+
await stopStaleServer(existing);
|
|
2825
|
+
await rm5(lockPath, { force: true });
|
|
2826
|
+
return void 0;
|
|
2827
|
+
}
|
|
2828
|
+
if (await processAlive(existing.pid, existing.startedAt)) {
|
|
2829
|
+
try {
|
|
2830
|
+
await waitForServer(existing.port);
|
|
2831
|
+
return existing;
|
|
2832
|
+
} catch {
|
|
2833
|
+
await stopStaleServer(existing);
|
|
2834
|
+
await rm5(lockPath, { force: true });
|
|
2835
|
+
return void 0;
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
await rm5(lockPath, { force: true });
|
|
2839
|
+
return void 0;
|
|
2840
|
+
}
|
|
2841
|
+
async function ensureTrajectoryServer(agentDir, configuredPort) {
|
|
2842
|
+
const lockPath = trajectoryLockPath(agentDir);
|
|
2843
|
+
const serverPath = trajectoryServerPath();
|
|
2844
|
+
const fingerprint = await trajectoryFingerprint(serverPath);
|
|
2845
|
+
await mkdir3(dirname5(lockPath), { recursive: true, mode: 448 });
|
|
2846
|
+
const existing = await readLock(lockPath);
|
|
2847
|
+
if (existing) {
|
|
2848
|
+
const reused = await resolveExistingServer(lockPath, existing, fingerprint);
|
|
2849
|
+
if (reused) return reused;
|
|
2850
|
+
}
|
|
2851
|
+
let lockHandle;
|
|
2852
|
+
try {
|
|
2853
|
+
lockHandle = await open3(lockPath, "wx", 384);
|
|
2854
|
+
await lockHandle.writeFile(`${JSON.stringify({ pid: process.pid, port: configuredPort, fingerprint, startedAt: Date.now() })}
|
|
2855
|
+
`, "utf8");
|
|
2856
|
+
} catch (error) {
|
|
2857
|
+
if (isNodeError(error, "EEXIST")) {
|
|
2858
|
+
const raced = await readLock(lockPath);
|
|
2859
|
+
if (raced) {
|
|
2860
|
+
const reused = await resolveExistingServer(lockPath, raced, fingerprint);
|
|
2861
|
+
if (reused) return reused;
|
|
2862
|
+
}
|
|
2863
|
+
await rm5(lockPath, { force: true });
|
|
2864
|
+
lockHandle = await open3(lockPath, "wx", 384);
|
|
2865
|
+
await lockHandle.writeFile(`${JSON.stringify({ pid: process.pid, port: configuredPort, fingerprint, startedAt: Date.now() })}
|
|
2866
|
+
`, "utf8");
|
|
2867
|
+
} else {
|
|
2868
|
+
throw error;
|
|
2869
|
+
}
|
|
2870
|
+
} finally {
|
|
2871
|
+
await lockHandle?.close();
|
|
2872
|
+
}
|
|
2873
|
+
try {
|
|
2874
|
+
const child = spawn(process.versions.bun ? "node" : process.execPath, [serverPath, "--port", String(configuredPort), "--lock", lockPath, "--fingerprint", fingerprint], { detached: true, stdio: "ignore" });
|
|
2875
|
+
const startupError = new Promise((_resolve, reject) => {
|
|
2876
|
+
child.once("error", reject);
|
|
2877
|
+
});
|
|
2878
|
+
child.unref();
|
|
2879
|
+
await Promise.race([waitForServer(configuredPort), startupError]);
|
|
2880
|
+
return { port: configuredPort };
|
|
2881
|
+
} catch (error) {
|
|
2882
|
+
await rm5(lockPath, { force: true });
|
|
2883
|
+
throw error;
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
function trajectoryWebSocket() {
|
|
2887
|
+
const candidate = globalThis.WebSocket;
|
|
2888
|
+
return typeof candidate === "function" ? candidate : void 0;
|
|
2889
|
+
}
|
|
2890
|
+
function openBrowser(url) {
|
|
2891
|
+
const command = process.platform === "win32" ? "rundll32" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
2892
|
+
const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
2893
|
+
try {
|
|
2894
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore", windowsHide: true });
|
|
2895
|
+
child.once("error", () => void 0);
|
|
2896
|
+
child.unref();
|
|
2897
|
+
} catch {
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
function trajectoryUrl(port) {
|
|
2901
|
+
return `http://127.0.0.1:${String(port)}/`;
|
|
2902
|
+
}
|
|
2903
|
+
function openTrajectoryUrl(url) {
|
|
2904
|
+
openBrowser(url);
|
|
2905
|
+
}
|
|
2906
|
+
var MAX_FRAME_BYTES = 32 * 1024 * 1024;
|
|
2907
|
+
var MAX_LIVE_STATE_BYTES = MAX_FRAME_BYTES - 1024;
|
|
2908
|
+
var MAX_TRANSCRIPT_REQUESTS = 64;
|
|
2909
|
+
var TRANSCRIPT_REQUEST_TIMEOUT_MS = 1e4;
|
|
2910
|
+
var RECONNECT_INITIAL_DELAY_MS = 100;
|
|
2911
|
+
var RECONNECT_MAX_DELAY_MS = 5e3;
|
|
2912
|
+
function isTimingEntry(value) {
|
|
2913
|
+
return object(value) && value.type === "custom" && value.customType === "pi-workflows:tool-timing";
|
|
2914
|
+
}
|
|
2915
|
+
function boundedString(value, maxLength = 1024) {
|
|
2916
|
+
if (typeof value !== "string") return value;
|
|
2917
|
+
const bytes = Buffer.from(value);
|
|
2918
|
+
return bytes.length > maxLength ? bytes.subarray(0, maxLength).toString("utf8") : value;
|
|
2919
|
+
}
|
|
2920
|
+
var MAX_LIVE_STRING_BYTES = 64 * 1024;
|
|
2921
|
+
var MAX_LIVE_ARRAY_ENTRIES = 256;
|
|
2922
|
+
var MAX_LIVE_OBJECT_KEYS = 64;
|
|
2923
|
+
var LIVE_METADATA_ARRAY_KEYS = /* @__PURE__ */ new Set(["agents", "runs", "subagents"]);
|
|
2924
|
+
var LIVE_TRUNCATABLE_ARRAY_KEYS = /* @__PURE__ */ new Set(["events", "phaseHistory"]);
|
|
2925
|
+
var LIVE_METADATA_OBJECT_KEYS = /* @__PURE__ */ new Set(["transcripts"]);
|
|
2926
|
+
function boundedTiming(value) {
|
|
2927
|
+
const entries = Array.isArray(value) ? value.filter(isTimingEntry) : [];
|
|
2928
|
+
const retained = [];
|
|
2929
|
+
let bytes = 2;
|
|
2930
|
+
for (const entry of entries) {
|
|
2931
|
+
let serialized;
|
|
2932
|
+
try {
|
|
2933
|
+
serialized = JSON.stringify(entry);
|
|
2934
|
+
} catch {
|
|
2935
|
+
continue;
|
|
2936
|
+
}
|
|
2937
|
+
const nextBytes = bytes + (retained.length ? 1 : 0) + Buffer.byteLength(serialized);
|
|
2938
|
+
if (nextBytes >= 64 * 1024) break;
|
|
2939
|
+
retained.push(entry);
|
|
2940
|
+
bytes = nextBytes;
|
|
2941
|
+
}
|
|
2942
|
+
return retained;
|
|
2943
|
+
}
|
|
2944
|
+
function boundedLiveValue(value, key = "", depth = 0) {
|
|
2945
|
+
if (typeof value === "string") return boundedString(value, MAX_LIVE_STRING_BYTES);
|
|
2946
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) return value;
|
|
2947
|
+
if (depth >= 12) return void 0;
|
|
2948
|
+
if (key === "timing") return boundedTiming(value);
|
|
2949
|
+
if (Array.isArray(value)) {
|
|
2950
|
+
const array = value;
|
|
2951
|
+
const maxEntries = LIVE_TRUNCATABLE_ARRAY_KEYS.has(key) ? MAX_LIVE_ARRAY_ENTRIES - 1 : MAX_LIVE_ARRAY_ENTRIES;
|
|
2952
|
+
const entries = key === "attemptDetails" ? array.slice(-8) : LIVE_METADATA_ARRAY_KEYS.has(key) ? array : array.length <= maxEntries ? array : [...array.slice(0, 32), ...array.slice(-maxEntries + 32)];
|
|
2953
|
+
const bounded = entries.map((entry) => boundedLiveValue(entry, "", depth + 1));
|
|
2954
|
+
if (entries.length !== array.length && LIVE_TRUNCATABLE_ARRAY_KEYS.has(key)) bounded.push({ type: "trajectory:truncated", field: key, omitted: array.length - entries.length });
|
|
2955
|
+
return bounded;
|
|
2956
|
+
}
|
|
2957
|
+
if (object(value)) {
|
|
2958
|
+
const result = {};
|
|
2959
|
+
const properties = LIVE_METADATA_OBJECT_KEYS.has(key) ? Object.keys(value).sort() : Object.keys(value).sort().slice(0, MAX_LIVE_OBJECT_KEYS);
|
|
2960
|
+
for (const property of properties) result[property] = boundedLiveValue(value[property], property, depth + 1);
|
|
2961
|
+
return result;
|
|
2962
|
+
}
|
|
2963
|
+
return void 0;
|
|
2964
|
+
}
|
|
2965
|
+
function transcriptRevisionProjection(value, depth = 0) {
|
|
2966
|
+
if (depth >= 8) return typeof value === "string" ? boundedString(value, 1024) : typeof value === "number" || typeof value === "boolean" || value === null ? value : void 0;
|
|
2967
|
+
if (Array.isArray(value)) {
|
|
2968
|
+
const entries = value;
|
|
2969
|
+
const sample = entries.length <= 16 ? entries : [...entries.slice(0, 8), ...entries.slice(-8)];
|
|
2970
|
+
return { length: entries.length, values: sample.map((entry) => transcriptRevisionProjection(entry, depth + 1)) };
|
|
2971
|
+
}
|
|
2972
|
+
if (object(value)) {
|
|
2973
|
+
const result = {};
|
|
2974
|
+
for (const key of Object.keys(value).sort().slice(0, 32)) result[key] = transcriptRevisionProjection(value[key], depth + 1);
|
|
2975
|
+
return result;
|
|
2976
|
+
}
|
|
2977
|
+
return value;
|
|
2978
|
+
}
|
|
2979
|
+
function sourceMetadata(value, key, revisions) {
|
|
2980
|
+
if (object(value) && !Array.isArray(value) && typeof value.revision === "number" && typeof value.status === "string") return { ...value, timing: boundedTiming(value.timing) };
|
|
2981
|
+
const entries = Array.isArray(value) ? value : [];
|
|
2982
|
+
const signature = createHash3("sha256").update(JSON.stringify(transcriptRevisionProjection(entries))).digest("hex");
|
|
2983
|
+
const previous = revisions.get(key);
|
|
2984
|
+
const revision = previous?.signature === signature ? previous.revision : (previous?.revision ?? 0) + 1;
|
|
2985
|
+
revisions.set(key, { signature, revision });
|
|
2986
|
+
return { revision, status: entries.length ? "available" : "empty", timing: boundedTiming(entries) };
|
|
2987
|
+
}
|
|
2988
|
+
function projectRun(value, key, revisions) {
|
|
2989
|
+
const run = object(value.run) && !Array.isArray(value.run) ? value.run : {};
|
|
2990
|
+
const rawAgents = Array.isArray(run.agents) ? run.agents : [];
|
|
2991
|
+
const agents = rawAgents.map((agent) => {
|
|
2992
|
+
if (!object(agent)) return agent;
|
|
2993
|
+
if (agent.activity === void 0) return agent;
|
|
2994
|
+
const activity = object(agent.activity) ? agent.activity : {};
|
|
2995
|
+
return { ...agent, activity: { ...activity, text: boundedString(activity.text) } };
|
|
2996
|
+
});
|
|
2997
|
+
const transcripts = {};
|
|
2998
|
+
const source = object(value.transcripts) && !Array.isArray(value.transcripts) ? value.transcripts : {};
|
|
2999
|
+
for (const [agentId, transcript] of Object.entries(source)) transcripts[agentId] = sourceMetadata(transcript, `${key} ${agentId}`, revisions);
|
|
3000
|
+
return { ...value, run: { ...run, agents }, transcripts };
|
|
3001
|
+
}
|
|
3002
|
+
function projectSubagent(value, key, revisions) {
|
|
3003
|
+
return { ...value, transcript: sourceMetadata(value.transcript, key, revisions) };
|
|
3004
|
+
}
|
|
3005
|
+
function projectPublisher(metadata, publisher, revisions) {
|
|
3006
|
+
const publisherId2 = typeof publisher.id === "string" ? publisher.id : "";
|
|
3007
|
+
return { ...publisher, runs: metadata.runs.map((run) => projectRun(run, `${publisherId2} ${run.run.id}`, revisions)), subagents: metadata.subagents.map((subagent) => projectSubagent(subagent, `${publisherId2} subagent ${subagent.id}`, revisions)) };
|
|
3008
|
+
}
|
|
3009
|
+
function minimalStatePublisher(publisher) {
|
|
3010
|
+
return boundedLiveValue(publisher) ?? {};
|
|
3011
|
+
}
|
|
3012
|
+
function publisherStateFrame(publisher, runs, subagents, truncated = false) {
|
|
3013
|
+
const publisherSummary = { ...publisher };
|
|
3014
|
+
delete publisherSummary.runs;
|
|
3015
|
+
delete publisherSummary.subagents;
|
|
3016
|
+
return { type: "publisher:state", publisher: { ...publisherSummary, ...truncated ? { truncated: true } : {} }, runs, subagents, ...truncated ? { truncated: true } : {} };
|
|
3017
|
+
}
|
|
3018
|
+
function encodeLiveState(publisher) {
|
|
3019
|
+
const projected = minimalStatePublisher(publisher);
|
|
3020
|
+
const publisherJson = JSON.stringify(publisherStateFrame(projected, [], []).publisher);
|
|
3021
|
+
const runs = Array.isArray(projected.runs) ? projected.runs : [];
|
|
3022
|
+
const subagents = Array.isArray(projected.subagents) ? projected.subagents : [];
|
|
3023
|
+
const prefix = `{"type":"publisher:state","publisher":${publisherJson},"runs":[`;
|
|
3024
|
+
const baseBytes = Buffer.byteLength(`${prefix}],"subagents":[]}`);
|
|
3025
|
+
const truncatedBytes = Buffer.byteLength(',"truncated":true');
|
|
3026
|
+
const selectedRuns = [];
|
|
3027
|
+
const selectedSubagents = [];
|
|
3028
|
+
let bytes = baseBytes;
|
|
3029
|
+
const state = { truncated: false };
|
|
3030
|
+
const add = (serialized, target) => {
|
|
3031
|
+
const nextBytes = bytes + Buffer.byteLength(serialized) + (target.length ? 1 : 0);
|
|
3032
|
+
if (nextBytes + truncatedBytes >= MAX_LIVE_STATE_BYTES) return false;
|
|
3033
|
+
target.push(serialized);
|
|
3034
|
+
bytes = nextBytes;
|
|
3035
|
+
return true;
|
|
3036
|
+
};
|
|
3037
|
+
const addValue = (value, target) => {
|
|
3038
|
+
if (bytes + truncatedBytes >= MAX_LIVE_STATE_BYTES) {
|
|
3039
|
+
state.truncated = true;
|
|
3040
|
+
return;
|
|
3041
|
+
}
|
|
3042
|
+
let serializedValue;
|
|
3043
|
+
try {
|
|
3044
|
+
serializedValue = JSON.stringify(value);
|
|
3045
|
+
} catch {
|
|
3046
|
+
state.truncated = true;
|
|
3047
|
+
return;
|
|
3048
|
+
}
|
|
3049
|
+
if (typeof serializedValue !== "string" || !add(serializedValue, target)) state.truncated = true;
|
|
3050
|
+
};
|
|
3051
|
+
for (let index = 0; index < Math.max(runs.length, subagents.length); index += 1) {
|
|
3052
|
+
const run = runs[index];
|
|
3053
|
+
if (run !== void 0) addValue(run, selectedRuns);
|
|
3054
|
+
const subagent = subagents[index];
|
|
3055
|
+
if (subagent !== void 0) addValue(subagent, selectedSubagents);
|
|
3056
|
+
}
|
|
3057
|
+
return `${prefix}${selectedRuns.join(",")}],"subagents":[${selectedSubagents.join(",")}]${state.truncated ? ',"truncated":true' : ""}}`;
|
|
3058
|
+
}
|
|
3059
|
+
function createTrajectoryController(agentDir) {
|
|
3060
|
+
let socket;
|
|
3061
|
+
let connectionGeneration = 0;
|
|
3062
|
+
let pollTimer;
|
|
3063
|
+
let reconnectTimer;
|
|
3064
|
+
let reconnectLoop;
|
|
3065
|
+
let currentInput;
|
|
3066
|
+
let configuredPort;
|
|
3067
|
+
let closing = false;
|
|
3068
|
+
const transcriptRevisions = /* @__PURE__ */ new Map();
|
|
3069
|
+
const pendingTranscripts = /* @__PURE__ */ new Map();
|
|
3070
|
+
const stopPolling = () => {
|
|
3071
|
+
if (pollTimer !== void 0) {
|
|
3072
|
+
clearInterval(pollTimer);
|
|
3073
|
+
pollTimer = void 0;
|
|
3074
|
+
}
|
|
3075
|
+
};
|
|
3076
|
+
const clearTranscriptRequests = () => {
|
|
3077
|
+
for (const request of pendingTranscripts.values()) clearTimeout(request.timer);
|
|
3078
|
+
pendingTranscripts.clear();
|
|
3079
|
+
};
|
|
3080
|
+
const startPolling = () => {
|
|
3081
|
+
stopPolling();
|
|
3082
|
+
pollTimer = setInterval(() => {
|
|
3083
|
+
void sendState().catch((error) => {
|
|
3084
|
+
if (!closing) console.error(`Trajectory state publish failed: ${errorText(error)}`);
|
|
3085
|
+
});
|
|
3086
|
+
}, 1e3);
|
|
3087
|
+
pollTimer.unref();
|
|
3088
|
+
};
|
|
3089
|
+
let stateLoad;
|
|
3090
|
+
let lastState;
|
|
3091
|
+
const publisherValue = (input) => ({ id: publisherId(input.cwd, input.sessionId), title: `session ${input.sessionId.slice(0, 8)}`, cwd: input.cwd, sessionId: input.sessionId, themes: input.themes, connected: true });
|
|
3092
|
+
const sendState = async () => {
|
|
3093
|
+
const activeSocket = socket;
|
|
3094
|
+
if (activeSocket !== void 0 && stateLoad?.socket === activeSocket) return stateLoad.task;
|
|
3095
|
+
const task = (async () => {
|
|
3096
|
+
const input = currentInput;
|
|
3097
|
+
if (!activeSocket || !input || activeSocket.readyState !== 1) return;
|
|
3098
|
+
const metadata = await input.loadMetadata();
|
|
3099
|
+
if (closing || socket !== activeSocket) return;
|
|
3100
|
+
const publisher = projectPublisher(metadata, publisherValue(input), transcriptRevisions);
|
|
3101
|
+
const serialized = encodeLiveState(publisher);
|
|
3102
|
+
if (serialized === lastState) return;
|
|
3103
|
+
lastState = serialized;
|
|
3104
|
+
activeSocket.send(serialized);
|
|
3105
|
+
})();
|
|
3106
|
+
if (activeSocket !== void 0) stateLoad = { socket: activeSocket, task };
|
|
3107
|
+
try {
|
|
3108
|
+
await task;
|
|
3109
|
+
} finally {
|
|
3110
|
+
if (stateLoad?.task === task) stateLoad = void 0;
|
|
3111
|
+
}
|
|
3112
|
+
};
|
|
3113
|
+
const abandonSocket = (candidate) => {
|
|
3114
|
+
if (socket !== candidate) return;
|
|
3115
|
+
socket = void 0;
|
|
3116
|
+
lastState = void 0;
|
|
3117
|
+
stopPolling();
|
|
3118
|
+
clearTranscriptRequests();
|
|
3119
|
+
candidate.close();
|
|
3120
|
+
};
|
|
3121
|
+
const publishConnectedState = async (next, input) => {
|
|
3122
|
+
startPolling();
|
|
3123
|
+
try {
|
|
3124
|
+
await sendState();
|
|
3125
|
+
if (closing || currentInput !== input) return;
|
|
3126
|
+
if (socket !== next || next.readyState !== 1) throw new Error("Trajectory connection closed before state publish");
|
|
3127
|
+
} catch (error) {
|
|
3128
|
+
if (!closing && socket === next && currentInput === input) abandonSocket(next);
|
|
3129
|
+
throw error;
|
|
3130
|
+
}
|
|
3131
|
+
};
|
|
3132
|
+
const transcriptFromFallback = async (input, request) => {
|
|
3133
|
+
const boundedTranscriptResult = (result) => {
|
|
3134
|
+
try {
|
|
3135
|
+
if (result.status === "available" && Buffer.byteLength(JSON.stringify(result.entries)) > TRAJECTORY_MAX_TRANSCRIPT_BYTES) return { status: "oversized", revision: result.revision, entries: [], error: "Transcript is too large" };
|
|
3136
|
+
} catch {
|
|
3137
|
+
return { status: "failed", revision: result.revision, entries: [], error: "Transcript failed" };
|
|
3138
|
+
}
|
|
3139
|
+
return result;
|
|
3140
|
+
};
|
|
3141
|
+
const boundedResult = (entries, revision) => {
|
|
3142
|
+
try {
|
|
3143
|
+
if (Buffer.byteLength(JSON.stringify(entries)) > TRAJECTORY_MAX_TRANSCRIPT_BYTES) return { status: "oversized", revision, entries: [], error: "Transcript is too large" };
|
|
3144
|
+
} catch {
|
|
3145
|
+
return { status: "failed", revision, entries: [], error: "Transcript failed" };
|
|
3146
|
+
}
|
|
3147
|
+
return { status: entries.length ? "available" : "empty", revision, entries };
|
|
3148
|
+
};
|
|
3149
|
+
if (input.loadTranscript) {
|
|
3150
|
+
const result = await input.loadTranscript(request);
|
|
3151
|
+
const revisionChanged = request.revision !== void 0 && result.revision !== request.revision && result.status !== "missing" && result.status !== "failed" && result.status !== "oversized" && result.status !== "disconnected";
|
|
3152
|
+
return boundedTranscriptResult(revisionChanged ? { ...result, status: "available", entries: [], error: "Transcript revision is stale" } : result);
|
|
3153
|
+
}
|
|
3154
|
+
if (request.runId !== void 0 && request.agentId !== void 0 && request.subagentId === void 0) {
|
|
3155
|
+
const runs = await input.loadRuns();
|
|
3156
|
+
const run = runs.find((candidate) => candidate.run.id === request.runId);
|
|
3157
|
+
const entries = run?.transcripts[request.agentId];
|
|
3158
|
+
const metadata = sourceMetadata(entries, `${publisherId(input.cwd, input.sessionId)} ${request.runId} ${request.agentId}`, transcriptRevisions);
|
|
3159
|
+
if (request.revision !== void 0 && request.revision !== metadata.revision) return { status: "available", revision: Number(metadata.revision), entries: [], error: "Transcript revision is stale" };
|
|
3160
|
+
if (run === void 0 || entries === void 0) return { status: "missing", revision: Number(metadata.revision), entries: [], error: "Transcript not found" };
|
|
3161
|
+
return boundedResult(entries, Number(metadata.revision));
|
|
3162
|
+
}
|
|
3163
|
+
if (request.subagentId !== void 0 && request.runId === void 0 && request.agentId === void 0) {
|
|
3164
|
+
const subagents = await input.loadSubagents();
|
|
3165
|
+
const subagent = subagents.find((candidate) => candidate.id === request.subagentId);
|
|
3166
|
+
const entries = subagent?.transcript;
|
|
3167
|
+
const metadata = sourceMetadata(entries, `${publisherId(input.cwd, input.sessionId)} subagent ${request.subagentId}`, transcriptRevisions);
|
|
3168
|
+
if (request.revision !== void 0 && request.revision !== metadata.revision) return { status: "available", revision: Number(metadata.revision), entries: [], error: "Transcript revision is stale" };
|
|
3169
|
+
if (subagent === void 0 || entries === void 0) return { status: "missing", revision: Number(metadata.revision), entries: [], error: "Transcript not found" };
|
|
3170
|
+
return boundedResult(entries, Number(metadata.revision));
|
|
3171
|
+
}
|
|
3172
|
+
return { status: "missing", revision: 0, entries: [], error: "Transcript not found" };
|
|
3173
|
+
};
|
|
3174
|
+
const sendTranscriptResult = (next, generation, message, result) => {
|
|
3175
|
+
const requestId = String(message.requestId);
|
|
3176
|
+
const pending = pendingTranscripts.get(requestId);
|
|
3177
|
+
if (!pending || pending.generation !== generation || socket !== next || closing || pending.publisherId !== message.publisherId || pending.runId !== message.runId || pending.agentId !== message.agentId || pending.subagentId !== message.subagentId || pending.revision !== message.revision) return;
|
|
3178
|
+
clearTimeout(pending.timer);
|
|
3179
|
+
pendingTranscripts.delete(requestId);
|
|
3180
|
+
const failed = result.error !== void 0 || result.status === "missing" || result.status === "failed" || result.status === "oversized" || result.status === "disconnected";
|
|
3181
|
+
let safeResult = result;
|
|
3182
|
+
try {
|
|
3183
|
+
if (!failed && Buffer.byteLength(JSON.stringify(result.entries)) > TRAJECTORY_MAX_TRANSCRIPT_BYTES) safeResult = { status: "oversized", revision: result.revision, entries: [], error: "Transcript is too large" };
|
|
3184
|
+
} catch {
|
|
3185
|
+
safeResult = { status: "failed", revision: result.revision, entries: [], error: "Transcript failed" };
|
|
3186
|
+
}
|
|
3187
|
+
const safeFailed = safeResult.error !== void 0 || safeResult.status === "missing" || safeResult.status === "failed" || safeResult.status === "oversized" || safeResult.status === "disconnected";
|
|
3188
|
+
const error = typeof safeResult.error === "string" ? safeResult.error.slice(0, 1024) : safeResult.status;
|
|
3189
|
+
next.send(JSON.stringify({ type: "publisher:transcript-result", requestId: message.requestId, publisherId: message.publisherId, ...message.runId === void 0 ? {} : { runId: message.runId }, ...message.agentId === void 0 ? {} : { agentId: message.agentId }, ...message.subagentId === void 0 ? {} : { subagentId: message.subagentId }, ...message.revision === void 0 ? {} : { requestedRevision: message.revision }, ok: !safeFailed, status: safeResult.status, revision: safeResult.revision, ...safeFailed ? { error } : { entries: safeResult.entries } }));
|
|
3190
|
+
};
|
|
3191
|
+
const handleTranscriptRequest = (next, generation, message, input) => {
|
|
3192
|
+
const requestId = typeof message.requestId === "string" ? message.requestId : "";
|
|
3193
|
+
const revision = typeof message.revision === "number" && Number.isSafeInteger(message.revision) && message.revision >= 0 ? message.revision : void 0;
|
|
3194
|
+
const target = { ...typeof message.runId === "string" ? { runId: message.runId } : {}, ...typeof message.agentId === "string" ? { agentId: message.agentId } : {}, ...typeof message.subagentId === "string" ? { subagentId: message.subagentId } : {}, ...revision === void 0 ? {} : { requestedRevision: revision } };
|
|
3195
|
+
const existing = pendingTranscripts.get(requestId);
|
|
3196
|
+
if (!requestId || existing === void 0 && pendingTranscripts.size >= MAX_TRANSCRIPT_REQUESTS) {
|
|
3197
|
+
next.send(JSON.stringify({ type: "publisher:transcript-result", requestId, publisherId: message.publisherId, ...target, ok: false, status: "failed", revision: revision ?? 0, error: "Too many transcript requests" }));
|
|
3198
|
+
return;
|
|
3199
|
+
}
|
|
3200
|
+
if (existing !== void 0) {
|
|
3201
|
+
const original = { requestId, publisherId: existing.publisherId, ...existing.runId === void 0 ? {} : { runId: existing.runId }, ...existing.agentId === void 0 ? {} : { agentId: existing.agentId }, ...existing.subagentId === void 0 ? {} : { subagentId: existing.subagentId }, ...existing.revision === void 0 ? {} : { revision: existing.revision } };
|
|
3202
|
+
sendTranscriptResult(next, generation, original, { status: "failed", revision: existing.revision ?? 0, entries: [], error: "Duplicate transcript request" });
|
|
3203
|
+
return;
|
|
3204
|
+
}
|
|
3205
|
+
const timer = setTimeout(() => {
|
|
3206
|
+
const pending2 = pendingTranscripts.get(requestId);
|
|
3207
|
+
if (!pending2 || pending2.timer !== timer) return;
|
|
3208
|
+
pendingTranscripts.delete(requestId);
|
|
3209
|
+
if (socket === next && !closing && next.readyState === 1) next.send(JSON.stringify({ type: "publisher:transcript-result", requestId, publisherId: pending2.publisherId, ...pending2.runId === void 0 ? {} : { runId: pending2.runId }, ...pending2.agentId === void 0 ? {} : { agentId: pending2.agentId }, ...pending2.subagentId === void 0 ? {} : { subagentId: pending2.subagentId }, ...pending2.revision === void 0 ? {} : { requestedRevision: pending2.revision }, ok: false, status: "failed", revision: pending2.revision ?? 0, error: "Transcript request timed out" }));
|
|
3210
|
+
}, TRANSCRIPT_REQUEST_TIMEOUT_MS);
|
|
3211
|
+
const pending = { generation, timer, publisherId: message.publisherId, ...typeof message.runId === "string" ? { runId: message.runId } : {}, ...typeof message.agentId === "string" ? { agentId: message.agentId } : {}, ...typeof message.subagentId === "string" ? { subagentId: message.subagentId } : {}, ...revision === void 0 ? {} : { revision } };
|
|
3212
|
+
pendingTranscripts.set(requestId, pending);
|
|
3213
|
+
const request = { ...typeof message.runId === "string" ? { runId: message.runId } : {}, ...typeof message.agentId === "string" ? { agentId: message.agentId } : {}, ...typeof message.subagentId === "string" ? { subagentId: message.subagentId } : {}, ...revision === void 0 ? {} : { revision } };
|
|
3214
|
+
const requestMessage = { ...message, revision };
|
|
3215
|
+
void transcriptFromFallback(input, request).then((result) => {
|
|
3216
|
+
sendTranscriptResult(next, generation, requestMessage, result);
|
|
3217
|
+
}, (error) => {
|
|
3218
|
+
sendTranscriptResult(next, generation, requestMessage, { status: "failed", revision: 0, entries: [], error: errorText(error) });
|
|
3219
|
+
}).catch(() => void 0);
|
|
3220
|
+
};
|
|
3221
|
+
const connect = async (port, input) => {
|
|
3222
|
+
const Constructor = trajectoryWebSocket();
|
|
3223
|
+
if (!Constructor) throw new Error("Trajectory requires a WebSocket-capable Node runtime");
|
|
3224
|
+
const next = new Constructor(`ws://127.0.0.1:${String(port)}/ws`);
|
|
3225
|
+
const generation = connectionGeneration + 1;
|
|
3226
|
+
let established = false;
|
|
3227
|
+
connectionGeneration = generation;
|
|
3228
|
+
socket = next;
|
|
3229
|
+
const onClose = () => {
|
|
3230
|
+
if (socket !== next) return;
|
|
3231
|
+
socket = void 0;
|
|
3232
|
+
lastState = void 0;
|
|
3233
|
+
stopPolling();
|
|
3234
|
+
clearTranscriptRequests();
|
|
3235
|
+
if (established && !closing) scheduleReconnect();
|
|
3236
|
+
};
|
|
3237
|
+
next.addEventListener("close", onClose);
|
|
3238
|
+
next.addEventListener("error", onClose);
|
|
3239
|
+
next.addEventListener("message", (event) => {
|
|
3240
|
+
try {
|
|
3241
|
+
const message = JSON.parse(typeof event === "object" && event !== null && "data" in event ? String(event.data) : "");
|
|
3242
|
+
if (!object(message)) return;
|
|
3243
|
+
if (message.type === "publisher:replaced") {
|
|
3244
|
+
established = false;
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
if (message.type === "publisher:transcript" && typeof message.requestId === "string" && typeof message.publisherId === "string" && (typeof message.runId === "string" && typeof message.agentId === "string" || typeof message.subagentId === "string")) {
|
|
3248
|
+
handleTranscriptRequest(next, generation, message, input);
|
|
3249
|
+
return;
|
|
3250
|
+
}
|
|
3251
|
+
if (message.type !== "publisher:action" || typeof message.requestId !== "string") return;
|
|
3252
|
+
const sendActionResponse = (response) => {
|
|
3253
|
+
if (socket !== next || closing || next.readyState !== 1) return;
|
|
3254
|
+
let serialized;
|
|
3255
|
+
try {
|
|
3256
|
+
serialized = JSON.stringify(response);
|
|
3257
|
+
} catch {
|
|
3258
|
+
serialized = JSON.stringify({ type: "publisher:action-result", requestId: response.requestId, publisherId: response.publisherId, ok: false, error: "Trajectory action result is invalid" });
|
|
3259
|
+
}
|
|
3260
|
+
if (Buffer.byteLength(serialized) >= MAX_FRAME_BYTES) serialized = JSON.stringify({ type: "publisher:action-result", requestId: response.requestId, publisherId: response.publisherId, ok: false, error: "Trajectory action result is too large" });
|
|
3261
|
+
next.send(serialized);
|
|
3262
|
+
};
|
|
3263
|
+
const sendActionResult = (value) => {
|
|
3264
|
+
sendActionResponse({ type: "publisher:action-result", requestId: message.requestId, publisherId: publisherId(input.cwd, input.sessionId), ...object(value) ? { ok: true, result: value } : { ok: true } });
|
|
3265
|
+
};
|
|
3266
|
+
if (!isTrajectoryAction(message.action)) {
|
|
3267
|
+
sendActionResponse({ type: "publisher:action-result", requestId: message.requestId, publisherId: publisherId(input.cwd, input.sessionId), ok: false, error: "Unsupported Trajectory action" });
|
|
3268
|
+
return;
|
|
3269
|
+
}
|
|
3270
|
+
if (!isTrajectoryTarget(message.target)) {
|
|
3271
|
+
sendActionResponse({ type: "publisher:action-result", requestId: message.requestId, publisherId: publisherId(input.cwd, input.sessionId), ok: false, error: "Invalid Trajectory action target" });
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
const target = message.target;
|
|
3275
|
+
const actionError = trajectoryActionError(message.action, target);
|
|
3276
|
+
if (actionError !== void 0) {
|
|
3277
|
+
sendActionResponse({ type: "publisher:action-result", requestId: message.requestId, publisherId: publisherId(input.cwd, input.sessionId), ok: false, error: actionError });
|
|
3278
|
+
return;
|
|
3279
|
+
}
|
|
3280
|
+
if (message.action === "share") {
|
|
3281
|
+
void shareTrajectoryRun({ cwd: input.cwd, sessionId: input.sessionId, runId: target.id }).then((result) => {
|
|
3282
|
+
sendActionResult(result);
|
|
3283
|
+
}, (error) => {
|
|
3284
|
+
sendActionResponse({ type: "publisher:action-result", requestId: message.requestId, publisherId: publisherId(input.cwd, input.sessionId), ok: false, error: errorText(error).slice(0, 1024) });
|
|
3285
|
+
}).catch(() => void 0);
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
void input.handleAction({ action: message.action, target, ...typeof message.name === "string" ? { name: message.name } : {}, ...message.payload === void 0 ? {} : { payload: message.payload } }).then((result) => {
|
|
3289
|
+
sendActionResult(result);
|
|
3290
|
+
}, (error) => {
|
|
3291
|
+
sendActionResponse({ type: "publisher:action-result", requestId: message.requestId, publisherId: publisherId(input.cwd, input.sessionId), ok: false, error: errorText(error).slice(0, 1024) });
|
|
3292
|
+
}).catch(() => void 0);
|
|
3293
|
+
} catch {
|
|
3294
|
+
}
|
|
3295
|
+
});
|
|
3296
|
+
await new Promise((resolve5, reject) => {
|
|
3297
|
+
let settled = false;
|
|
3298
|
+
const finish = (error) => {
|
|
3299
|
+
if (settled) return;
|
|
3300
|
+
settled = true;
|
|
3301
|
+
if (error) reject(error);
|
|
3302
|
+
else resolve5();
|
|
3303
|
+
};
|
|
3304
|
+
next.addEventListener("open", () => {
|
|
3305
|
+
finish();
|
|
3306
|
+
});
|
|
3307
|
+
next.addEventListener("error", () => {
|
|
3308
|
+
finish(new Error("Could not connect to Trajectory server"));
|
|
3309
|
+
});
|
|
3310
|
+
next.addEventListener("close", () => {
|
|
3311
|
+
finish(new Error("Could not connect to Trajectory server"));
|
|
3312
|
+
});
|
|
3313
|
+
});
|
|
3314
|
+
if (closing || currentInput !== input || socket !== next || next.readyState !== 1) {
|
|
3315
|
+
next.close();
|
|
3316
|
+
throw new Error("Trajectory connection was replaced");
|
|
3317
|
+
}
|
|
3318
|
+
established = true;
|
|
3319
|
+
next.send(JSON.stringify({ type: "publisher:attach", publisherId: publisherId(input.cwd, input.sessionId) }));
|
|
3320
|
+
};
|
|
3321
|
+
let reconnectCancel;
|
|
3322
|
+
const waitReconnect = (ms) => new Promise((resolve5) => {
|
|
3323
|
+
const timer = setTimeout(() => {
|
|
3324
|
+
if (reconnectTimer === timer) reconnectTimer = void 0;
|
|
3325
|
+
reconnectCancel = void 0;
|
|
3326
|
+
resolve5();
|
|
3327
|
+
}, ms);
|
|
3328
|
+
timer.unref();
|
|
3329
|
+
reconnectTimer = timer;
|
|
3330
|
+
reconnectCancel = () => {
|
|
3331
|
+
clearTimeout(timer);
|
|
3332
|
+
if (reconnectTimer === timer) reconnectTimer = void 0;
|
|
3333
|
+
reconnectCancel = void 0;
|
|
3334
|
+
resolve5();
|
|
3335
|
+
};
|
|
3336
|
+
});
|
|
3337
|
+
const scheduleReconnect = () => {
|
|
3338
|
+
if (closing || reconnectLoop !== void 0 || configuredPort === void 0) return;
|
|
3339
|
+
const reconnectInput = () => closing || socket !== void 0 || currentInput === void 0 || configuredPort === void 0 ? void 0 : { port: configuredPort, input: currentInput };
|
|
3340
|
+
reconnectLoop = (async () => {
|
|
3341
|
+
let backoff = RECONNECT_INITIAL_DELAY_MS;
|
|
3342
|
+
while (reconnectInput() !== void 0) {
|
|
3343
|
+
await waitReconnect(backoff);
|
|
3344
|
+
const active = reconnectInput();
|
|
3345
|
+
if (active === void 0) return;
|
|
3346
|
+
const { port, input } = active;
|
|
3347
|
+
const isCurrent = () => !closing && currentInput === input && configuredPort === port;
|
|
3348
|
+
try {
|
|
3349
|
+
const server = await ensureTrajectoryServer(agentDir, port);
|
|
3350
|
+
if (!isCurrent()) return;
|
|
3351
|
+
await connect(server.port, input);
|
|
3352
|
+
if (!isCurrent()) return;
|
|
3353
|
+
const activeSocket = socket;
|
|
3354
|
+
if (!activeSocket) throw new Error("Trajectory connection closed before state publish");
|
|
3355
|
+
await publishConnectedState(activeSocket, input);
|
|
3356
|
+
return;
|
|
3357
|
+
} catch (error) {
|
|
3358
|
+
console.error(`Trajectory reconnect failed: ${errorText(error)}`);
|
|
3359
|
+
backoff = Math.min(RECONNECT_MAX_DELAY_MS, backoff * 2);
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
})().finally(() => {
|
|
3363
|
+
reconnectLoop = void 0;
|
|
3364
|
+
if (!closing && socket === void 0 && currentInput !== void 0 && configuredPort !== void 0) scheduleReconnect();
|
|
3365
|
+
});
|
|
3366
|
+
};
|
|
3367
|
+
return {
|
|
3368
|
+
async open(input) {
|
|
3369
|
+
closing = false;
|
|
3370
|
+
currentInput = input;
|
|
3371
|
+
const envPort = process.env.PI_WORKFLOW_TRAJECTORY_PORT;
|
|
3372
|
+
configuredPort = envPort !== void 0 && /^\d+$/.test(envPort) ? trajectoryPort(Number(envPort)) : trajectoryPort(input.port);
|
|
3373
|
+
const server = await ensureTrajectoryServer(agentDir, configuredPort);
|
|
3374
|
+
if (!socket || socket.readyState !== 1) await connect(server.port, input);
|
|
3375
|
+
stopPolling();
|
|
3376
|
+
const activeSocket = socket;
|
|
3377
|
+
if (!activeSocket) throw new Error("Trajectory connection closed before state publish");
|
|
3378
|
+
await publishConnectedState(activeSocket, input);
|
|
3379
|
+
return server;
|
|
3380
|
+
},
|
|
3381
|
+
async close() {
|
|
3382
|
+
closing = true;
|
|
3383
|
+
const input = currentInput;
|
|
3384
|
+
const reconnect = reconnectLoop;
|
|
3385
|
+
currentInput = void 0;
|
|
3386
|
+
configuredPort = void 0;
|
|
3387
|
+
reconnectCancel?.();
|
|
3388
|
+
stopPolling();
|
|
3389
|
+
clearTranscriptRequests();
|
|
3390
|
+
lastState = void 0;
|
|
3391
|
+
connectionGeneration += 1;
|
|
3392
|
+
const activeSocket = socket;
|
|
3393
|
+
if (activeSocket?.readyState === 1 && input) activeSocket.send(JSON.stringify({ type: "publisher:detach", publisherId: publisherId(input.cwd, input.sessionId) }));
|
|
3394
|
+
socket = void 0;
|
|
3395
|
+
activeSocket?.close();
|
|
3396
|
+
if (reconnect) await reconnect;
|
|
3397
|
+
}
|
|
3398
|
+
};
|
|
3399
|
+
}
|
|
3400
|
+
function registerTrajectoryExtension(pi, options = {}) {
|
|
3401
|
+
const controller = options.controller ?? createTrajectoryController(options.agentDir ?? getAgentDir3());
|
|
3402
|
+
const openUrl = options.openUrl ?? openTrajectoryUrl;
|
|
3403
|
+
let autoAttached = false;
|
|
3404
|
+
const notify = (context, message, level) => {
|
|
3405
|
+
const current = object(context) ? context : void 0;
|
|
3406
|
+
const ui = current && object(current.ui) ? current.ui : void 0;
|
|
3407
|
+
if (typeof ui?.notify === "function") Reflect.apply(ui.notify, ui, [message, level]);
|
|
3408
|
+
};
|
|
3409
|
+
const attach = async (provider, context) => {
|
|
3410
|
+
try {
|
|
3411
|
+
return await controller.open(provider(context));
|
|
3412
|
+
} catch (error) {
|
|
3413
|
+
notify(context, `Unable to attach Trajectory: ${errorText(error)}`, "error");
|
|
3414
|
+
return void 0;
|
|
3415
|
+
}
|
|
3416
|
+
};
|
|
3417
|
+
const open4 = async (provider, context) => {
|
|
3418
|
+
const server = await attach(provider, context);
|
|
3419
|
+
if (server === void 0) return;
|
|
3420
|
+
const url = trajectoryUrl(server.port);
|
|
3421
|
+
openUrl(url);
|
|
3422
|
+
notify(context, `Trajectory opened at ${url}`, "info");
|
|
3423
|
+
};
|
|
3424
|
+
const host = {
|
|
3425
|
+
open: open4,
|
|
3426
|
+
autoAttach(provider, context) {
|
|
3427
|
+
if (autoAttached || !context.hasUI) return;
|
|
3428
|
+
autoAttached = true;
|
|
3429
|
+
void attach(provider, context);
|
|
3430
|
+
},
|
|
3431
|
+
close: () => controller.close()
|
|
3432
|
+
};
|
|
3433
|
+
setTrajectoryHost(host);
|
|
3434
|
+
pi.on("session_shutdown", async () => {
|
|
3435
|
+
try {
|
|
3436
|
+
await host.close();
|
|
3437
|
+
} finally {
|
|
3438
|
+
clearTrajectoryHost(host);
|
|
3439
|
+
}
|
|
3440
|
+
});
|
|
3441
|
+
return host;
|
|
3442
|
+
}
|
|
3443
|
+
function extension(pi) {
|
|
3444
|
+
registerTrajectoryExtension(pi);
|
|
3445
|
+
}
|
|
3446
|
+
export {
|
|
3447
|
+
DEFAULT_TRAJECTORY_PORT,
|
|
3448
|
+
TRAJECTORY_IDLE_EXIT_MS,
|
|
3449
|
+
TRAJECTORY_LOCK_NAME,
|
|
3450
|
+
createTrajectoryController,
|
|
3451
|
+
extension as default,
|
|
3452
|
+
exportTrajectoryRunHtml,
|
|
3453
|
+
openTrajectoryUrl,
|
|
3454
|
+
registerTrajectoryExtension,
|
|
3455
|
+
shareTrajectoryRun,
|
|
3456
|
+
trajectoryServerPath,
|
|
3457
|
+
trajectoryUrl
|
|
3458
|
+
};
|
|
3459
|
+
//# sourceMappingURL=index.js.map
|