@marcoscale98/pi-extensible-workflows 5.14.1-fork.1 → 5.17.0-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 +67 -0
- package/dist/src/agent-execution.js +36 -51
- package/dist/src/background-widget.d.ts +56 -0
- package/dist/src/background-widget.js +22 -15
- package/dist/src/decoders.js +2 -1
- package/dist/src/host-navigator.d.ts +18 -0
- package/dist/src/host-navigator.js +71 -13
- package/dist/src/host-view.d.ts +16 -1
- package/dist/src/host-view.js +109 -42
- package/dist/src/host.js +57 -24
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +867 -545
- package/dist/src/index.js.map +3 -3
- package/dist/src/registry.js +2 -1
- package/dist/src/roles.d.ts +70 -0
- package/dist/src/roles.js +325 -0
- package/dist/src/settings.d.ts +13 -0
- package/dist/src/settings.js +253 -0
- package/dist/src/trajectory.d.ts +29 -4
- package/dist/src/trajectory.js +58 -3
- package/dist/src/types.d.ts +0 -1
- package/dist/src/utils.d.ts +2 -1
- package/dist/src/utils.js +10 -0
- package/dist/src/validation.d.ts +5 -17
- package/dist/src/validation.js +9 -458
- package/dist/subagents/index.js +1132 -694
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +4 -0
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +2 -1
- package/dist/subagents/src/manager.js +29 -10
- package/dist/subagents/src/navigator.js +396 -247
- package/dist/subagents/src/view.d.ts +23 -2
- package/dist/subagents/src/view.js +96 -30
- package/dist/trajectory/assets/index.html +347 -231
- package/dist/trajectory/assets/prism.min.js +13 -0
- package/dist/trajectory/index.js +377 -257
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +347 -231
- package/dist/trajectory/src/assets/prism.min.js +13 -0
- package/dist/trajectory/src/export.js +4 -2
- package/dist/trajectory/src/index.d.ts +2 -0
- package/dist/trajectory/src/index.js +143 -42
- package/dist/trajectory/src/server.js +134 -9
- package/dist/trajectory/src/server.js.map +1 -1
- package/package.json +2 -1
- package/src/agent-execution.ts +36 -42
- package/src/background-widget.ts +25 -18
- package/src/decoders.ts +2 -1
- package/src/host-navigator.ts +58 -14
- package/src/host-view.ts +93 -37
- package/src/host.ts +51 -21
- package/src/index.ts +3 -1
- package/src/registry.ts +2 -1
- package/src/roles.ts +326 -0
- package/src/settings.ts +206 -0
- package/src/trajectory.ts +76 -7
- package/src/types.ts +1 -1
- package/src/utils.ts +8 -1
- package/src/validation.ts +12 -356
- package/subagents/README.md +1 -1
- package/subagents/src/contracts.ts +4 -0
- package/subagents/src/index.ts +3 -2
- package/subagents/src/manager.ts +28 -10
- package/subagents/src/navigator.ts +336 -180
- package/subagents/src/view.ts +97 -28
- package/trajectory/src/assets/index.html +347 -231
- package/trajectory/src/assets/prism.min.js +13 -0
- package/trajectory/src/export.ts +4 -2
- package/trajectory/src/index.ts +118 -38
- package/trajectory/src/server.ts +112 -12
package/dist/src/index.js
CHANGED
|
@@ -370,6 +370,12 @@ function createLaunchSnapshot(input) {
|
|
|
370
370
|
function loadLaunchSnapshot(input) {
|
|
371
371
|
return deepFreeze(structuredClone(input));
|
|
372
372
|
}
|
|
373
|
+
function validateSchema(schema, at = "schema") {
|
|
374
|
+
if (!object(schema) || Object.getPrototypeOf(schema) !== Object.prototype || !jsonValue(schema)) fail("INVALID_SCHEMA", `${at} must be a plain JSON-compatible Schema object`);
|
|
375
|
+
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`);
|
|
376
|
+
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`);
|
|
377
|
+
if (schema.properties !== void 0 && !object(schema.properties)) fail("INVALID_SCHEMA", `${at}.properties must be an object`);
|
|
378
|
+
}
|
|
373
379
|
|
|
374
380
|
// src/paths.ts
|
|
375
381
|
import { createHash } from "node:crypto";
|
|
@@ -670,6 +676,23 @@ function resumeBudgetAllowed(budget, usage) {
|
|
|
670
676
|
return exhaustedBudgetDimensions(budget, usage).length === 0;
|
|
671
677
|
}
|
|
672
678
|
|
|
679
|
+
// src/validation.ts
|
|
680
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
681
|
+
import { resolve as resolve5 } from "node:path";
|
|
682
|
+
import * as acorn from "acorn";
|
|
683
|
+
import { Script } from "node:vm";
|
|
684
|
+
|
|
685
|
+
// src/roles.ts
|
|
686
|
+
import { readFileSync as readFileSync3, readdirSync, statSync } from "node:fs";
|
|
687
|
+
import { basename as basename3, dirname as dirname4, extname, join as join6, resolve as resolve4 } from "node:path";
|
|
688
|
+
import { getAgentDir as getAgentDir2, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
689
|
+
|
|
690
|
+
// src/registry.ts
|
|
691
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
692
|
+
import { basename as basename2, dirname as dirname3, isAbsolute, join as join5 } from "node:path";
|
|
693
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
694
|
+
import { Value } from "typebox/value";
|
|
695
|
+
|
|
673
696
|
// src/io.ts
|
|
674
697
|
import { execFile } from "node:child_process";
|
|
675
698
|
import { randomUUID } from "node:crypto";
|
|
@@ -690,7 +713,7 @@ async function renameWithRetry(from, to) {
|
|
|
690
713
|
} catch (error) {
|
|
691
714
|
const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
|
|
692
715
|
if (!retryableRenameError(error) || delayMs === void 0) throw error;
|
|
693
|
-
await new Promise((
|
|
716
|
+
await new Promise((resolve9) => setTimeout(resolve9, delayMs));
|
|
694
717
|
}
|
|
695
718
|
}
|
|
696
719
|
}
|
|
@@ -894,7 +917,7 @@ function decodeWorkflowExtensions(value) {
|
|
|
894
917
|
}
|
|
895
918
|
if (namespace === "trajectory") {
|
|
896
919
|
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;
|
|
897
|
-
normalized.trajectory = { ...raw.port === void 0 ? {} : { port: raw.port }
|
|
920
|
+
normalized.trajectory = { ...raw.port === void 0 ? {} : { port: raw.port } };
|
|
898
921
|
continue;
|
|
899
922
|
}
|
|
900
923
|
if (!jsonValue(raw)) return void 0;
|
|
@@ -2356,20 +2379,234 @@ var RunStore = class _RunStore {
|
|
|
2356
2379
|
}
|
|
2357
2380
|
};
|
|
2358
2381
|
|
|
2359
|
-
// src/
|
|
2360
|
-
import { mkdirSync, readFileSync
|
|
2382
|
+
// src/settings.ts
|
|
2383
|
+
import { mkdirSync, readFileSync } from "node:fs";
|
|
2361
2384
|
import { homedir as homedir4 } from "node:os";
|
|
2362
|
-
import {
|
|
2363
|
-
import { fileURLToPath
|
|
2364
|
-
import
|
|
2365
|
-
|
|
2366
|
-
|
|
2385
|
+
import { dirname as dirname2, join as join4, resolve as resolve3 } from "node:path";
|
|
2386
|
+
import { fileURLToPath } from "node:url";
|
|
2387
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
2388
|
+
var ROLE_DIRECTORY = "pi-extensible-workflows";
|
|
2389
|
+
var DEFAULT_SETTINGS = Object.freeze({ concurrency: 8, backgroundWidget: true });
|
|
2390
|
+
function workflowSettingsPath(agentDir = getAgentDir()) {
|
|
2391
|
+
return join4(agentDir, ROLE_DIRECTORY, "settings.json");
|
|
2392
|
+
}
|
|
2393
|
+
function normalizedResourcePath(value, settingsPath) {
|
|
2394
|
+
if (value === "*") return value;
|
|
2395
|
+
let expanded = value === "~" ? homedir4() : value.startsWith("~/") || value.startsWith("~\\") ? join4(homedir4(), value.slice(2)) : value;
|
|
2396
|
+
if (expanded.startsWith("file://")) expanded = fileURLToPath(expanded);
|
|
2397
|
+
const resolved = resolve3(dirname2(settingsPath), expanded);
|
|
2398
|
+
if (expanded === "**" || expanded.startsWith("**/") || expanded.startsWith("**\\")) return expanded;
|
|
2399
|
+
if (resourcePatternHasMagic(expanded)) {
|
|
2400
|
+
const magicIndex = resolved.search(/[*?\x5b\x5d{}()]/);
|
|
2401
|
+
const separatorIndex = Math.max(resolved.lastIndexOf("/", magicIndex), resolved.lastIndexOf("\\", magicIndex));
|
|
2402
|
+
const rootBoundary = separatorIndex === 0 || separatorIndex === 2 && /^[A-Za-z]:[\\/]/.test(resolved);
|
|
2403
|
+
const prefix = rootBoundary ? resolved.slice(0, separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(0, separatorIndex) : resolved;
|
|
2404
|
+
const suffix = rootBoundary ? resolved.slice(separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(separatorIndex) : "";
|
|
2405
|
+
return `${canonicalPath(prefix)}${suffix}`;
|
|
2406
|
+
}
|
|
2407
|
+
return canonicalPath(resolved);
|
|
2408
|
+
}
|
|
2409
|
+
function validateSelectorList(value, path, kind, errorCode2 = "INVALID_SETTINGS", normalizeExtensions = true) {
|
|
2410
|
+
if (value === void 0) return void 0;
|
|
2411
|
+
if (!Array.isArray(value)) fail(errorCode2, `${path}.${kind} must be an array`);
|
|
2412
|
+
const normalized = [];
|
|
2413
|
+
for (const [index, entry] of value.entries()) {
|
|
2414
|
+
if (typeof entry !== "string" || !entry.trim()) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a non-empty string`);
|
|
2415
|
+
let selector = entry.trim();
|
|
2416
|
+
if (kind === "extensions" && normalizeExtensions) {
|
|
2417
|
+
const negated = selector.startsWith("!");
|
|
2418
|
+
const body = negated ? selector.slice(1) : selector;
|
|
2419
|
+
if (!body) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: Empty minimatch pattern ${JSON.stringify(selector)}`);
|
|
2420
|
+
try {
|
|
2421
|
+
selector = `${negated ? "!" : ""}${normalizedResourcePath(body, path)}`;
|
|
2422
|
+
} catch (error) {
|
|
2423
|
+
fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid path: ${errorText(error)}`);
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
try {
|
|
2427
|
+
validateResourcePattern(selector);
|
|
2428
|
+
} catch (error) {
|
|
2429
|
+
fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: ${errorText(error)}`);
|
|
2430
|
+
}
|
|
2431
|
+
normalized.push(selector);
|
|
2432
|
+
}
|
|
2433
|
+
return Object.freeze(normalized);
|
|
2434
|
+
}
|
|
2435
|
+
function validateWorktreePostCreateCommand(value, settingsPath) {
|
|
2436
|
+
if (value === void 0) return void 0;
|
|
2437
|
+
if (!Array.isArray(value) || value.length === 0) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand must be a non-empty array`);
|
|
2438
|
+
return Object.freeze(value.map((entry, index) => {
|
|
2439
|
+
if (typeof entry !== "string" || !entry.trim()) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand[${String(index)}] must be a non-empty string`);
|
|
2440
|
+
return entry;
|
|
2441
|
+
}));
|
|
2442
|
+
}
|
|
2443
|
+
function selectorsFromSettings(settings) {
|
|
2444
|
+
return {
|
|
2445
|
+
...settings.skills === void 0 ? {} : { skills: settings.skills },
|
|
2446
|
+
...settings.extensions === void 0 ? {} : { extensions: settings.extensions },
|
|
2447
|
+
...settings.tools === void 0 ? {} : { tools: settings.tools }
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
function selectorSet(value) {
|
|
2451
|
+
return { skills: [...value?.skills ?? []], extensions: [...value?.extensions ?? []], ...value?.tools === void 0 ? {} : { tools: [...value.tools] } };
|
|
2452
|
+
}
|
|
2453
|
+
function validateContextFileScopes(value, rolePath) {
|
|
2454
|
+
if (value === void 0) return void 0;
|
|
2455
|
+
if (!Array.isArray(value) || !value.every(isContextFileScope)) fail("INVALID_METADATA", `${rolePath}.contextFiles must be an array containing only global, project, or cwd`);
|
|
2456
|
+
return [...value];
|
|
2457
|
+
}
|
|
2458
|
+
function validateWorkflowExtensionSettings(value, settingsPath, errorCode2 = "INVALID_SETTINGS") {
|
|
2459
|
+
if (value === void 0) return void 0;
|
|
2460
|
+
const base = `${settingsPath}.extensionSettings`;
|
|
2461
|
+
if (!object(value)) fail(errorCode2, `${base} must be an object`);
|
|
2462
|
+
const normalized = {};
|
|
2463
|
+
for (const [namespace, raw] of Object.entries(value)) {
|
|
2464
|
+
if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode2, `${base} contains an invalid namespace: ${namespace}`);
|
|
2465
|
+
if (namespace === "herdr") {
|
|
2466
|
+
if (!object(raw)) fail(errorCode2, `${base}.herdr must be an object`);
|
|
2467
|
+
if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode")) fail(errorCode2, `${base}.herdr contains an unsupported setting`);
|
|
2468
|
+
if (raw.enableFullyInspectableMode !== void 0 && typeof raw.enableFullyInspectableMode !== "boolean") fail(errorCode2, `${base}.herdr.enableFullyInspectableMode must be a boolean`);
|
|
2469
|
+
normalized.herdr = Object.freeze({ ...raw.enableFullyInspectableMode === void 0 ? {} : { enableFullyInspectableMode: raw.enableFullyInspectableMode } });
|
|
2470
|
+
continue;
|
|
2471
|
+
}
|
|
2472
|
+
if (namespace === "trajectory") {
|
|
2473
|
+
if (!object(raw)) fail(errorCode2, `${base}.trajectory must be an object`);
|
|
2474
|
+
if (Object.keys(raw).some((key) => key !== "port")) fail(errorCode2, `${base}.trajectory contains an unsupported setting`);
|
|
2475
|
+
if (raw.port !== void 0 && (!positiveInteger(raw.port) || raw.port > 65535)) fail(errorCode2, `${base}.trajectory.port must be an integer from 1 to 65535`);
|
|
2476
|
+
normalized.trajectory = Object.freeze({ ...raw.port === void 0 ? {} : { port: raw.port } });
|
|
2477
|
+
continue;
|
|
2478
|
+
}
|
|
2479
|
+
if (!jsonValue(raw)) fail(errorCode2, `${base}.${namespace} must be JSON-compatible`);
|
|
2480
|
+
normalized[namespace] = structuredClone(raw);
|
|
2481
|
+
}
|
|
2482
|
+
return deepFreeze(normalized);
|
|
2483
|
+
}
|
|
2484
|
+
function positiveRetentionInteger(value) {
|
|
2485
|
+
return positiveInteger(value) && Number.isSafeInteger(value);
|
|
2486
|
+
}
|
|
2487
|
+
function validateRetention(value, settingsPath) {
|
|
2488
|
+
if (value === void 0) return void 0;
|
|
2489
|
+
const base = `${settingsPath}.retention`;
|
|
2490
|
+
if (!object(value)) fail("INVALID_SETTINGS", `${base} must be an object`);
|
|
2491
|
+
const unknown = Object.keys(value).find((key) => key !== "olderThanDays" && key !== "maxTerminalRuns");
|
|
2492
|
+
if (unknown) fail("INVALID_SETTINGS", `Unknown retention setting at ${base}: ${unknown}`);
|
|
2493
|
+
if (value.olderThanDays !== void 0 && !positiveRetentionInteger(value.olderThanDays)) fail("INVALID_SETTINGS", `${base}.olderThanDays must be a positive integer`);
|
|
2494
|
+
if (value.maxTerminalRuns !== void 0 && !positiveRetentionInteger(value.maxTerminalRuns)) fail("INVALID_SETTINGS", `${base}.maxTerminalRuns must be a positive integer`);
|
|
2495
|
+
return Object.freeze({ ...value.olderThanDays === void 0 ? {} : { olderThanDays: value.olderThanDays }, ...value.maxTerminalRuns === void 0 ? {} : { maxTerminalRuns: value.maxTerminalRuns } });
|
|
2496
|
+
}
|
|
2497
|
+
function parseSettings(path, partial) {
|
|
2498
|
+
let parsed;
|
|
2499
|
+
try {
|
|
2500
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
2501
|
+
} catch (error) {
|
|
2502
|
+
if (isNodeError(error, "ENOENT")) return partial ? Object.freeze({}) : DEFAULT_SETTINGS;
|
|
2503
|
+
fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
|
|
2504
|
+
}
|
|
2505
|
+
if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
|
|
2506
|
+
const allowed = /* @__PURE__ */ new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...partial ? [] : ["backgroundWidget"]]);
|
|
2507
|
+
const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
|
|
2508
|
+
if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources")) fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
|
|
2509
|
+
if (unknown) fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
|
|
2510
|
+
const concurrency = parsed.concurrency === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.concurrency : parsed.concurrency;
|
|
2511
|
+
if (concurrency !== void 0 && (!positiveInteger(concurrency) || concurrency > 16)) fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
|
|
2512
|
+
const backgroundWidget = parsed.backgroundWidget === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.backgroundWidget : parsed.backgroundWidget;
|
|
2513
|
+
if (backgroundWidget !== void 0 && typeof backgroundWidget !== "boolean") fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
|
|
2514
|
+
const modelAliases = parsed.modelAliases === void 0 ? void 0 : validateModelAliases(parsed.modelAliases, path);
|
|
2515
|
+
const skills = validateSelectorList(parsed.skills, path, "skills");
|
|
2516
|
+
const tools = validateSelectorList(parsed.tools, path, "tools");
|
|
2517
|
+
const extensions = validateSelectorList(parsed.extensions, path, "extensions");
|
|
2518
|
+
const extensionSettings = parsed.extensionSettings === void 0 ? void 0 : validateWorkflowExtensionSettings(parsed.extensionSettings, path, "INVALID_SETTINGS");
|
|
2519
|
+
const retention = validateRetention(parsed.retention, path);
|
|
2520
|
+
const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
|
|
2521
|
+
return Object.freeze({
|
|
2522
|
+
...concurrency === void 0 ? {} : { concurrency },
|
|
2523
|
+
...backgroundWidget === void 0 ? {} : { backgroundWidget },
|
|
2524
|
+
...modelAliases === void 0 ? {} : { modelAliases },
|
|
2525
|
+
...skills === void 0 ? {} : { skills },
|
|
2526
|
+
...extensions === void 0 ? {} : { extensions },
|
|
2527
|
+
...extensionSettings === void 0 ? {} : { extensionSettings },
|
|
2528
|
+
...tools === void 0 ? {} : { tools },
|
|
2529
|
+
...retention === void 0 ? {} : { retention },
|
|
2530
|
+
...worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand }
|
|
2531
|
+
});
|
|
2532
|
+
}
|
|
2533
|
+
function loadSettings(path = workflowSettingsPath()) {
|
|
2534
|
+
return parseSettings(path, false);
|
|
2535
|
+
}
|
|
2536
|
+
function loadSettingsOverrides(path) {
|
|
2537
|
+
return parseSettings(path, true);
|
|
2538
|
+
}
|
|
2539
|
+
function resolveWorkflowSettings(cwd, projectTrusted2, globalSettingsPath = workflowSettingsPath()) {
|
|
2540
|
+
const projectSettingsPath = workflowProjectSettingsPath(cwd);
|
|
2541
|
+
const global = loadSettings(globalSettingsPath);
|
|
2542
|
+
const project = projectTrusted2 ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
|
|
2543
|
+
const projectHas = (key) => Object.prototype.hasOwnProperty.call(project, key);
|
|
2544
|
+
const sourceFor = (key) => projectHas(key) ? projectSettingsPath : globalSettingsPath;
|
|
2545
|
+
const globalSelectors = selectorsFromSettings(global);
|
|
2546
|
+
const projectSelectors = selectorsFromSettings(project);
|
|
2547
|
+
const effectiveSelectors = selectorSet({
|
|
2548
|
+
skills: [...globalSelectors.skills ?? [], ...projectSelectors.skills ?? []],
|
|
2549
|
+
extensions: [...globalSelectors.extensions ?? [], ...projectSelectors.extensions ?? []],
|
|
2550
|
+
...globalSelectors.tools === void 0 && projectSelectors.tools === void 0 ? {} : { tools: [...globalSelectors.tools ?? [], ...projectSelectors.tools ?? []] }
|
|
2551
|
+
});
|
|
2552
|
+
const hasExtensionSelectors = global.extensions !== void 0 || project.extensions !== void 0;
|
|
2553
|
+
const extensionSettings = projectHas("extensionSettings") ? project.extensionSettings : global.extensionSettings;
|
|
2554
|
+
const sources = {
|
|
2555
|
+
concurrency: projectHas("concurrency") ? projectSettingsPath : globalSettingsPath,
|
|
2556
|
+
modelAliases: projectHas("modelAliases") ? projectSettingsPath : globalSettingsPath,
|
|
2557
|
+
skills: sourceFor("skills"),
|
|
2558
|
+
extensions: sourceFor("extensions"),
|
|
2559
|
+
tools: sourceFor("tools"),
|
|
2560
|
+
...extensionSettings === void 0 ? {} : { extensionSettings: projectHas("extensionSettings") ? projectSettingsPath : globalSettingsPath },
|
|
2561
|
+
...project.retention === void 0 && global.retention === void 0 ? {} : { retention: project.retention === void 0 ? globalSettingsPath : projectSettingsPath },
|
|
2562
|
+
...projectHas("worktreePostCreateCommand") || global.worktreePostCreateCommand !== void 0 ? { worktreePostCreateCommand: projectHas("worktreePostCreateCommand") ? projectSettingsPath : globalSettingsPath } : {}
|
|
2563
|
+
};
|
|
2564
|
+
const effective = Object.freeze({
|
|
2565
|
+
concurrency: project.concurrency ?? global.concurrency,
|
|
2566
|
+
backgroundWidget: global.backgroundWidget ?? true,
|
|
2567
|
+
...projectHas("modelAliases") ? { modelAliases: project.modelAliases } : global.modelAliases === void 0 ? {} : { modelAliases: global.modelAliases },
|
|
2568
|
+
...effectiveSelectors.skills.length ? { skills: effectiveSelectors.skills } : global.skills !== void 0 || project.skills !== void 0 ? { skills: effectiveSelectors.skills } : {},
|
|
2569
|
+
...hasExtensionSelectors ? { extensions: effectiveSelectors.extensions } : {},
|
|
2570
|
+
...extensionSettings === void 0 ? {} : { extensionSettings },
|
|
2571
|
+
...effectiveSelectors.tools?.length ? { tools: effectiveSelectors.tools } : global.tools !== void 0 || project.tools !== void 0 ? { tools: effectiveSelectors.tools } : {},
|
|
2572
|
+
...(project.retention ?? global.retention) === void 0 ? {} : { retention: project.retention ?? global.retention },
|
|
2573
|
+
...projectHas("worktreePostCreateCommand") ? { worktreePostCreateCommand: project.worktreePostCreateCommand } : global.worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand: global.worktreePostCreateCommand }
|
|
2574
|
+
});
|
|
2575
|
+
return { globalSettingsPath, projectSettingsPath, projectTrusted: projectTrusted2, global, project, effective, sources };
|
|
2576
|
+
}
|
|
2577
|
+
function validateModelAliasAvailability(aliases, names, availableModels, knownModels, settingsPath) {
|
|
2578
|
+
for (const name of names) {
|
|
2579
|
+
try {
|
|
2580
|
+
const target = modelCapability(name, aliases, knownModels, settingsPath);
|
|
2581
|
+
if (!availableModels.has(target)) unknownModel(name, target, settingsPath);
|
|
2582
|
+
} catch (error) {
|
|
2583
|
+
throw annotateModelAliasError(error, name);
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
function resolveAgentResourcePolicy(cwd, projectTrusted2, globalSettingsPath = workflowSettingsPath()) {
|
|
2588
|
+
const resolved = resolveWorkflowSettings(cwd, projectTrusted2, globalSettingsPath);
|
|
2589
|
+
const global = selectorSet(selectorsFromSettings(resolved.global));
|
|
2590
|
+
const project = selectorSet(selectorsFromSettings(resolved.project));
|
|
2591
|
+
const effective = selectorSet(selectorsFromSettings(resolved.effective));
|
|
2592
|
+
return { globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted: projectTrusted2, global, project, effective, unmatchedSkills: [], unmatchedExtensions: [], unmatchedTools: [], selectorSources: { global: selectorsFromSettings(resolved.global), project: selectorsFromSettings(resolved.project) } };
|
|
2593
|
+
}
|
|
2594
|
+
function saveModelAliases(path = workflowSettingsPath(), aliases = {}) {
|
|
2595
|
+
const normalized = validateModelAliases(aliases, path);
|
|
2596
|
+
let parsed = {};
|
|
2597
|
+
try {
|
|
2598
|
+
loadSettings(path);
|
|
2599
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
2600
|
+
} catch (error) {
|
|
2601
|
+
if (!isNodeError(error, "ENOENT")) throw error;
|
|
2602
|
+
}
|
|
2603
|
+
if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
|
|
2604
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
2605
|
+
atomicWriteFile(path, `${JSON.stringify({ ...parsed, modelAliases: normalized }, null, 2)}
|
|
2606
|
+
`, true);
|
|
2607
|
+
}
|
|
2367
2608
|
|
|
2368
2609
|
// src/registry.ts
|
|
2369
|
-
import { readFileSync } from "node:fs";
|
|
2370
|
-
import { basename as basename2, dirname as dirname2, isAbsolute, join as join4 } from "node:path";
|
|
2371
|
-
import { fileURLToPath } from "node:url";
|
|
2372
|
-
import { Value } from "typebox/value";
|
|
2373
2610
|
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"]);
|
|
2374
2611
|
var IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
2375
2612
|
var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
@@ -2378,10 +2615,10 @@ function normalizeRoleDirectory(value) {
|
|
|
2378
2615
|
try {
|
|
2379
2616
|
if (value instanceof URL) {
|
|
2380
2617
|
if (value.protocol !== "file:") fail("INVALID_METADATA", "Workflow role directories require file URLs or absolute filesystem paths");
|
|
2381
|
-
return
|
|
2618
|
+
return fileURLToPath2(value);
|
|
2382
2619
|
}
|
|
2383
2620
|
if (typeof value === "string") {
|
|
2384
|
-
if (value.startsWith("file:")) return
|
|
2621
|
+
if (value.startsWith("file:")) return fileURLToPath2(value);
|
|
2385
2622
|
if (isAbsolute(value)) return value;
|
|
2386
2623
|
}
|
|
2387
2624
|
} catch (error) {
|
|
@@ -2391,12 +2628,12 @@ function normalizeRoleDirectory(value) {
|
|
|
2391
2628
|
}
|
|
2392
2629
|
function isBuiltinRoleDirectory(path) {
|
|
2393
2630
|
const canonical = canonicalPath(path);
|
|
2394
|
-
const starterDirectory =
|
|
2631
|
+
const starterDirectory = dirname3(canonical);
|
|
2395
2632
|
if (basename2(canonical) !== "roles" || basename2(starterDirectory) !== "starter") return false;
|
|
2396
|
-
const distributionDirectory =
|
|
2397
|
-
const packageRoot2 = basename2(distributionDirectory) === "dist" ?
|
|
2633
|
+
const distributionDirectory = dirname3(starterDirectory);
|
|
2634
|
+
const packageRoot2 = basename2(distributionDirectory) === "dist" ? dirname3(distributionDirectory) : distributionDirectory;
|
|
2398
2635
|
try {
|
|
2399
|
-
const metadata = JSON.parse(
|
|
2636
|
+
const metadata = JSON.parse(readFileSync2(join5(packageRoot2, "package.json"), "utf8"));
|
|
2400
2637
|
return object(metadata) && isCorePackageName(metadata.name);
|
|
2401
2638
|
} catch {
|
|
2402
2639
|
return false;
|
|
@@ -2671,276 +2908,52 @@ function beginWorkflowExtensionLoading() {
|
|
|
2671
2908
|
const host = workflowRegistryHost();
|
|
2672
2909
|
if (host.api.frozen && host.activeHosts === 0) resetWorkflowRegistry();
|
|
2673
2910
|
}
|
|
2674
|
-
function retainWorkflowRegistry() {
|
|
2675
|
-
const host = workflowRegistryHost();
|
|
2676
|
-
host.activeHosts += 1;
|
|
2677
|
-
let retained = true;
|
|
2678
|
-
return () => {
|
|
2679
|
-
if (!retained) return;
|
|
2680
|
-
retained = false;
|
|
2681
|
-
host.activeHosts -= 1;
|
|
2682
|
-
if (host.activeHosts === 0) resetWorkflowRegistry();
|
|
2683
|
-
};
|
|
2684
|
-
}
|
|
2685
|
-
function loadingRegistry() {
|
|
2686
|
-
return workflowRegistryHost().api;
|
|
2687
|
-
}
|
|
2688
|
-
beginWorkflowExtensionLoading();
|
|
2689
|
-
function registerWorkflowExtension(extension) {
|
|
2690
|
-
loadingRegistry().register(extension);
|
|
2691
|
-
}
|
|
2692
|
-
function workflowCatalog(context) {
|
|
2693
|
-
return loadingRegistry().catalog(context);
|
|
2694
|
-
}
|
|
2695
|
-
function workflowCatalogIndex(context) {
|
|
2696
|
-
return loadingRegistry().catalogIndex(context);
|
|
2697
|
-
}
|
|
2698
|
-
function workflowCatalogDetail(name, context) {
|
|
2699
|
-
return loadingRegistry().catalogDetail(name, context);
|
|
2700
|
-
}
|
|
2701
|
-
function registeredWorkflowFunctions() {
|
|
2702
|
-
return loadingRegistry().functions();
|
|
2703
|
-
}
|
|
2704
|
-
function registeredWorkflowFunctionSources() {
|
|
2705
|
-
const sources = loadingRegistry().functionSources;
|
|
2706
|
-
return typeof sources === "function" ? sources() : {};
|
|
2707
|
-
}
|
|
2708
|
-
function registeredWorkflowRoleDirectories() {
|
|
2709
|
-
const directories = loadingRegistry().roleDirectories;
|
|
2710
|
-
return typeof directories === "function" ? directories() : [];
|
|
2711
|
-
}
|
|
2712
|
-
function registeredWorkflowRoleDirectoryRegistrations() {
|
|
2713
|
-
const registrations = loadingRegistry().roleDirectoryRegistrations;
|
|
2714
|
-
return typeof registrations === "function" ? registrations() : [];
|
|
2715
|
-
}
|
|
2716
|
-
|
|
2717
|
-
// src/validation.ts
|
|
2718
|
-
var DEFAULT_SETTINGS = Object.freeze({ concurrency: 8, backgroundWidget: true });
|
|
2719
|
-
function validateCheckpoint(value) {
|
|
2720
|
-
if (!object(value) || Object.keys(value).some((key) => !["name", "prompt", "context"].includes(key)) || typeof value.name !== "string" || value.name.trim() === "" || typeof value.prompt !== "string" || !jsonValue(value.context)) fail("INVALID_METADATA", "checkpoint requires only name, prompt, and JSON context");
|
|
2721
|
-
if (Buffer.byteLength(value.prompt) > 1024) fail("INVALID_METADATA", "checkpoint prompt exceeds 1024 UTF-8 bytes");
|
|
2722
|
-
if (Buffer.byteLength(JSON.stringify(value.context)) > 4096) fail("INVALID_METADATA", "checkpoint context exceeds 4096 UTF-8 bytes");
|
|
2723
|
-
return { name: value.name, prompt: value.prompt, context: value.context };
|
|
2724
|
-
}
|
|
2725
|
-
function workflowSettingsPath(agentDir = getAgentDir()) {
|
|
2726
|
-
return join5(agentDir, ROLE_DIRECTORY, "settings.json");
|
|
2727
|
-
}
|
|
2728
|
-
function normalizedResourcePath(value, settingsPath) {
|
|
2729
|
-
if (value === "*") return value;
|
|
2730
|
-
let expanded = value === "~" ? homedir4() : value.startsWith("~/") || value.startsWith("~\\") ? join5(homedir4(), value.slice(2)) : value;
|
|
2731
|
-
if (expanded.startsWith("file://")) expanded = fileURLToPath2(expanded);
|
|
2732
|
-
const resolved = resolve3(dirname3(settingsPath), expanded);
|
|
2733
|
-
if (expanded === "**" || expanded.startsWith("**/") || expanded.startsWith("**\\")) return expanded;
|
|
2734
|
-
if (resourcePatternHasMagic(expanded)) {
|
|
2735
|
-
const magicIndex = resolved.search(/[*?\x5b\x5d{}()]/);
|
|
2736
|
-
const separatorIndex = Math.max(resolved.lastIndexOf("/", magicIndex), resolved.lastIndexOf("\\", magicIndex));
|
|
2737
|
-
const rootBoundary = separatorIndex === 0 || separatorIndex === 2 && /^[A-Za-z]:[\\/]/.test(resolved);
|
|
2738
|
-
const prefix = rootBoundary ? resolved.slice(0, separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(0, separatorIndex) : resolved;
|
|
2739
|
-
const suffix = rootBoundary ? resolved.slice(separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(separatorIndex) : "";
|
|
2740
|
-
return `${canonicalPath(prefix)}${suffix}`;
|
|
2741
|
-
}
|
|
2742
|
-
return canonicalPath(resolved);
|
|
2743
|
-
}
|
|
2744
|
-
function validateSelectorList(value, path, kind, errorCode2 = "INVALID_SETTINGS", normalizeExtensions = true) {
|
|
2745
|
-
if (value === void 0) return void 0;
|
|
2746
|
-
if (!Array.isArray(value)) fail(errorCode2, `${path}.${kind} must be an array`);
|
|
2747
|
-
const normalized = [];
|
|
2748
|
-
for (const [index, entry] of value.entries()) {
|
|
2749
|
-
if (typeof entry !== "string" || !entry.trim()) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a non-empty string`);
|
|
2750
|
-
let selector = entry.trim();
|
|
2751
|
-
if (kind === "extensions" && normalizeExtensions) {
|
|
2752
|
-
const negated = selector.startsWith("!");
|
|
2753
|
-
const body = negated ? selector.slice(1) : selector;
|
|
2754
|
-
if (!body) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: Empty minimatch pattern ${JSON.stringify(selector)}`);
|
|
2755
|
-
try {
|
|
2756
|
-
selector = `${negated ? "!" : ""}${normalizedResourcePath(body, path)}`;
|
|
2757
|
-
} catch (error) {
|
|
2758
|
-
fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid path: ${errorText(error)}`);
|
|
2759
|
-
}
|
|
2760
|
-
}
|
|
2761
|
-
try {
|
|
2762
|
-
validateResourcePattern(selector);
|
|
2763
|
-
} catch (error) {
|
|
2764
|
-
fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: ${errorText(error)}`);
|
|
2765
|
-
}
|
|
2766
|
-
normalized.push(selector);
|
|
2767
|
-
}
|
|
2768
|
-
return Object.freeze(normalized);
|
|
2769
|
-
}
|
|
2770
|
-
function validateWorktreePostCreateCommand(value, settingsPath) {
|
|
2771
|
-
if (value === void 0) return void 0;
|
|
2772
|
-
if (!Array.isArray(value) || value.length === 0) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand must be a non-empty array`);
|
|
2773
|
-
return Object.freeze(value.map((entry, index) => {
|
|
2774
|
-
if (typeof entry !== "string" || !entry.trim()) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand[${String(index)}] must be a non-empty string`);
|
|
2775
|
-
return entry;
|
|
2776
|
-
}));
|
|
2777
|
-
}
|
|
2778
|
-
function selectorsFromSettings(settings) {
|
|
2779
|
-
return {
|
|
2780
|
-
...settings.skills === void 0 ? {} : { skills: settings.skills },
|
|
2781
|
-
...settings.extensions === void 0 ? {} : { extensions: settings.extensions },
|
|
2782
|
-
...settings.tools === void 0 ? {} : { tools: settings.tools }
|
|
2783
|
-
};
|
|
2784
|
-
}
|
|
2785
|
-
function selectorSet(value) {
|
|
2786
|
-
return { skills: [...value?.skills ?? []], extensions: [...value?.extensions ?? []], ...value?.tools === void 0 ? {} : { tools: [...value.tools] } };
|
|
2787
|
-
}
|
|
2788
|
-
function validateContextFileScopes(value, rolePath) {
|
|
2789
|
-
if (value === void 0) return void 0;
|
|
2790
|
-
if (!Array.isArray(value) || !value.every(isContextFileScope)) fail("INVALID_METADATA", `${rolePath}.contextFiles must be an array containing only global, project, or cwd`);
|
|
2791
|
-
return [...value];
|
|
2792
|
-
}
|
|
2793
|
-
function validateWorkflowExtensionSettings(value, settingsPath, errorCode2 = "INVALID_SETTINGS", applyDefaults = true) {
|
|
2794
|
-
if (value === void 0) return void 0;
|
|
2795
|
-
const base = `${settingsPath}.extensionSettings`;
|
|
2796
|
-
if (!object(value)) fail(errorCode2, `${base} must be an object`);
|
|
2797
|
-
const normalized = {};
|
|
2798
|
-
for (const [namespace, raw] of Object.entries(value)) {
|
|
2799
|
-
if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode2, `${base} contains an invalid namespace: ${namespace}`);
|
|
2800
|
-
if (namespace === "herdr") {
|
|
2801
|
-
if (!object(raw)) fail(errorCode2, `${base}.herdr must be an object`);
|
|
2802
|
-
if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode")) fail(errorCode2, `${base}.herdr contains an unsupported setting`);
|
|
2803
|
-
if (raw.enableFullyInspectableMode !== void 0 && typeof raw.enableFullyInspectableMode !== "boolean") fail(errorCode2, `${base}.herdr.enableFullyInspectableMode must be a boolean`);
|
|
2804
|
-
normalized.herdr = Object.freeze({ ...raw.enableFullyInspectableMode === void 0 ? {} : { enableFullyInspectableMode: raw.enableFullyInspectableMode } });
|
|
2805
|
-
continue;
|
|
2806
|
-
}
|
|
2807
|
-
if (namespace === "trajectory") {
|
|
2808
|
-
if (!object(raw)) fail(errorCode2, `${base}.trajectory must be an object`);
|
|
2809
|
-
if (Object.keys(raw).some((key) => key !== "port" && key !== "themes")) fail(errorCode2, `${base}.trajectory contains an unsupported setting`);
|
|
2810
|
-
if (raw.port !== void 0 && (!positiveInteger(raw.port) || raw.port > 65535)) fail(errorCode2, `${base}.trajectory.port must be an integer from 1 to 65535`);
|
|
2811
|
-
if (raw.themes !== void 0 && typeof raw.themes !== "boolean") fail(errorCode2, `${base}.trajectory.themes must be a boolean`);
|
|
2812
|
-
normalized.trajectory = Object.freeze({ ...raw.port === void 0 ? {} : { port: raw.port }, ...raw.themes === void 0 ? applyDefaults ? { themes: false } : {} : { themes: raw.themes } });
|
|
2813
|
-
continue;
|
|
2814
|
-
}
|
|
2815
|
-
if (!jsonValue(raw)) fail(errorCode2, `${base}.${namespace} must be JSON-compatible`);
|
|
2816
|
-
normalized[namespace] = structuredClone(raw);
|
|
2817
|
-
}
|
|
2818
|
-
return deepFreeze(normalized);
|
|
2911
|
+
function retainWorkflowRegistry() {
|
|
2912
|
+
const host = workflowRegistryHost();
|
|
2913
|
+
host.activeHosts += 1;
|
|
2914
|
+
let retained = true;
|
|
2915
|
+
return () => {
|
|
2916
|
+
if (!retained) return;
|
|
2917
|
+
retained = false;
|
|
2918
|
+
host.activeHosts -= 1;
|
|
2919
|
+
if (host.activeHosts === 0) resetWorkflowRegistry();
|
|
2920
|
+
};
|
|
2819
2921
|
}
|
|
2820
|
-
function
|
|
2821
|
-
return
|
|
2922
|
+
function loadingRegistry() {
|
|
2923
|
+
return workflowRegistryHost().api;
|
|
2822
2924
|
}
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
if (!object(value)) fail("INVALID_SETTINGS", `${base} must be an object`);
|
|
2827
|
-
const unknown = Object.keys(value).find((key) => key !== "olderThanDays" && key !== "maxTerminalRuns");
|
|
2828
|
-
if (unknown) fail("INVALID_SETTINGS", `Unknown retention setting at ${base}: ${unknown}`);
|
|
2829
|
-
if (value.olderThanDays !== void 0 && !positiveRetentionInteger(value.olderThanDays)) fail("INVALID_SETTINGS", `${base}.olderThanDays must be a positive integer`);
|
|
2830
|
-
if (value.maxTerminalRuns !== void 0 && !positiveRetentionInteger(value.maxTerminalRuns)) fail("INVALID_SETTINGS", `${base}.maxTerminalRuns must be a positive integer`);
|
|
2831
|
-
return Object.freeze({ ...value.olderThanDays === void 0 ? {} : { olderThanDays: value.olderThanDays }, ...value.maxTerminalRuns === void 0 ? {} : { maxTerminalRuns: value.maxTerminalRuns } });
|
|
2925
|
+
beginWorkflowExtensionLoading();
|
|
2926
|
+
function registerWorkflowExtension(extension) {
|
|
2927
|
+
loadingRegistry().register(extension);
|
|
2832
2928
|
}
|
|
2833
|
-
function
|
|
2834
|
-
|
|
2835
|
-
try {
|
|
2836
|
-
parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
2837
|
-
} catch (error) {
|
|
2838
|
-
if (isNodeError(error, "ENOENT")) return partial ? Object.freeze({}) : DEFAULT_SETTINGS;
|
|
2839
|
-
fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
|
|
2840
|
-
}
|
|
2841
|
-
if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
|
|
2842
|
-
const allowed = /* @__PURE__ */ new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...partial ? [] : ["backgroundWidget"]]);
|
|
2843
|
-
const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
|
|
2844
|
-
if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources")) fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
|
|
2845
|
-
if (unknown) fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
|
|
2846
|
-
const concurrency = parsed.concurrency === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.concurrency : parsed.concurrency;
|
|
2847
|
-
if (concurrency !== void 0 && (!positiveInteger(concurrency) || concurrency > 16)) fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
|
|
2848
|
-
const backgroundWidget = parsed.backgroundWidget === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.backgroundWidget : parsed.backgroundWidget;
|
|
2849
|
-
if (backgroundWidget !== void 0 && typeof backgroundWidget !== "boolean") fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
|
|
2850
|
-
const modelAliases = parsed.modelAliases === void 0 ? void 0 : validateModelAliases(parsed.modelAliases, path);
|
|
2851
|
-
const skills = validateSelectorList(parsed.skills, path, "skills");
|
|
2852
|
-
const tools = validateSelectorList(parsed.tools, path, "tools");
|
|
2853
|
-
const extensions = validateSelectorList(parsed.extensions, path, "extensions");
|
|
2854
|
-
const extensionSettings = parsed.extensionSettings === void 0 ? void 0 : validateWorkflowExtensionSettings(parsed.extensionSettings, path, "INVALID_SETTINGS", !partial);
|
|
2855
|
-
const retention = validateRetention(parsed.retention, path);
|
|
2856
|
-
const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
|
|
2857
|
-
return Object.freeze({
|
|
2858
|
-
...concurrency === void 0 ? {} : { concurrency },
|
|
2859
|
-
...backgroundWidget === void 0 ? {} : { backgroundWidget },
|
|
2860
|
-
...modelAliases === void 0 ? {} : { modelAliases },
|
|
2861
|
-
...skills === void 0 ? {} : { skills },
|
|
2862
|
-
...extensions === void 0 ? {} : { extensions },
|
|
2863
|
-
...extensionSettings === void 0 ? {} : { extensionSettings },
|
|
2864
|
-
...tools === void 0 ? {} : { tools },
|
|
2865
|
-
...retention === void 0 ? {} : { retention },
|
|
2866
|
-
...worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand }
|
|
2867
|
-
});
|
|
2929
|
+
function workflowCatalog(context) {
|
|
2930
|
+
return loadingRegistry().catalog(context);
|
|
2868
2931
|
}
|
|
2869
|
-
function
|
|
2870
|
-
return
|
|
2932
|
+
function workflowCatalogIndex(context) {
|
|
2933
|
+
return loadingRegistry().catalogIndex(context);
|
|
2871
2934
|
}
|
|
2872
|
-
function
|
|
2873
|
-
return
|
|
2935
|
+
function workflowCatalogDetail(name, context) {
|
|
2936
|
+
return loadingRegistry().catalogDetail(name, context);
|
|
2874
2937
|
}
|
|
2875
|
-
function
|
|
2876
|
-
|
|
2877
|
-
const global = loadSettings(globalSettingsPath);
|
|
2878
|
-
const project = projectTrusted2 ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
|
|
2879
|
-
const projectHas = (key) => Object.prototype.hasOwnProperty.call(project, key);
|
|
2880
|
-
const sourceFor = (key) => projectHas(key) ? projectSettingsPath : globalSettingsPath;
|
|
2881
|
-
const globalSelectors = selectorsFromSettings(global);
|
|
2882
|
-
const projectSelectors = selectorsFromSettings(project);
|
|
2883
|
-
const effectiveSelectors = selectorSet({
|
|
2884
|
-
skills: [...globalSelectors.skills ?? [], ...projectSelectors.skills ?? []],
|
|
2885
|
-
extensions: [...globalSelectors.extensions ?? [], ...projectSelectors.extensions ?? []],
|
|
2886
|
-
...globalSelectors.tools === void 0 && projectSelectors.tools === void 0 ? {} : { tools: [...globalSelectors.tools ?? [], ...projectSelectors.tools ?? []] }
|
|
2887
|
-
});
|
|
2888
|
-
const hasExtensionSelectors = global.extensions !== void 0 || project.extensions !== void 0;
|
|
2889
|
-
const extensionSettings = projectHas("extensionSettings") ? project.extensionSettings : global.extensionSettings;
|
|
2890
|
-
const sources = {
|
|
2891
|
-
concurrency: projectHas("concurrency") ? projectSettingsPath : globalSettingsPath,
|
|
2892
|
-
modelAliases: projectHas("modelAliases") ? projectSettingsPath : globalSettingsPath,
|
|
2893
|
-
skills: sourceFor("skills"),
|
|
2894
|
-
extensions: sourceFor("extensions"),
|
|
2895
|
-
tools: sourceFor("tools"),
|
|
2896
|
-
...extensionSettings === void 0 ? {} : { extensionSettings: projectHas("extensionSettings") ? projectSettingsPath : globalSettingsPath },
|
|
2897
|
-
...project.retention === void 0 && global.retention === void 0 ? {} : { retention: project.retention === void 0 ? globalSettingsPath : projectSettingsPath },
|
|
2898
|
-
...projectHas("worktreePostCreateCommand") || global.worktreePostCreateCommand !== void 0 ? { worktreePostCreateCommand: projectHas("worktreePostCreateCommand") ? projectSettingsPath : globalSettingsPath } : {}
|
|
2899
|
-
};
|
|
2900
|
-
const effective = Object.freeze({
|
|
2901
|
-
concurrency: project.concurrency ?? global.concurrency,
|
|
2902
|
-
backgroundWidget: global.backgroundWidget ?? true,
|
|
2903
|
-
...projectHas("modelAliases") ? { modelAliases: project.modelAliases } : global.modelAliases === void 0 ? {} : { modelAliases: global.modelAliases },
|
|
2904
|
-
...effectiveSelectors.skills.length ? { skills: effectiveSelectors.skills } : global.skills !== void 0 || project.skills !== void 0 ? { skills: effectiveSelectors.skills } : {},
|
|
2905
|
-
...hasExtensionSelectors ? { extensions: effectiveSelectors.extensions } : {},
|
|
2906
|
-
...extensionSettings === void 0 ? {} : { extensionSettings },
|
|
2907
|
-
...effectiveSelectors.tools?.length ? { tools: effectiveSelectors.tools } : global.tools !== void 0 || project.tools !== void 0 ? { tools: effectiveSelectors.tools } : {},
|
|
2908
|
-
...(project.retention ?? global.retention) === void 0 ? {} : { retention: project.retention ?? global.retention },
|
|
2909
|
-
...projectHas("worktreePostCreateCommand") ? { worktreePostCreateCommand: project.worktreePostCreateCommand } : global.worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand: global.worktreePostCreateCommand }
|
|
2910
|
-
});
|
|
2911
|
-
return { globalSettingsPath, projectSettingsPath, projectTrusted: projectTrusted2, global, project, effective, sources };
|
|
2938
|
+
function registeredWorkflowFunctions() {
|
|
2939
|
+
return loadingRegistry().functions();
|
|
2912
2940
|
}
|
|
2913
|
-
function
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
const target = modelCapability(name, aliases, knownModels, settingsPath);
|
|
2917
|
-
if (!availableModels.has(target)) unknownModel(name, target, settingsPath);
|
|
2918
|
-
} catch (error) {
|
|
2919
|
-
throw annotateModelAliasError(error, name);
|
|
2920
|
-
}
|
|
2921
|
-
}
|
|
2941
|
+
function registeredWorkflowFunctionSources() {
|
|
2942
|
+
const sources = loadingRegistry().functionSources;
|
|
2943
|
+
return typeof sources === "function" ? sources() : {};
|
|
2922
2944
|
}
|
|
2923
|
-
function
|
|
2924
|
-
const
|
|
2925
|
-
|
|
2926
|
-
const project = selectorSet(selectorsFromSettings(resolved.project));
|
|
2927
|
-
const effective = selectorSet(selectorsFromSettings(resolved.effective));
|
|
2928
|
-
return { globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted: projectTrusted2, global, project, effective, unmatchedSkills: [], unmatchedExtensions: [], unmatchedTools: [], selectorSources: { global: selectorsFromSettings(resolved.global), project: selectorsFromSettings(resolved.project) } };
|
|
2945
|
+
function registeredWorkflowRoleDirectories() {
|
|
2946
|
+
const directories = loadingRegistry().roleDirectories;
|
|
2947
|
+
return typeof directories === "function" ? directories() : [];
|
|
2929
2948
|
}
|
|
2930
|
-
function
|
|
2931
|
-
const
|
|
2932
|
-
|
|
2933
|
-
try {
|
|
2934
|
-
loadSettings(path);
|
|
2935
|
-
parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
2936
|
-
} catch (error) {
|
|
2937
|
-
if (!isNodeError(error, "ENOENT")) throw error;
|
|
2938
|
-
}
|
|
2939
|
-
if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
|
|
2940
|
-
mkdirSync(dirname3(path), { recursive: true });
|
|
2941
|
-
atomicWriteFile(path, `${JSON.stringify({ ...parsed, modelAliases: normalized }, null, 2)}
|
|
2942
|
-
`, true);
|
|
2949
|
+
function registeredWorkflowRoleDirectoryRegistrations() {
|
|
2950
|
+
const registrations = loadingRegistry().roleDirectoryRegistrations;
|
|
2951
|
+
return typeof registrations === "function" ? registrations() : [];
|
|
2943
2952
|
}
|
|
2953
|
+
|
|
2954
|
+
// src/roles.ts
|
|
2955
|
+
var ROLE_DIRECTORY2 = "pi-extensible-workflows";
|
|
2956
|
+
var roleDefinitionPaths = /* @__PURE__ */ new WeakMap();
|
|
2944
2957
|
function parseRoleMarkdown(content, strict = false, rolePath) {
|
|
2945
2958
|
if (!strict) {
|
|
2946
2959
|
if (!content.startsWith("---\n")) return { prompt: content };
|
|
@@ -2967,7 +2980,7 @@ function parseRoleMarkdown(content, strict = false, rolePath) {
|
|
|
2967
2980
|
}
|
|
2968
2981
|
}
|
|
2969
2982
|
const rolePathValue2 = rolePath ?? "<role>";
|
|
2970
|
-
const normalizedExtensionSettings2 = validateWorkflowExtensionSettings(extensionSettings2, rolePathValue2, "INVALID_METADATA"
|
|
2983
|
+
const normalizedExtensionSettings2 = validateWorkflowExtensionSettings(extensionSettings2, rolePathValue2, "INVALID_METADATA");
|
|
2971
2984
|
const definition2 = { prompt: content.slice(end + 4).replace(/^\n/, "") };
|
|
2972
2985
|
if (meta.model) {
|
|
2973
2986
|
const model2 = unquote(meta.model);
|
|
@@ -3007,7 +3020,7 @@ function parseRoleMarkdown(content, strict = false, rolePath) {
|
|
|
3007
3020
|
const normalizedTools = validateSelectorList(tools, rolePathValue, "tools", "INVALID_METADATA");
|
|
3008
3021
|
const normalizedSkills = validateSelectorList(skills, rolePathValue, "skills", "INVALID_METADATA");
|
|
3009
3022
|
const normalizedExtensions = validateSelectorList(extensions, rolePathValue, "extensions", "INVALID_METADATA");
|
|
3010
|
-
const normalizedExtensionSettings = validateWorkflowExtensionSettings(extensionSettings, rolePathValue, "INVALID_METADATA"
|
|
3023
|
+
const normalizedExtensionSettings = validateWorkflowExtensionSettings(extensionSettings, rolePathValue, "INVALID_METADATA");
|
|
3011
3024
|
const normalizedDescription = typeof description === "string" ? description.trim() : void 0;
|
|
3012
3025
|
const normalizedModel = typeof model === "string" ? model.trim() : void 0;
|
|
3013
3026
|
if (normalizedModel !== void 0) assertModelThinking(normalizedModel, "Role model");
|
|
@@ -3022,12 +3035,11 @@ function parseRoleMarkdown(content, strict = false, rolePath) {
|
|
|
3022
3035
|
if (normalizedContextFiles !== void 0) definition.contextFiles = normalizedContextFiles;
|
|
3023
3036
|
return definition;
|
|
3024
3037
|
}
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
return [join5(agentDir, ROLE_DIRECTORY, "roles")];
|
|
3038
|
+
function workflowRoleDirectories(agentDir = getAgentDir2()) {
|
|
3039
|
+
return [join6(agentDir, ROLE_DIRECTORY2, "roles")];
|
|
3028
3040
|
}
|
|
3029
3041
|
function projectRoleDirectories(root) {
|
|
3030
|
-
return [
|
|
3042
|
+
return [join6(root, ROLE_DIRECTORY2, "roles")];
|
|
3031
3043
|
}
|
|
3032
3044
|
function roleDirectorySources(dirs) {
|
|
3033
3045
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3046,7 +3058,7 @@ function isRoleFile(dir, entry) {
|
|
|
3046
3058
|
if (entry.isFile()) return true;
|
|
3047
3059
|
if (!entry.isSymbolicLink()) return false;
|
|
3048
3060
|
try {
|
|
3049
|
-
return statSync(
|
|
3061
|
+
return statSync(join6(dir, entry.name)).isFile();
|
|
3050
3062
|
} catch (error) {
|
|
3051
3063
|
if (isNodeError(error, "ENOENT")) return false;
|
|
3052
3064
|
throw error;
|
|
@@ -3062,11 +3074,11 @@ function scanRoleFiles(dirs, extension) {
|
|
|
3062
3074
|
if (!extension && isNodeError(error, "ENOENT")) continue;
|
|
3063
3075
|
fail("INVALID_METADATA", `${roleDirectoryLabel(source, extension)} role directory "${source.path}" could not be scanned: ${errorText(error)}`);
|
|
3064
3076
|
}
|
|
3065
|
-
for (const entry of entries) if (isRoleFile(source.path, entry)) files.push({ name: basename3(entry.name, ".md"), path:
|
|
3077
|
+
for (const entry of entries) if (isRoleFile(source.path, entry)) files.push({ name: basename3(entry.name, ".md"), path: join6(source.path, entry.name), source });
|
|
3066
3078
|
}
|
|
3067
3079
|
return files.sort((left, right) => left.name.localeCompare(right.name) || left.path.localeCompare(right.path));
|
|
3068
3080
|
}
|
|
3069
|
-
function
|
|
3081
|
+
function roleEntries(dirs, extension) {
|
|
3070
3082
|
const files = scanRoleFiles(dirs, extension);
|
|
3071
3083
|
const starterFiles = files.filter(({ source }) => source.builtin === true);
|
|
3072
3084
|
const regularFiles = files.filter(({ source }) => source.builtin !== true);
|
|
@@ -3077,7 +3089,9 @@ function readRoleDefinitions(dirs, extension = false) {
|
|
|
3077
3089
|
}
|
|
3078
3090
|
const read = (roleFiles) => Object.fromEntries(roleFiles.map(({ name, path, source }) => {
|
|
3079
3091
|
try {
|
|
3080
|
-
|
|
3092
|
+
const definition = parseRoleMarkdown(readFileSync3(path, "utf8"), true, path);
|
|
3093
|
+
roleDefinitionPaths.set(definition, path);
|
|
3094
|
+
return [name, { name, path, source, definition }];
|
|
3081
3095
|
} catch (error) {
|
|
3082
3096
|
if (extension) fail("INVALID_METADATA", `${roleDirectoryLabel(source)} role directory "${source.path}" contains invalid role "${name}" at "${path}": ${errorText(error)}`);
|
|
3083
3097
|
throw error;
|
|
@@ -3085,11 +3099,127 @@ function readRoleDefinitions(dirs, extension = false) {
|
|
|
3085
3099
|
}));
|
|
3086
3100
|
return extension ? { ...read(starterFiles), ...read(regularFiles) } : read(files);
|
|
3087
3101
|
}
|
|
3102
|
+
function discoveryInput(input) {
|
|
3103
|
+
return { cwd: input.cwd, agentDir: input.agentDir ?? getAgentDir2(), projectTrusted: input.projectTrusted ?? true, extensionRoleDirectories: input.extensionRoleDirectories ?? registeredWorkflowRoleDirectoryRegistrations() };
|
|
3104
|
+
}
|
|
3105
|
+
function discoveredRoleEntries(input) {
|
|
3106
|
+
const resolved = discoveryInput(input);
|
|
3107
|
+
return {
|
|
3108
|
+
...roleEntries(resolved.extensionRoleDirectories, true),
|
|
3109
|
+
...roleEntries(workflowRoleDirectories(resolved.agentDir), false),
|
|
3110
|
+
...resolved.projectTrusted ? roleEntries(projectRoleDirectories(join6(resolved.cwd, ".pi")), false) : {}
|
|
3111
|
+
};
|
|
3112
|
+
}
|
|
3088
3113
|
function loadProjectAgentDefinitions(cwd) {
|
|
3089
|
-
return
|
|
3114
|
+
return deepFreeze(Object.fromEntries(Object.entries(roleEntries(projectRoleDirectories(join6(cwd, ".pi")), false)).map(([name, entry]) => [name, entry.definition])));
|
|
3115
|
+
}
|
|
3116
|
+
function discoverRoles(input) {
|
|
3117
|
+
return deepFreeze(Object.fromEntries(Object.entries(discoveredRoleEntries(input)).map(([name, entry]) => [name, entry.definition])));
|
|
3118
|
+
}
|
|
3119
|
+
function loadRole(name, input) {
|
|
3120
|
+
const entry = discoveredRoleEntries(input)[name];
|
|
3121
|
+
if (!entry) fail("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${name}`);
|
|
3122
|
+
return entry.definition;
|
|
3123
|
+
}
|
|
3124
|
+
function loadAgentDefinitions(cwd, agentDir = getAgentDir2(), projectTrusted2 = true, extensionRoleDirectories = registeredWorkflowRoleDirectoryRegistrations()) {
|
|
3125
|
+
return discoverRoles({ cwd, agentDir, projectTrusted: projectTrusted2, extensionRoleDirectories });
|
|
3126
|
+
}
|
|
3127
|
+
function canonicalExtensionSelector(selector, base = process.cwd()) {
|
|
3128
|
+
const negated = selector.startsWith("!");
|
|
3129
|
+
const body = negated ? selector.slice(1) : selector;
|
|
3130
|
+
if (body === "*" || body === "**" || body.startsWith("**/")) return selector;
|
|
3131
|
+
const resolved = resolve4(base, body);
|
|
3132
|
+
if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
|
|
3133
|
+
return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
|
|
3090
3134
|
}
|
|
3091
|
-
function
|
|
3092
|
-
return
|
|
3135
|
+
function selectorsFromSettings2(settings) {
|
|
3136
|
+
return { ...settings.skills === void 0 ? {} : { skills: settings.skills }, ...settings.extensions === void 0 ? {} : { extensions: settings.extensions }, ...settings.tools === void 0 ? {} : { tools: settings.tools } };
|
|
3137
|
+
}
|
|
3138
|
+
function rootToolNames(value) {
|
|
3139
|
+
return value === void 0 ? void 0 : value instanceof Set ? [...value] : [...value];
|
|
3140
|
+
}
|
|
3141
|
+
function roleDefinitionFor(name, options) {
|
|
3142
|
+
if (name === void 0) return {};
|
|
3143
|
+
if (options.definition !== void 0) {
|
|
3144
|
+
const path = roleDefinitionPaths.get(options.definition);
|
|
3145
|
+
return { definition: options.definition, ...path === void 0 ? {} : { path } };
|
|
3146
|
+
}
|
|
3147
|
+
if (options.definitions?.[name] !== void 0) {
|
|
3148
|
+
const definition = options.definitions[name];
|
|
3149
|
+
const path = roleDefinitionPaths.get(definition);
|
|
3150
|
+
return { definition, ...path === void 0 ? {} : { path } };
|
|
3151
|
+
}
|
|
3152
|
+
const entries = discoveredRoleEntries(options);
|
|
3153
|
+
const entry = entries[name];
|
|
3154
|
+
if (!entry) fail("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${name}`);
|
|
3155
|
+
return entry;
|
|
3156
|
+
}
|
|
3157
|
+
function resolveRole(name, options) {
|
|
3158
|
+
const { definition, path } = roleDefinitionFor(name, options);
|
|
3159
|
+
const settings = options.selectorSources === void 0 ? resolveWorkflowSettings(options.cwd, options.projectTrusted ?? true, options.settingsPath ?? workflowSettingsPath(options.agentDir ?? getAgentDir2())) : void 0;
|
|
3160
|
+
const baseSources = options.selectorSources ?? { global: selectorsFromSettings2(settings?.global ?? {}), project: selectorsFromSettings2(settings?.project ?? {}) };
|
|
3161
|
+
const canonicalSources = (sources) => ({
|
|
3162
|
+
...sources,
|
|
3163
|
+
global: { ...sources.global, ...sources.global.extensions === void 0 ? {} : { extensions: sources.global.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } },
|
|
3164
|
+
project: { ...sources.project, ...sources.project.extensions === void 0 ? {} : { extensions: sources.project.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } },
|
|
3165
|
+
...sources.role === void 0 ? {} : { role: { ...sources.role, ...sources.role.extensions === void 0 ? {} : { extensions: sources.role.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } } },
|
|
3166
|
+
...sources.call === void 0 ? {} : { call: { ...sources.call, ...sources.call.extensions === void 0 ? {} : { extensions: sources.call.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } } }
|
|
3167
|
+
});
|
|
3168
|
+
const roleLayer = definition?.skills !== void 0 || definition?.extensions !== void 0 || definition?.tools !== void 0 ? { ...definition.skills === void 0 ? {} : { skills: definition.skills }, ...definition.extensions === void 0 ? {} : { extensions: definition.extensions.map((selector) => canonicalExtensionSelector(selector, path ? dirname4(path) : options.cwd)) }, ...definition.tools === void 0 ? {} : { tools: definition.tools } } : baseSources.role;
|
|
3169
|
+
const callLayer = options.skills !== void 0 || options.extensions !== void 0 || options.tools !== void 0 ? { ...options.skills === void 0 ? {} : { skills: options.skills }, ...options.extensions === void 0 ? {} : { extensions: options.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) }, ...options.tools === void 0 ? {} : { tools: options.tools } } : baseSources.call;
|
|
3170
|
+
const selectorSources = canonicalSources({ global: baseSources.global, project: baseSources.project, ...roleLayer === void 0 ? {} : { role: roleLayer }, ...callLayer === void 0 ? {} : { call: callLayer } });
|
|
3171
|
+
const callSelectors = selectorSources.call;
|
|
3172
|
+
const roleSelectors = selectorSources.role;
|
|
3173
|
+
const selectorLayers = {
|
|
3174
|
+
skills: [selectorSources.global.skills, selectorSources.project.skills, roleSelectors?.skills, callSelectors?.skills],
|
|
3175
|
+
extensions: [selectorSources.global.extensions, selectorSources.project.extensions, roleSelectors?.extensions, callSelectors?.extensions],
|
|
3176
|
+
tools: [selectorSources.global.tools, selectorSources.project.tools, roleSelectors?.tools, callSelectors?.tools]
|
|
3177
|
+
};
|
|
3178
|
+
const resources = options.resources ?? {};
|
|
3179
|
+
const candidateTools = options.inheritedTools ?? rootToolNames(options.rootTools) ?? resources.tools;
|
|
3180
|
+
const tools = options.effectiveTools ?? (candidateTools === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.tools, candidateTools));
|
|
3181
|
+
const rootTools = rootToolNames(options.rootTools);
|
|
3182
|
+
if (rootTools !== void 0) {
|
|
3183
|
+
const outsideEffectiveTool = options.effectiveTools?.find((tool) => !candidateTools?.includes(tool));
|
|
3184
|
+
if (outsideEffectiveTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideEffectiveTool}`);
|
|
3185
|
+
const outsideCallTool = (options.tools ?? []).find((tool) => !tool.startsWith("!") && !resourcePatternHasMagic(tool) && !candidateTools?.includes(tool));
|
|
3186
|
+
if (outsideCallTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideCallTool}`);
|
|
3187
|
+
const outsideRootTool = tools?.find((tool) => !rootTools.includes(tool));
|
|
3188
|
+
if (outsideRootTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideRootTool}`);
|
|
3189
|
+
}
|
|
3190
|
+
const selectedSkills = resources.skills === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.skills, resources.skills);
|
|
3191
|
+
const extensionResources = resources.extensions?.map((extension) => canonicalPath(extension));
|
|
3192
|
+
const selectedExtensions = extensionResources === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.extensions, extensionResources);
|
|
3193
|
+
const unmatchedSkills = resources.skills === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.skills.flatMap((layer) => layer ?? []), resources.skills);
|
|
3194
|
+
const unmatchedExtensions = extensionResources === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.extensions.flatMap((layer) => layer ?? []), extensionResources);
|
|
3195
|
+
const unmatchedTools = candidateTools === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.tools.flatMap((layer) => layer ?? []), candidateTools);
|
|
3196
|
+
const aliases = options.modelAliases ?? settings?.effective.modelAliases ?? {};
|
|
3197
|
+
const requestedModel = options.model ?? (definition?.model && definition.thinking && !definition.model.includes(":") ? `${definition.model}:${definition.thinking}` : definition?.model);
|
|
3198
|
+
const blockedAlias = requestedModel?.split(":", 1)[0];
|
|
3199
|
+
if (requestedModel !== void 0 && blockedAlias && options.blockedAliases?.has(blockedAlias) && !modelAliasName(requestedModel, aliases)) {
|
|
3200
|
+
const target = options.blockedAliasTargets?.[blockedAlias];
|
|
3201
|
+
fail("UNKNOWN_MODEL", `Unknown model alias ${requestedModel}${target ? ` resolved to ${target}` : ""}${options.settingsPath ? ` (settings: ${options.settingsPath})` : ""}`);
|
|
3202
|
+
}
|
|
3203
|
+
let model = options.modelOverride;
|
|
3204
|
+
if (model === void 0 && requestedModel !== void 0) {
|
|
3205
|
+
assertModelThinking(requestedModel);
|
|
3206
|
+
model = resolveModelReference(requestedModel, aliases, options.knownModels ?? options.availableModels, options.settingsPath ?? settings?.sources.modelAliases);
|
|
3207
|
+
}
|
|
3208
|
+
if (model === void 0) model = options.rootModel;
|
|
3209
|
+
const availableModels = options.knownModels ?? options.availableModels ?? (options.rootModel === void 0 ? void 0 : /* @__PURE__ */ new Set([modelCapability(options.rootModel)]));
|
|
3210
|
+
if (model !== void 0 && availableModels !== void 0 && !availableModels.has(modelCapability(model))) fail("UNKNOWN_MODEL", `Unknown model${requestedModel ? ` ${requestedModel} resolved to ${modelCapability(model)}` : ""}${options.settingsPath ? ` (settings: ${options.settingsPath})` : ""}`);
|
|
3211
|
+
const prompt = definition?.prompt ?? "";
|
|
3212
|
+
const overrideSystemPrompt = definition?.overrideSystemPrompt === true;
|
|
3213
|
+
const contextFiles = options.contextFiles ?? definition?.contextFiles;
|
|
3214
|
+
return { ...name === void 0 ? {} : { name }, ...definition === void 0 ? {} : { definition }, ...model === void 0 ? {} : { model }, ...modelAliasName(requestedModel ?? "", aliases) && requestedModel ? { requestedModel } : {}, ...tools === void 0 ? {} : { tools }, prompt, overrideSystemPrompt, systemPrompt: { mode: overrideSystemPrompt ? "override" : "append", text: prompt }, ...contextFiles === void 0 ? {} : { contextFiles: [...contextFiles] }, ...definition?.extensionSettings === void 0 ? {} : { extensionSettings: definition.extensionSettings }, selectorSources, selectorLayers, ...selectedSkills === void 0 ? {} : { selectedSkills }, ...selectedExtensions === void 0 ? {} : { selectedExtensions }, ...unmatchedSkills === void 0 ? {} : { unmatchedSkills }, ...unmatchedExtensions === void 0 ? {} : { unmatchedExtensions }, ...unmatchedTools === void 0 ? {} : { unmatchedTools } };
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
// src/validation.ts
|
|
3218
|
+
function validateCheckpoint(value) {
|
|
3219
|
+
if (!object(value) || Object.keys(value).some((key) => !["name", "prompt", "context"].includes(key)) || typeof value.name !== "string" || value.name.trim() === "" || typeof value.prompt !== "string" || !jsonValue(value.context)) fail("INVALID_METADATA", "checkpoint requires only name, prompt, and JSON context");
|
|
3220
|
+
if (Buffer.byteLength(value.prompt) > 1024) fail("INVALID_METADATA", "checkpoint prompt exceeds 1024 UTF-8 bytes");
|
|
3221
|
+
if (Buffer.byteLength(JSON.stringify(value.context)) > 4096) fail("INVALID_METADATA", "checkpoint context exceeds 4096 UTF-8 bytes");
|
|
3222
|
+
return { name: value.name, prompt: value.prompt, context: value.context };
|
|
3093
3223
|
}
|
|
3094
3224
|
function validateRolePolicies(definitions, roles, availableModels, aliases = {}, knownModels = availableModels, settingsPath) {
|
|
3095
3225
|
for (const role of roles) {
|
|
@@ -3346,12 +3476,6 @@ function workflowPrompt(template, values) {
|
|
|
3346
3476
|
return typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
3347
3477
|
});
|
|
3348
3478
|
}
|
|
3349
|
-
function validateSchema(schema, at = "schema") {
|
|
3350
|
-
if (!object(schema) || Object.getPrototypeOf(schema) !== Object.prototype || !jsonValue(schema)) fail("INVALID_SCHEMA", `${at} must be a plain JSON-compatible Schema object`);
|
|
3351
|
-
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`);
|
|
3352
|
-
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`);
|
|
3353
|
-
if (schema.properties !== void 0 && !object(schema.properties)) fail("INVALID_SCHEMA", `${at}.properties must be an object`);
|
|
3354
|
-
}
|
|
3355
3479
|
var AGENT_OPTION_KEYS = /* @__PURE__ */ new Set(["label", "model", "tools", "skills", "extensions", "contextFiles", "role", "outputSchema", "retries", "timeoutMs"]);
|
|
3356
3480
|
function validateAgentOption(key, value, aliases, knownModels, settingsPath) {
|
|
3357
3481
|
switch (key) {
|
|
@@ -3597,7 +3721,7 @@ function validateWorkflowLaunchWithRegistry(params, context, registry) {
|
|
|
3597
3721
|
let fileScript;
|
|
3598
3722
|
if (scriptPath !== void 0) {
|
|
3599
3723
|
try {
|
|
3600
|
-
fileScript =
|
|
3724
|
+
fileScript = readFileSync4(resolve5(context.cwd, scriptPath), "utf8");
|
|
3601
3725
|
} catch (error) {
|
|
3602
3726
|
fail("INVALID_SYNTAX", `Cannot read workflow script file ${scriptPath}: ${errorText(error)}`);
|
|
3603
3727
|
}
|
|
@@ -3631,7 +3755,7 @@ function validateWorkflowLaunchWithRegistry(params, context, registry) {
|
|
|
3631
3755
|
import { fork, spawn } from "node:child_process";
|
|
3632
3756
|
import { mkdtempSync, realpathSync as realpathSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3633
3757
|
import { tmpdir } from "node:os";
|
|
3634
|
-
import { join as
|
|
3758
|
+
import { join as join7 } from "node:path";
|
|
3635
3759
|
import { StringDecoder } from "node:string_decoder";
|
|
3636
3760
|
var RPC_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
3637
3761
|
var HEARTBEAT_TIMEOUT_MS = 5e3;
|
|
@@ -4037,7 +4161,7 @@ function shellProcessKill(child) {
|
|
|
4037
4161
|
forceKill.unref();
|
|
4038
4162
|
}
|
|
4039
4163
|
function executeShellCommand(command, options, signal, cwd = process.cwd()) {
|
|
4040
|
-
return new Promise((
|
|
4164
|
+
return new Promise((resolve9, reject) => {
|
|
4041
4165
|
let child;
|
|
4042
4166
|
try {
|
|
4043
4167
|
child = spawn(command, { shell: true, cwd, env: { ...process.env, ...options.env ?? {} }, detached: process.platform !== "win32", stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -4107,7 +4231,7 @@ function executeShellCommand(command, options, signal, cwd = process.cwd()) {
|
|
|
4107
4231
|
reject(error instanceof WorkflowError ? error : new WorkflowError("RPC_LIMIT_EXCEEDED", errorText(error)));
|
|
4108
4232
|
return;
|
|
4109
4233
|
}
|
|
4110
|
-
|
|
4234
|
+
resolve9(result);
|
|
4111
4235
|
});
|
|
4112
4236
|
if (signal.aborted) {
|
|
4113
4237
|
onAbort();
|
|
@@ -4131,8 +4255,8 @@ function workflowErrorFromWorker(error) {
|
|
|
4131
4255
|
function runWorkflow(script, args = null, bridge = {}, signal) {
|
|
4132
4256
|
encoded(args);
|
|
4133
4257
|
const config = JSON.stringify({ script: instrumentWorkflow(script), args: structuredClone(args), functions: bridge.functions ?? {} });
|
|
4134
|
-
const childDir = realpathSync2(mkdtempSync(
|
|
4135
|
-
const childFile =
|
|
4258
|
+
const childDir = realpathSync2(mkdtempSync(join7(tmpdir(), "pi-wf-")));
|
|
4259
|
+
const childFile = join7(childDir, "child.cjs");
|
|
4136
4260
|
writeFileSync2(childFile, childSource);
|
|
4137
4261
|
const child = fork(childFile, [String(RPC_LIMIT_BYTES), config], {
|
|
4138
4262
|
execArgv: (() => {
|
|
@@ -4172,7 +4296,7 @@ function runWorkflow(script, args = null, bridge = {}, signal) {
|
|
|
4172
4296
|
}
|
|
4173
4297
|
if (now - lastHeartbeatAt >= HEARTBEAT_TIMEOUT_MS) stop("WORKER_UNRESPONSIVE", "Workflow worker missed its five-second heartbeat");
|
|
4174
4298
|
}, HEARTBEAT_INTERVAL_MS);
|
|
4175
|
-
const result = new Promise((
|
|
4299
|
+
const result = new Promise((resolve9, reject) => {
|
|
4176
4300
|
rejectResult = reject;
|
|
4177
4301
|
child.on("message", (raw) => {
|
|
4178
4302
|
try {
|
|
@@ -4187,7 +4311,7 @@ function runWorkflow(script, args = null, bridge = {}, signal) {
|
|
|
4187
4311
|
if (message.type === "result") {
|
|
4188
4312
|
encoded(message.value);
|
|
4189
4313
|
finish();
|
|
4190
|
-
|
|
4314
|
+
resolve9(message.value);
|
|
4191
4315
|
return;
|
|
4192
4316
|
}
|
|
4193
4317
|
if (message.type === "error") {
|
|
@@ -4343,20 +4467,20 @@ async function persistAgentAttempts(store, id, attempts) {
|
|
|
4343
4467
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
4344
4468
|
import { existsSync as existsSync2 } from "node:fs";
|
|
4345
4469
|
import { copyFile as copyFile2, mkdir as mkdir4 } from "node:fs/promises";
|
|
4346
|
-
import { dirname as
|
|
4470
|
+
import { dirname as dirname8, join as join16 } from "node:path";
|
|
4347
4471
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
4348
4472
|
import { Type as Type3 } from "@earendil-works/pi-ai";
|
|
4349
|
-
import { copyToClipboard, getAgentDir as
|
|
4473
|
+
import { copyToClipboard, getAgentDir as getAgentDir5, ModelSelectorComponent } from "@earendil-works/pi-coding-agent";
|
|
4350
4474
|
|
|
4351
4475
|
// src/agent-execution.ts
|
|
4352
|
-
import { existsSync, realpathSync as realpathSync3, readFileSync as
|
|
4476
|
+
import { existsSync, realpathSync as realpathSync3, readFileSync as readFileSync5 } from "node:fs";
|
|
4353
4477
|
import { copyFile } from "node:fs/promises";
|
|
4354
4478
|
import { homedir as homedir5 } from "node:os";
|
|
4355
|
-
import { basename as basename4, dirname as
|
|
4479
|
+
import { basename as basename4, dirname as dirname5, join as join8, resolve as resolve6 } from "node:path";
|
|
4356
4480
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
4357
4481
|
import { Type } from "@earendil-works/pi-ai";
|
|
4358
4482
|
import { Compile } from "typebox/compile";
|
|
4359
|
-
import { createAgentSession, DefaultPackageManager, DefaultResourceLoader, defineTool, getAgentDir as
|
|
4483
|
+
import { createAgentSession, DefaultPackageManager, DefaultResourceLoader, defineTool, getAgentDir as getAgentDir3, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
4360
4484
|
|
|
4361
4485
|
// src/pi-runtime-adapter.ts
|
|
4362
4486
|
var TURN_START_EVENTS = /* @__PURE__ */ new Set(["turn_start", "turn_started", "turnStarted"]);
|
|
@@ -4631,8 +4755,8 @@ function createLiveSessionHandoff() {
|
|
|
4631
4755
|
let request;
|
|
4632
4756
|
const waitForBoundary = () => {
|
|
4633
4757
|
if (!turnActive) return Promise.resolve();
|
|
4634
|
-
boundary ??= new Promise((
|
|
4635
|
-
resolveBoundary =
|
|
4758
|
+
boundary ??= new Promise((resolve9) => {
|
|
4759
|
+
resolveBoundary = resolve9;
|
|
4636
4760
|
});
|
|
4637
4761
|
return boundary;
|
|
4638
4762
|
};
|
|
@@ -4664,11 +4788,11 @@ function createLiveSessionHandoff() {
|
|
|
4664
4788
|
if (request) return request;
|
|
4665
4789
|
if (isReleased()) return;
|
|
4666
4790
|
state = "handoff-pending";
|
|
4667
|
-
takeover = new Promise((
|
|
4668
|
-
resolveTakeover =
|
|
4791
|
+
takeover = new Promise((resolve9) => {
|
|
4792
|
+
resolveTakeover = resolve9;
|
|
4669
4793
|
});
|
|
4670
|
-
resume = new Promise((
|
|
4671
|
-
resolveResume =
|
|
4794
|
+
resume = new Promise((resolve9) => {
|
|
4795
|
+
resolveResume = resolve9;
|
|
4672
4796
|
});
|
|
4673
4797
|
request = (async () => {
|
|
4674
4798
|
await waitForBoundary();
|
|
@@ -5195,36 +5319,28 @@ function parseModel(value, fallback, aliases = {}, knownModels, settingsPath) {
|
|
|
5195
5319
|
function accounting(stats) {
|
|
5196
5320
|
return { input: stats.tokens.input, output: stats.tokens.output, cacheRead: stats.tokens.cacheRead, cacheWrite: stats.tokens.cacheWrite, cost: stats.cost };
|
|
5197
5321
|
}
|
|
5198
|
-
var extensionDirectory =
|
|
5199
|
-
var workflowPackageRoot = basename4(
|
|
5322
|
+
var extensionDirectory = dirname5(fileURLToPath3(import.meta.url));
|
|
5323
|
+
var workflowPackageRoot = basename4(dirname5(extensionDirectory)) === "dist" ? resolve6(extensionDirectory, "../..") : resolve6(extensionDirectory, "..");
|
|
5200
5324
|
var WORKFLOW_HOST_ENTRIES = /* @__PURE__ */ new Set([
|
|
5201
|
-
canonicalPath(
|
|
5202
|
-
canonicalPath(
|
|
5203
|
-
canonicalPath(
|
|
5204
|
-
canonicalPath(
|
|
5205
|
-
canonicalPath(
|
|
5206
|
-
canonicalPath(
|
|
5325
|
+
canonicalPath(resolve6(workflowPackageRoot, "src/index.ts")),
|
|
5326
|
+
canonicalPath(resolve6(workflowPackageRoot, "dist/src/index.js")),
|
|
5327
|
+
canonicalPath(resolve6(workflowPackageRoot, "starter/index.ts")),
|
|
5328
|
+
canonicalPath(resolve6(workflowPackageRoot, "dist/starter/index.js")),
|
|
5329
|
+
canonicalPath(resolve6(workflowPackageRoot, "subagents/index.ts")),
|
|
5330
|
+
canonicalPath(resolve6(workflowPackageRoot, "dist/subagents/index.js"))
|
|
5207
5331
|
]);
|
|
5208
|
-
function canonicalExtensionSelector(selector, base = process.cwd()) {
|
|
5209
|
-
const negated = selector.startsWith("!");
|
|
5210
|
-
const body = negated ? selector.slice(1) : selector;
|
|
5211
|
-
if (body === "*" || body === "**" || body.startsWith("**/")) return selector;
|
|
5212
|
-
const resolved = resolve4(base, body);
|
|
5213
|
-
if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
|
|
5214
|
-
return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
|
|
5215
|
-
}
|
|
5216
5332
|
var WORKFLOW_DIRECTORY = "pi-extensible-workflows";
|
|
5217
5333
|
function workflowSystemPromptPath(cwd, agentDir, projectTrusted2) {
|
|
5218
|
-
const projectPath =
|
|
5334
|
+
const projectPath = join8(cwd, ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md");
|
|
5219
5335
|
if (projectTrusted2 && existsSync(projectPath)) return projectPath;
|
|
5220
|
-
const globalPaths = [
|
|
5336
|
+
const globalPaths = [join8(homedir5(), ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md"), join8(agentDir, WORKFLOW_DIRECTORY, "SYSTEM.md")];
|
|
5221
5337
|
return globalPaths.find((path) => existsSync(path));
|
|
5222
5338
|
}
|
|
5223
5339
|
function filterContextFiles(base, scopes, cwd, agentDir) {
|
|
5224
5340
|
const globalPath = canonicalPath(agentDir);
|
|
5225
5341
|
const cwdPath = canonicalPath(cwd);
|
|
5226
5342
|
return base.filter(({ path }) => {
|
|
5227
|
-
const resolvedPath = canonicalPath(
|
|
5343
|
+
const resolvedPath = canonicalPath(dirname5(path));
|
|
5228
5344
|
return scopes.some((scope) => scope === "global" ? resolvedPath === globalPath : scope === "cwd" ? resolvedPath === cwdPath : resolvedPath !== globalPath && resolvedPath !== cwdPath);
|
|
5229
5345
|
});
|
|
5230
5346
|
}
|
|
@@ -5250,10 +5366,10 @@ async function loadNativePromptTemplateModule() {
|
|
|
5250
5366
|
const packageDirectory = await loadPiPackageDirectory();
|
|
5251
5367
|
let promptTemplatePath;
|
|
5252
5368
|
if (packageDirectory.directory) {
|
|
5253
|
-
const candidate =
|
|
5369
|
+
const candidate = resolve6(packageDirectory.directory, "dist/core/prompt-templates.js");
|
|
5254
5370
|
if (existsSync(candidate)) promptTemplatePath = candidate;
|
|
5255
5371
|
}
|
|
5256
|
-
promptTemplatePath ??=
|
|
5372
|
+
promptTemplatePath ??= resolve6(dirname5(fileURLToPath3(import.meta.resolve("@earendil-works/pi-coding-agent"))), "core/prompt-templates.js");
|
|
5257
5373
|
const packageDirectoryError = packageDirectory.error ? ` Package directory lookup failed: ${packageDirectory.error}` : "";
|
|
5258
5374
|
return nativePromptTemplateModule ??= import(promptTemplatePath).then((module) => module).catch((error) => {
|
|
5259
5375
|
throw new Error(`Could not load Pi prompt templates.${packageDirectoryError}`, { cause: error });
|
|
@@ -5328,13 +5444,13 @@ function flushExtensionProviders(resourceLoader, modelRuntime) {
|
|
|
5328
5444
|
return failures;
|
|
5329
5445
|
}
|
|
5330
5446
|
async function createLocalPiSessionHandle(input, sessionStartEvent) {
|
|
5331
|
-
const agentDir = input.agentDir ??
|
|
5447
|
+
const agentDir = input.agentDir ?? getAgentDir3();
|
|
5332
5448
|
const systemPromptSource = workflowSystemPromptPath(input.cwd, agentDir, input.resourcePolicy?.projectTrusted ?? true);
|
|
5333
5449
|
const systemPromptOptions = input.systemPrompt !== void 0 ? { systemPromptOverride: () => input.systemPrompt } : systemPromptSource !== void 0 ? { systemPrompt: systemPromptSource } : {};
|
|
5334
5450
|
const contextFilesOverride = input.contextFiles === void 0 ? void 0 : (base) => ({ ...base, agentsFiles: filterContextFiles(base.agentsFiles, input.contextFiles ?? [], input.cwd, agentDir) });
|
|
5335
|
-
const manager = input.sessionManager ?? (input.sessionPath ? SessionManager.open(input.sessionPath,
|
|
5451
|
+
const manager = input.sessionManager ?? (input.sessionPath ? SessionManager.open(input.sessionPath, join8(agentDir, "sessions"), input.cwd) : input.agentDir ? SessionManager.create(input.cwd, join8(agentDir, "sessions")) : SessionManager.create(input.cwd));
|
|
5336
5452
|
if (!input.sessionPath) manager.appendSessionInfo(input.sessionLabel);
|
|
5337
|
-
const modelRuntime = await ModelRuntime.create({ authPath:
|
|
5453
|
+
const modelRuntime = await ModelRuntime.create({ authPath: join8(agentDir, "auth.json"), modelsPath: join8(agentDir, "models.json") });
|
|
5338
5454
|
const customTools = [...input.customTools ?? [], ...input.resultTool ? [input.resultTool] : []];
|
|
5339
5455
|
const tools = [.../* @__PURE__ */ new Set([...input.tools, ...customTools.map(({ name }) => name)])];
|
|
5340
5456
|
let settingsManager;
|
|
@@ -5347,20 +5463,22 @@ async function createLocalPiSessionHandle(input, sessionStartEvent) {
|
|
|
5347
5463
|
const resolved = await packageManager.resolve();
|
|
5348
5464
|
const discoveredExtensions = [...new Set(resolved.extensions.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => canonicalPath(path)))];
|
|
5349
5465
|
const selectorSources = policy.selectorSources;
|
|
5350
|
-
const
|
|
5351
|
-
|
|
5352
|
-
|
|
5466
|
+
const selection = resolveRole(void 0, {
|
|
5467
|
+
cwd: input.cwd,
|
|
5468
|
+
selectorSources,
|
|
5469
|
+
resources: { extensions: discoveredExtensions }
|
|
5470
|
+
});
|
|
5471
|
+
const selectedExtensions = selection.selectedExtensions ?? [];
|
|
5353
5472
|
const extensionPaths = selectedExtensions.filter((path) => !WORKFLOW_HOST_ENTRIES.has(path));
|
|
5354
|
-
const unmatchedExtensions = extensionSelectors.filter(({ matching }) => unmatchedResourcePatterns([matching], discoveredExtensions).length > 0).map(({ original }) => original);
|
|
5355
5473
|
policy.selectedExtensions = selectedExtensions;
|
|
5356
|
-
policy.unmatchedExtensions = unmatchedExtensions;
|
|
5474
|
+
policy.unmatchedExtensions = selection.unmatchedExtensions ?? [];
|
|
5357
5475
|
const skillPaths = [...new Set(resolved.skills.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => path))];
|
|
5358
5476
|
const updateSkillMatches = (skills) => {
|
|
5359
5477
|
const names = [...new Set(skills.map(({ name }) => name))];
|
|
5360
|
-
const
|
|
5361
|
-
const selectedSkills =
|
|
5478
|
+
const selected = resolveRole(void 0, { cwd: input.cwd, selectorSources, resources: { skills: names } });
|
|
5479
|
+
const selectedSkills = selected.selectedSkills ?? [];
|
|
5362
5480
|
policy.selectedSkills = selectedSkills;
|
|
5363
|
-
policy.unmatchedSkills =
|
|
5481
|
+
policy.unmatchedSkills = selected.unmatchedSkills ?? [];
|
|
5364
5482
|
return new Set(names.filter((name) => !selectedSkills.includes(name)));
|
|
5365
5483
|
};
|
|
5366
5484
|
resourceLoader = new DefaultResourceLoader({
|
|
@@ -5852,27 +5970,27 @@ function appendResourcePolicyNarrowing(ceiling, candidate) {
|
|
|
5852
5970
|
};
|
|
5853
5971
|
}
|
|
5854
5972
|
function packageRoot(start) {
|
|
5855
|
-
let current =
|
|
5973
|
+
let current = dirname5(realpathSync3(start));
|
|
5856
5974
|
for (; ; ) {
|
|
5857
|
-
const candidates = [current,
|
|
5975
|
+
const candidates = [current, join8(current, "..", "@earendil-works", "pi-coding-agent"), join8(current, "..", "pi-coding-agent")];
|
|
5858
5976
|
for (const candidate of candidates) {
|
|
5859
|
-
const packageJsonPath =
|
|
5977
|
+
const packageJsonPath = join8(candidate, "package.json");
|
|
5860
5978
|
if (!existsSync(packageJsonPath)) continue;
|
|
5861
|
-
const packageJson = JSON.parse(
|
|
5979
|
+
const packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf8"));
|
|
5862
5980
|
if (object(packageJson) && typeof packageJson.name === "string" && packageJson.name === "@earendil-works/pi-coding-agent") return candidate;
|
|
5863
5981
|
}
|
|
5864
|
-
const parent =
|
|
5982
|
+
const parent = dirname5(current);
|
|
5865
5983
|
if (parent === current) return void 0;
|
|
5866
5984
|
current = parent;
|
|
5867
5985
|
}
|
|
5868
5986
|
}
|
|
5869
5987
|
function piPackageEntrypoint(root) {
|
|
5870
|
-
const packageJson = JSON.parse(
|
|
5988
|
+
const packageJson = JSON.parse(readFileSync5(join8(root, "package.json"), "utf8"));
|
|
5871
5989
|
if (!object(packageJson)) throw new Error("the @earendil-works/pi-coding-agent package does not declare a pi CLI");
|
|
5872
5990
|
const bin = packageJson.bin;
|
|
5873
5991
|
const cli = typeof bin === "string" ? bin : object(bin) && typeof bin.pi === "string" ? bin.pi : void 0;
|
|
5874
5992
|
if (!cli) throw new Error("the @earendil-works/pi-coding-agent package does not declare a pi CLI");
|
|
5875
|
-
const entrypoint =
|
|
5993
|
+
const entrypoint = resolve6(root, cli);
|
|
5876
5994
|
if (!existsSync(entrypoint)) throw new Error(`the originating Pi CLI entrypoint is unavailable: ${entrypoint}`);
|
|
5877
5995
|
return entrypoint;
|
|
5878
5996
|
}
|
|
@@ -5913,7 +6031,7 @@ ${instruction}` : ""}`;
|
|
|
5913
6031
|
}
|
|
5914
6032
|
async function preparedAgentSession(input, initialPrompt) {
|
|
5915
6033
|
const { runtime: piRuntime, error: piRuntimeError } = await piRuntimeResolution;
|
|
5916
|
-
const systemPromptPath = input.systemPrompt === void 0 ? workflowSystemPromptPath(input.cwd, input.agentDir ??
|
|
6034
|
+
const systemPromptPath = input.systemPrompt === void 0 ? workflowSystemPromptPath(input.cwd, input.agentDir ?? getAgentDir3(), input.resourcePolicy?.projectTrusted ?? true) : void 0;
|
|
5917
6035
|
const prepared = {
|
|
5918
6036
|
cwd: input.cwd,
|
|
5919
6037
|
model: Object.freeze({ ...input.model }),
|
|
@@ -5943,7 +6061,7 @@ function agentSetupSummary(setup, hookNames) {
|
|
|
5943
6061
|
return { hookNames: [...hookNames], model: { provider: model.provider, model: model.model, ...model.thinking ? { thinking: model.thinking } : {} }, tools: [...setup.sessionInput.tools, ...setup.sessionInput.resultTool ? [setup.sessionInput.resultTool.name] : []], cwd: setup.sessionInput.cwd, ...setup.sessionInput.resourcePolicy ? { resourceSelectors: resourcePolicySummary(setup.sessionInput.resourcePolicy, setup.sessionInput.tools) } : {} };
|
|
5944
6062
|
}
|
|
5945
6063
|
async function attemptSessionInput(source, attempt) {
|
|
5946
|
-
const target =
|
|
6064
|
+
const target = join8(dirname5(source), `${basename4(source, ".jsonl")}-attempt-${String(attempt)}.jsonl`);
|
|
5947
6065
|
await copyFile(source, target);
|
|
5948
6066
|
return target;
|
|
5949
6067
|
}
|
|
@@ -6053,7 +6171,6 @@ var WorkflowAgentExecutor = class {
|
|
|
6053
6171
|
const roleName = typeof options.role === "string" ? options.role : void 0;
|
|
6054
6172
|
const roleDefinition = roleName ? this.root.agentDefinitions?.[roleName] : void 0;
|
|
6055
6173
|
if (roleName && !roleDefinition) throw new WorkflowError("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${roleName}`);
|
|
6056
|
-
const candidateTools = inheritedTools !== void 0 ? [...inheritedTools] : [...this.root.tools];
|
|
6057
6174
|
if (inheritedTools === void 0) for (const pattern of roleDefinition?.tools ?? []) {
|
|
6058
6175
|
const body = pattern.startsWith("!") ? pattern.slice(1) : pattern;
|
|
6059
6176
|
if (!pattern.startsWith("!") && !resourcePatternHasMagic(pattern) && !this.root.tools.has(body)) {
|
|
@@ -6064,33 +6181,30 @@ var WorkflowAgentExecutor = class {
|
|
|
6064
6181
|
}
|
|
6065
6182
|
}
|
|
6066
6183
|
}
|
|
6067
|
-
const
|
|
6068
|
-
this.root.
|
|
6069
|
-
roleDefinition
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
const overrideSystemPrompt = roleDefinition?.overrideSystemPrompt === true;
|
|
6092
|
-
const contextFiles = options.contextFiles ?? roleDefinition?.contextFiles;
|
|
6093
|
-
return { model, ...alias && requestedModel ? { requestedModel } : {}, tools: requested, ...overrideSystemPrompt ? { systemPrompt: roleDefinition.prompt ?? "" } : {}, systemPromptAppend: overrideSystemPrompt ? "" : roleDefinition?.prompt ?? "", ...contextFiles === void 0 ? {} : { contextFiles: [...contextFiles] } };
|
|
6184
|
+
const resolved = resolveRole(roleName, {
|
|
6185
|
+
cwd: this.root.cwd,
|
|
6186
|
+
...roleDefinition === void 0 ? {} : { definition: roleDefinition },
|
|
6187
|
+
...this.root.agentDefinitions === void 0 ? {} : { definitions: this.root.agentDefinitions },
|
|
6188
|
+
...this.root.agentDir === void 0 ? {} : { agentDir: this.root.agentDir },
|
|
6189
|
+
selectorSources: { global: this.root.resourceSelectors ?? {}, project: {} },
|
|
6190
|
+
...this.root.modelAliases === void 0 ? {} : { modelAliases: this.root.modelAliases },
|
|
6191
|
+
...this.root.knownModels === void 0 ? {} : { knownModels: this.root.knownModels },
|
|
6192
|
+
...this.root.availableModels === void 0 ? {} : { availableModels: this.root.availableModels },
|
|
6193
|
+
...this.root.blockedAliases === void 0 ? {} : { blockedAliases: this.root.blockedAliases },
|
|
6194
|
+
...this.root.blockedAliasTargets === void 0 ? {} : { blockedAliasTargets: this.root.blockedAliasTargets },
|
|
6195
|
+
...this.root.settingsPath === void 0 ? {} : { settingsPath: this.root.settingsPath },
|
|
6196
|
+
rootModel: this.root.model,
|
|
6197
|
+
rootTools: this.root.tools,
|
|
6198
|
+
...inheritedTools === void 0 ? {} : { inheritedTools },
|
|
6199
|
+
...options.model === void 0 ? {} : { model: options.model },
|
|
6200
|
+
...options.modelOverride === void 0 ? {} : { modelOverride: options.modelOverride },
|
|
6201
|
+
...options.tools === void 0 ? {} : { tools: options.tools },
|
|
6202
|
+
...options.skills === void 0 ? {} : { skills: options.skills },
|
|
6203
|
+
...options.extensions === void 0 ? {} : { extensions: options.extensions },
|
|
6204
|
+
...options.contextFiles === void 0 ? {} : { contextFiles: options.contextFiles },
|
|
6205
|
+
...options.effectiveTools === void 0 ? {} : { effectiveTools: options.effectiveTools }
|
|
6206
|
+
});
|
|
6207
|
+
return { model: resolved.model ?? this.root.model, ...resolved.requestedModel === void 0 ? {} : { requestedModel: resolved.requestedModel }, tools: resolved.tools ?? [], ...resolved.overrideSystemPrompt ? { systemPrompt: resolved.prompt } : {}, systemPromptAppend: resolved.overrideSystemPrompt ? "" : resolved.prompt, ...resolved.contextFiles === void 0 ? {} : { contextFiles: resolved.contextFiles } };
|
|
6094
6208
|
}
|
|
6095
6209
|
async execute(task, options, signal, customTools = [], setSteer, beforeRetry) {
|
|
6096
6210
|
const executionSignal = signal ?? this.root.runContext?.signal;
|
|
@@ -6376,12 +6490,12 @@ var FairAgentScheduler = class {
|
|
|
6376
6490
|
const id = `${runId}:${String(++this.#nextId)}`;
|
|
6377
6491
|
const tuiIndex = ++run.nextIndex;
|
|
6378
6492
|
let resolveResult = () => void 0;
|
|
6379
|
-
const promise = new Promise((
|
|
6380
|
-
resolveResult =
|
|
6493
|
+
const promise = new Promise((resolve9) => {
|
|
6494
|
+
resolveResult = resolve9;
|
|
6381
6495
|
});
|
|
6382
6496
|
let resolveCompletion = () => void 0;
|
|
6383
|
-
const completion = new Promise((
|
|
6384
|
-
resolveCompletion =
|
|
6497
|
+
const completion = new Promise((resolve9) => {
|
|
6498
|
+
resolveCompletion = resolve9;
|
|
6385
6499
|
});
|
|
6386
6500
|
const node = { id, runId, ...parentId ? { parentId } : {}, prompt, options: effective, children: /* @__PURE__ */ new Set(), collected: false, collecting: false, resultQueue: Promise.resolve(), state: "queued", controller: new AbortController(), promise, resolve: resolveResult, completion, resolveCompletion, task: async () => void 0, restored: false };
|
|
6387
6501
|
node.task = async () => {
|
|
@@ -6419,8 +6533,8 @@ var FairAgentScheduler = class {
|
|
|
6419
6533
|
const childPromise = child.promise;
|
|
6420
6534
|
const previousCollection = parent.resultQueue;
|
|
6421
6535
|
let releaseCollection = () => void 0;
|
|
6422
|
-
parent.resultQueue = new Promise((
|
|
6423
|
-
releaseCollection =
|
|
6536
|
+
parent.resultQueue = new Promise((resolve9) => {
|
|
6537
|
+
releaseCollection = resolve9;
|
|
6424
6538
|
});
|
|
6425
6539
|
try {
|
|
6426
6540
|
await previousCollection;
|
|
@@ -6429,9 +6543,9 @@ var FairAgentScheduler = class {
|
|
|
6429
6543
|
this.#persist(parent.runId);
|
|
6430
6544
|
this.#release(parent.runId);
|
|
6431
6545
|
const outcome = await childPromise;
|
|
6432
|
-
await new Promise((
|
|
6546
|
+
await new Promise((resolve9) => {
|
|
6433
6547
|
this.#enqueue(parent.runId, void 0, () => {
|
|
6434
|
-
|
|
6548
|
+
resolve9();
|
|
6435
6549
|
});
|
|
6436
6550
|
});
|
|
6437
6551
|
if (this.#cancelledOrSettled(parent)) {
|
|
@@ -6564,12 +6678,12 @@ var FairAgentScheduler = class {
|
|
|
6564
6678
|
for (const record3 of ownership) {
|
|
6565
6679
|
if (record3.id.split(":").slice(0, -1).join(":") !== runId) throw new WorkflowError("RESUME_INCOMPATIBLE", `Persisted agent belongs to another run: ${record3.id}`);
|
|
6566
6680
|
let resolveResult = () => void 0;
|
|
6567
|
-
const promise = new Promise((
|
|
6568
|
-
resolveResult =
|
|
6681
|
+
const promise = new Promise((resolve9) => {
|
|
6682
|
+
resolveResult = resolve9;
|
|
6569
6683
|
});
|
|
6570
6684
|
let resolveCompletion = () => void 0;
|
|
6571
|
-
const completion = new Promise((
|
|
6572
|
-
resolveCompletion =
|
|
6685
|
+
const completion = new Promise((resolve9) => {
|
|
6686
|
+
resolveCompletion = resolve9;
|
|
6573
6687
|
});
|
|
6574
6688
|
const node = { id: record3.id, runId, ...record3.parentId ? { parentId: record3.parentId } : {}, ...record3.prompt === void 0 ? {} : { prompt: record3.prompt }, options: this.#inherit(run, void 0, record3.options), children: /* @__PURE__ */ new Set(), collected: false, collecting: false, resultQueue: Promise.resolve(), state: record3.state, controller: new AbortController(), promise, resolve: resolveResult, completion, resolveCompletion, task: async () => void 0, restored: true };
|
|
6575
6689
|
this.#nodes.set(node.id, node);
|
|
@@ -6658,9 +6772,9 @@ var FairAgentScheduler = class {
|
|
|
6658
6772
|
const child = this.#nodes.get(childId);
|
|
6659
6773
|
if (child && !child.collected) this.#cancelTree(child);
|
|
6660
6774
|
}
|
|
6661
|
-
const
|
|
6775
|
+
const resolve9 = node.resolve;
|
|
6662
6776
|
node.resolve = void 0;
|
|
6663
|
-
|
|
6777
|
+
resolve9?.(result);
|
|
6664
6778
|
node.resolveCompletion();
|
|
6665
6779
|
}
|
|
6666
6780
|
#cancelTree(node) {
|
|
@@ -6723,8 +6837,8 @@ var RunLifecycle = class {
|
|
|
6723
6837
|
return this.#state;
|
|
6724
6838
|
}
|
|
6725
6839
|
async enter() {
|
|
6726
|
-
while (this.#state === "pausing" || this.#state === "paused" || this.#state === "awaiting_input") await new Promise((
|
|
6727
|
-
this.#waiters.push(
|
|
6840
|
+
while (this.#state === "pausing" || this.#state === "paused" || this.#state === "awaiting_input") await new Promise((resolve9) => {
|
|
6841
|
+
this.#waiters.push(resolve9);
|
|
6728
6842
|
});
|
|
6729
6843
|
if (this.#state !== "running") throw new WorkflowError("CANCELLED", `Run is ${this.#state}`);
|
|
6730
6844
|
this.#active += 1;
|
|
@@ -6734,8 +6848,8 @@ var RunLifecycle = class {
|
|
|
6734
6848
|
if (this.#state === "pausing" && this.#active === 0) await this.#set("paused", "pause");
|
|
6735
6849
|
}
|
|
6736
6850
|
async enterAwaitingInput() {
|
|
6737
|
-
while (this.#state === "pausing" || this.#state === "paused") await new Promise((
|
|
6738
|
-
this.#waiters.push(
|
|
6851
|
+
while (this.#state === "pausing" || this.#state === "paused") await new Promise((resolve9) => {
|
|
6852
|
+
this.#waiters.push(resolve9);
|
|
6739
6853
|
});
|
|
6740
6854
|
if (this.#state === "awaiting_input") return;
|
|
6741
6855
|
if (this.#state !== "running") throw new WorkflowError("RESUME_INCOMPATIBLE", `Cannot await input for ${this.#state} run`);
|
|
@@ -6744,7 +6858,7 @@ var RunLifecycle = class {
|
|
|
6744
6858
|
async resolveAwaitingInput() {
|
|
6745
6859
|
if (this.#state !== "awaiting_input") return;
|
|
6746
6860
|
await this.#set("running", "checkpoint_resolved");
|
|
6747
|
-
for (const
|
|
6861
|
+
for (const resolve9 of this.#waiters.splice(0)) resolve9();
|
|
6748
6862
|
}
|
|
6749
6863
|
async pause() {
|
|
6750
6864
|
if (this.#state !== "running") throw new WorkflowError("RESUME_INCOMPATIBLE", `Cannot pause ${this.#state} run`);
|
|
@@ -6755,7 +6869,7 @@ var RunLifecycle = class {
|
|
|
6755
6869
|
async resume() {
|
|
6756
6870
|
if (this.#state !== "pausing" && this.#state !== "paused" && this.#state !== "interrupted" && this.#state !== "budget_exhausted") throw new WorkflowError("RESUME_INCOMPATIBLE", `Cannot resume ${this.#state} run`);
|
|
6757
6871
|
await this.#set("running", "resume");
|
|
6758
|
-
for (const
|
|
6872
|
+
for (const resolve9 of this.#waiters.splice(0)) resolve9();
|
|
6759
6873
|
}
|
|
6760
6874
|
async providerPause() {
|
|
6761
6875
|
await this.leave();
|
|
@@ -6765,7 +6879,7 @@ var RunLifecycle = class {
|
|
|
6765
6879
|
async terminal(state, reason) {
|
|
6766
6880
|
if (HARD_TERMINAL_RUN_STATES.has(this.#state)) throw new WorkflowError("RESUME_INCOMPATIBLE", `${this.#state} runs are terminal`);
|
|
6767
6881
|
await this.#set(state, reason ?? state);
|
|
6768
|
-
for (const
|
|
6882
|
+
for (const resolve9 of this.#waiters.splice(0)) resolve9();
|
|
6769
6883
|
}
|
|
6770
6884
|
async #set(state, reason) {
|
|
6771
6885
|
const previousState = this.#state;
|
|
@@ -7041,7 +7155,7 @@ function withWorkflowFunctions(bridge, store, runContext, registry, settings = O
|
|
|
7041
7155
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
7042
7156
|
|
|
7043
7157
|
// src/host-delivery.ts
|
|
7044
|
-
import { join as
|
|
7158
|
+
import { join as join9 } from "node:path";
|
|
7045
7159
|
import { DEFAULT_MAX_BYTES } from "@earendil-works/pi-coding-agent";
|
|
7046
7160
|
var FAILURE_DELIVERY_STATES = /* @__PURE__ */ new Set(["failed", "stopped", "interrupted", "budget_exhausted"]);
|
|
7047
7161
|
var ForegroundDeliveryController = class {
|
|
@@ -7366,7 +7480,7 @@ async function createWorkflowFailureDiagnostics(store, metadata, error, run) {
|
|
|
7366
7480
|
completedSiblingAgents,
|
|
7367
7481
|
completedSiblingPaths,
|
|
7368
7482
|
...retry ? { retry } : {},
|
|
7369
|
-
artifacts: { runDirectory: store.directory, statePath:
|
|
7483
|
+
artifacts: { runDirectory: store.directory, statePath: join9(store.directory, "state.json"), journalPath: join9(store.directory, "journal.json") }
|
|
7370
7484
|
});
|
|
7371
7485
|
}
|
|
7372
7486
|
function formatWorkflowFailureDiagnostics(diagnostic) {
|
|
@@ -7393,7 +7507,7 @@ function formatWorkflowFailureDeliveryFallback(workflowName, runId, runDirectory
|
|
|
7393
7507
|
const code = errorCode(error) ?? "INTERNAL_ERROR";
|
|
7394
7508
|
const failedPath = workflowFailedAt(error);
|
|
7395
7509
|
const nextAction = retryable && code !== "BUDGET_EXHAUSTED" && code !== "CANCELLED" ? `; next action: workflow_retry({ runId: ${JSON.stringify(runId)} })` : "";
|
|
7396
|
-
const line = `Workflow ${deliveryPart(workflowName, 128)} failed (runId=${deliveryPart(runId, 128)}): error=${code}: ${deliveryPart(formatWorkflowFailure(error), 768)}${failedPath ? `; failed path=${deliveryPart(failedPath, 512)}` : ""}${nextAction}; artifacts: runDirectory=${deliveryPart(runDirectory, 512)} statePath=${deliveryPart(
|
|
7510
|
+
const line = `Workflow ${deliveryPart(workflowName, 128)} failed (runId=${deliveryPart(runId, 128)}): error=${code}: ${deliveryPart(formatWorkflowFailure(error), 768)}${failedPath ? `; failed path=${deliveryPart(failedPath, 512)}` : ""}${nextAction}; artifacts: runDirectory=${deliveryPart(runDirectory, 512)} statePath=${deliveryPart(join9(runDirectory, "state.json"), 512)} journalPath=${deliveryPart(join9(runDirectory, "journal.json"), 512)}`;
|
|
7397
7511
|
return Buffer.byteLength(line) <= DELIVERY_LIMIT_BYTES ? line : utf8Prefix(line, DELIVERY_LIMIT_BYTES);
|
|
7398
7512
|
}
|
|
7399
7513
|
function serializeWorkflowFailureDiagnostics(diagnostic) {
|
|
@@ -7728,14 +7842,14 @@ function createWorkflowRecovery(deps) {
|
|
|
7728
7842
|
|
|
7729
7843
|
// src/host-navigator.ts
|
|
7730
7844
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
7731
|
-
import { join as
|
|
7845
|
+
import { join as join13 } from "node:path";
|
|
7732
7846
|
import { SettingsManager as SettingsManager2, truncateToVisualLines as truncateToVisualLines2 } from "@earendil-works/pi-coding-agent";
|
|
7733
7847
|
|
|
7734
7848
|
// src/workflow-artifacts.ts
|
|
7735
7849
|
import { spawn as spawn2 } from "node:child_process";
|
|
7736
7850
|
import { mkdtemp, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
|
|
7737
7851
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
7738
|
-
import { join as
|
|
7852
|
+
import { join as join10 } from "node:path";
|
|
7739
7853
|
function workflowScriptArtifact(script) {
|
|
7740
7854
|
return { extension: ".js", content: script };
|
|
7741
7855
|
}
|
|
@@ -7749,23 +7863,23 @@ function workflowResultArtifact(value) {
|
|
|
7749
7863
|
async function spawnWorkflowEditor(command, path) {
|
|
7750
7864
|
const [editor, ...editorArgs] = command.split(" ");
|
|
7751
7865
|
if (!editor) return null;
|
|
7752
|
-
return new Promise((
|
|
7866
|
+
return new Promise((resolve9) => {
|
|
7753
7867
|
try {
|
|
7754
7868
|
const child = spawn2(editor, [...editorArgs, path], { stdio: "inherit", shell: process.platform === "win32" });
|
|
7755
7869
|
child.once("error", () => {
|
|
7756
|
-
|
|
7870
|
+
resolve9(null);
|
|
7757
7871
|
});
|
|
7758
7872
|
child.once("close", (code) => {
|
|
7759
|
-
|
|
7873
|
+
resolve9(code);
|
|
7760
7874
|
});
|
|
7761
7875
|
} catch {
|
|
7762
|
-
|
|
7876
|
+
resolve9(null);
|
|
7763
7877
|
}
|
|
7764
7878
|
});
|
|
7765
7879
|
}
|
|
7766
7880
|
async function openWorkflowArtifact(tui, command, artifact) {
|
|
7767
|
-
const directory = await mkdtemp(
|
|
7768
|
-
const path =
|
|
7881
|
+
const directory = await mkdtemp(join10(tmpdir2(), "pi-workflow-editor-"));
|
|
7882
|
+
const path = join10(directory, `artifact${artifact.extension}`);
|
|
7769
7883
|
try {
|
|
7770
7884
|
await writeFile3(path, artifact.content, { encoding: "utf8", mode: 384 });
|
|
7771
7885
|
tui.stop();
|
|
@@ -7782,11 +7896,11 @@ async function openWorkflowArtifact(tui, command, artifact) {
|
|
|
7782
7896
|
|
|
7783
7897
|
// src/host-view.ts
|
|
7784
7898
|
import { keyHint, truncateToVisualLines } from "@earendil-works/pi-coding-agent";
|
|
7785
|
-
import { truncateToWidth as truncateToWidth2 } from "@earendil-works/pi-tui";
|
|
7899
|
+
import { truncateToWidth as truncateToWidth2, visibleWidth as visibleWidth2 } from "@earendil-works/pi-tui";
|
|
7786
7900
|
|
|
7787
7901
|
// src/background-widget.ts
|
|
7788
|
-
import { readFileSync as
|
|
7789
|
-
import { join as
|
|
7902
|
+
import { readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
|
|
7903
|
+
import { join as join11 } from "node:path";
|
|
7790
7904
|
import { isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
7791
7905
|
var KEY = "piewf-widget";
|
|
7792
7906
|
var ENTRY_TYPE = "piewf-run-receipt";
|
|
@@ -7876,7 +7990,7 @@ function renderableRun(value) {
|
|
|
7876
7990
|
}
|
|
7877
7991
|
function readRun(directory) {
|
|
7878
7992
|
try {
|
|
7879
|
-
const state = JSON.parse(
|
|
7993
|
+
const state = JSON.parse(readFileSync6(join11(directory, "state.json"), "utf8"));
|
|
7880
7994
|
if (!renderableRun(state)) return void 0;
|
|
7881
7995
|
const startedAt = statSync2(directory).birthtimeMs;
|
|
7882
7996
|
return { ...state, directory, startedAt };
|
|
@@ -8105,7 +8219,6 @@ function renderFrame(runs, now, width, offset = 0, theme, scrolling = false, siz
|
|
|
8105
8219
|
const title = live.length === 1 ? "Workflow" : `Workflows \xB7 ${String(live.length)} runs`;
|
|
8106
8220
|
sizeOut.rows = rows.length;
|
|
8107
8221
|
const body = collapse(rows, MAX_ROWS - 2, paint, offset, scrolling);
|
|
8108
|
-
const inner = Math.max(20, width - 4);
|
|
8109
8222
|
const total = rows.length;
|
|
8110
8223
|
const shown = body.length;
|
|
8111
8224
|
const scrollbar = (index) => {
|
|
@@ -8117,6 +8230,11 @@ function renderFrame(runs, now, width, offset = 0, theme, scrolling = false, siz
|
|
|
8117
8230
|
const top = limit === 0 ? 0 : start >= limit ? shown - size : Math.floor(start / limit * (shown - size));
|
|
8118
8231
|
return index >= top && index < top + size ? "\u2588" : "\u2591";
|
|
8119
8232
|
};
|
|
8233
|
+
const hint = !__navigationForTests.enabled || maxOffset(total, MAX_ROWS - 2) === 0 ? "" : scrolling ? "\u2191/\u2193 to scroll \xB7 Esc to exit" : "Alt+O to scroll";
|
|
8234
|
+
return drawFrame(title, hint, body, width, paint, accent, scrollbar);
|
|
8235
|
+
}
|
|
8236
|
+
function drawFrame(title, hint, body, width, paint, accent, scrollbar = () => void 0) {
|
|
8237
|
+
const inner = Math.max(20, width - 4);
|
|
8120
8238
|
const lines = body.map((row, index) => {
|
|
8121
8239
|
const right = row.right ?? "";
|
|
8122
8240
|
const rightWidth = right === "" ? 0 : visibleLength(right) + 2;
|
|
@@ -8128,7 +8246,6 @@ function renderFrame(runs, now, width, offset = 0, theme, scrolling = false, siz
|
|
|
8128
8246
|
const bar = scrollbar(index);
|
|
8129
8247
|
return `${rule} ${text} ${bar === void 0 ? rule : accent(bar)}`;
|
|
8130
8248
|
});
|
|
8131
|
-
const hint = !__navigationForTests.enabled || maxOffset(total, MAX_ROWS - 2) === 0 ? "" : scrolling ? "\u2191/\u2193 to scroll \xB7 Esc to exit" : "Alt+O to scroll";
|
|
8132
8249
|
const label = ` ${title} `;
|
|
8133
8250
|
const tail = hint === "" ? "" : ` ${hint} `;
|
|
8134
8251
|
const spare = inner + 2 - visibleLength(label) - visibleLength(tail);
|
|
@@ -8507,7 +8624,7 @@ function widget(pi, enabled = true) {
|
|
|
8507
8624
|
continue;
|
|
8508
8625
|
}
|
|
8509
8626
|
try {
|
|
8510
|
-
const mtime = statSync2(
|
|
8627
|
+
const mtime = statSync2(join11(run.directory, "state.json")).mtimeMs;
|
|
8511
8628
|
if (mtime === seen.get(runId)) continue;
|
|
8512
8629
|
seen.set(runId, mtime);
|
|
8513
8630
|
const fresh = readRun(run.directory);
|
|
@@ -8624,7 +8741,7 @@ function widget(pi, enabled = true) {
|
|
|
8624
8741
|
const waiting = runs.get(runId)?.waiting;
|
|
8625
8742
|
runs.set(runId, waiting ? { ...run, waiting } : run);
|
|
8626
8743
|
try {
|
|
8627
|
-
seen.set(runId, statSync2(
|
|
8744
|
+
seen.set(runId, statSync2(join11(directory, "state.json")).mtimeMs);
|
|
8628
8745
|
} catch {
|
|
8629
8746
|
}
|
|
8630
8747
|
if (isRunTerminal(run.state) && !awaitingDedicatedReceipt.has(runId)) receipt(runId);
|
|
@@ -9020,7 +9137,7 @@ function formatLogTimestamp(timestamp) {
|
|
|
9020
9137
|
if (Number.isNaN(date.getTime())) return "--:--:--";
|
|
9021
9138
|
return [date.getHours(), date.getMinutes(), date.getSeconds()].map((part) => String(part).padStart(2, "0")).join(":");
|
|
9022
9139
|
}
|
|
9023
|
-
function workflowLogLines(run, styles, expanded, width, showHint = false) {
|
|
9140
|
+
function workflowLogLines(run, styles, expanded, width, showHint = false, tail = 5) {
|
|
9024
9141
|
const events = (run.events ?? []).filter((event) => event.type === "log");
|
|
9025
9142
|
if (!events.length) return [];
|
|
9026
9143
|
const indent = " ".repeat(11);
|
|
@@ -9030,7 +9147,7 @@ function workflowLogLines(run, styles, expanded, width, showHint = false) {
|
|
|
9030
9147
|
const chunks = truncateToVisualLines(message, Number.MAX_SAFE_INTEGER, Math.max(1, width - 11), 0).visualLines;
|
|
9031
9148
|
return chunks.map((chunk, chunkIndex) => `${chunkIndex === 0 ? prefix : indent}${chunk}`);
|
|
9032
9149
|
}));
|
|
9033
|
-
const visible = expanded ? visualLines : visualLines.slice(-
|
|
9150
|
+
const visible = expanded ? visualLines : visualLines.slice(-tail);
|
|
9034
9151
|
let hint = "";
|
|
9035
9152
|
if (!expanded && showHint) {
|
|
9036
9153
|
try {
|
|
@@ -9040,7 +9157,47 @@ function workflowLogLines(run, styles, expanded, width, showHint = false) {
|
|
|
9040
9157
|
}
|
|
9041
9158
|
return [expanded ? ` ${styles.muted("Logs")}` : ` ${styles.muted(`Logs${hint}`)}`, ...visible];
|
|
9042
9159
|
}
|
|
9043
|
-
|
|
9160
|
+
var ACTIVE_AGENT_STATES = /* @__PURE__ */ new Set(["running", "waiting_for_child", "retrying", "paused"]);
|
|
9161
|
+
var COMPACT_RECENT_SETTLED = 3;
|
|
9162
|
+
var COMPACT_LOG_LINES = 3;
|
|
9163
|
+
function expandHint() {
|
|
9164
|
+
try {
|
|
9165
|
+
return ` (${keyHint("app.tools.expand", "to expand")})`;
|
|
9166
|
+
} catch {
|
|
9167
|
+
return "";
|
|
9168
|
+
}
|
|
9169
|
+
}
|
|
9170
|
+
function agentEnd(agent) {
|
|
9171
|
+
return (agent.startedAt ?? 0) + (agent.durationMs ?? 0);
|
|
9172
|
+
}
|
|
9173
|
+
function phaseSummary(agents, styles) {
|
|
9174
|
+
const count = (state) => agents.filter((agent) => agent.state === state).length;
|
|
9175
|
+
const starts = agents.map((agent) => agent.startedAt).filter((value) => value !== void 0);
|
|
9176
|
+
const span = starts.length ? Math.max(...agents.map(agentEnd)) - Math.min(...starts) : 0;
|
|
9177
|
+
const accounting2 = sumAccounting(agents.map((agent) => agent.accounting ?? zeroAccounting()));
|
|
9178
|
+
const failed = count("failed");
|
|
9179
|
+
const parts = [`${String(count("completed"))} done`, ...failed ? [styles.error(`${String(failed)} failed`)] : [], ...count("cancelled") ? [`${String(count("cancelled"))} cancelled`] : [], ...span > 0 ? [formatWorkflowRuntime(span)] : [], formatTokens(accounting2.input + accounting2.output), formatCost(accounting2.cost)].filter(Boolean);
|
|
9180
|
+
return styles.muted(parts.join(" \xB7 "));
|
|
9181
|
+
}
|
|
9182
|
+
function formatWorkflowProgress(run, spinner2 = "\u25C7", styles = PLAIN_WORKFLOW_PROGRESS_STYLES, now = Date.now(), expanded = false, width, showHint = false, maxLines) {
|
|
9183
|
+
const full = workflowProgressLines(run, spinner2, styles, now, expanded, width, showHint);
|
|
9184
|
+
if (expanded || maxLines === void 0 || full.length <= maxLines) return full.join("\n");
|
|
9185
|
+
const phases = run.phaseHistory?.length ? run.phaseHistory : run.phase ? [{ phase: run.phase, afterAgent: 0 }] : [];
|
|
9186
|
+
const currentStart = Math.min(run.agents.length, phases.at(-1)?.afterAgent ?? 0);
|
|
9187
|
+
const current = run.agents.slice(currentStart);
|
|
9188
|
+
const recent = current.filter((agent) => SETTLED_AGENT_STATES.has(agent.state) && agent.state !== "failed").sort((left, right) => agentEnd(right) - agentEnd(left)).slice(0, COMPACT_RECENT_SETTLED);
|
|
9189
|
+
const candidates = [...current.filter((agent) => agent.state === "failed"), ...current.filter((agent) => ACTIVE_AGENT_STATES.has(agent.state)), ...recent];
|
|
9190
|
+
const render = (count) => workflowProgressLines(run, spinner2, styles, now, false, width, showHint, new Set(candidates.slice(0, count)));
|
|
9191
|
+
let low = 0;
|
|
9192
|
+
let high = candidates.length;
|
|
9193
|
+
while (low < high) {
|
|
9194
|
+
const middle = Math.ceil((low + high) / 2);
|
|
9195
|
+
if (render(middle).length <= maxLines) low = middle;
|
|
9196
|
+
else high = middle - 1;
|
|
9197
|
+
}
|
|
9198
|
+
return render(low).join("\n");
|
|
9199
|
+
}
|
|
9200
|
+
function workflowProgressLines(run, spinner2, styles, now, expanded, width, showHint, shown) {
|
|
9044
9201
|
const done = run.agents.filter((agent) => SETTLED_AGENT_STATES.has(agent.state)).length;
|
|
9045
9202
|
const workflowIcon = runStateGlyph(run.state, spinner2);
|
|
9046
9203
|
const iconStyle = workflowIconStyle(run.state, styles);
|
|
@@ -9055,14 +9212,15 @@ function formatWorkflowProgress(run, spinner2 = "\u25C7", styles = PLAIN_WORKFLO
|
|
|
9055
9212
|
const shellActivity = scopedShells ? void 0 : formatShellActivity(run.activeShells, run.activeShellStartedAt, spinner2, styles, now);
|
|
9056
9213
|
if (shellActivity) lines.push(` ${shellActivity}`);
|
|
9057
9214
|
const byId = new Map(run.agents.map((agent) => [agent.id, agent]));
|
|
9058
|
-
const
|
|
9215
|
+
const position = new Map(run.agents.map((agent, index) => [agent, index]));
|
|
9216
|
+
const renderAgents = (agents, nested2) => renderGroupedAgents(agents, ({ agent, depth }, grouped) => {
|
|
9059
9217
|
const icon = agentStateGlyph(agent.state, spinner2);
|
|
9060
9218
|
const indent = " ".repeat((grouped ? 2 : 1) + depth);
|
|
9061
9219
|
const activity = SETTLED_AGENT_STATES.has(agent.state) ? "" : formatAgentActivity(agent, spinner2, styles, now);
|
|
9062
9220
|
const name = grouped ? agent.label ?? agent.name : styledAgentBreadcrumb(agent, byId, styles);
|
|
9063
9221
|
const state2 = progressStyleForState(agent.state, styles);
|
|
9064
9222
|
const detail = expanded ? formatWorkflowAgentDetail(agent, now) : "";
|
|
9065
|
-
return `${indent}#${String(
|
|
9223
|
+
return `${indent}#${String((position.get(agent) ?? 0) + 1)} ${state2(icon)} ${name} ${state2(`[${agent.state}]`)}${activity ? ` ${activity}` : ""}${detail ? ` ${detail}` : ""}`;
|
|
9066
9224
|
}, run.agents, (label) => styles.muted(label)).map((line) => nested2 ? ` ${line}` : line);
|
|
9067
9225
|
const phases = run.phaseHistory?.length ? run.phaseHistory : run.phase ? [{ phase: run.phase, afterAgent: 0 }] : [];
|
|
9068
9226
|
if (scopedShells) {
|
|
@@ -9075,10 +9233,22 @@ function formatWorkflowProgress(run, spinner2 = "\u25C7", styles = PLAIN_WORKFLO
|
|
|
9075
9233
|
}
|
|
9076
9234
|
let renderedAgents = 0;
|
|
9077
9235
|
let nested = false;
|
|
9236
|
+
const segment = (agents, closed) => {
|
|
9237
|
+
if (!shown) return renderAgents(agents, nested);
|
|
9238
|
+
const listed = agents.filter((agent) => closed ? agent.state === "failed" : shown.has(agent));
|
|
9239
|
+
const hidden = closed ? [] : agents.filter((agent) => !shown.has(agent));
|
|
9240
|
+
const counts = [["queued", hidden.filter((agent) => !SETTLED_AGENT_STATES.has(agent.state) && !ACTIVE_AGENT_STATES.has(agent.state)).length], ["running", hidden.filter((agent) => ACTIVE_AGENT_STATES.has(agent.state)).length], ["done", hidden.filter((agent) => SETTLED_AGENT_STATES.has(agent.state)).length]];
|
|
9241
|
+
const summary = counts.filter(([, count]) => count > 0).map(([label, count]) => `+${String(count)} ${label}`).join(" \xB7 ");
|
|
9242
|
+
return [...renderAgents(listed, nested), ...summary ? [`${nested ? " " : " "}${styles.muted(`\u2026 ${summary}${showHint ? expandHint() : ""}`)}`] : []];
|
|
9243
|
+
};
|
|
9078
9244
|
for (const [phaseIndex, phase] of phases.entries()) {
|
|
9079
9245
|
const boundary = Math.max(renderedAgents, Math.min(run.agents.length, phase.afterAgent));
|
|
9080
|
-
|
|
9081
|
-
lines.push(` ${
|
|
9246
|
+
const before = run.agents.slice(renderedAgents, boundary);
|
|
9247
|
+
if (shown && !nested && before.length) lines.push(` ${phaseSummary(before, styles)}`);
|
|
9248
|
+
lines.push(...segment(before, true));
|
|
9249
|
+
const next = phases[phaseIndex + 1];
|
|
9250
|
+
const phaseAgents = next ? run.agents.slice(boundary, Math.max(boundary, Math.min(run.agents.length, next.afterAgent))) : [];
|
|
9251
|
+
lines.push(` ${styles.muted(`[Phase: ${phase.phase}]`)}${shown && next ? ` ${phaseSummary(phaseAgents, styles)}` : ""}`);
|
|
9082
9252
|
const phaseShell = scopedShells ? shellActivityFor(run, phaseIndex) : void 0;
|
|
9083
9253
|
if (phaseShell) {
|
|
9084
9254
|
const rendered = formatShellActivity(phaseShell.active, phaseShell.startedAt, spinner2, styles, now);
|
|
@@ -9087,9 +9257,9 @@ function formatWorkflowProgress(run, spinner2 = "\u25C7", styles = PLAIN_WORKFLO
|
|
|
9087
9257
|
renderedAgents = boundary;
|
|
9088
9258
|
nested = true;
|
|
9089
9259
|
}
|
|
9090
|
-
lines.push(...
|
|
9091
|
-
lines.push(...workflowLogLines(run, styles, expanded, width, showHint));
|
|
9092
|
-
return lines
|
|
9260
|
+
lines.push(...segment(run.agents.slice(renderedAgents), false));
|
|
9261
|
+
lines.push(...workflowLogLines(run, styles, expanded, width, showHint, shown ? COMPACT_LOG_LINES : 5));
|
|
9262
|
+
return lines;
|
|
9093
9263
|
}
|
|
9094
9264
|
var workflowSpinner = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
9095
9265
|
var WORKFLOW_PROGRESS_REFRESH_MS = 1e3;
|
|
@@ -9331,7 +9501,9 @@ function workflowProgressBlock(run, theme, progress, refresh, invalidate, prefix
|
|
|
9331
9501
|
}
|
|
9332
9502
|
previousState = displayed.state;
|
|
9333
9503
|
const frame = displayed.state === "running" ? workflowSpinner[Math.floor(now / 80) % workflowSpinner.length] ?? "\u25C7" : "\u25C7";
|
|
9334
|
-
const
|
|
9504
|
+
const rows = freezeAt === void 0 && !terminal ? process.stdout.rows : void 0;
|
|
9505
|
+
const maxLines = rows ? Math.max(6, rows - 10) - (prefix ? prefix.split("\n").length + 1 : 0) : void 0;
|
|
9506
|
+
const progressText = formatWorkflowProgress(displayed, frame, styles, now, expanded, width, true, maxLines);
|
|
9335
9507
|
return truncateWorkflowProgress(prefix ? `${prefix}
|
|
9336
9508
|
|
|
9337
9509
|
${progressText}` : progressText, width);
|
|
@@ -9627,10 +9799,7 @@ function formatWorkflowPhaseDashboard(run, snapshot, width, selection = {}, styl
|
|
|
9627
9799
|
const model = buildWorkflowPhaseModel(run, snapshot);
|
|
9628
9800
|
const tree = buildWorkflowPhaseTree(model);
|
|
9629
9801
|
const expanded = selection.expandedNodeIds === void 0 ? workflowPhaseTreeInitialExpanded(tree) : new Set(selection.expandedNodeIds);
|
|
9630
|
-
const wrap = (text
|
|
9631
|
-
const ansiPattern = new RegExp(ANSI_SGR_SOURCE, "g");
|
|
9632
|
-
const visibleLength2 = (text) => text.replace(ansiPattern, "").length;
|
|
9633
|
-
const padTo = (text, limit) => `${text}${" ".repeat(Math.max(0, limit - visibleLength2(text)))}`;
|
|
9802
|
+
const wrap = (text) => wrapNavigatorLine(text, safeWidth);
|
|
9634
9803
|
const phaseStyle = (state) => phaseStyleForState(state, styles);
|
|
9635
9804
|
const phase = selection.phaseId ? model.phases.find((candidate) => candidate.id === selection.phaseId) : void 0;
|
|
9636
9805
|
const selectedByAgent = selection.agentId ? tree.nodes.find((node) => node.kind === "agent" && node.agentId === selection.agentId && (!selection.phaseId || node.phaseId === selection.phaseId)) : void 0;
|
|
@@ -9696,28 +9865,33 @@ function formatWorkflowPhaseDashboard(run, snapshot, width, selection = {}, styl
|
|
|
9696
9865
|
const scopedShells = (run.activeShellsByPhase?.length ?? 0) > 0;
|
|
9697
9866
|
const shellActivity = scopedShells ? void 0 : formatShellActivity(run.activeShells, run.activeShellStartedAt, "\u2826", styles, now);
|
|
9698
9867
|
if (shellActivity) lines.push(` ${shellActivity}`);
|
|
9699
|
-
const renderTree = (limit) => [styles.bold("Tree"), ...visibleNodes.length ? visibleNodes.flatMap((node) => wrap(treeLine(node), limit)) : [styles.muted("(empty)")]];
|
|
9700
9868
|
const actionRows = () => {
|
|
9701
9869
|
const actions = selection.actions;
|
|
9702
9870
|
if (!actions) return [];
|
|
9703
9871
|
return ["", styles.bold(actions.title), ...actions.options.map((option, index) => `${index === actions.index ? "\u2192 " : " "}${index === actions.index ? styles.accent(option) : option}`)];
|
|
9704
9872
|
};
|
|
9705
|
-
const
|
|
9873
|
+
const treeRows = [styles.bold("Tree"), ...visibleNodes.length ? visibleNodes.map(treeLine) : [styles.muted("(empty)")]];
|
|
9874
|
+
lines.push(...formatNavigatorColumns(treeRows, [...details(selectedNode), ...actionRows()], safeWidth, selection));
|
|
9875
|
+
if (model.unassignedAgents?.length && !tree.nodes.some((node) => node.phaseId === "unassigned")) lines.push(...wrap(styles.muted(`Unassigned agents: ${String(model.unassignedAgents.length)}`)));
|
|
9876
|
+
return lines.flatMap((line) => wrap(line));
|
|
9877
|
+
}
|
|
9878
|
+
function wrapNavigatorLine(text, width) {
|
|
9879
|
+
return truncateToVisualLines(text, Number.MAX_SAFE_INTEGER, Math.max(1, width), 0).visualLines.map((line) => line.trimEnd());
|
|
9880
|
+
}
|
|
9881
|
+
function formatNavigatorColumns(sidebar, detail, width, layout = {}) {
|
|
9882
|
+
const safeWidth = Math.max(1, width);
|
|
9706
9883
|
if (safeWidth >= 80) {
|
|
9707
9884
|
const sidebarWidth = Math.min(42, Math.max(24, Math.floor((safeWidth - 3) * 0.38)));
|
|
9708
|
-
const
|
|
9709
|
-
const
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
lines.push(...detailRows().flatMap((line) => wrap(line)));
|
|
9715
|
-
} else {
|
|
9716
|
-
lines.push(...renderTree(safeWidth));
|
|
9717
|
-
if (!selection.treeOnly) lines.push("", ...detailRows().flatMap((line) => wrap(line)));
|
|
9885
|
+
const left2 = sidebar.flatMap((line) => wrapNavigatorLine(line, sidebarWidth));
|
|
9886
|
+
const right = detail.flatMap((line) => wrapNavigatorLine(line, safeWidth - sidebarWidth - 3));
|
|
9887
|
+
return Array.from({ length: Math.max(left2.length, right.length) }, (_, index) => {
|
|
9888
|
+
const cell = left2[index] ?? "";
|
|
9889
|
+
return `${cell}${" ".repeat(Math.max(0, sidebarWidth - visibleWidth2(cell)))} | ${right[index] ?? ""}`;
|
|
9890
|
+
});
|
|
9718
9891
|
}
|
|
9719
|
-
if (
|
|
9720
|
-
|
|
9892
|
+
if (layout.detailsOnly) return detail.flatMap((line) => wrapNavigatorLine(line, safeWidth));
|
|
9893
|
+
const left = sidebar.flatMap((line) => wrapNavigatorLine(line, safeWidth));
|
|
9894
|
+
return layout.treeOnly ? left : [...left, ...detail.flatMap((line) => wrapNavigatorLine(line, safeWidth))];
|
|
9721
9895
|
}
|
|
9722
9896
|
function formatCheckpointReview(checkpoint) {
|
|
9723
9897
|
const context = JSON.stringify(checkpoint.context, null, 2);
|
|
@@ -9727,7 +9901,7 @@ function formatCheckpointReview(checkpoint) {
|
|
|
9727
9901
|
// src/retention.ts
|
|
9728
9902
|
import { lstat, readdir as readdir4, readFile as readFile4, stat as stat3 } from "node:fs/promises";
|
|
9729
9903
|
import { homedir as homedir6 } from "node:os";
|
|
9730
|
-
import { join as
|
|
9904
|
+
import { join as join12, resolve as resolve7 } from "node:path";
|
|
9731
9905
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
9732
9906
|
var REQUIRED_FILES = /* @__PURE__ */ new Set(["workflow.js", "state.json", "snapshot.json", "journal.json", "ownership.json", "worktrees.json", "borrowed-worktrees.json", "system-prompts.json"]);
|
|
9733
9907
|
var OPTIONAL_FILES = /* @__PURE__ */ new Set(["result.json", "summary.json"]);
|
|
@@ -9736,21 +9910,21 @@ async function validateInventory(store, loaded) {
|
|
|
9736
9910
|
const entries = await readdir4(store.directory, { withFileTypes: true });
|
|
9737
9911
|
for (const entry of entries) {
|
|
9738
9912
|
if (REQUIRED_DIRECTORIES.has(entry.name)) {
|
|
9739
|
-
if (!entry.isDirectory() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular directory: ${
|
|
9913
|
+
if (!entry.isDirectory() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular directory: ${join12(store.directory, entry.name)}`);
|
|
9740
9914
|
continue;
|
|
9741
9915
|
}
|
|
9742
|
-
if (!REQUIRED_FILES.has(entry.name) && !OPTIONAL_FILES.has(entry.name)) throw new Error(`Run inventory contains an unrecognized artifact: ${
|
|
9743
|
-
if (!entry.isFile() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${
|
|
9916
|
+
if (!REQUIRED_FILES.has(entry.name) && !OPTIONAL_FILES.has(entry.name)) throw new Error(`Run inventory contains an unrecognized artifact: ${join12(store.directory, entry.name)}`);
|
|
9917
|
+
if (!entry.isFile() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${join12(store.directory, entry.name)}`);
|
|
9744
9918
|
}
|
|
9745
9919
|
for (const name of REQUIRED_FILES) {
|
|
9746
|
-
const entry = await lstat(
|
|
9747
|
-
if (!entry.isFile() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${
|
|
9920
|
+
const entry = await lstat(join12(store.directory, name));
|
|
9921
|
+
if (!entry.isFile() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${join12(store.directory, name)}`);
|
|
9748
9922
|
}
|
|
9749
9923
|
if (loaded.run.state === "completed") {
|
|
9750
|
-
const result = await lstat(
|
|
9751
|
-
if (!result.isFile() || result.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${
|
|
9924
|
+
const result = await lstat(join12(store.directory, "result.json"));
|
|
9925
|
+
if (!result.isFile() || result.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${join12(store.directory, "result.json")}`);
|
|
9752
9926
|
}
|
|
9753
|
-
if (await readFile4(
|
|
9927
|
+
if (await readFile4(join12(store.directory, "workflow.js"), "utf8") !== loaded.snapshot.script) throw new Error("Persisted workflow source does not match its launch snapshot");
|
|
9754
9928
|
}
|
|
9755
9929
|
function runDependencyIds(run, borrowed) {
|
|
9756
9930
|
const dependencies = /* @__PURE__ */ new Set();
|
|
@@ -9772,7 +9946,7 @@ async function scanRun(cwd, sessionId, runId, home) {
|
|
|
9772
9946
|
await store.validateDeletionWorktrees();
|
|
9773
9947
|
await store.validateBorrowedWorktrees();
|
|
9774
9948
|
if (loaded.run.retry) await store.validateRetrySource();
|
|
9775
|
-
const stateMtimeMs = (await stat3(
|
|
9949
|
+
const stateMtimeMs = (await stat3(join12(store.directory, "state.json"))).mtimeMs;
|
|
9776
9950
|
const dependencies = runDependencyIds(loaded.run, await store.borrowedWorktrees());
|
|
9777
9951
|
if (dependencies.includes(runId)) throw new Error(`Run ${runId} depends on itself`);
|
|
9778
9952
|
return { sessionId, runId, store, run: loaded.run, stateMtimeMs, dependencies };
|
|
@@ -9827,9 +10001,9 @@ function cleanupOrder(runs, candidates) {
|
|
|
9827
10001
|
return ordered;
|
|
9828
10002
|
}
|
|
9829
10003
|
async function recheck(run) {
|
|
9830
|
-
const before = await stat3(
|
|
10004
|
+
const before = await stat3(join12(run.store.directory, "state.json"));
|
|
9831
10005
|
const loaded = await run.store.load();
|
|
9832
|
-
const after = await stat3(
|
|
10006
|
+
const after = await stat3(join12(run.store.directory, "state.json"));
|
|
9833
10007
|
if (loaded.run.id !== run.runId || !HARD_TERMINAL_RUN_STATES.has(loaded.run.state) || before.mtimeMs !== after.mtimeMs || after.mtimeMs !== run.stateMtimeMs) throw new Error(`Retention candidate ${run.runId} changed before deletion`);
|
|
9834
10008
|
}
|
|
9835
10009
|
async function releaseLeases(leases) {
|
|
@@ -9837,8 +10011,8 @@ async function releaseLeases(leases) {
|
|
|
9837
10011
|
for (const release of releases) if (release.status === "rejected") throw release.reason;
|
|
9838
10012
|
}
|
|
9839
10013
|
async function retainTerminalRuns(options) {
|
|
9840
|
-
const home =
|
|
9841
|
-
const cwd =
|
|
10014
|
+
const home = resolve7(options.home ?? homedir6());
|
|
10015
|
+
const cwd = resolve7(options.cwd);
|
|
9842
10016
|
const now = options.now ?? Date.now();
|
|
9843
10017
|
if (!Number.isFinite(now)) throw new Error("Retention start time is invalid");
|
|
9844
10018
|
if (options.retention.olderThanDays === void 0 && options.retention.maxTerminalRuns === void 0) return { candidates: [], deleted: [] };
|
|
@@ -9999,12 +10173,54 @@ var WORKFLOW_VIM_KEYS = { "tui.select.up": "k", "tui.select.down": "j", "tui.edi
|
|
|
9999
10173
|
function workflowKeyMatches(keybindings, data, binding) {
|
|
10000
10174
|
return keybindings.matches(data, binding) || WORKFLOW_VIM_KEYS[binding] === data;
|
|
10001
10175
|
}
|
|
10002
|
-
|
|
10176
|
+
var WORKFLOW_KEY_LABELS = { up: "\u2191", down: "\u2193", left: "\u2190", right: "\u2192", pageUp: "pgup", pageDown: "pgdn" };
|
|
10177
|
+
function workflowKeyLabel(keybindings, binding, fallback, labels = WORKFLOW_KEY_LABELS) {
|
|
10003
10178
|
const keys = keybindingKeys(keybindings, binding);
|
|
10004
10179
|
const configured = keys?.length ? keys.map((key) => labels[key] ?? key) : [fallback];
|
|
10005
10180
|
const vim = WORKFLOW_VIM_KEYS[binding];
|
|
10006
10181
|
return [...new Set(vim ? [...configured, vim] : configured)].join("/");
|
|
10007
10182
|
}
|
|
10183
|
+
function createInlineConfirm(requestRender) {
|
|
10184
|
+
let pending;
|
|
10185
|
+
const answer = (value) => {
|
|
10186
|
+
const current = pending;
|
|
10187
|
+
pending = void 0;
|
|
10188
|
+
current?.resolve(value);
|
|
10189
|
+
requestRender();
|
|
10190
|
+
};
|
|
10191
|
+
return {
|
|
10192
|
+
ask(title, message) {
|
|
10193
|
+
answer(false);
|
|
10194
|
+
return new Promise((resolve9) => {
|
|
10195
|
+
pending = { title, message, yes: true, resolve: resolve9 };
|
|
10196
|
+
requestRender();
|
|
10197
|
+
});
|
|
10198
|
+
},
|
|
10199
|
+
active() {
|
|
10200
|
+
return pending !== void 0;
|
|
10201
|
+
},
|
|
10202
|
+
rows(width, styles) {
|
|
10203
|
+
if (!pending) return [];
|
|
10204
|
+
const yes = pending.yes;
|
|
10205
|
+
const message = truncateToVisualLines2(pending.message, Number.MAX_SAFE_INTEGER, Math.max(1, width), 0).visualLines.map((line) => line.trimEnd());
|
|
10206
|
+
return [styles.bold(pending.title), ...message, ...["Yes", "No"].map((option) => option === "Yes" === yes ? `\u2192 ${styles.accent(option)}` : ` ${option}`)];
|
|
10207
|
+
},
|
|
10208
|
+
hint(keybindings) {
|
|
10209
|
+
return `${workflowKeyLabel(keybindings, "tui.select.up", "\u2191")}/${workflowKeyLabel(keybindings, "tui.select.down", "\u2193")} select \xB7 ${workflowKeyLabel(keybindings, "tui.select.confirm", "enter")} confirm \xB7 ${workflowKeyLabel(keybindings, "tui.select.cancel", "esc")} cancel`;
|
|
10210
|
+
},
|
|
10211
|
+
handleInput(keybindings, data) {
|
|
10212
|
+
if (!pending) return;
|
|
10213
|
+
if (workflowKeyMatches(keybindings, data, "tui.select.up") || workflowKeyMatches(keybindings, data, "tui.select.down")) {
|
|
10214
|
+
pending.yes = !pending.yes;
|
|
10215
|
+
requestRender();
|
|
10216
|
+
} else if (workflowKeyMatches(keybindings, data, "tui.select.confirm")) answer(pending.yes);
|
|
10217
|
+
else if (workflowKeyMatches(keybindings, data, "tui.select.cancel")) answer(false);
|
|
10218
|
+
},
|
|
10219
|
+
cancel() {
|
|
10220
|
+
answer(false);
|
|
10221
|
+
}
|
|
10222
|
+
};
|
|
10223
|
+
}
|
|
10008
10224
|
function registerWorkflowNavigator(deps) {
|
|
10009
10225
|
const { pi, home, clipboard, extensionAgentDir, runs, terminalRunStates, hardTerminalRunStates, ensureSessionLease, answerCheckpoint, recovery, stopWorkflowRun, moveForegroundToBackground, isForegroundAttached, liveAgents, registry, projectTrusted: projectTrusted2, resumeHostContext: resumeHostContext2, resumeSelectedWorkflow, reportBlocked, setNavigatorOpen, trajectoryProvider, coordinateRunMutation: coordinateRunMutationDependency } = deps;
|
|
10010
10226
|
const coordinateRunMutation = coordinateRunMutationDependency ?? ((task) => task());
|
|
@@ -10046,7 +10262,7 @@ function registerWorkflowNavigator(deps) {
|
|
|
10046
10262
|
const setStatus = uiHostCapabilities(ctx.ui)?.setStatus;
|
|
10047
10263
|
setStatus?.call(ctx.ui, "workflow-stop", text);
|
|
10048
10264
|
};
|
|
10049
|
-
const runAction = async (actionCommand, status = setWorkflowStatus) => {
|
|
10265
|
+
const runAction = async (actionCommand, status = setWorkflowStatus, confirm = (title, message) => confirmWithBlocked(ctx.ui, reportBlocked, title, message)) => {
|
|
10050
10266
|
const [action, runId, ...rest] = actionCommand.split(/\s+/);
|
|
10051
10267
|
try {
|
|
10052
10268
|
const run = runId ? runs.get(runId) : void 0;
|
|
@@ -10133,7 +10349,7 @@ function registerWorkflowNavigator(deps) {
|
|
|
10133
10349
|
}
|
|
10134
10350
|
if (action === "stop" && run) {
|
|
10135
10351
|
const workflowName = stored?.loaded.run.workflowName ?? run.metadata.name;
|
|
10136
|
-
if (!await
|
|
10352
|
+
if (!await confirm("Stop workflow?", `Stop workflow ${workflowName} (${run.store.runId})? This cannot be undone.`)) return "dashboard";
|
|
10137
10353
|
status(`Stopping workflow ${workflowName}...`);
|
|
10138
10354
|
await stopWorkflowRun(run.store.runId);
|
|
10139
10355
|
status(`Workflow ${run.store.runId} stopped.`);
|
|
@@ -10494,8 +10710,18 @@ Mutations are available through workflow tools.`, "info");
|
|
|
10494
10710
|
let selectedNodeId = tree.nodes[0]?.id;
|
|
10495
10711
|
let expandedNodeIds = new Set(workflowPhaseTreeInitialExpanded(tree));
|
|
10496
10712
|
const terminalRows = () => Math.max(1, tuiRows(tui) - WORKFLOW_PANEL_FOOTER_ROWS);
|
|
10497
|
-
const
|
|
10498
|
-
const
|
|
10713
|
+
const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback);
|
|
10714
|
+
const inlineConfirm = createInlineConfirm(() => {
|
|
10715
|
+
if (!disposed) tui.requestRender();
|
|
10716
|
+
});
|
|
10717
|
+
const confirmInDashboard = async (title, message) => {
|
|
10718
|
+
reportBlocked?.(true, title);
|
|
10719
|
+
try {
|
|
10720
|
+
return await inlineConfirm.ask(title, message);
|
|
10721
|
+
} finally {
|
|
10722
|
+
reportBlocked?.(false);
|
|
10723
|
+
}
|
|
10724
|
+
};
|
|
10499
10725
|
const progressNow = () => {
|
|
10500
10726
|
const now = Date.now();
|
|
10501
10727
|
if (hardTerminalRunStates.has(view.run.state)) {
|
|
@@ -10572,7 +10798,7 @@ Mutations are available through workflow tools.`, "info");
|
|
|
10572
10798
|
stopStatus = status;
|
|
10573
10799
|
setWorkflowStatus(status);
|
|
10574
10800
|
if (!disposed) tui.requestRender();
|
|
10575
|
-
}).then(async (outcome2) => {
|
|
10801
|
+
}, confirmInDashboard).then(async (outcome2) => {
|
|
10576
10802
|
if (outcome2 === "stopped") {
|
|
10577
10803
|
done("__stopped__");
|
|
10578
10804
|
return;
|
|
@@ -10609,7 +10835,8 @@ Mutations are available through workflow tools.`, "info");
|
|
|
10609
10835
|
const content = [...statusLines, ...phaseLines];
|
|
10610
10836
|
const rows = terminalRows();
|
|
10611
10837
|
const hintRows = rows >= 3 ? 1 : 0;
|
|
10612
|
-
const
|
|
10838
|
+
const confirmRows = inlineConfirm.rows(width, styles);
|
|
10839
|
+
const viewport = Math.max(1, rows - hintRows - confirmRows.length);
|
|
10613
10840
|
const maxOffset2 = Math.max(0, content.length - viewport);
|
|
10614
10841
|
dashboardOffset = Math.max(0, Math.min(maxOffset2, dashboardOffset));
|
|
10615
10842
|
if (actionMode) {
|
|
@@ -10630,12 +10857,16 @@ Mutations are available through workflow tools.`, "info");
|
|
|
10630
10857
|
dashboardOffset = Math.max(0, Math.min(maxOffset2, dashboardOffset));
|
|
10631
10858
|
const selectedNode = selectedNodeId ? tree.byId.get(selectedNodeId) : void 0;
|
|
10632
10859
|
const enterAction = selectedNode?.kind === "workflow" ? "run actions" : selectedNode?.kind === "agent" ? "agent actions" : selectedNode?.children.length ? "expand/collapse" : narrow ? "inspect" : "focus details";
|
|
10633
|
-
const hint = truncateToVisualLines2(theme.fg("dim", actionMode ? `${keyLabel("tui.select.up", "\u2191")}/${keyLabel("tui.select.down", "\u2193")} actions \xB7 ${keyLabel("tui.select.confirm", "enter")} run \xB7 ${keyLabel("tui.editor.cursorLeft", "\u2190")} tree \xB7 ${keyLabel("tui.select.cancel", "esc")} tree` : `${keyLabel("tui.select.up", "\u2191")}/${keyLabel("tui.select.down", "\u2193")} tree \xB7 ${keyLabel("tui.editor.cursorLeft", "\u2190")}/${keyLabel("tui.editor.cursorRight", "\u2192")} collapse/expand \xB7 ${keyLabel("tui.select.confirm", "enter")} ${enterAction} \xB7 a actions \xB7 ${keyLabel("tui.select.cancel", "esc")} ${narrow && detailsMode ? "tree" : "back"}${content.length > viewport ? ` \xB7 ${keyLabel("tui.select.pageUp", "pgup")}/${keyLabel("tui.select.pageDown", "pgdn")} scroll` : ""} \xB7 auto-refresh 1s`), Number.MAX_SAFE_INTEGER, width, 1).visualLines[0] ?? "";
|
|
10634
|
-
return [...content.slice(dashboardOffset, dashboardOffset + viewport), ...hintRows ? [hint] : []];
|
|
10860
|
+
const hint = truncateToVisualLines2(theme.fg("dim", inlineConfirm.active() ? inlineConfirm.hint(keybindings) : actionMode ? `${keyLabel("tui.select.up", "\u2191")}/${keyLabel("tui.select.down", "\u2193")} actions \xB7 ${keyLabel("tui.select.confirm", "enter")} run \xB7 ${keyLabel("tui.editor.cursorLeft", "\u2190")} tree \xB7 ${keyLabel("tui.select.cancel", "esc")} tree` : `${keyLabel("tui.select.up", "\u2191")}/${keyLabel("tui.select.down", "\u2193")} tree \xB7 ${keyLabel("tui.editor.cursorLeft", "\u2190")}/${keyLabel("tui.editor.cursorRight", "\u2192")} collapse/expand \xB7 ${keyLabel("tui.select.confirm", "enter")} ${enterAction} \xB7 a actions \xB7 ${keyLabel("tui.select.cancel", "esc")} ${narrow && detailsMode ? "tree" : "back"}${content.length > viewport ? ` \xB7 ${keyLabel("tui.select.pageUp", "pgup")}/${keyLabel("tui.select.pageDown", "pgdn")} scroll` : ""} \xB7 auto-refresh 1s`), Number.MAX_SAFE_INTEGER, width, 1).visualLines[0] ?? "";
|
|
10861
|
+
return [...content.slice(dashboardOffset, dashboardOffset + viewport), ...confirmRows, ...hintRows ? [hint] : []];
|
|
10635
10862
|
},
|
|
10636
10863
|
invalidate() {
|
|
10637
10864
|
},
|
|
10638
10865
|
handleInput(data) {
|
|
10866
|
+
if (inlineConfirm.active()) {
|
|
10867
|
+
inlineConfirm.handleInput(keybindings, data);
|
|
10868
|
+
return;
|
|
10869
|
+
}
|
|
10639
10870
|
if (stopRequested || editorRunning) return;
|
|
10640
10871
|
const narrow = renderedWidth < 80;
|
|
10641
10872
|
if (!actionMode && (data === "a" || data === "A")) {
|
|
@@ -10687,7 +10918,7 @@ Mutations are available through workflow tools.`, "info");
|
|
|
10687
10918
|
});
|
|
10688
10919
|
}
|
|
10689
10920
|
}
|
|
10690
|
-
} else if (action === "Open script in editor") void openArtifact(readFile5(
|
|
10921
|
+
} else if (action === "Open script in editor") void openArtifact(readFile5(join13(store.directory, "workflow.js"), "utf8").then(workflowScriptArtifact), "workflow script");
|
|
10691
10922
|
else if (action === "Stop") requestStop();
|
|
10692
10923
|
else done(action);
|
|
10693
10924
|
}
|
|
@@ -10755,6 +10986,7 @@ Mutations are available through workflow tools.`, "info");
|
|
|
10755
10986
|
disposed = true;
|
|
10756
10987
|
stopTimer();
|
|
10757
10988
|
setWorkflowStatus(void 0);
|
|
10989
|
+
inlineConfirm.cancel();
|
|
10758
10990
|
}
|
|
10759
10991
|
};
|
|
10760
10992
|
}) : await ctx.ui.select(view.dashboard, [...view.actions.keys(), "Back"]);
|
|
@@ -10809,8 +11041,7 @@ Mutations are available through workflow tools.`, "info");
|
|
|
10809
11041
|
const currentLayout = layout();
|
|
10810
11042
|
const maxOffset2 = Math.max(0, renderedLines.length - currentLayout.contentViewport);
|
|
10811
11043
|
offset = Math.min(offset, maxOffset2);
|
|
10812
|
-
const
|
|
10813
|
-
const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback, keyLabels);
|
|
11044
|
+
const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback);
|
|
10814
11045
|
const hint = truncateToVisualLines2(theme.fg("dim", `${keyLabel("tui.select.up", "\u2191")}/${keyLabel("tui.select.down", "\u2193")}/pgup/pgdn scroll \xB7 enter select \xB7 esc cancel`), Number.MAX_SAFE_INTEGER, width, 1).visualLines[0] ?? "";
|
|
10815
11046
|
const controls = currentLayout.compactControls ? [options.map((option, index) => `${index === selectedIndex ? "[" : " "}${option}${index === selectedIndex ? "]" : " "}`).join(" ")] : options.map((option, index) => `${index === selectedIndex ? "\u2192 " : " "}${option}`);
|
|
10816
11047
|
return [
|
|
@@ -10872,7 +11103,7 @@ Mutations are available through workflow tools.`, "info");
|
|
|
10872
11103
|
|
|
10873
11104
|
// src/changelog.ts
|
|
10874
11105
|
import { mkdir as mkdir3, readFile as readFile6 } from "node:fs/promises";
|
|
10875
|
-
import { dirname as
|
|
11106
|
+
import { dirname as dirname6, join as join14 } from "node:path";
|
|
10876
11107
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
10877
11108
|
var STATE_DIRECTORY = "pi-extensible-workflows";
|
|
10878
11109
|
var STATE_FILE = "changelog-state.json";
|
|
@@ -10883,15 +11114,15 @@ function packageMetadata(value, directory) {
|
|
|
10883
11114
|
}
|
|
10884
11115
|
async function packageMetadataAt(directory) {
|
|
10885
11116
|
try {
|
|
10886
|
-
const value = JSON.parse(await readFile6(
|
|
11117
|
+
const value = JSON.parse(await readFile6(join14(directory, "package.json"), "utf8"));
|
|
10887
11118
|
return packageMetadata(value, directory);
|
|
10888
11119
|
} catch {
|
|
10889
11120
|
return void 0;
|
|
10890
11121
|
}
|
|
10891
11122
|
}
|
|
10892
11123
|
async function installedPackageMetadata() {
|
|
10893
|
-
const moduleDirectory =
|
|
10894
|
-
for (const directory of [
|
|
11124
|
+
const moduleDirectory = dirname6(fileURLToPath4(import.meta.url));
|
|
11125
|
+
for (const directory of [join14(moduleDirectory, ".."), join14(moduleDirectory, "../..")]) {
|
|
10895
11126
|
const metadata = await packageMetadataAt(directory);
|
|
10896
11127
|
if (metadata) return metadata;
|
|
10897
11128
|
}
|
|
@@ -10918,7 +11149,7 @@ ${line}` : line;
|
|
|
10918
11149
|
}
|
|
10919
11150
|
async function readChangelog(directory) {
|
|
10920
11151
|
try {
|
|
10921
|
-
return parseChangelog(await readFile6(
|
|
11152
|
+
return parseChangelog(await readFile6(join14(directory, "CHANGELOG.md"), "utf8"));
|
|
10922
11153
|
} catch {
|
|
10923
11154
|
return [];
|
|
10924
11155
|
}
|
|
@@ -10955,14 +11186,14 @@ async function showChangelogNotice(context, agentDir, packageDirectory) {
|
|
|
10955
11186
|
try {
|
|
10956
11187
|
const metadata = packageDirectory === void 0 ? await installedPackageMetadata() : await packageMetadataAt(packageDirectory);
|
|
10957
11188
|
if (!metadata) return;
|
|
10958
|
-
const statePath =
|
|
11189
|
+
const statePath = join14(agentDir, STATE_DIRECTORY, STATE_FILE);
|
|
10959
11190
|
const previousVersion = await lastNotifiedVersion(statePath);
|
|
10960
11191
|
if (previousVersion === metadata.version) return;
|
|
10961
11192
|
const entries = releaseEntries(await readChangelog(metadata.directory), metadata.version, previousVersion);
|
|
10962
11193
|
if (!entries.length) return;
|
|
10963
11194
|
ui.notify(noticeText(metadata.version, entries), "info");
|
|
10964
11195
|
try {
|
|
10965
|
-
await mkdir3(
|
|
11196
|
+
await mkdir3(dirname6(statePath), { recursive: true, mode: 448 });
|
|
10966
11197
|
await atomicJson(statePath, { lastNotifiedVersion: metadata.version });
|
|
10967
11198
|
} catch {
|
|
10968
11199
|
}
|
|
@@ -10974,8 +11205,8 @@ async function showChangelogNotice(context, agentDir, packageDirectory) {
|
|
|
10974
11205
|
import { createHash as createHash3 } from "node:crypto";
|
|
10975
11206
|
import { open as open2, readFile as readFile7, readdir as readdir5, stat as stat4 } from "node:fs/promises";
|
|
10976
11207
|
import { homedir as homedir7 } from "node:os";
|
|
10977
|
-
import { basename as basename5, dirname as
|
|
10978
|
-
import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DefaultPackageManager as DefaultPackageManager2, getAgentDir as
|
|
11208
|
+
import { basename as basename5, dirname as dirname7, join as join15, resolve as resolve8 } from "node:path";
|
|
11209
|
+
import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DefaultPackageManager as DefaultPackageManager2, getAgentDir as getAgentDir4, SettingsManager as SettingsManager3, DEFAULT_MAX_BYTES as DEFAULT_MAX_BYTES2 } from "@earendil-works/pi-coding-agent";
|
|
10979
11210
|
|
|
10980
11211
|
// subagents/src/contracts.ts
|
|
10981
11212
|
import { Type as Type2 } from "@earendil-works/pi-ai";
|
|
@@ -11193,6 +11424,18 @@ function statusValue(value) {
|
|
|
11193
11424
|
// src/trajectory.ts
|
|
11194
11425
|
var TRAJECTORY_MAX_TRANSCRIPT_BYTES = 2 * 1024 * 1024;
|
|
11195
11426
|
var TRAJECTORY_MAX_NON_TIMING_ENTRIES = 400;
|
|
11427
|
+
function subagentOutput(options) {
|
|
11428
|
+
if (options.resultTruncated) return { status: "truncated", kind: "result", path: options.resultPath, bytes: options.resultBytes ?? 0 };
|
|
11429
|
+
if (options.result !== void 0) return { status: "available", value: options.result, bytes: serializedJsonBytes(options.result) };
|
|
11430
|
+
if (options.failureTruncated) return { status: "truncated", kind: "failure", path: options.failurePath, bytes: options.failureBytes ?? 0 };
|
|
11431
|
+
if (options.failure !== void 0) return { status: "failed", code: options.failure.code, message: options.failure.message };
|
|
11432
|
+
if (options.state === "failed" || options.state === "stopped") {
|
|
11433
|
+
const error = options.statusError ?? options.attempt?.error;
|
|
11434
|
+
return { status: options.state === "stopped" ? "cancelled" : "failed", code: error?.code ?? (options.state === "stopped" ? "CANCELLED" : "AGENT_FAILED"), message: error?.message ?? `Subagent ${options.state}` };
|
|
11435
|
+
}
|
|
11436
|
+
if (options.state === "completed") return { status: "unavailable" };
|
|
11437
|
+
return { status: "pending" };
|
|
11438
|
+
}
|
|
11196
11439
|
function sessionFile(reference) {
|
|
11197
11440
|
const locator = reference?.locator;
|
|
11198
11441
|
if (!object(locator) || typeof locator.sessionFile !== "string" || !locator.sessionFile) return void 0;
|
|
@@ -11330,20 +11573,20 @@ function canonicalExtensionSelector2(selector, base) {
|
|
|
11330
11573
|
const negated = selector.startsWith("!");
|
|
11331
11574
|
const body = negated ? selector.slice(1) : selector;
|
|
11332
11575
|
if (body === "*" || body === "**" || body.startsWith("**/")) return selector;
|
|
11333
|
-
const resolved =
|
|
11576
|
+
const resolved = resolve8(base, body);
|
|
11334
11577
|
if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
|
|
11335
11578
|
return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
|
|
11336
11579
|
}
|
|
11337
11580
|
function skillNameFromPath(path) {
|
|
11338
11581
|
const file = basename5(path);
|
|
11339
|
-
return file.toLowerCase() === "skill.md" ? basename5(
|
|
11582
|
+
return file.toLowerCase() === "skill.md" ? basename5(dirname7(path)) : file;
|
|
11340
11583
|
}
|
|
11341
11584
|
var discoveredResourcesByCwd = /* @__PURE__ */ new Map();
|
|
11342
11585
|
async function discoveredResources(cwd) {
|
|
11343
11586
|
const cached = discoveredResourcesByCwd.get(cwd);
|
|
11344
11587
|
if (cached) return cached;
|
|
11345
11588
|
const pending = (async () => {
|
|
11346
|
-
const agentDir =
|
|
11589
|
+
const agentDir = getAgentDir4();
|
|
11347
11590
|
const settingsManager = SettingsManager3.create(cwd, agentDir, { projectTrusted: false });
|
|
11348
11591
|
const packageManager = new DefaultPackageManager2({ cwd, agentDir, settingsManager });
|
|
11349
11592
|
const resolved = await packageManager.resolve();
|
|
@@ -11395,11 +11638,46 @@ function withoutAgentActivities(run) {
|
|
|
11395
11638
|
});
|
|
11396
11639
|
return agents.every((agent, index) => agent === run.agents[index]) ? run : { ...run, agents };
|
|
11397
11640
|
}
|
|
11641
|
+
function serializedJsonBytes(value) {
|
|
11642
|
+
const serialized = JSON.stringify(value);
|
|
11643
|
+
return Buffer.byteLength(serialized, "utf8");
|
|
11644
|
+
}
|
|
11645
|
+
function failedAgentOutput(agent) {
|
|
11646
|
+
const error = [...agent.attemptDetails ?? []].reverse().find((attempt) => attempt.error)?.error;
|
|
11647
|
+
const status = agent.state === "cancelled" ? "cancelled" : "failed";
|
|
11648
|
+
return { status, code: error?.code ?? (status === "cancelled" ? "CANCELLED" : "AGENT_FAILED"), message: error?.message ?? `Agent ${agent.state}` };
|
|
11649
|
+
}
|
|
11650
|
+
function outputForAgent(agent, operation) {
|
|
11651
|
+
if (operation !== void 0) {
|
|
11652
|
+
const bytes = serializedJsonBytes(operation.value);
|
|
11653
|
+
return bytes > DEFAULT_MAX_BYTES2 ? { status: "truncated", kind: "result", bytes } : { status: "available", value: operation.value, bytes };
|
|
11654
|
+
}
|
|
11655
|
+
if (agent.state === "failed" || agent.state === "cancelled") return failedAgentOutput(agent);
|
|
11656
|
+
if (agent.state === "completed") return { status: "unavailable" };
|
|
11657
|
+
return { status: "pending" };
|
|
11658
|
+
}
|
|
11659
|
+
function applyTrajectoryAgentOutputs(run, operations) {
|
|
11660
|
+
const byPath = new Map(operations.map((operation) => [operation.path, operation]));
|
|
11661
|
+
const agents = run.agents.map((agent) => ({ ...agent, output: outputForAgent(agent, agent.resultPath === void 0 ? void 0 : byPath.get(agent.resultPath)) }));
|
|
11662
|
+
return { ...run, agents };
|
|
11663
|
+
}
|
|
11664
|
+
function overlayTrajectoryRun(run, overlay) {
|
|
11665
|
+
const overlaid = overlay(run);
|
|
11666
|
+
const outputs = new Map(run.agents.map((agent) => [agent.id, agent.output]));
|
|
11667
|
+
const agents = overlaid.agents.map((agent) => {
|
|
11668
|
+
const previous = outputs.get(agent.id);
|
|
11669
|
+
const output = previous?.status === "available" || previous?.status === "truncated" ? previous : outputForAgent(agent, void 0);
|
|
11670
|
+
return { ...agent, output };
|
|
11671
|
+
});
|
|
11672
|
+
return { ...overlaid, agents };
|
|
11673
|
+
}
|
|
11398
11674
|
async function loadTrajectoryRun(store, includeTranscripts = true) {
|
|
11399
11675
|
const value = await store.load();
|
|
11400
11676
|
const summary = await store.loadSummary().catch(() => void 0);
|
|
11401
11677
|
const prompts = await store.systemPrompts().catch(() => []);
|
|
11402
|
-
const
|
|
11678
|
+
const prepared = withoutAgentActivities(await withResolvedResources(withPiToolDescriptions(applySystemPrompts(value.run, prompts)), store.cwd));
|
|
11679
|
+
const operations = await store.replayableOperations().catch(() => []);
|
|
11680
|
+
const run = applyTrajectoryAgentOutputs(prepared, operations);
|
|
11403
11681
|
return { run, snapshot: value.snapshot, awaiting: await store.awaitingCheckpoints(), ...summary?.createdAt === void 0 ? {} : { createdAt: summary.createdAt }, transcripts: includeTranscripts ? await runTranscripts(run) : {} };
|
|
11404
11682
|
}
|
|
11405
11683
|
async function fileMtime(path) {
|
|
@@ -11408,8 +11686,8 @@ async function fileMtime(path) {
|
|
|
11408
11686
|
function createCachedRunLoader(cwd, sessionId, home, load, transcriptSignature, overlay) {
|
|
11409
11687
|
const cache = /* @__PURE__ */ new Map();
|
|
11410
11688
|
const changed = async (store, entry) => {
|
|
11411
|
-
if (await fileMtime(
|
|
11412
|
-
if (await fileMtime(
|
|
11689
|
+
if (await fileMtime(join15(store.directory, "state.json")) !== entry.stateMtimeMs) return true;
|
|
11690
|
+
if (await fileMtime(join15(store.directory, "journal.json")) !== entry.journalMtimeMs) return true;
|
|
11413
11691
|
for (const [agentId, path] of transcriptPaths(entry.value.run)) if (await transcriptSignature(path) !== entry.transcripts.get(agentId)) return true;
|
|
11414
11692
|
return false;
|
|
11415
11693
|
};
|
|
@@ -11426,10 +11704,10 @@ function createCachedRunLoader(cwd, sessionId, home, load, transcriptSignature,
|
|
|
11426
11704
|
const value = await load(store);
|
|
11427
11705
|
const transcripts = /* @__PURE__ */ new Map();
|
|
11428
11706
|
for (const [agentId, path] of transcriptPaths(value.run)) transcripts.set(agentId, await transcriptSignature(path));
|
|
11429
|
-
entry = { value, stateMtimeMs: await fileMtime(
|
|
11707
|
+
entry = { value, stateMtimeMs: await fileMtime(join15(store.directory, "state.json")) ?? 0, journalMtimeMs: await fileMtime(join15(store.directory, "journal.json")) ?? 0, transcripts };
|
|
11430
11708
|
cache.set(runId, entry);
|
|
11431
11709
|
}
|
|
11432
|
-
loaded.push(overlay ? { ...entry.value, run:
|
|
11710
|
+
loaded.push(overlay ? { ...entry.value, run: overlayTrajectoryRun(entry.value.run, overlay) } : entry.value);
|
|
11433
11711
|
} catch {
|
|
11434
11712
|
cache.delete(runId);
|
|
11435
11713
|
}
|
|
@@ -11512,7 +11790,7 @@ function createTrajectoryTranscriptLoader(cwd, sessionId, home = homedir7(), age
|
|
|
11512
11790
|
path = sessionFile(value.run.agents.find((agent) => agent.id === request.agentId)?.attemptDetails?.at(-1)?.session);
|
|
11513
11791
|
} else if (request.subagentId !== void 0 && request.runId === void 0 && request.agentId === void 0) {
|
|
11514
11792
|
if (!safeTrajectorySubagentId(request.subagentId)) return { status: "missing", revision: 0, entries: [], error: "Transcript not found" };
|
|
11515
|
-
const rawStatus = await readSubagentJson(
|
|
11793
|
+
const rawStatus = await readSubagentJson(join15(agentDir, "subagents", request.subagentId, "status.json"));
|
|
11516
11794
|
const status = statusValue(rawStatus);
|
|
11517
11795
|
const statusRecord = object(rawStatus) ? rawStatus : void 0;
|
|
11518
11796
|
const statusCwd = statusRecord === void 0 ? void 0 : statusRecord.cwd;
|
|
@@ -11573,13 +11851,13 @@ function withoutProgressActivity(progress) {
|
|
|
11573
11851
|
async function loadTrajectorySubagent(directory, cwd, sessionId, includeTranscript = true) {
|
|
11574
11852
|
try {
|
|
11575
11853
|
const id = basename5(directory);
|
|
11576
|
-
const status = statusValue(await readSubagentJson(
|
|
11854
|
+
const status = statusValue(await readSubagentJson(join15(directory, "status.json")));
|
|
11577
11855
|
if (!status || status.id !== id) return void 0;
|
|
11578
11856
|
if (status.sessionId !== sessionId) return "foreign";
|
|
11579
11857
|
const progress = status.progress === void 0 ? void 0 : withoutProgressActivity(status.progress);
|
|
11580
|
-
const request = normalizeSubagentRunRequest(await readSubagentJson(
|
|
11581
|
-
const resultPath =
|
|
11582
|
-
const failurePath =
|
|
11858
|
+
const request = normalizeSubagentRunRequest(await readSubagentJson(join15(directory, "request.json")));
|
|
11859
|
+
const resultPath = join15(directory, "result.json");
|
|
11860
|
+
const failurePath = join15(directory, "failure.json");
|
|
11583
11861
|
const resultBytes = await subagentFileBytes(resultPath);
|
|
11584
11862
|
const failureBytes = await subagentFileBytes(failurePath);
|
|
11585
11863
|
const resultValue = await readSubagentJson(resultPath, true, DEFAULT_MAX_BYTES2);
|
|
@@ -11619,19 +11897,20 @@ async function loadTrajectorySubagent(directory, cwd, sessionId, includeTranscri
|
|
|
11619
11897
|
...progress === void 0 ? {} : { progress },
|
|
11620
11898
|
...attempt === void 0 ? {} : { attempt },
|
|
11621
11899
|
...result === void 0 ? {} : { result },
|
|
11622
|
-
...failure === void 0 ? {} : { failure }
|
|
11900
|
+
...failure === void 0 ? {} : { failure },
|
|
11901
|
+
output: subagentOutput({ state: status.state, statusError: status.error, attempt, result: resultValue === void 0 ? void 0 : boundedSubagentJson(resultValue), resultTruncated, resultPath, resultBytes, failure: failureValue === void 0 ? void 0 : subagentErrorValue(failureValue), failureTruncated, failurePath, failureBytes })
|
|
11623
11902
|
};
|
|
11624
11903
|
} catch {
|
|
11625
11904
|
return void 0;
|
|
11626
11905
|
}
|
|
11627
11906
|
}
|
|
11628
11907
|
async function subagentCacheEntryChanged(directory, entry) {
|
|
11629
|
-
for (const file of TRAJECTORY_SUBAGENT_FILES) if (await fileMtime(
|
|
11908
|
+
for (const file of TRAJECTORY_SUBAGENT_FILES) if (await fileMtime(join15(directory, file)) !== entry.fileMtimes.get(file)) return true;
|
|
11630
11909
|
const transcriptPath = sessionFile(entry.value.attempt?.session);
|
|
11631
11910
|
return transcriptPath !== void 0 && await fileMtime(transcriptPath) !== entry.transcriptMtime;
|
|
11632
11911
|
}
|
|
11633
11912
|
function createTrajectorySubagentLoader(cwd, sessionId, agentDir, overlay, includeTranscript = true) {
|
|
11634
|
-
const root =
|
|
11913
|
+
const root = join15(agentDir, "subagents");
|
|
11635
11914
|
const cache = /* @__PURE__ */ new Map();
|
|
11636
11915
|
const negativeCache = /* @__PURE__ */ new Map();
|
|
11637
11916
|
return async () => {
|
|
@@ -11648,8 +11927,8 @@ function createTrajectorySubagentLoader(cwd, sessionId, agentDir, overlay, inclu
|
|
|
11648
11927
|
for (const id of negativeCache.keys()) if (!current.has(id)) negativeCache.delete(id);
|
|
11649
11928
|
const loaded = [];
|
|
11650
11929
|
for (const id of ids) {
|
|
11651
|
-
const directory =
|
|
11652
|
-
const statusPath =
|
|
11930
|
+
const directory = join15(root, id);
|
|
11931
|
+
const statusPath = join15(directory, "status.json");
|
|
11653
11932
|
try {
|
|
11654
11933
|
const statusMtime = await fileMtime(statusPath);
|
|
11655
11934
|
if (negativeCache.has(id) && negativeCache.get(id) === statusMtime) continue;
|
|
@@ -11667,7 +11946,7 @@ function createTrajectorySubagentLoader(cwd, sessionId, agentDir, overlay, inclu
|
|
|
11667
11946
|
continue;
|
|
11668
11947
|
}
|
|
11669
11948
|
const fileMtimes = /* @__PURE__ */ new Map();
|
|
11670
|
-
for (const file of TRAJECTORY_SUBAGENT_FILES) fileMtimes.set(file, await fileMtime(
|
|
11949
|
+
for (const file of TRAJECTORY_SUBAGENT_FILES) fileMtimes.set(file, await fileMtime(join15(directory, file)));
|
|
11671
11950
|
const transcriptPath = sessionFile(value.attempt?.session);
|
|
11672
11951
|
entry = { value, fileMtimes, transcriptMtime: transcriptPath === void 0 ? void 0 : await fileMtime(transcriptPath) };
|
|
11673
11952
|
cache.set(id, entry);
|
|
@@ -11784,7 +12063,8 @@ var WORKFLOW_TOOL_PARAMETERS = Type3.Object({
|
|
|
11784
12063
|
var WORKFLOW_STATUS_PARAMETERS = Type3.Object({ runId: Type3.String({ description: "Workflow run ID visible in the current project" }) }, { additionalProperties: false });
|
|
11785
12064
|
var WORKFLOW_RETRY_PARAMETERS = Type3.Object({ runId: Type3.String({ description: "Explicit failed workflow run ID" }), expectedState: Type3.Optional(Type3.String({ description: "Persisted source state observed before recovery" })), foreground: Type3.Optional(Type3.Boolean({ description: "Override the source launch mode for this recovery" })) });
|
|
11786
12065
|
function workflowToolUpdate(run) {
|
|
11787
|
-
|
|
12066
|
+
const done = run.agents.filter((agent) => SETTLED_AGENT_STATES.has(agent.state)).length;
|
|
12067
|
+
return { content: [{ type: "text", text: `Workflow: ${run.workflowName} (${String(done)}/${String(run.agents.length)} done) [${run.state}]` }], details: { runId: run.id, run } };
|
|
11788
12068
|
}
|
|
11789
12069
|
function agentWithProgress(agent, progress) {
|
|
11790
12070
|
const next = { ...agent, accounting: progress.accounting, toolCalls: progress.toolCalls };
|
|
@@ -11805,6 +12085,7 @@ function deliver(pi, content) {
|
|
|
11805
12085
|
pi.sendMessage({ customType: "workflow", content, display: true }, { deliverAs: "followUp", triggerTurn: true });
|
|
11806
12086
|
}
|
|
11807
12087
|
var WORKFLOW_WARNING_ENTRY = "workflow-warning";
|
|
12088
|
+
var PROGRESS_COALESCE_MS = 100;
|
|
11808
12089
|
function deliverWarning(pi, content) {
|
|
11809
12090
|
pi.appendEntry(WORKFLOW_WARNING_ENTRY, { message: content });
|
|
11810
12091
|
}
|
|
@@ -11882,7 +12163,7 @@ async function resolveLaunchAliases(registry, staticAliases, context, availableM
|
|
|
11882
12163
|
function workflowExtension(pi, home, clipboard = copyToClipboard, transport = localAgentTransport, agentDir, additionalSkillPaths = []) {
|
|
11883
12164
|
beginWorkflowExtensionLoading();
|
|
11884
12165
|
const registry = loadingRegistry();
|
|
11885
|
-
const extensionAgentDir = agentDir ??
|
|
12166
|
+
const extensionAgentDir = agentDir ?? getAgentDir5();
|
|
11886
12167
|
const registerEntryRenderer = piHostCapabilities(pi).registerEntryRenderer;
|
|
11887
12168
|
registerEntryRenderer?.(WORKFLOW_LOG_ENTRY, (entry) => {
|
|
11888
12169
|
const data = entry.data;
|
|
@@ -11925,8 +12206,8 @@ function workflowExtension(pi, home, clipboard = copyToClipboard, transport = lo
|
|
|
11925
12206
|
};
|
|
11926
12207
|
pi.on("resources_discover", () => {
|
|
11927
12208
|
if (!pi.getActiveTools().includes("workflow")) return;
|
|
11928
|
-
const extensionDir =
|
|
11929
|
-
const skillPath = [
|
|
12209
|
+
const extensionDir = dirname8(fileURLToPath5(import.meta.url));
|
|
12210
|
+
const skillPath = [join16(extensionDir, "../skills"), join16(extensionDir, "../../skills")].find((path) => existsSync2(path));
|
|
11930
12211
|
return skillPath ? { skillPaths: [skillPath] } : void 0;
|
|
11931
12212
|
});
|
|
11932
12213
|
const runs = /* @__PURE__ */ new Map();
|
|
@@ -12064,7 +12345,6 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
|
|
|
12064
12345
|
const trusted = projectTrusted(context);
|
|
12065
12346
|
const settings = resolveWorkflowSettings(cwd, trusted, workflowSettingsPath(extensionAgentDir)).effective.extensionSettings?.trajectory;
|
|
12066
12347
|
const port = settings?.port;
|
|
12067
|
-
const themes = settings?.themes ?? false;
|
|
12068
12348
|
const loadRuns = createTrajectoryRunLoader(cwd, sessionId, home, overlayLiveRun);
|
|
12069
12349
|
const loadSubagents = createTrajectorySubagentLoader(cwd, sessionId, extensionAgentDir, overlayLiveSubagent);
|
|
12070
12350
|
const loadRunsMetadata = createTrajectoryRunMetadataLoader(cwd, sessionId, home, overlayLiveRun);
|
|
@@ -12073,7 +12353,7 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
|
|
|
12073
12353
|
const live = liveSubagents.get(subagentId);
|
|
12074
12354
|
return live?.status.sessionId === sessionId ? live.status.attemptDetails?.at(-1)?.session : void 0;
|
|
12075
12355
|
});
|
|
12076
|
-
return { cwd, sessionId, ...port === void 0 ? {} : { port },
|
|
12356
|
+
return { cwd, sessionId, ...port === void 0 ? {} : { port }, loadRuns, loadSubagents, loadMetadata: async () => ({ runs: await loadRunsMetadata(), subagents: await loadSubagentsMetadata() }), loadTranscript, handleAction: (request) => trajectoryAction(request, context) };
|
|
12077
12357
|
};
|
|
12078
12358
|
const withLiveActivities = (run) => liveAgents.overlay(run);
|
|
12079
12359
|
const overlayLiveRun = (run) => {
|
|
@@ -12246,22 +12526,48 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
|
|
|
12246
12526
|
const scheduler = new FairAgentScheduler(async ({ id, runId, tuiIndex, parentId, prompt, options, signal, setSteer }) => {
|
|
12247
12527
|
const run = runs.get(runId);
|
|
12248
12528
|
if (!run) throw new WorkflowError("INTERNAL_ERROR", `Unknown production run: ${runId}`);
|
|
12529
|
+
const applyProgress = async (progress) => {
|
|
12530
|
+
let runState;
|
|
12531
|
+
if (progress.persist) {
|
|
12532
|
+
runState = await persistRunState(run.store, run.metadata, (current) => current.agents.some((agent) => agent.id === id) ? { ...current, ...run.budget.snapshot(), agents: current.agents.map((agent) => agent.id === id ? agentWithProgress(agent, progress) : agent) } : current);
|
|
12533
|
+
} else {
|
|
12534
|
+
const loaded = await run.store.loadStatus();
|
|
12535
|
+
if (!loaded.agents.some((agent) => agent.id === id)) return;
|
|
12536
|
+
runState = { ...loaded, ...run.budget.snapshot(), agents: loaded.agents.map((agent) => agent.id === id ? agentWithProgress(agent, progress) : agent) };
|
|
12537
|
+
}
|
|
12538
|
+
if (!runState.agents.some((agent) => agent.id === id)) return;
|
|
12539
|
+
liveAgents.setActivity(runId, id, progress.activity);
|
|
12540
|
+
liveAgents.setEventTime(runId, id, progress.lastEventAt);
|
|
12541
|
+
run.update?.(workflowToolUpdate(withLiveActivities(runState)));
|
|
12542
|
+
};
|
|
12543
|
+
let deferred;
|
|
12544
|
+
let deferredTimer;
|
|
12545
|
+
let appliedAt = 0;
|
|
12546
|
+
const cancelDeferredProgress = () => {
|
|
12547
|
+
if (deferredTimer) clearTimeout(deferredTimer);
|
|
12548
|
+
deferredTimer = void 0;
|
|
12549
|
+
deferred = void 0;
|
|
12550
|
+
};
|
|
12551
|
+
const onProgress = async (progress) => {
|
|
12552
|
+
const now = Date.now();
|
|
12553
|
+
if (progress.persist || !deferredTimer && now - appliedAt >= PROGRESS_COALESCE_MS) {
|
|
12554
|
+
cancelDeferredProgress();
|
|
12555
|
+
appliedAt = now;
|
|
12556
|
+
await applyProgress(progress);
|
|
12557
|
+
return;
|
|
12558
|
+
}
|
|
12559
|
+
deferred = progress;
|
|
12560
|
+
deferredTimer ??= setTimeout(() => {
|
|
12561
|
+
deferredTimer = void 0;
|
|
12562
|
+
const next = deferred;
|
|
12563
|
+
deferred = void 0;
|
|
12564
|
+
if (!next) return;
|
|
12565
|
+
appliedAt = Date.now();
|
|
12566
|
+
void applyProgress(next).catch(() => void 0);
|
|
12567
|
+
}, Math.max(0, PROGRESS_COALESCE_MS - (now - appliedAt))).unref();
|
|
12568
|
+
};
|
|
12249
12569
|
try {
|
|
12250
12570
|
const budget = run.budget.forAgent(id);
|
|
12251
|
-
const onProgress = async (progress) => {
|
|
12252
|
-
let runState;
|
|
12253
|
-
if (progress.persist) {
|
|
12254
|
-
runState = await persistRunState(run.store, run.metadata, (current) => current.agents.some((agent) => agent.id === id) ? { ...current, ...run.budget.snapshot(), agents: current.agents.map((agent) => agent.id === id ? agentWithProgress(agent, progress) : agent) } : current);
|
|
12255
|
-
} else {
|
|
12256
|
-
const loaded = await run.store.load();
|
|
12257
|
-
if (!loaded.run.agents.some((agent) => agent.id === id)) return;
|
|
12258
|
-
runState = { ...loaded.run, ...run.budget.snapshot(), agents: loaded.run.agents.map((agent) => agent.id === id ? agentWithProgress(agent, progress) : agent) };
|
|
12259
|
-
}
|
|
12260
|
-
if (!runState.agents.some((agent) => agent.id === id)) return;
|
|
12261
|
-
liveAgents.setActivity(runId, id, progress.activity);
|
|
12262
|
-
liveAgents.setEventTime(runId, id, progress.lastEventAt);
|
|
12263
|
-
run.update?.(workflowToolUpdate(withLiveActivities(runState)));
|
|
12264
|
-
};
|
|
12265
12571
|
const onAttempt = async (attempt) => {
|
|
12266
12572
|
liveAgents.setSession(runId, id, attempt.liveSession);
|
|
12267
12573
|
liveAgents.setHandoff(runId, id, attempt);
|
|
@@ -12281,6 +12587,7 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
|
|
|
12281
12587
|
scheduler.cancelChildren(id);
|
|
12282
12588
|
scheduler.retry(id);
|
|
12283
12589
|
});
|
|
12590
|
+
cancelDeferredProgress();
|
|
12284
12591
|
const before = (await run.store.load()).run;
|
|
12285
12592
|
await persistAgentAttempts(run.store, id, result.attempts);
|
|
12286
12593
|
const completed = (await run.store.load()).run;
|
|
@@ -12291,6 +12598,7 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
|
|
|
12291
12598
|
run.update?.(workflowToolUpdate(withLiveActivities(persisted)));
|
|
12292
12599
|
return result.value;
|
|
12293
12600
|
} catch (error) {
|
|
12601
|
+
cancelDeferredProgress();
|
|
12294
12602
|
liveAgents.setSession(runId, id);
|
|
12295
12603
|
const attempts = getAgentAttempts(error);
|
|
12296
12604
|
if (attempts?.length) {
|
|
@@ -12426,8 +12734,8 @@ Respond with workflow_respond.`);
|
|
|
12426
12734
|
const run = runs.get(runId);
|
|
12427
12735
|
await run?.lifecycle.enterAwaitingInput();
|
|
12428
12736
|
if (!alreadyAwaiting && (!isForeground() || !ui?.select)) deliverBackgroundCheckpoint(metadata.name, runId, { ...input, name: label, path });
|
|
12429
|
-
const decision = new Promise((
|
|
12430
|
-
run?.checkpointResolvers.set(path,
|
|
12737
|
+
const decision = new Promise((resolve9, reject) => {
|
|
12738
|
+
run?.checkpointResolvers.set(path, resolve9);
|
|
12431
12739
|
if (signal.aborted) reject(new WorkflowError("CANCELLED", "Workflow cancelled"));
|
|
12432
12740
|
else signal.addEventListener("abort", () => {
|
|
12433
12741
|
run?.checkpointResolvers.delete(path);
|
|
@@ -12745,7 +13053,7 @@ Respond with workflow_respond.`);
|
|
|
12745
13053
|
const deliverStaleTerminal = async (store, run) => {
|
|
12746
13054
|
if (!(run.delivery?.mode === "foreground" && run.delivery.state === "attached" || run.delivery?.mode === "background" && run.delivery.state === "pending")) return;
|
|
12747
13055
|
if (run.state === "completed") {
|
|
12748
|
-
const resultPath =
|
|
13056
|
+
const resultPath = join16(store.directory, "result.json");
|
|
12749
13057
|
let resultBytes = 0;
|
|
12750
13058
|
let hasResult = true;
|
|
12751
13059
|
try {
|
|
@@ -12875,8 +13183,8 @@ ${content}` };
|
|
|
12875
13183
|
let resolveDetached;
|
|
12876
13184
|
let foregroundStore;
|
|
12877
13185
|
let completionInstalled = false;
|
|
12878
|
-
const detachedResult = params.foreground ? new Promise((
|
|
12879
|
-
resolveDetached =
|
|
13186
|
+
const detachedResult = params.foreground ? new Promise((resolve9) => {
|
|
13187
|
+
resolveDetached = resolve9;
|
|
12880
13188
|
}) : void 0;
|
|
12881
13189
|
const detachedToolResult = (run) => {
|
|
12882
13190
|
const detached = { runId: run.id, state: "running", detached: true };
|
|
@@ -13109,8 +13417,7 @@ ${content}` };
|
|
|
13109
13417
|
state.workflowProgressComponent = workflowProgressBlock(currentProgress?.run ?? incoming, theme, currentProgress, async () => {
|
|
13110
13418
|
const active = runs.get(incoming.id);
|
|
13111
13419
|
const store = active?.store ?? new RunStore(incoming.cwd, incoming.sessionId, incoming.id, home);
|
|
13112
|
-
|
|
13113
|
-
return withLiveActivities(loaded.run);
|
|
13420
|
+
return withLiveActivities(await store.loadStatus());
|
|
13114
13421
|
}, () => {
|
|
13115
13422
|
if (state.workflowProgress === currentProgress) requestRender();
|
|
13116
13423
|
}, void 0, state.workflowProgressFrozenAt);
|
|
@@ -13223,9 +13530,9 @@ async function handleTurnInput(store, handle, turn) {
|
|
|
13223
13530
|
}
|
|
13224
13531
|
if (!completed) return void 0;
|
|
13225
13532
|
if (source === void 0) fail("AGENT_FAILED", `Agent handle ${handle} cannot continue from its previous turn: the completed turn recorded no session file`);
|
|
13226
|
-
const target =
|
|
13533
|
+
const target = join16(store.directory, "handles", encodeURIComponent(handle), `turn-${String(turn)}-input.jsonl`);
|
|
13227
13534
|
try {
|
|
13228
|
-
await mkdir4(
|
|
13535
|
+
await mkdir4(dirname8(target), { recursive: true });
|
|
13229
13536
|
await copyFile2(source, target);
|
|
13230
13537
|
} catch (error) {
|
|
13231
13538
|
fail("AGENT_FAILED", `Agent handle ${handle} cannot continue from its previous turn: ${errorText(error)}`);
|
|
@@ -13246,13 +13553,13 @@ function modelSpec(value, fallback) {
|
|
|
13246
13553
|
|
|
13247
13554
|
// src/herdr.ts
|
|
13248
13555
|
import { execFile as execFile3 } from "node:child_process";
|
|
13249
|
-
var herdrCommandRunner = (args) => new Promise((
|
|
13556
|
+
var herdrCommandRunner = (args) => new Promise((resolve9, reject) => {
|
|
13250
13557
|
execFile3("herdr", [...args], { encoding: "utf8", maxBuffer: 1024 * 1024 }, (error, stdout) => {
|
|
13251
13558
|
if (error) {
|
|
13252
13559
|
reject(new Error(error.message));
|
|
13253
13560
|
return;
|
|
13254
13561
|
}
|
|
13255
|
-
|
|
13562
|
+
resolve9(stdout);
|
|
13256
13563
|
});
|
|
13257
13564
|
});
|
|
13258
13565
|
function herdrPaneId(env = process.env) {
|
|
@@ -13384,9 +13691,9 @@ async function waitForHerdrPane(paneId, runner = herdrCommandRunner, options = {
|
|
|
13384
13691
|
const isAborted = () => monitorController.signal.aborted;
|
|
13385
13692
|
let markAborted = () => {
|
|
13386
13693
|
};
|
|
13387
|
-
const aborted = new Promise((
|
|
13694
|
+
const aborted = new Promise((resolve9) => {
|
|
13388
13695
|
markAborted = () => {
|
|
13389
|
-
|
|
13696
|
+
resolve9("aborted");
|
|
13390
13697
|
};
|
|
13391
13698
|
});
|
|
13392
13699
|
const abortMonitor = () => {
|
|
@@ -13395,16 +13702,16 @@ async function waitForHerdrPane(paneId, runner = herdrCommandRunner, options = {
|
|
|
13395
13702
|
};
|
|
13396
13703
|
if (options.signal?.aborted) abortMonitor();
|
|
13397
13704
|
else options.signal?.addEventListener("abort", abortMonitor, { once: true });
|
|
13398
|
-
const delay = () => new Promise((
|
|
13705
|
+
const delay = () => new Promise((resolve9) => {
|
|
13399
13706
|
if (isAborted()) {
|
|
13400
|
-
|
|
13707
|
+
resolve9();
|
|
13401
13708
|
return;
|
|
13402
13709
|
}
|
|
13403
13710
|
const timer = setTimeout(finish, intervalMs);
|
|
13404
13711
|
function finish() {
|
|
13405
13712
|
clearTimeout(timer);
|
|
13406
13713
|
monitorController.signal.removeEventListener("abort", finish);
|
|
13407
|
-
|
|
13714
|
+
resolve9();
|
|
13408
13715
|
}
|
|
13409
13716
|
monitorController.signal.addEventListener("abort", finish, { once: true });
|
|
13410
13717
|
});
|
|
@@ -13516,6 +13823,7 @@ export {
|
|
|
13516
13823
|
HEARTBEAT_TIMEOUT_MS,
|
|
13517
13824
|
LAUNCH_SNAPSHOT_IDENTITY_VERSION,
|
|
13518
13825
|
MODEL_ALIAS_NAME,
|
|
13826
|
+
PLAIN_WORKFLOW_PROGRESS_STYLES,
|
|
13519
13827
|
PiRuntimeAgentRunner,
|
|
13520
13828
|
RPC_LIMIT_BYTES,
|
|
13521
13829
|
RUN_STATES,
|
|
@@ -13569,9 +13877,11 @@ export {
|
|
|
13569
13877
|
buildWorkflowPhaseModel,
|
|
13570
13878
|
buildWorkflowPhaseTree,
|
|
13571
13879
|
byPriorityThenName,
|
|
13880
|
+
canonicalExtensionSelector,
|
|
13572
13881
|
canonicalPath,
|
|
13573
13882
|
coerceWorkflowError,
|
|
13574
13883
|
createHerdrAgentReporter,
|
|
13884
|
+
createInlineConfirm,
|
|
13575
13885
|
createLaunchSnapshot,
|
|
13576
13886
|
createLiveSessionHandoff,
|
|
13577
13887
|
createLocalPiSession,
|
|
@@ -13583,6 +13893,7 @@ export {
|
|
|
13583
13893
|
deepFreeze,
|
|
13584
13894
|
workflowExtension as default,
|
|
13585
13895
|
defineWorkflowFunction,
|
|
13896
|
+
discoverRoles,
|
|
13586
13897
|
encoded,
|
|
13587
13898
|
errorCode,
|
|
13588
13899
|
errorText,
|
|
@@ -13591,8 +13902,10 @@ export {
|
|
|
13591
13902
|
fail,
|
|
13592
13903
|
finiteNumber,
|
|
13593
13904
|
formatAgentDetail,
|
|
13905
|
+
formatAgentError,
|
|
13594
13906
|
formatBudgetStatus,
|
|
13595
13907
|
formatCost,
|
|
13908
|
+
formatNavigatorColumns,
|
|
13596
13909
|
formatNavigatorDashboard,
|
|
13597
13910
|
formatNavigatorRun,
|
|
13598
13911
|
formatStalledDuration,
|
|
@@ -13602,6 +13915,7 @@ export {
|
|
|
13602
13915
|
formatWorkflowPhaseDashboard,
|
|
13603
13916
|
formatWorkflowPreview,
|
|
13604
13917
|
formatWorkflowProgress,
|
|
13918
|
+
formatWorkflowRuntime,
|
|
13605
13919
|
hasLiveSessionLease,
|
|
13606
13920
|
herdrAvailable,
|
|
13607
13921
|
herdrCommandRunner,
|
|
@@ -13628,6 +13942,7 @@ export {
|
|
|
13628
13942
|
loadAgentDefinitions,
|
|
13629
13943
|
loadLaunchSnapshot,
|
|
13630
13944
|
loadProjectAgentDefinitions,
|
|
13945
|
+
loadRole,
|
|
13631
13946
|
loadSettings,
|
|
13632
13947
|
loadSettingsOverrides,
|
|
13633
13948
|
loadingRegistry,
|
|
@@ -13659,6 +13974,7 @@ export {
|
|
|
13659
13974
|
prepareAgentSetupForInspection,
|
|
13660
13975
|
preserveWorkflowPhaseSelection,
|
|
13661
13976
|
preserveWorkflowPhaseTreeSelection,
|
|
13977
|
+
progressStyleForState,
|
|
13662
13978
|
projectSessionsDirectory,
|
|
13663
13979
|
projectStorageKey,
|
|
13664
13980
|
readShellResult,
|
|
@@ -13671,6 +13987,7 @@ export {
|
|
|
13671
13987
|
resetWorkflowRegistryIfIdle,
|
|
13672
13988
|
resolveAgentResourcePolicy,
|
|
13673
13989
|
resolveModelReference,
|
|
13990
|
+
resolveRole,
|
|
13674
13991
|
resolveWorkflowSettings,
|
|
13675
13992
|
resourcePatternHasMagic,
|
|
13676
13993
|
resourcePatternMatches,
|
|
@@ -13678,6 +13995,7 @@ export {
|
|
|
13678
13995
|
retainTerminalRuns,
|
|
13679
13996
|
retainWorkflowRegistry,
|
|
13680
13997
|
roleNameOf,
|
|
13998
|
+
runStateGlyph,
|
|
13681
13999
|
runWorkflow,
|
|
13682
14000
|
runsDirectory,
|
|
13683
14001
|
runtimeProgressToAgentProgress,
|
|
@@ -13697,10 +14015,12 @@ export {
|
|
|
13697
14015
|
validateBudget,
|
|
13698
14016
|
validateBudgetPatch,
|
|
13699
14017
|
validateCheckpoint,
|
|
14018
|
+
validateContextFileScopes,
|
|
13700
14019
|
validateModelAliasAvailability,
|
|
13701
14020
|
validateModelAliases,
|
|
13702
14021
|
validateResourcePattern,
|
|
13703
14022
|
validateSchema,
|
|
14023
|
+
validateSelectorList,
|
|
13704
14024
|
validateShellCommand,
|
|
13705
14025
|
validateShellOptions,
|
|
13706
14026
|
validateWorkflowExtensionSettings,
|
|
@@ -13711,6 +14031,8 @@ export {
|
|
|
13711
14031
|
workflowCatalog,
|
|
13712
14032
|
workflowCatalogDetail,
|
|
13713
14033
|
workflowCatalogIndex,
|
|
14034
|
+
workflowKeyLabel,
|
|
14035
|
+
workflowKeyMatches,
|
|
13714
14036
|
workflowPhaseTreeInitialExpanded,
|
|
13715
14037
|
workflowPhaseTreeVisibleNodes,
|
|
13716
14038
|
workflowProjectSettingsPath,
|