@xingular/harness 1.3.1-unstable.4 → 1.3.1-unstable.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/dist/cli.js +775 -354
- package/dist/sdk/commands/extensionOwners.d.ts +4 -0
- package/dist/sdk/commands/extensionOwners.js +17 -0
- package/dist/sdk/commands/list.js +15 -2
- package/dist/sdk/commands/promptLoader.d.ts +5 -0
- package/dist/sdk/commands/promptLoader.js +76 -0
- package/dist/sdk/commands/promptRoutes.d.ts +13 -0
- package/dist/sdk/commands/promptRoutes.js +46 -0
- package/dist/sdk/commands/runtimeRouter.d.ts +4 -0
- package/dist/sdk/commands/runtimeRouter.js +48 -0
- package/dist/sdk/computer/boundary.d.ts +40 -0
- package/dist/sdk/computer/boundary.js +79 -0
- package/dist/sdk/computer/contracts.d.ts +127 -0
- package/dist/sdk/computer/contracts.js +13 -0
- package/dist/sdk/computer/control.d.ts +34 -0
- package/dist/sdk/computer/control.js +112 -0
- package/dist/sdk/computer/terminalCapture.d.ts +26 -0
- package/dist/sdk/computer/terminalCapture.js +64 -0
- package/dist/sdk/computer/volatileImages.d.ts +9 -0
- package/dist/sdk/computer/volatileImages.js +50 -0
- package/dist/sdk/context/runDetails.d.ts +16 -0
- package/dist/sdk/context/runDetails.js +22 -0
- package/dist/sdk/doctor/checks.js +3 -0
- package/dist/sdk/doctor/probe.js +8 -6
- package/dist/sdk/history/trajectory.js +2 -0
- package/dist/sdk/hub/schema.d.ts +2 -2
- package/dist/sdk/plugins/manifest.d.ts +2 -0
- package/dist/sdk/plugins/manifest.js +1 -0
- package/dist/sdk/prompt/compile.d.ts +2 -0
- package/dist/sdk/prompt/compile.js +4 -2
- package/dist/sdk/refine/trajectory.js +7 -2
- package/dist/sdk/remote/webClient.js +7 -3
- package/dist/sdk/server/methods.js +2 -0
- package/dist/sdk/sessions/contracts.d.ts +124 -2
- package/dist/sdk/sessions/contracts.js +114 -3
- package/dist/sdk/sessions/registry.d.ts +6 -2
- package/dist/sdk/sessions/registry.js +16 -13
- package/dist/sdk/sessions/runStore.d.ts +3 -1
- package/dist/sdk/sessions/runStore.js +25 -8
- package/dist/sdk/sessions/runner.d.ts +5 -5
- package/dist/sdk/sessions/runner.js +78 -15
- package/dist/sdk/sessions/sessions.js +28 -21
- package/dist/sdk/sessions/workSlice.d.ts +2 -0
- package/dist/sdk/sessions/workSlice.js +4 -3
- package/dist/sdk/terminal/client.d.ts +3 -0
- package/dist/sdk/terminal/client.js +13 -0
- package/dist/sdk/terminal/controller.js +11 -6
- package/dist/sdk/terminal/piExtension.js +73 -13
- package/dist/sdk/terminal/terminals.d.ts +1 -1
- package/dist/sdk/terminal/terminals.js +20 -5
- package/dist/sdk/terminal/trajectory.js +2 -2
- package/dist/sdk/terminal/tui/commandPages.d.ts +1 -1
- package/dist/sdk/terminal/tui/commandPages.js +4 -2
- package/dist/sdk/terminal/tui/commands.js +9 -65
- package/dist/sdk/terminal/tui/pages/contextPage.d.ts +30 -1
- package/dist/sdk/terminal/tui/pages/contextPage.js +109 -8
- package/dist/sdk/terminal/tui/pages/trajectoryPage.d.ts +1 -0
- package/dist/sdk/terminal/tui/pages/trajectoryPage.js +12 -3
- package/dist/sdk/terminal/tui/runtime.js +15 -2
- package/dist/sdk/terminal/tui/sideQuestion.d.ts +3 -0
- package/dist/sdk/terminal/tui/sideQuestion.js +7 -1
- package/dist/sdk/terminal/tui/status.d.ts +0 -6
- package/dist/sdk/terminal/tui/status.js +1 -15
- package/dist/sdk/tools/definitions.js +1 -1
- package/dist/sdk/tools/dispatch.d.ts +1 -1
- package/dist/sdk/tools/dispatch.js +17 -3
- package/dist/sdk/world/actions.js +1 -1
- package/dist/terminal-opentui.js +984 -361
- package/dist/terminal-pi.js +145 -18
- package/docs/architecture/02-sessions-and-runs.md +5 -2
- package/docs/architecture/04-prompt.md +5 -3
- package/docs/architecture/06-plugins-and-capabilities.md +13 -2
- package/docs/architecture/07-tools-and-gate.md +14 -0
- package/docs/architecture/08-memory-and-history.md +7 -3
- package/docs/architecture/10-server-protocol.md +15 -3
- package/docs/architecture/11-terminal.md +28 -14
- package/docs/architecture/13-world-tools.md +19 -0
- package/docs/architecture/14-surfaces-and-parity.md +3 -3
- package/docs/architecture/README.md +1 -0
- package/docs/architecture/proposals/PROP-0049-context-attribution.md +7 -1
- package/docs/architecture/proposals/PROP-0050-run-stop-codes-and-refusals.md +30 -0
- package/docs/architecture/proposals/README.md +1 -0
- package/docs/decisions/ADR-0059-run-diagnostic-evidence.md +35 -0
- package/docs/decisions/ADR-0060-approvals-are-controls.md +40 -0
- package/docs/decisions/ADR-0061-strict-plugin-command-namespaces.md +41 -0
- package/docs/doctor.md +2 -0
- package/docs/parity.md +16 -5
- package/docs/releases/1.3.1-unstable.5.md +53 -0
- package/docs/terminal-tui.md +16 -10
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -161,7 +161,7 @@ function runSetup(input) {
|
|
|
161
161
|
if (!input.trust.trusted(input.repoRoot, script.hash)) return Promise.resolve({ state: "untrusted", hash: script.hash, log: null });
|
|
162
162
|
mkdirSync20(dirname17(input.logFile), { recursive: true });
|
|
163
163
|
const log2 = openSync4(input.logFile, "w");
|
|
164
|
-
return new Promise((
|
|
164
|
+
return new Promise((resolve23) => {
|
|
165
165
|
const child = spawn3("/bin/sh", [script.path], {
|
|
166
166
|
cwd: input.checkout,
|
|
167
167
|
env: { ...process.env, HARNESS_WORKTREE_PATH: input.checkout, HARNESS_WORKTREE_SOURCE: input.source, HARNESS_WORKTREE_NAME: input.name },
|
|
@@ -174,7 +174,7 @@ function runSetup(input) {
|
|
|
174
174
|
settled3 = true;
|
|
175
175
|
clearTimeout(timer);
|
|
176
176
|
closeSync4(log2);
|
|
177
|
-
|
|
177
|
+
resolve23({ state: exitCode2 === 0 ? "ran" : "failed", hash: script.hash, log: input.logFile, exitCode: exitCode2 });
|
|
178
178
|
};
|
|
179
179
|
child.once("error", () => finish(null));
|
|
180
180
|
child.once("close", (code) => finish(code));
|
|
@@ -209,12 +209,12 @@ var init_setup = __esm({
|
|
|
209
209
|
});
|
|
210
210
|
|
|
211
211
|
// src/cli.ts
|
|
212
|
-
import { resolve as
|
|
212
|
+
import { resolve as resolve22 } from "node:path";
|
|
213
213
|
|
|
214
214
|
// package.json
|
|
215
215
|
var package_default = {
|
|
216
216
|
name: "@xingular/harness",
|
|
217
|
-
version: "1.3.1-unstable.
|
|
217
|
+
version: "1.3.1-unstable.5",
|
|
218
218
|
type: "module",
|
|
219
219
|
description: "Xingular Harness \u2014 a Pi agent harness built on recursive harnessing (Harness\xB2): versioned eight-component variants, refinement from evidence, multi-account sessions, a compiled prompt, project notes, tools, and MCP",
|
|
220
220
|
repository: {
|
|
@@ -457,6 +457,7 @@ var runEventTypeSchema = z.enum([
|
|
|
457
457
|
"message.steered",
|
|
458
458
|
"tool.started",
|
|
459
459
|
"tool.completed",
|
|
460
|
+
"tool.refused",
|
|
460
461
|
"retry.started",
|
|
461
462
|
"retry.ended",
|
|
462
463
|
"compaction.started",
|
|
@@ -517,7 +518,38 @@ var toolPayload = z.object({
|
|
|
517
518
|
});
|
|
518
519
|
var retryStartedPayload = z.object({ attempt: z.number(), maxAttempts: z.number(), delayMs: z.number(), error: z.string().max(1e3).optional() });
|
|
519
520
|
var retryEndedPayload = z.object({ attempt: z.number(), success: z.boolean(), error: z.string().max(1e3).optional() });
|
|
520
|
-
var
|
|
521
|
+
var refusalCodeSchema = z.enum([
|
|
522
|
+
/** Plan mode allows only research tools and read-only Bash. */
|
|
523
|
+
"plan_mode",
|
|
524
|
+
/** A file write or command outside the active project workspace, or a workspace that could not be verified. */
|
|
525
|
+
"workspace",
|
|
526
|
+
/** The host's work scope does not cover the call. */
|
|
527
|
+
"scope",
|
|
528
|
+
/** The host's tool policy disabled the tool. */
|
|
529
|
+
"policy",
|
|
530
|
+
/** The tool is outside the run's capability set. */
|
|
531
|
+
"capability",
|
|
532
|
+
/** A person was asked and declined. */
|
|
533
|
+
"approval_denied",
|
|
534
|
+
/** The call needs a person and the run has nobody to ask: refinement, reflection, diagnostics. */
|
|
535
|
+
"unattended",
|
|
536
|
+
/** The reply reached its work-slice budget. */
|
|
537
|
+
"work_slice",
|
|
538
|
+
/** The terminal controller could not be reached to decide. */
|
|
539
|
+
"controller_unavailable"
|
|
540
|
+
]);
|
|
541
|
+
var toolRefusedPayload = z.object({
|
|
542
|
+
toolCallId: z.string(),
|
|
543
|
+
name: z.string(),
|
|
544
|
+
code: refusalCodeSchema,
|
|
545
|
+
reason: z.string().max(2e3)
|
|
546
|
+
});
|
|
547
|
+
var compactionStartedPayload = z.object({
|
|
548
|
+
reason: z.string(),
|
|
549
|
+
/** The latest request's size and the model's limit when compaction began, when known. */
|
|
550
|
+
contextTokens: z.number().optional(),
|
|
551
|
+
contextWindow: z.number().optional()
|
|
552
|
+
});
|
|
521
553
|
var compactionEndedPayload = z.object({
|
|
522
554
|
reason: z.string(),
|
|
523
555
|
aborted: z.boolean(),
|
|
@@ -565,6 +597,34 @@ var questionOptionSchema = z.object({
|
|
|
565
597
|
recommended: z.boolean().optional(),
|
|
566
598
|
sketch: questionSketchSchema.optional()
|
|
567
599
|
});
|
|
600
|
+
function questionAnswerProblem(question, answer) {
|
|
601
|
+
const name = question.summary?.trim() ? `Question "${question.summary.trim()}"` : "This question";
|
|
602
|
+
const selected = answer.selected;
|
|
603
|
+
if (question.multiSelect !== true && selected.length > 1) {
|
|
604
|
+
return { field: "selection", message: `${name} takes one choice, but ${selected.length} were selected.` };
|
|
605
|
+
}
|
|
606
|
+
if (new Set(selected).size !== selected.length) {
|
|
607
|
+
return { field: "selection", message: `${name} lists the same choice more than once.` };
|
|
608
|
+
}
|
|
609
|
+
const unknown = selected.find((label) => !question.options.some((option) => option.label === label));
|
|
610
|
+
if (unknown !== void 0) {
|
|
611
|
+
return { field: "selection", message: `${name} does not offer "${unknown}".` };
|
|
612
|
+
}
|
|
613
|
+
if (answer.text?.trim() && question.allowText === false) {
|
|
614
|
+
return { field: "text", message: `${name} does not take a typed answer; choose one of its options.` };
|
|
615
|
+
}
|
|
616
|
+
for (const [label, note] of Object.entries(answer.notes ?? {})) {
|
|
617
|
+
const normalized = typeof note === "string" ? note.trim() : "";
|
|
618
|
+
if (!normalized) continue;
|
|
619
|
+
if (!selected.includes(label)) {
|
|
620
|
+
return { field: "notes", message: `${name}: a note is attached to "${label}", which is not selected.` };
|
|
621
|
+
}
|
|
622
|
+
if (normalized.length > 1e4) {
|
|
623
|
+
return { field: "notes", message: `${name}: the note for "${label}" is longer than 10,000 characters.` };
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
568
628
|
var questionBatchFields = {
|
|
569
629
|
batchId: z.string().min(1).optional(),
|
|
570
630
|
batchIndex: z.number().int().min(1).max(5).optional(),
|
|
@@ -720,7 +780,37 @@ var accountStatusPayload = z.object({
|
|
|
720
780
|
plan: z.string().optional(),
|
|
721
781
|
limits: z.array(accountLimitSchema).optional()
|
|
722
782
|
});
|
|
723
|
-
var
|
|
783
|
+
var runStopCodeSchema = z.enum([
|
|
784
|
+
"completed",
|
|
785
|
+
/** A person stopped the run. */
|
|
786
|
+
"user_abort",
|
|
787
|
+
/** The reply reached its work-slice budget and handed off. */
|
|
788
|
+
"slice_budget",
|
|
789
|
+
/** Three compactions in one reply ended it. */
|
|
790
|
+
"compaction_thrash",
|
|
791
|
+
/** The model request failed (auth, quota, rate limit, network, unsupported model). */
|
|
792
|
+
"provider_error",
|
|
793
|
+
/** The run stopped reporting progress or heartbeats. */
|
|
794
|
+
"stalled",
|
|
795
|
+
/** The owning process went away without settling the run. */
|
|
796
|
+
"owner_gone",
|
|
797
|
+
/** The harness started and found the run unsettled. */
|
|
798
|
+
"harness_restart",
|
|
799
|
+
/** The terminal needs attention: a startup error or a lost decision callback. */
|
|
800
|
+
"terminal_attention",
|
|
801
|
+
/** The terminal closed while the run was live. */
|
|
802
|
+
"terminal_closed",
|
|
803
|
+
/** An ancestor run stopped, so this one did too. */
|
|
804
|
+
"parent_stopped",
|
|
805
|
+
/** Any other failure. */
|
|
806
|
+
"failed"
|
|
807
|
+
]);
|
|
808
|
+
var stopCodeOf = (code) => {
|
|
809
|
+
const parsed = runStopCodeSchema.safeParse(code);
|
|
810
|
+
if (parsed.success) return parsed.data;
|
|
811
|
+
return code.startsWith("provider_") ? "provider_error" : "failed";
|
|
812
|
+
};
|
|
813
|
+
var statusPayload = z.object({ status: runStatusSchema, reason: z.string().optional(), code: runStopCodeSchema.optional() });
|
|
724
814
|
var failedPayload = z.object({ code: z.string(), message: z.string() });
|
|
725
815
|
var emptyPayload = z.object({}).passthrough();
|
|
726
816
|
var runEventPayloadSchemas = {
|
|
@@ -731,6 +821,7 @@ var runEventPayloadSchemas = {
|
|
|
731
821
|
"message.steered": messageSteeredPayload,
|
|
732
822
|
"tool.started": toolPayload,
|
|
733
823
|
"tool.completed": toolPayload,
|
|
824
|
+
"tool.refused": toolRefusedPayload,
|
|
734
825
|
"retry.started": retryStartedPayload,
|
|
735
826
|
"retry.ended": retryEndedPayload,
|
|
736
827
|
"compaction.started": compactionStartedPayload,
|
|
@@ -777,9 +868,11 @@ var runRecordSchema = z.object({
|
|
|
777
868
|
parentRunId: text,
|
|
778
869
|
relation: text,
|
|
779
870
|
terminalReason: text,
|
|
871
|
+
terminalCode: runStopCodeSchema.nullish().transform((value) => value ?? null),
|
|
780
872
|
id: z.string(),
|
|
781
873
|
projectId: text,
|
|
782
874
|
mode: z.string(),
|
|
875
|
+
effectiveMode: z.enum(["plan", "normal"]).optional(),
|
|
783
876
|
harness: z.string(),
|
|
784
877
|
sessionType: z.string(),
|
|
785
878
|
sessionId: z.string(),
|
|
@@ -802,7 +895,9 @@ var runRecordSchema = z.object({
|
|
|
802
895
|
profile: text.optional(),
|
|
803
896
|
title: text.optional(),
|
|
804
897
|
model: text.optional(),
|
|
805
|
-
effort: text.optional()
|
|
898
|
+
effort: text.optional(),
|
|
899
|
+
loadedSkills: z.array(z.string()).optional(),
|
|
900
|
+
activatedPlugins: z.array(z.string()).optional()
|
|
806
901
|
});
|
|
807
902
|
function upgradeRunRecord(raw) {
|
|
808
903
|
if (!raw || typeof raw !== "object") return raw;
|
|
@@ -2434,6 +2529,7 @@ var REQUIRED_SKILLS_POINTER = "Some required skill text was omitted to fit. Load
|
|
|
2434
2529
|
var REFERENCES_BUDGET = 12e3;
|
|
2435
2530
|
var VARIANT_BUDGET = 3e4;
|
|
2436
2531
|
var NOTES_LISTED = 25;
|
|
2532
|
+
var APPROVAL_GUIDANCE = "Approvals: when a call needs a person, the Harness pauses that exact call and shows them Allow and Deny. Never ask for permission in a message, never end your turn to wait for an answer, and never call a task blocked on approval before trying: a denial arrives as the tool result, and you continue from it.";
|
|
2437
2533
|
var SECTION_ORDER = { harnessIdentity: 5, system: 25, sessionState: 30, guardrails: 40, memory: 45, references: 50, requiredSkills: 52, subagents: 64, scripts: 66, wiki: 80, tools: 90, reporting: 95 };
|
|
2438
2534
|
var REPORTING_GUIDANCE = `When you finish work, write the final reply for a person who did not watch you work.
|
|
2439
2535
|
|
|
@@ -2540,7 +2636,7 @@ function toolGroups(context, launch, dataRoot) {
|
|
|
2540
2636
|
const native = NATIVE_GROUP.filter((name) => enabled.includes(name));
|
|
2541
2637
|
const wikiTools = WIKI_GROUP.filter((name) => name !== "wiki_write" || enabled.includes(name));
|
|
2542
2638
|
const readGuidance = `Read-only file tools accept absolute paths and paths beginning with ~ anywhere this process has operating-system read access. The working folder is a default location, not a read boundary.${dataRoot ? ` The Harness data root is ${dataRoot}.` : ""}`;
|
|
2543
|
-
const writeGuidance = "Keep native file writes and Bash filesystem effects inside the active project workspace.
|
|
2639
|
+
const writeGuidance = "Keep native file writes and Bash filesystem effects inside the active project workspace, where they run without asking. An outside file target is allowed too: call the tool and the Harness pauses that one call until the person clicks Allow or Deny, so nothing outside changes first. Remembered tool grants do not answer an outside target; only the explicit Full Permission switch answers it for the run. Bash, including an initial ! command, may read as before but writes are sandboxed to the workspace unless that switch is on. Keep task outputs in the project, following an existing artifacts/ or outputs/ convention when present.";
|
|
2544
2640
|
const nativeGuidance = `Files and shell: ${readGuidance} ${writeGuidance} Edits and commands still pass through the Harness gate.`;
|
|
2545
2641
|
const groups = [
|
|
2546
2642
|
{
|
|
@@ -2589,7 +2685,9 @@ function toolGroups(context, launch, dataRoot) {
|
|
|
2589
2685
|
return present2.map((group, index) => ({ group, index })).sort((left, right) => left.group.order - right.group.order || left.index - right.index).map(({ group }) => group);
|
|
2590
2686
|
}
|
|
2591
2687
|
function tools2(groups, guidance, pointer) {
|
|
2592
|
-
let text11 =
|
|
2688
|
+
let text11 = `${APPROVAL_GUIDANCE}
|
|
2689
|
+
|
|
2690
|
+
`;
|
|
2593
2691
|
for (const group of groups) if (group.guidance) text11 += `${group.guidance}
|
|
2594
2692
|
|
|
2595
2693
|
`;
|
|
@@ -3205,9 +3303,11 @@ function ordered(record3) {
|
|
|
3205
3303
|
parentRunId: record3.parentRunId,
|
|
3206
3304
|
relation: record3.relation,
|
|
3207
3305
|
terminalReason: record3.terminalReason,
|
|
3306
|
+
...record3.terminalCode !== void 0 ? { terminalCode: record3.terminalCode } : {},
|
|
3208
3307
|
id: record3.id,
|
|
3209
3308
|
projectId: record3.projectId,
|
|
3210
3309
|
mode: record3.mode,
|
|
3310
|
+
...record3.effectiveMode !== void 0 ? { effectiveMode: record3.effectiveMode } : {},
|
|
3211
3311
|
harness: record3.harness,
|
|
3212
3312
|
sessionType: record3.sessionType,
|
|
3213
3313
|
sessionId: record3.sessionId,
|
|
@@ -3230,7 +3330,9 @@ function ordered(record3) {
|
|
|
3230
3330
|
...record3.profile ? { profile: record3.profile } : {},
|
|
3231
3331
|
...record3.title ? { title: record3.title } : {},
|
|
3232
3332
|
...record3.model ? { model: record3.model } : {},
|
|
3233
|
-
...record3.effort ? { effort: record3.effort } : {}
|
|
3333
|
+
...record3.effort ? { effort: record3.effort } : {},
|
|
3334
|
+
...record3.loadedSkills !== void 0 ? { loadedSkills: record3.loadedSkills } : {},
|
|
3335
|
+
...record3.activatedPlugins !== void 0 ? { activatedPlugins: record3.activatedPlugins } : {}
|
|
3234
3336
|
};
|
|
3235
3337
|
}
|
|
3236
3338
|
function write2(root, record3) {
|
|
@@ -3287,9 +3389,9 @@ function recordedSubagents(root, parentRunId) {
|
|
|
3287
3389
|
function updateRun(root, runId, update3) {
|
|
3288
3390
|
const record3 = readRecord(recordPath(root, runId));
|
|
3289
3391
|
if (!record3) return;
|
|
3290
|
-
const { status, endedAt, terminalReason } = record3;
|
|
3392
|
+
const { status, endedAt, terminalReason, terminalCode } = record3;
|
|
3291
3393
|
update3(record3);
|
|
3292
|
-
if (!isActiveStatus(status)) Object.assign(record3, { status, endedAt, terminalReason });
|
|
3394
|
+
if (!isActiveStatus(status)) Object.assign(record3, { status, endedAt, terminalReason, terminalCode });
|
|
3293
3395
|
write2(root, record3);
|
|
3294
3396
|
}
|
|
3295
3397
|
function terminalStatus(event) {
|
|
@@ -3304,6 +3406,12 @@ function reasonOf(event) {
|
|
|
3304
3406
|
const reason = payload2?.reason ?? payload2?.message;
|
|
3305
3407
|
return typeof reason === "string" ? reason : null;
|
|
3306
3408
|
}
|
|
3409
|
+
function stopCodeOfEvent(event) {
|
|
3410
|
+
const payload2 = event.payload;
|
|
3411
|
+
if (event.type === "completed") return "completed";
|
|
3412
|
+
if (event.type === "failed") return stopCodeOf(typeof payload2?.code === "string" ? payload2.code : "failed");
|
|
3413
|
+
return typeof payload2?.code === "string" ? stopCodeOf(payload2.code) : null;
|
|
3414
|
+
}
|
|
3307
3415
|
function appendEvent(root, event) {
|
|
3308
3416
|
const directory = runDir(root, event.runId);
|
|
3309
3417
|
mkdirSync5(directory, { recursive: true });
|
|
@@ -3347,6 +3455,7 @@ function persistEvent(root, event) {
|
|
|
3347
3455
|
record3.status = outcome;
|
|
3348
3456
|
record3.endedAt = event.timestamp;
|
|
3349
3457
|
record3.terminalReason = reasonOf(event);
|
|
3458
|
+
record3.terminalCode = stopCodeOfEvent(event);
|
|
3350
3459
|
changed2 = true;
|
|
3351
3460
|
} else if (event.type === "status.changed") {
|
|
3352
3461
|
const status = event.payload.status;
|
|
@@ -3373,10 +3482,11 @@ function markInterruptedRuns(root, reason = STOPPED) {
|
|
|
3373
3482
|
if (settled3) {
|
|
3374
3483
|
const status = terminalStatus(settled3);
|
|
3375
3484
|
const settledReason = reasonOf(settled3);
|
|
3376
|
-
|
|
3485
|
+
const settledCode = stopCodeOfEvent(settled3);
|
|
3486
|
+
if (record3.status !== status || record3.endedAt !== settled3.timestamp || record3.terminalReason !== settledReason || settledCode !== null && record3.terminalCode !== settledCode) {
|
|
3377
3487
|
const backup2 = `${path4}.before-outcome-repair`;
|
|
3378
3488
|
if (!existsSync9(backup2)) copyFileSync2(path4, backup2);
|
|
3379
|
-
Object.assign(record3, { status, endedAt: settled3.timestamp, terminalReason: settledReason });
|
|
3489
|
+
Object.assign(record3, { status, endedAt: settled3.timestamp, terminalReason: settledReason, ...settledCode ? { terminalCode: settledCode } : {} });
|
|
3380
3490
|
write2(root, record3);
|
|
3381
3491
|
}
|
|
3382
3492
|
}
|
|
@@ -3384,9 +3494,10 @@ function markInterruptedRuns(root, reason = STOPPED) {
|
|
|
3384
3494
|
if (ownedElsewhere(root, record3.id)) continue;
|
|
3385
3495
|
record3.status = "interrupted";
|
|
3386
3496
|
record3.terminalReason = reason;
|
|
3497
|
+
record3.terminalCode = "harness_restart";
|
|
3387
3498
|
record3.endedAt ??= now();
|
|
3388
3499
|
const last = events2.reduce((highest, event) => Math.max(highest, event.sequence), -1);
|
|
3389
|
-
appendEvent(root, { id: newId(), runId: record3.id, sessionId: record3.sessionId, sequence: last + 1, timestamp: now(), type: "status.changed", payload: { status: "interrupted", reason } });
|
|
3500
|
+
appendEvent(root, { id: newId(), runId: record3.id, sessionId: record3.sessionId, sequence: last + 1, timestamp: now(), type: "status.changed", payload: { status: "interrupted", reason, code: "harness_restart" } });
|
|
3390
3501
|
write2(root, record3);
|
|
3391
3502
|
interrupted.push(record3.id);
|
|
3392
3503
|
}
|
|
@@ -3408,9 +3519,10 @@ function reconcileStaleRuns(root, at = Date.now(), options2 = {}) {
|
|
|
3408
3519
|
const reason = lastHeartbeat ? "stalled" : "owner_gone";
|
|
3409
3520
|
record3.status = "interrupted";
|
|
3410
3521
|
record3.terminalReason = reason;
|
|
3522
|
+
record3.terminalCode = reason;
|
|
3411
3523
|
record3.endedAt = now();
|
|
3412
3524
|
const sequence = events2.reduce((highest, event) => Math.max(highest, event.sequence), -1) + 1;
|
|
3413
|
-
appendEvent(root, { id: newId(), runId: record3.id, sessionId: record3.sessionId, sequence, timestamp: now(), type: "status.changed", payload: { status: "interrupted", reason } });
|
|
3525
|
+
appendEvent(root, { id: newId(), runId: record3.id, sessionId: record3.sessionId, sequence, timestamp: now(), type: "status.changed", payload: { status: "interrupted", reason, code: reason } });
|
|
3414
3526
|
write2(root, record3);
|
|
3415
3527
|
stalled.push(record3.id);
|
|
3416
3528
|
}
|
|
@@ -3648,10 +3760,10 @@ function until(promise, signal) {
|
|
|
3648
3760
|
});
|
|
3649
3761
|
return Promise.reject(new HarnessError("run_not_active", "The parent run stopped."));
|
|
3650
3762
|
}
|
|
3651
|
-
return new Promise((
|
|
3763
|
+
return new Promise((resolve23, reject2) => {
|
|
3652
3764
|
const abort = () => reject2(new HarnessError("run_not_active", "The parent run stopped."));
|
|
3653
3765
|
signal.addEventListener("abort", abort, { once: true });
|
|
3654
|
-
promise.then(
|
|
3766
|
+
promise.then(resolve23, reject2).finally(() => signal.removeEventListener("abort", abort));
|
|
3655
3767
|
});
|
|
3656
3768
|
}
|
|
3657
3769
|
var Subagents = class {
|
|
@@ -3692,8 +3804,8 @@ var Subagents = class {
|
|
|
3692
3804
|
contributions: capabilities.contributions.filter((id2) => frozen.contributions.includes(id2))
|
|
3693
3805
|
};
|
|
3694
3806
|
let started;
|
|
3695
|
-
const ready = new Promise((
|
|
3696
|
-
started =
|
|
3807
|
+
const ready = new Promise((resolve23) => {
|
|
3808
|
+
started = resolve23;
|
|
3697
3809
|
});
|
|
3698
3810
|
if (!deferUntilPrompt) started();
|
|
3699
3811
|
this.parents.set(input.run.id, { ready, started, input: structuredClone({ ...input, text: "", images: void 0, requiresImageInput: false, preamble: null, resume: null }), accountId, capabilities: ceiling, stopped: new AbortController(), recovered: /* @__PURE__ */ new Set(), launching: /* @__PURE__ */ new Map() });
|
|
@@ -4113,7 +4225,7 @@ It stopped before finishing. ${continueHint(result.runId)}` : ""}`;
|
|
|
4113
4225
|
|
|
4114
4226
|
// src/harness.ts
|
|
4115
4227
|
import { mkdirSync as mkdirSync26 } from "node:fs";
|
|
4116
|
-
import { join as
|
|
4228
|
+
import { join as join70 } from "node:path";
|
|
4117
4229
|
|
|
4118
4230
|
// src/accounts/accounts.ts
|
|
4119
4231
|
import { chmodSync as chmodSync3, existsSync as existsSync10, mkdirSync as mkdirSync9, readdirSync as readdirSync11, rmSync as rmSync7 } from "node:fs";
|
|
@@ -4480,7 +4592,7 @@ async function withCredentialLock(dir2, key, work2, options2 = {}) {
|
|
|
4480
4592
|
const blocked = participants().some((entry2) => entry2.id !== id2 && (entry2.ticket === null || entry2.ticket < ticket || entry2.ticket === ticket && entry2.id < id2));
|
|
4481
4593
|
if (!blocked) return await work2();
|
|
4482
4594
|
if (performance.now() - started >= timeout) throw new Error("Timed out waiting for credential modification; no credentials were changed.");
|
|
4483
|
-
await new Promise((
|
|
4595
|
+
await new Promise((resolve23) => setTimeout(resolve23, Math.min(poll, Math.max(1, timeout - (performance.now() - started)))));
|
|
4484
4596
|
}
|
|
4485
4597
|
} finally {
|
|
4486
4598
|
rmSync6(own, { recursive: true, force: true });
|
|
@@ -5156,9 +5268,9 @@ var Accounts = class {
|
|
|
5156
5268
|
const interaction = {
|
|
5157
5269
|
signal: controller.signal,
|
|
5158
5270
|
notify: (notification) => event(notification),
|
|
5159
|
-
prompt: (prompt) => new Promise((
|
|
5271
|
+
prompt: (prompt) => new Promise((resolve23, reject2) => {
|
|
5160
5272
|
const promptId = randomUUID4();
|
|
5161
|
-
login.prompts.set(promptId, { resolve:
|
|
5273
|
+
login.prompts.set(promptId, { resolve: resolve23, reject: reject2 });
|
|
5162
5274
|
prompt.signal?.addEventListener("abort", () => {
|
|
5163
5275
|
login.prompts.delete(promptId);
|
|
5164
5276
|
event({ type: "promptClosed", promptId });
|
|
@@ -5667,6 +5779,7 @@ function packageManifest(packageRoot) {
|
|
|
5667
5779
|
version: text3(manifest.version) ?? text3(plugin.version),
|
|
5668
5780
|
extends: (Array.isArray(own.extends) ? own.extends : typeof own.extends === "string" ? [own.extends] : []).filter((id2) => typeof id2 === "string" && !!id2),
|
|
5669
5781
|
commandPrefix: typeof own.commandPrefix === "string" && /^[a-z][a-z0-9-]*$/i.test(own.commandPrefix) && !["plugin", "skill"].includes(own.commandPrefix.toLowerCase()) ? own.commandPrefix.toLowerCase() : null,
|
|
5782
|
+
defaultPrompt: typeof own.defaultPrompt === "string" && /^[a-z][a-z0-9-]*$/.test(own.defaultPrompt) ? own.defaultPrompt : null,
|
|
5670
5783
|
hooks: inside2(own.hooks),
|
|
5671
5784
|
agents: inside2(own.agents) ?? "agents",
|
|
5672
5785
|
connectorTemplates: (Array.isArray(own.connectors) ? own.connectors : []).flatMap((value) => {
|
|
@@ -5725,14 +5838,14 @@ var piResolver = async ({ cwd, agentDir, install: install3, packages }) => {
|
|
|
5725
5838
|
const settings = pi.SettingsManager.create(cwd, agentDir);
|
|
5726
5839
|
const shared = new Set((settings.getGlobalSettings().packages ?? []).map((entry2) => typeof entry2 === "string" ? entry2 : entry2.source));
|
|
5727
5840
|
const manager2 = () => new pi.DefaultPackageManager({ cwd, agentDir, settingsManager: settings, builtinExtensions: [...PI_BUILTINS] });
|
|
5728
|
-
const
|
|
5841
|
+
const resolve23 = (installing) => manager2().resolve(async (source) => installing && shared.has(source) ? "install" : "skip");
|
|
5729
5842
|
let resolved2;
|
|
5730
5843
|
try {
|
|
5731
|
-
resolved2 = install3 ? await withoutInstallScripts(() =>
|
|
5844
|
+
resolved2 = install3 ? await withoutInstallScripts(() => resolve23(true)) : await resolve23(false);
|
|
5732
5845
|
} catch (error) {
|
|
5733
5846
|
if (!install3) throw error;
|
|
5734
5847
|
log("a plugin package could not be installed", error instanceof Error ? error.message : String(error));
|
|
5735
|
-
resolved2 = await
|
|
5848
|
+
resolved2 = await resolve23(false);
|
|
5736
5849
|
}
|
|
5737
5850
|
const found = [];
|
|
5738
5851
|
const add = (paths2) => {
|
|
@@ -7222,7 +7335,7 @@ var TRAJECTORY_TOOLS = [
|
|
|
7222
7335
|
name: "trajectory_search",
|
|
7223
7336
|
call: "trajectory.search",
|
|
7224
7337
|
label: "Search trajectories",
|
|
7225
|
-
description: "List recorded runs across this {product} data root, including active runs. Filter by agent name or id, project name or slug, mode, status, or start time. Results are newest first; use the returned cursor to continue. Then use trajectory_read for captured prompts, context snapshots, model and tool events, decisions, retries, compaction, and usage. Hidden agents excluded by the host stay hidden.",
|
|
7338
|
+
description: "List recorded runs across this {product} data root, including active runs. Filter by agent name or id, project name or slug, mode, status, or start time. Results include terminalReason and terminalCode (null when unavailable) and are newest first; use the returned cursor to continue. Then use trajectory_read for captured prompts, context snapshots, model and tool events, decisions, retries, compaction, and usage. Hidden agents excluded by the host stay hidden.",
|
|
7226
7339
|
parameters: {
|
|
7227
7340
|
type: "object",
|
|
7228
7341
|
properties: {
|
|
@@ -7419,13 +7532,17 @@ function workSliceGuard(options2) {
|
|
|
7419
7532
|
});
|
|
7420
7533
|
pi.on("tool_call", (event) => {
|
|
7421
7534
|
const name = event.toolName;
|
|
7535
|
+
const refuse = (result) => {
|
|
7536
|
+
options2.onRefuse?.(event, result.reason);
|
|
7537
|
+
return result;
|
|
7538
|
+
};
|
|
7422
7539
|
state.steps += 1;
|
|
7423
7540
|
state.lastTool = name;
|
|
7424
7541
|
const canCheckpoint = checkpointAvailable();
|
|
7425
7542
|
const handoff = canCheckpoint && HANDOFF_TOOLS.has(name);
|
|
7426
7543
|
if (state.endedBy) {
|
|
7427
7544
|
const reason = state.checkpointWritten ? "This reply is over. Its checkpoint is recorded; nothing else runs." : "This reply is over. The Harness will record an evidence-derived checkpoint; nothing else runs.";
|
|
7428
|
-
return handoff ? void 0 : { block: true, terminate: true, reason };
|
|
7545
|
+
return handoff ? void 0 : refuse({ block: true, terminate: true, reason });
|
|
7429
7546
|
}
|
|
7430
7547
|
if (state.compactions >= 3) {
|
|
7431
7548
|
if (handoff) {
|
|
@@ -7433,14 +7550,14 @@ function workSliceGuard(options2) {
|
|
|
7433
7550
|
state.endedByLimit = "three compactions";
|
|
7434
7551
|
return void 0;
|
|
7435
7552
|
}
|
|
7436
|
-
return end("compaction-thrash", "three compactions");
|
|
7553
|
+
return refuse(end("compaction-thrash", "three compactions"));
|
|
7437
7554
|
}
|
|
7438
7555
|
const limit2 = sliceLimitReached(policy, state);
|
|
7439
7556
|
if (limit2) {
|
|
7440
7557
|
state.endedBy = "budget";
|
|
7441
7558
|
state.endedByLimit = limit2;
|
|
7442
7559
|
state.refusedAt ??= state.steps;
|
|
7443
|
-
return handoff ? void 0 : { block: true, reason: handoffRequest(limit2, state, canCheckpoint) };
|
|
7560
|
+
return handoff ? void 0 : refuse({ block: true, reason: handoffRequest(limit2, state, canCheckpoint) });
|
|
7444
7561
|
}
|
|
7445
7562
|
});
|
|
7446
7563
|
};
|
|
@@ -7642,14 +7759,14 @@ var safeRequest = async (start, options2) => {
|
|
|
7642
7759
|
}
|
|
7643
7760
|
};
|
|
7644
7761
|
function readOnce(url, options2) {
|
|
7645
|
-
return new Promise((
|
|
7762
|
+
return new Promise((resolve23, reject2) => {
|
|
7646
7763
|
const start = url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
7647
7764
|
let settled3 = false;
|
|
7648
7765
|
const finish = (value) => {
|
|
7649
7766
|
if (settled3) return;
|
|
7650
7767
|
settled3 = true;
|
|
7651
7768
|
clearTimeout(total);
|
|
7652
|
-
|
|
7769
|
+
resolve23(value);
|
|
7653
7770
|
};
|
|
7654
7771
|
const fail3 = (error) => {
|
|
7655
7772
|
if (settled3) return;
|
|
@@ -8105,9 +8222,9 @@ import { isIP as isIP2 } from "node:net";
|
|
|
8105
8222
|
var OPEN_SCHEMES = /* @__PURE__ */ new Set(["data:", "blob:", "about:"]);
|
|
8106
8223
|
var CACHE_MS2 = 6e4;
|
|
8107
8224
|
var NetworkPolicy = class {
|
|
8108
|
-
constructor(config,
|
|
8225
|
+
constructor(config, resolve23 = async (host) => (await lookup(host, { all: true, verbatim: true })).map((entry2) => entry2.address)) {
|
|
8109
8226
|
this.config = config;
|
|
8110
|
-
this.resolve =
|
|
8227
|
+
this.resolve = resolve23;
|
|
8111
8228
|
}
|
|
8112
8229
|
config;
|
|
8113
8230
|
resolve;
|
|
@@ -8364,7 +8481,7 @@ async function askForAction(ask) {
|
|
|
8364
8481
|
rememberable: false,
|
|
8365
8482
|
standing: "none"
|
|
8366
8483
|
});
|
|
8367
|
-
return decision === "approved" ? null : "The user denied this action.
|
|
8484
|
+
return decision === "approved" ? null : "The user denied this action. Do not repeat it or ask for permission in a message; choose another approach or report what is blocked.";
|
|
8368
8485
|
}
|
|
8369
8486
|
|
|
8370
8487
|
// src/browser/contracts.ts
|
|
@@ -12196,6 +12313,8 @@ function summary2(env, run2, projects) {
|
|
|
12196
12313
|
project: project ? { id: project.id, name: project.name, slug: project.slug } : null,
|
|
12197
12314
|
mode: run2.mode,
|
|
12198
12315
|
status: run2.status,
|
|
12316
|
+
terminalReason: run2.terminalReason ?? null,
|
|
12317
|
+
terminalCode: run2.terminalCode ?? null,
|
|
12199
12318
|
goal: goal.length > 500 ? `${goal.slice(0, 497)}\u2026` : goal || null,
|
|
12200
12319
|
startedAt: run2.startedAt,
|
|
12201
12320
|
endedAt: run2.endedAt,
|
|
@@ -12485,12 +12604,19 @@ ${tail}` : ""}`;
|
|
|
12485
12604
|
return `Proposed ${proposal.name} (${proposal.id}). A person can review it in the plugin manager; it is not installed or enabled.`;
|
|
12486
12605
|
}
|
|
12487
12606
|
case "skill.load":
|
|
12488
|
-
return loadSkill(options2.variantDir ?? null, str(params, "name"))
|
|
12607
|
+
return loadSkill(options2.variantDir ?? null, str(params, "name"), (name) => {
|
|
12608
|
+
if (!context.runId) return;
|
|
12609
|
+
const run2 = findRun(env.root, context.runId);
|
|
12610
|
+
if (!run2 || !isActiveStatus(run2.status) || !run2.loadedSkills) return;
|
|
12611
|
+
updateRun(env.root, context.runId, (record3) => {
|
|
12612
|
+
if (record3.loadedSkills && !record3.loadedSkills.includes(name)) record3.loadedSkills.push(name);
|
|
12613
|
+
});
|
|
12614
|
+
});
|
|
12489
12615
|
default:
|
|
12490
12616
|
throw new HarnessError("unknown_method", `Unknown harness call ${call}.`);
|
|
12491
12617
|
}
|
|
12492
12618
|
}
|
|
12493
|
-
function loadSkill(variantDir, name) {
|
|
12619
|
+
function loadSkill(variantDir, name, onLoad) {
|
|
12494
12620
|
const skills = variantDir && existsSync17(join40(variantDir, "skills")) ? readdirSync20(join40(variantDir, "skills"), { withFileTypes: true }).filter((entry2) => entry2.isDirectory() && existsSync17(join40(variantDir, "skills", entry2.name, "SKILL.md"))).map((entry2) => entry2.name).sort() : [];
|
|
12495
12621
|
if (!skills.includes("plugin-authoring") && defaultSkillPaths(variantDir).length) skills.push("plugin-authoring");
|
|
12496
12622
|
skills.sort();
|
|
@@ -12503,7 +12629,9 @@ function loadSkill(variantDir, name) {
|
|
|
12503
12629
|
const helperNote = skill.helpers.length ? `
|
|
12504
12630
|
|
|
12505
12631
|
(This skill's helpers are in ${join40(skill.baseDir, "scripts")}: ${skill.helpers.join(", ")}.)` : "";
|
|
12506
|
-
|
|
12632
|
+
const text11 = readFileSync20(skill.path, "utf8");
|
|
12633
|
+
onLoad?.(wanted);
|
|
12634
|
+
return `${text11}${helperNote}`;
|
|
12507
12635
|
}
|
|
12508
12636
|
|
|
12509
12637
|
// src/tools/harnessTools.ts
|
|
@@ -14182,8 +14310,8 @@ var cancelActive = null;
|
|
|
14182
14310
|
function listenForCallback(expectedState, clientName) {
|
|
14183
14311
|
return new Promise((ready, failed) => {
|
|
14184
14312
|
let settle;
|
|
14185
|
-
const result = new Promise((
|
|
14186
|
-
settle = { resolve:
|
|
14313
|
+
const result = new Promise((resolve23, reject2) => {
|
|
14314
|
+
settle = { resolve: resolve23, reject: reject2 };
|
|
14187
14315
|
});
|
|
14188
14316
|
result.catch(() => void 0);
|
|
14189
14317
|
const server = createServer((request2, response) => {
|
|
@@ -14207,7 +14335,7 @@ function listenForCallback(expectedState, clientName) {
|
|
|
14207
14335
|
clearTimeout(timer);
|
|
14208
14336
|
failed(new HarnessError("invalid_request", error.code === "EADDRINUSE" ? `Port ${MCP_OAUTH_PORT} is in use. Finish or cancel the other sign-in, or quit the program using that port, then try again.` : "The sign-in listener could not start."));
|
|
14209
14337
|
});
|
|
14210
|
-
const closed = new Promise((
|
|
14338
|
+
const closed = new Promise((resolve23) => server.once("close", resolve23));
|
|
14211
14339
|
const close = () => {
|
|
14212
14340
|
clearTimeout(timer);
|
|
14213
14341
|
if (cancelActive === cancel) cancelActive = null;
|
|
@@ -14887,6 +15015,9 @@ ${clip4(payload2.text, options2.obsTrunc)}`);
|
|
|
14887
15015
|
${clip4(text11, options2.obsTrunc)}${payload2.outputTruncated ? "\n(the tool output was already bounded when it was recorded)" : ""}`);
|
|
14888
15016
|
break;
|
|
14889
15017
|
}
|
|
15018
|
+
case "tool.refused":
|
|
15019
|
+
out.push(`[ERROR] ${String(payload2.name ?? "tool")} refused${typeof payload2.code === "string" ? ` (${payload2.code})` : ""}${typeof payload2.reason === "string" && payload2.reason ? `: ${clip4(payload2.reason, options2.obsTrunc)}` : ""}`);
|
|
15020
|
+
break;
|
|
14890
15021
|
case "retry.started":
|
|
14891
15022
|
out.push(`[ERROR] model request failed, retrying (${String(payload2.attempt)}/${String(payload2.maxAttempts)}): ${String(payload2.error ?? "")}`);
|
|
14892
15023
|
break;
|
|
@@ -14903,7 +15034,10 @@ ${clip4(text11, options2.obsTrunc)}${payload2.outputTruncated ? "\n(the tool out
|
|
|
14903
15034
|
out.push(`[ERROR] run failed: ${String(payload2.message ?? payload2.code ?? "")}`);
|
|
14904
15035
|
break;
|
|
14905
15036
|
case "status.changed":
|
|
14906
|
-
if (payload2.status === "interrupted")
|
|
15037
|
+
if (payload2.status === "interrupted") {
|
|
15038
|
+
const reason = typeof payload2.reason === "string" && payload2.reason ? payload2.reason : typeof payload2.code === "string" ? payload2.code : "";
|
|
15039
|
+
out.push(`[ERROR] run interrupted${reason ? `: ${clip4(reason, options2.obsTrunc)}` : ""}`);
|
|
15040
|
+
}
|
|
14907
15041
|
break;
|
|
14908
15042
|
default:
|
|
14909
15043
|
break;
|
|
@@ -14931,7 +15065,7 @@ var PROBE_TRIGGER = "probe";
|
|
|
14931
15065
|
var IMPROVER_TRIGGER = "improver";
|
|
14932
15066
|
var IMPROVER_IDENTITY = "You improve the harness an autonomous agent runs with, from evidence of how it ran. Work only inside the current folder: the evidence is under `context/`. Follow the mission in the user message exactly.";
|
|
14933
15067
|
function settled(runtime, runId, signal, timeoutMs = 60 * 6e4) {
|
|
14934
|
-
return new Promise((
|
|
15068
|
+
return new Promise((resolve23) => {
|
|
14935
15069
|
let done = false;
|
|
14936
15070
|
const finish = (status) => {
|
|
14937
15071
|
if (done) return;
|
|
@@ -14939,7 +15073,7 @@ function settled(runtime, runId, signal, timeoutMs = 60 * 6e4) {
|
|
|
14939
15073
|
stop();
|
|
14940
15074
|
clearTimeout(timer);
|
|
14941
15075
|
signal?.removeEventListener("abort", abort);
|
|
14942
|
-
|
|
15076
|
+
resolve23(status);
|
|
14943
15077
|
};
|
|
14944
15078
|
const stop = runtime.runs.listen((event) => {
|
|
14945
15079
|
if (event.runId !== runId) return;
|
|
@@ -15924,8 +16058,8 @@ function shellPath() {
|
|
|
15924
16058
|
if (!shell) throw new Error("The workspace write-boundary sandbox requires Bash, but no Bash executable was found.");
|
|
15925
16059
|
return shell;
|
|
15926
16060
|
}
|
|
15927
|
-
function requireShell(
|
|
15928
|
-
const shell =
|
|
16061
|
+
function requireShell(resolve23) {
|
|
16062
|
+
const shell = resolve23();
|
|
15929
16063
|
if (!shell) {
|
|
15930
16064
|
throw new Error(process.platform === "win32" ? "No shell was found: neither Git Bash nor PowerShell is available. Bash was not run." : "No Bash executable was found. Bash was not run.");
|
|
15931
16065
|
}
|
|
@@ -16064,7 +16198,7 @@ function killProcess(child) {
|
|
|
16064
16198
|
}
|
|
16065
16199
|
function runProcess(spec, cwd, options2, marker) {
|
|
16066
16200
|
if (options2.signal?.aborted) return Promise.reject(new Error("aborted"));
|
|
16067
|
-
return new Promise((
|
|
16201
|
+
return new Promise((resolve23, reject2) => {
|
|
16068
16202
|
const child = spawn2(spec.executable, spec.args, {
|
|
16069
16203
|
cwd,
|
|
16070
16204
|
detached: process.platform !== "win32",
|
|
@@ -16107,7 +16241,7 @@ function runProcess(spec, cwd, options2, marker) {
|
|
|
16107
16241
|
if (!started && pending2) options2.onData(Buffer.from(pending2));
|
|
16108
16242
|
if (options2.signal?.aborted) reject2(new Error("aborted"));
|
|
16109
16243
|
else if (timedOut) reject2(new Error(`timeout:${options2.timeout}`));
|
|
16110
|
-
else
|
|
16244
|
+
else resolve23({ exitCode: code });
|
|
16111
16245
|
};
|
|
16112
16246
|
const onAbort = () => killProcess(child);
|
|
16113
16247
|
if (options2.timeout !== void 0 && options2.timeout > 0) {
|
|
@@ -16220,6 +16354,28 @@ function createWorkspaceBashOperations(workspaceRoot, options2 = {}) {
|
|
|
16220
16354
|
};
|
|
16221
16355
|
}
|
|
16222
16356
|
|
|
16357
|
+
// src/context/runDetails.ts
|
|
16358
|
+
function runDetails(root, runId) {
|
|
16359
|
+
const run2 = loadRun2(root, runId);
|
|
16360
|
+
const capabilities = loadRun(root, runId);
|
|
16361
|
+
const snapshot2 = readEvents(root, runId).filter((event) => event.type === "context.snapshot").at(-1);
|
|
16362
|
+
const payload2 = snapshot2?.payload;
|
|
16363
|
+
return {
|
|
16364
|
+
runId: run2.id,
|
|
16365
|
+
status: run2.status ?? null,
|
|
16366
|
+
mode: run2.effectiveMode ?? null,
|
|
16367
|
+
model: run2.model ?? null,
|
|
16368
|
+
effort: run2.effort ?? null,
|
|
16369
|
+
variantScope: run2.variantScope ?? null,
|
|
16370
|
+
variantVersion: run2.variantVersion ?? null,
|
|
16371
|
+
workspaceRoot: run2.workspaceRoot ?? null,
|
|
16372
|
+
tools: payload2 && !payload2.toolsTruncated && Array.isArray(payload2.tools) ? payload2.tools.map((tool) => tool.name) : null,
|
|
16373
|
+
connectors: capabilities?.connectors ?? null,
|
|
16374
|
+
skills: run2.loadedSkills ?? null,
|
|
16375
|
+
plugins: run2.activatedPlugins ?? null
|
|
16376
|
+
};
|
|
16377
|
+
}
|
|
16378
|
+
|
|
16223
16379
|
// src/terminal/terminals.ts
|
|
16224
16380
|
import { chmodSync as chmodSync11, existsSync as existsSync26, lstatSync as lstatSync13, mkdirSync as mkdirSync22, readFileSync as readFileSync30, unlinkSync as unlinkSync5, writeFileSync as writeFileSync11 } from "node:fs";
|
|
16225
16381
|
import { basename as basename12, dirname as dirname20, join as join59 } from "node:path";
|
|
@@ -16644,7 +16800,7 @@ function request(connection, body2, what, timeoutMs, signal) {
|
|
|
16644
16800
|
var StaleConnection = class extends Error {
|
|
16645
16801
|
};
|
|
16646
16802
|
function attempt(connection, body2, what, timeoutMs, reused, signal) {
|
|
16647
|
-
return new Promise((
|
|
16803
|
+
return new Promise((resolve23, reject2) => {
|
|
16648
16804
|
const stream = reused ?? createConnection(connection.socket);
|
|
16649
16805
|
let buffer = "";
|
|
16650
16806
|
let replied = false;
|
|
@@ -16662,7 +16818,7 @@ function attempt(connection, body2, what, timeoutMs, reused, signal) {
|
|
|
16662
16818
|
if (reply2.error) {
|
|
16663
16819
|
const error = typeof reply2.error === "string" ? { message: reply2.error } : reply2.error;
|
|
16664
16820
|
finish(() => reject2(new SocketError(error.code ?? "error", error.message ?? `${what} failed.`)), !rest);
|
|
16665
|
-
} else finish(() =>
|
|
16821
|
+
} else finish(() => resolve23(reply2.result), !rest);
|
|
16666
16822
|
} catch {
|
|
16667
16823
|
finish(() => reject2(new SocketError("invalid_reply", "The harness sent an unreadable reply.")));
|
|
16668
16824
|
}
|
|
@@ -17029,11 +17185,11 @@ var TerminalController = class {
|
|
|
17029
17185
|
}
|
|
17030
17186
|
async waitPending(revision) {
|
|
17031
17187
|
if (revision === this.pendingRevision && !this.disposed) {
|
|
17032
|
-
await new Promise((
|
|
17188
|
+
await new Promise((resolve23) => {
|
|
17033
17189
|
const wake = () => {
|
|
17034
17190
|
clearTimeout(timer);
|
|
17035
17191
|
this.pendingWaiters.delete(wake);
|
|
17036
|
-
|
|
17192
|
+
resolve23();
|
|
17037
17193
|
};
|
|
17038
17194
|
const timer = setTimeout(wake, 2e4);
|
|
17039
17195
|
this.pendingWaiters.add(wake);
|
|
@@ -17076,9 +17232,9 @@ var TerminalController = class {
|
|
|
17076
17232
|
this.delivery = null;
|
|
17077
17233
|
return run2;
|
|
17078
17234
|
}
|
|
17079
|
-
finish(outcome, message4, reason) {
|
|
17235
|
+
finish(outcome, message4, reason, code) {
|
|
17080
17236
|
this.stopStallWatch();
|
|
17081
|
-
this.run?.finish(outcome, message4 ? { code: "terminal_attention", message: message4 } : void 0, reason);
|
|
17237
|
+
this.run?.finish(outcome, message4 ? { code: code === "provider_error" ? "provider_error" : "terminal_attention", message: message4 } : void 0, reason, code);
|
|
17082
17238
|
this.run = null;
|
|
17083
17239
|
this.seen.clear();
|
|
17084
17240
|
if (this.pending.size) {
|
|
@@ -17118,15 +17274,15 @@ var TerminalController = class {
|
|
|
17118
17274
|
if (this.disposed) throw new HarnessError("terminal_not_ready", "Reopen the terminal before sending instructions.");
|
|
17119
17275
|
if (this.activeRun || this.delivery) throw new HarnessError("session_busy", "This terminal has an active task or delivery.");
|
|
17120
17276
|
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
17121
|
-
while (!this.ready && !this.startupError && !this.disposed && Date.now() < deadline) await new Promise((
|
|
17277
|
+
while (!this.ready && !this.startupError && !this.disposed && Date.now() < deadline) await new Promise((resolve23) => setTimeout(resolve23, 50));
|
|
17122
17278
|
if (this.disposed) throw new HarnessError("terminal_not_ready", "The terminal stopped during initialization.");
|
|
17123
17279
|
if (this.startupError) throw new HarnessError("terminal_not_ready", this.startupError);
|
|
17124
17280
|
if (!this.ready) throw new HarnessError("terminal_not_ready", `The native telemetry handshake is missing (${this.startupPhase}). Inspect the terminal sign-in or trust screen.`);
|
|
17125
17281
|
if (this.dirty) throw new HarnessError("terminal_has_draft", "The terminal contains user input. Submit or clear it before handing back control.");
|
|
17126
17282
|
let timer;
|
|
17127
|
-
const ack = new Promise((
|
|
17283
|
+
const ack = new Promise((resolve23, reject2) => {
|
|
17128
17284
|
timer = setTimeout(() => reject2(new HarnessError("delivery_uncertain", "The native prompt acknowledgement was not received. Inspect the terminal; this instruction will not be resent.")), ACK_TIMEOUT_MS);
|
|
17129
|
-
this.delivery = { runId, text: text11, resolve:
|
|
17285
|
+
this.delivery = { runId, text: text11, resolve: resolve23, reject: reject2 };
|
|
17130
17286
|
});
|
|
17131
17287
|
void ack.catch(() => {
|
|
17132
17288
|
});
|
|
@@ -17346,14 +17502,15 @@ var TerminalController = class {
|
|
|
17346
17502
|
}
|
|
17347
17503
|
if (event === "shutdown") {
|
|
17348
17504
|
this.ready = false;
|
|
17349
|
-
this.finish("interrupted");
|
|
17505
|
+
this.finish("interrupted", void 0, void 0, "terminal_closed");
|
|
17350
17506
|
return {};
|
|
17351
17507
|
}
|
|
17352
17508
|
if (event === "settled") {
|
|
17353
17509
|
const stalled = this.stallRequested !== null && this.stallRequested === this.run?.runId;
|
|
17354
|
-
const reason = stalled ? "stalled" : typeof data.reason === "string" ? data.reason : void 0;
|
|
17510
|
+
const reason = data.error ? void 0 : stalled ? "stalled" : typeof data.reason === "string" ? data.reason : void 0;
|
|
17355
17511
|
const outcome = data.error ? "failed" : data.aborted || reason ? "interrupted" : "completed";
|
|
17356
|
-
|
|
17512
|
+
const code = data.error ? data.errorCode === "provider_error" ? "provider_error" : void 0 : stalled ? "stalled" : data.code === "slice_budget" || data.code === "compaction_thrash" ? data.code : data.aborted ? "user_abort" : void 0;
|
|
17513
|
+
this.finish(outcome, data.error ? String(data.error) : void 0, reason, code);
|
|
17357
17514
|
return {};
|
|
17358
17515
|
}
|
|
17359
17516
|
const run2 = this.run;
|
|
@@ -17392,6 +17549,8 @@ var TerminalController = class {
|
|
|
17392
17549
|
}
|
|
17393
17550
|
}
|
|
17394
17551
|
if (event === "usage") run2.emit("usage.updated", data);
|
|
17552
|
+
else if (event === "refusal") run2.emit("tool.refused", data);
|
|
17553
|
+
else if (event === "compaction.started" || event === "compaction.ended") run2.emit(event, data);
|
|
17395
17554
|
else if (event === "heartbeat") run2.emit("heartbeat", data);
|
|
17396
17555
|
else if (event === "todo" || event === "checklist") run2.emit("todo.updated", { items: data.items });
|
|
17397
17556
|
else if (event === "checkpoint") run2.emit("checkpoint", data);
|
|
@@ -17416,7 +17575,7 @@ var TerminalController = class {
|
|
|
17416
17575
|
this.ready = false;
|
|
17417
17576
|
this.delivery?.reject(new Error("The terminal stopped before prompt acknowledgement."));
|
|
17418
17577
|
this.delivery = null;
|
|
17419
|
-
this.finish("interrupted");
|
|
17578
|
+
this.finish("interrupted", void 0, void 0, "terminal_closed");
|
|
17420
17579
|
this.subagents.clear();
|
|
17421
17580
|
this.subagentParentRunId = null;
|
|
17422
17581
|
}
|
|
@@ -17651,7 +17810,7 @@ var SessionSocket = class {
|
|
|
17651
17810
|
});
|
|
17652
17811
|
}
|
|
17653
17812
|
bind(path4) {
|
|
17654
|
-
return new Promise((
|
|
17813
|
+
return new Promise((resolve23, reject2) => {
|
|
17655
17814
|
const server = createServer2((client) => this.serve(client));
|
|
17656
17815
|
server.once("error", reject2);
|
|
17657
17816
|
server.listen(path4, () => {
|
|
@@ -17663,7 +17822,7 @@ var SessionSocket = class {
|
|
|
17663
17822
|
}
|
|
17664
17823
|
}
|
|
17665
17824
|
this.server = server;
|
|
17666
|
-
|
|
17825
|
+
resolve23();
|
|
17667
17826
|
});
|
|
17668
17827
|
});
|
|
17669
17828
|
}
|
|
@@ -17699,7 +17858,7 @@ var SessionSocket = class {
|
|
|
17699
17858
|
this.server = null;
|
|
17700
17859
|
this.listening = null;
|
|
17701
17860
|
for (const client of this.clients) client.destroy();
|
|
17702
|
-
if (server) await new Promise((
|
|
17861
|
+
if (server) await new Promise((resolve23) => server.close(() => resolve23()));
|
|
17703
17862
|
if (!isPipePath(this.path)) rmSync22(this.path, { force: true });
|
|
17704
17863
|
}
|
|
17705
17864
|
};
|
|
@@ -17751,7 +17910,7 @@ function summary3(run2) {
|
|
|
17751
17910
|
}
|
|
17752
17911
|
function listTerminalTrajectoryRuns(root, sessionId, nativeSessionId) {
|
|
17753
17912
|
if (!nativeSessionId) return [];
|
|
17754
|
-
return listRuns(root, { sessionId }).filter((run2) => run2.nativeSessionId === nativeSessionId
|
|
17913
|
+
return listRuns(root, { sessionId }).filter((run2) => run2.nativeSessionId === nativeSessionId).map(summary3);
|
|
17755
17914
|
}
|
|
17756
17915
|
function latestTerminalTodo(root, sessionId, nativeSessionId) {
|
|
17757
17916
|
if (!nativeSessionId) return { runId: "", todo: [] };
|
|
@@ -17761,7 +17920,7 @@ function latestTerminalTodo(root, sessionId, nativeSessionId) {
|
|
|
17761
17920
|
function readTerminalTrajectory(root, sessionId, nativeSessionId, runId, afterSequence) {
|
|
17762
17921
|
if (!nativeSessionId || !Number.isSafeInteger(afterSequence ?? 0) || (afterSequence ?? 0) < 0) throw notFound("Run");
|
|
17763
17922
|
const run2 = findRun(root, runId);
|
|
17764
|
-
if (!run2 || run2.sessionId !== sessionId || run2.nativeSessionId !== nativeSessionId
|
|
17923
|
+
if (!run2 || run2.sessionId !== sessionId || run2.nativeSessionId !== nativeSessionId) throw notFound("Run");
|
|
17765
17924
|
const events2 = readEvents(root, runId).filter((event) => afterSequence === void 0 || event.sequence > afterSequence);
|
|
17766
17925
|
return { run: summary3(run2), events: events2 };
|
|
17767
17926
|
}
|
|
@@ -18202,13 +18361,13 @@ var Pairing = class {
|
|
|
18202
18361
|
if (this.pending) throw new HarnessError("remote_busy", "Another device is waiting for approval in the terminal.");
|
|
18203
18362
|
if (this.devices.size >= MAX_DEVICES) throw new HarnessError("remote_full", `At most ${MAX_DEVICES} devices can control a session. Press n in /remote-control to start over.`);
|
|
18204
18363
|
const label = deviceLabel(body2.userAgent);
|
|
18205
|
-
const allowed3 = await new Promise((
|
|
18364
|
+
const allowed3 = await new Promise((resolve23) => {
|
|
18206
18365
|
const timer = setTimeout(() => decide2(false), confirmMs);
|
|
18207
18366
|
timer.unref?.();
|
|
18208
18367
|
const decide2 = (allow) => {
|
|
18209
18368
|
clearTimeout(timer);
|
|
18210
18369
|
if (this.pending?.decide === decide2) this.pending = null;
|
|
18211
|
-
|
|
18370
|
+
resolve23(allow);
|
|
18212
18371
|
this.changed();
|
|
18213
18372
|
};
|
|
18214
18373
|
this.pending = { id: id(), label, at, decide: decide2 };
|
|
@@ -18357,11 +18516,11 @@ async function listenRemote(host, port, routes) {
|
|
|
18357
18516
|
sockets.add(socket);
|
|
18358
18517
|
socket.on("close", () => sockets.delete(socket));
|
|
18359
18518
|
});
|
|
18360
|
-
await new Promise((
|
|
18519
|
+
await new Promise((resolve23, reject2) => {
|
|
18361
18520
|
server.once("error", reject2);
|
|
18362
18521
|
server.listen({ host, port, exclusive: true }, () => {
|
|
18363
18522
|
server.off("error", reject2);
|
|
18364
|
-
|
|
18523
|
+
resolve23();
|
|
18365
18524
|
});
|
|
18366
18525
|
});
|
|
18367
18526
|
const bound = server.address().port;
|
|
@@ -18371,8 +18530,8 @@ async function listenRemote(host, port, routes) {
|
|
|
18371
18530
|
port: bound,
|
|
18372
18531
|
close: async () => {
|
|
18373
18532
|
for (const stream of [...streams]) stream.end();
|
|
18374
|
-
await new Promise((
|
|
18375
|
-
server.close(() =>
|
|
18533
|
+
await new Promise((resolve23) => {
|
|
18534
|
+
server.close(() => resolve23());
|
|
18376
18535
|
for (const socket of sockets) socket.destroy();
|
|
18377
18536
|
});
|
|
18378
18537
|
}
|
|
@@ -18685,11 +18844,15 @@ var PAGE_JS = String.raw`
|
|
|
18685
18844
|
inputs.push(input)
|
|
18686
18845
|
void index
|
|
18687
18846
|
})
|
|
18688
|
-
var
|
|
18689
|
-
|
|
18847
|
+
var allowsText = request.allowText !== false
|
|
18848
|
+
var other = null
|
|
18849
|
+
if (allowsText) {
|
|
18850
|
+
other = document.createElement('input'); other.type = 'text'; other.placeholder = 'Or type an answer'
|
|
18851
|
+
card.appendChild(other)
|
|
18852
|
+
}
|
|
18690
18853
|
return function () {
|
|
18691
18854
|
var selected = inputs.filter(function (i) { return i.checked }).map(function (i) { return i.value })
|
|
18692
|
-
var typed = other.value.trim()
|
|
18855
|
+
var typed = allowsText && other ? other.value.trim() : ''
|
|
18693
18856
|
if (!selected.length && !typed) throw new Error('Choose an answer first.')
|
|
18694
18857
|
return typed ? { selected: selected, text: typed } : { selected: selected }
|
|
18695
18858
|
}
|
|
@@ -18927,11 +19090,11 @@ var RemoteControl = class {
|
|
|
18927
19090
|
/** Returns at once for a changed revision, otherwise after a change or 20 seconds. */
|
|
18928
19091
|
async waitStatus(revision) {
|
|
18929
19092
|
if (revision === this.revision && !this.ended) {
|
|
18930
|
-
await new Promise((
|
|
19093
|
+
await new Promise((resolve23) => {
|
|
18931
19094
|
const wake = () => {
|
|
18932
19095
|
clearTimeout(timer);
|
|
18933
19096
|
this.waiters.delete(wake);
|
|
18934
|
-
|
|
19097
|
+
resolve23();
|
|
18935
19098
|
};
|
|
18936
19099
|
const timer = setTimeout(wake, 2e4);
|
|
18937
19100
|
timer.unref?.();
|
|
@@ -19065,7 +19228,7 @@ var RemoteControl = class {
|
|
|
19065
19228
|
if (changed2) this.broadcast({ type: "pending", requests: snapshot2.requests.map(projectRequest) });
|
|
19066
19229
|
} catch {
|
|
19067
19230
|
if (loop.signal.aborted) return;
|
|
19068
|
-
await new Promise((
|
|
19231
|
+
await new Promise((resolve23) => setTimeout(resolve23, 1e3).unref?.());
|
|
19069
19232
|
revision = void 0;
|
|
19070
19233
|
}
|
|
19071
19234
|
}
|
|
@@ -19123,6 +19286,7 @@ function resourceIdentity(loading) {
|
|
|
19123
19286
|
builtins: loading.builtins,
|
|
19124
19287
|
skills: loading.skillPaths,
|
|
19125
19288
|
prompts: loading.promptPaths,
|
|
19289
|
+
resourceOwners: loading.found.filter((item) => (item.kind === "prompt" || item.kind === "extension") && item.packageRoot),
|
|
19126
19290
|
themes: loading.themePaths,
|
|
19127
19291
|
profiles: loading.profilePaths,
|
|
19128
19292
|
detached: loading.detached,
|
|
@@ -19304,7 +19468,8 @@ var Terminals = class {
|
|
|
19304
19468
|
ownNames = /* @__PURE__ */ new Map();
|
|
19305
19469
|
async ownTools(live, listing) {
|
|
19306
19470
|
const sessionId = live.caller.sessionId;
|
|
19307
|
-
const
|
|
19471
|
+
const caller = { ...live.caller, runId: live.controller.activeRun?.runId ?? null };
|
|
19472
|
+
const all3 = harnessTools(this.options.env, caller, { variantDir: this.variantDirs.get(sessionId) ?? null }).filter((tool) => DEFAULT_TOOL_NAMES.includes(tool.name));
|
|
19308
19473
|
if (!listing) return all3.filter((tool) => this.ownNames.get(sessionId)?.has(tool.name) ?? false);
|
|
19309
19474
|
const on = await this.worldOn(live);
|
|
19310
19475
|
const owner = (tool) => HARNESS_PLUGINS.find((plugin) => plugin.tools?.some((entry2) => entry2.name === tool))?.id ?? null;
|
|
@@ -19600,10 +19765,12 @@ var Terminals = class {
|
|
|
19600
19765
|
throw new HarnessError("unknown_method", `Unknown call ${call}.`);
|
|
19601
19766
|
}
|
|
19602
19767
|
}
|
|
19603
|
-
/** `/context`:
|
|
19768
|
+
/** `/context`: the selected run's request, and one row's text on request. */
|
|
19604
19769
|
contextCall(live, params) {
|
|
19605
|
-
|
|
19770
|
+
if (params.runId !== void 0 && (typeof params.runId !== "string" || !params.runId)) throw invalid("A valid run id is required.");
|
|
19771
|
+
const runId = typeof params.runId === "string" ? params.runId : live.controller.lastRun;
|
|
19606
19772
|
if (!runId) return { attribution: null, text: null };
|
|
19773
|
+
if (params.runId !== void 0) readTerminalTrajectory(this.options.env.root, live.input.sessionId, live.controller.nativeSessionId, runId);
|
|
19607
19774
|
const text11 = typeof params.section === "string" && params.section ? rowText(this.options.env, runId, params.section) : null;
|
|
19608
19775
|
return { attribution: attribution(this.options.env, runId), text: text11 };
|
|
19609
19776
|
}
|
|
@@ -19786,6 +19953,7 @@ var Terminals = class {
|
|
|
19786
19953
|
startedAt: now(),
|
|
19787
19954
|
endedAt: null,
|
|
19788
19955
|
status: "queued",
|
|
19956
|
+
loadedSkills: [],
|
|
19789
19957
|
exitCode: null,
|
|
19790
19958
|
handoffId: null,
|
|
19791
19959
|
accountId: live.account.id,
|
|
@@ -19951,6 +20119,9 @@ ${formatBashCommandForApproval(command)}`,
|
|
|
19951
20119
|
}, packaged).body
|
|
19952
20120
|
};
|
|
19953
20121
|
freezeRun(this.options.env.root, run2.runId, caller, capabilities);
|
|
20122
|
+
updateRun(this.options.env.root, run2.runId, (record3) => {
|
|
20123
|
+
record3.effectiveMode = params.plan === true ? "plan" : "normal";
|
|
20124
|
+
});
|
|
19954
20125
|
if (!service || live.input.subagents === null) return { runId: run2.runId, connectors: capabilities.connectors, subagentProfiles };
|
|
19955
20126
|
service.register({
|
|
19956
20127
|
run: { id: run2.runId, sessionId: live.input.sessionId, mode: live.input.run?.mode ?? "code", projectId: live.input.projectId, worktreeId: live.input.run?.worktreeId },
|
|
@@ -20027,6 +20198,11 @@ ${formatBashCommandForApproval(command)}`,
|
|
|
20027
20198
|
if (call === "terminal.trajectory.list") {
|
|
20028
20199
|
return { runs: listTerminalTrajectoryRuns(this.options.env.root, live.input.sessionId, live.controller.nativeSessionId) };
|
|
20029
20200
|
}
|
|
20201
|
+
if (call === "terminal.trajectory.details") {
|
|
20202
|
+
if (typeof params.runId !== "string" || !params.runId) throw invalid("A run id is required.");
|
|
20203
|
+
readTerminalTrajectory(this.options.env.root, live.input.sessionId, live.controller.nativeSessionId, params.runId);
|
|
20204
|
+
return runDetails(this.options.env.root, params.runId);
|
|
20205
|
+
}
|
|
20030
20206
|
if (call === "terminal.trajectory.events") {
|
|
20031
20207
|
if (typeof params.runId !== "string" || !params.runId) throw invalid("A run id is required.");
|
|
20032
20208
|
const afterSequence = params.afterSequence;
|
|
@@ -20266,7 +20442,8 @@ ${formatBashCommandForApproval(command)}`,
|
|
|
20266
20442
|
if (resourceIdentity(current2) !== resourceIdentity(loading) || variant && (currentVariant?.fingerprint !== variant.fingerprint || JSON.stringify(currentVariant.extensionPaths) !== JSON.stringify(variant.extensionPaths))) {
|
|
20267
20443
|
throw new HarnessError("terminal_resources_changed", "Trusted resources changed since this terminal was prepared. Review /plugins and reopen the terminal to load the new selection.");
|
|
20268
20444
|
}
|
|
20269
|
-
|
|
20445
|
+
const resourceOwners = (current2?.found ?? []).filter((item) => item.packageRoot && (item.kind === "prompt" && current2?.promptPaths.includes(item.path) || item.kind === "extension" && current2?.extensionPaths.includes(item.path)));
|
|
20446
|
+
return resourceOwners.length ? { resourceOwners } : {};
|
|
20270
20447
|
} }
|
|
20271
20448
|
});
|
|
20272
20449
|
live.token = token;
|
|
@@ -20502,6 +20679,7 @@ async function builtinChecks(harness, context, secrets, assets = defaultAssets()
|
|
|
20502
20679
|
}
|
|
20503
20680
|
}
|
|
20504
20681
|
},
|
|
20682
|
+
check2("computer.capture", "tools", "Application window capture", async () => context.tools.includes("computer_screenshot") ? { status: "unverified", evidence: "A computer screenshot tool is listed for this session; its backend and window isolation were not tested.", remedy: "Check the provider and approve an application window before capture." } : { status: "blocked", evidence: "No computer screenshot tool is listed for this session. An application window cannot be read through this capability.", remedy: "Use a host with an approved-window capture backend." }),
|
|
20505
20683
|
check2("runtime.pi", "runtime", "Pi runtime", async () => {
|
|
20506
20684
|
await loadPi();
|
|
20507
20685
|
return pass(`Pi ${PINNED_PI_VERSION} loaded with its required dispatcher.`);
|
|
@@ -20702,7 +20880,7 @@ async function diagnosticProbe(harness, context, inputSignal, action) {
|
|
|
20702
20880
|
const run2 = harness.runs.start(id2, id2);
|
|
20703
20881
|
run2.onInterrupt(() => interrupt.abort());
|
|
20704
20882
|
const abort = () => {
|
|
20705
|
-
run2.finish("interrupted");
|
|
20883
|
+
run2.finish("interrupted", void 0, void 0, "user_abort");
|
|
20706
20884
|
void harness.world.release(id2);
|
|
20707
20885
|
};
|
|
20708
20886
|
signal.addEventListener("abort", abort, { once: true });
|
|
@@ -20716,10 +20894,13 @@ async function diagnosticProbe(harness, context, inputSignal, action) {
|
|
|
20716
20894
|
call: async (name, input) => {
|
|
20717
20895
|
signal.throwIfAborted();
|
|
20718
20896
|
if (run2.finished || !capabilities.tools.includes(name)) throw new HarnessError("doctor_blocked", "This tool is outside the diagnostic capability set.");
|
|
20719
|
-
const refusal = await gate.check(name, input);
|
|
20720
|
-
if (refusal) throw new HarnessError("doctor_blocked", "The tool policy refused this diagnostic call.");
|
|
20721
|
-
signal.throwIfAborted();
|
|
20722
20897
|
const callId = randomUUID15();
|
|
20898
|
+
const refusal = await gate.checkDetailed?.(name, input);
|
|
20899
|
+
if (refusal) {
|
|
20900
|
+
run2.emit("tool.refused", { toolCallId: callId, name, code: refusal.code, reason: "The tool policy refused this diagnostic call." });
|
|
20901
|
+
throw new HarnessError("doctor_blocked", "The tool policy refused this diagnostic call.");
|
|
20902
|
+
}
|
|
20903
|
+
signal.throwIfAborted();
|
|
20723
20904
|
run2.emit("tool.started", { toolCallId: callId, name, summary: "Synthetic diagnostic input" });
|
|
20724
20905
|
try {
|
|
20725
20906
|
let result2;
|
|
@@ -20746,11 +20927,11 @@ async function diagnosticProbe(harness, context, inputSignal, action) {
|
|
|
20746
20927
|
});
|
|
20747
20928
|
return result;
|
|
20748
20929
|
} catch (error) {
|
|
20749
|
-
run2.finish(signal.aborted ? "interrupted" : "failed", { code: "doctor_failed", message: "The diagnostic probe failed." });
|
|
20930
|
+
run2.finish(signal.aborted ? "interrupted" : "failed", { code: "doctor_failed", message: "The diagnostic probe failed." }, void 0, signal.aborted ? "user_abort" : void 0);
|
|
20750
20931
|
throw error;
|
|
20751
20932
|
} finally {
|
|
20752
20933
|
signal.removeEventListener("abort", abort);
|
|
20753
|
-
run2.finish(signal.aborted ? "interrupted" : "completed");
|
|
20934
|
+
run2.finish(signal.aborted ? "interrupted" : "completed", void 0, void 0, signal.aborted ? "user_abort" : void 0);
|
|
20754
20935
|
harness.apps.revokeRun(id2, id2);
|
|
20755
20936
|
await harness.world.release(id2);
|
|
20756
20937
|
}
|
|
@@ -21054,7 +21235,7 @@ var RunRegistry = class {
|
|
|
21054
21235
|
if (finished || finishing || this.nativeCleared.delete(questionId)) return Promise.resolve({ selected: [] });
|
|
21055
21236
|
validateQuestionRecommendations(input);
|
|
21056
21237
|
if (this.questions.has(questionId)) throw new HarnessError("invalid_request", "This question id is already pending.");
|
|
21057
|
-
return new Promise((
|
|
21238
|
+
return new Promise((resolve23) => {
|
|
21058
21239
|
this.questions.set(questionId, {
|
|
21059
21240
|
runId,
|
|
21060
21241
|
input,
|
|
@@ -21063,7 +21244,7 @@ var RunRegistry = class {
|
|
|
21063
21244
|
this.questions.delete(questionId);
|
|
21064
21245
|
emit("question.answered", { questionId, ...answer, ...batch ?? {} });
|
|
21065
21246
|
emit("status.changed", { status: waitingStatus() });
|
|
21066
|
-
|
|
21247
|
+
resolve23(answer);
|
|
21067
21248
|
}
|
|
21068
21249
|
});
|
|
21069
21250
|
emit("question.requested", { questionId, ...payload2, ...batch ?? {} });
|
|
@@ -21106,7 +21287,7 @@ var RunRegistry = class {
|
|
|
21106
21287
|
}
|
|
21107
21288
|
emit("approval.requested", { approvalId, ...payload2 });
|
|
21108
21289
|
emit("status.changed", { status: "waiting_for_approval" });
|
|
21109
|
-
return new Promise((
|
|
21290
|
+
return new Promise((resolve23) => {
|
|
21110
21291
|
const timer = timeoutMs ? setTimeout(() => this.approvals.get(approvalId)?.resolve("denied"), timeoutMs) : null;
|
|
21111
21292
|
this.approvals.set(approvalId, {
|
|
21112
21293
|
runId,
|
|
@@ -21115,7 +21296,7 @@ var RunRegistry = class {
|
|
|
21115
21296
|
this.approvals.delete(approvalId);
|
|
21116
21297
|
emit("approval.resolved", { approvalId, decision });
|
|
21117
21298
|
emit("status.changed", { status: waitingStatus() });
|
|
21118
|
-
|
|
21299
|
+
resolve23(decision);
|
|
21119
21300
|
}
|
|
21120
21301
|
});
|
|
21121
21302
|
});
|
|
@@ -21136,17 +21317,17 @@ var RunRegistry = class {
|
|
|
21136
21317
|
emit("status.changed", { status: "waiting_for_input" });
|
|
21137
21318
|
return Promise.all(answers);
|
|
21138
21319
|
},
|
|
21139
|
-
finish: (outcome, failure3, reason) => {
|
|
21320
|
+
finish: (outcome, failure3, reason, code) => {
|
|
21140
21321
|
if (finished || finishing) return;
|
|
21141
21322
|
finishing = true;
|
|
21142
21323
|
for (const [, approval] of [...this.approvals]) if (approval.runId === runId) approval.resolve("denied");
|
|
21143
21324
|
for (const [, question] of [...this.questions]) if (question.runId === runId) question.resolve({ selected: [] });
|
|
21144
21325
|
if (outcome === "failed") emit("failed", failure3 ?? { code: "failed", message: "The run failed." });
|
|
21145
|
-
else if (outcome === "interrupted") emit("status.changed", { status: "interrupted", ...reason ? { reason } : {} });
|
|
21326
|
+
else if (outcome === "interrupted") emit("status.changed", { status: "interrupted", ...reason ? { reason } : {}, ...code ? { code } : {} });
|
|
21146
21327
|
else emit("completed", {});
|
|
21147
21328
|
finished = true;
|
|
21148
21329
|
this.runs.delete(runId);
|
|
21149
|
-
for (const
|
|
21330
|
+
for (const resolve23 of [...this.waiters.get(runId) ?? []]) resolve23();
|
|
21150
21331
|
this.waiters.delete(runId);
|
|
21151
21332
|
for (const [id2, owner] of this.nativeCleared) if (owner === runId) this.nativeCleared.delete(id2);
|
|
21152
21333
|
try {
|
|
@@ -21159,7 +21340,7 @@ var RunRegistry = class {
|
|
|
21159
21340
|
return finished;
|
|
21160
21341
|
},
|
|
21161
21342
|
interrupt: async () => {
|
|
21162
|
-
run2.finish("interrupted");
|
|
21343
|
+
run2.finish("interrupted", void 0, void 0, "user_abort");
|
|
21163
21344
|
for (const handler of interruptHandlers) {
|
|
21164
21345
|
try {
|
|
21165
21346
|
await handler();
|
|
@@ -21173,7 +21354,7 @@ var RunRegistry = class {
|
|
|
21173
21354
|
emit("started", {});
|
|
21174
21355
|
emit("status.changed", { status: "running" });
|
|
21175
21356
|
const record3 = findRun(this.options.root(), runId);
|
|
21176
|
-
if (record3?.parentRunId && this.hasStoppedAncestor(record3.parentRunId)) run2.finish("interrupted");
|
|
21357
|
+
if (record3?.parentRunId && this.hasStoppedAncestor(record3.parentRunId)) run2.finish("interrupted", void 0, void 0, "parent_stopped");
|
|
21177
21358
|
return run2;
|
|
21178
21359
|
}
|
|
21179
21360
|
hasStoppedAncestor(runId) {
|
|
@@ -21196,7 +21377,7 @@ var RunRegistry = class {
|
|
|
21196
21377
|
if (!isActiveStatus(record3.status)) return Promise.resolve();
|
|
21197
21378
|
if (!this.runs.has(runId)) throw new HarnessError("run_not_active", "This run is not active in this process.");
|
|
21198
21379
|
if (signal?.aborted) return Promise.reject(new HarnessError("run_not_active", "The wait was interrupted."));
|
|
21199
|
-
return new Promise((
|
|
21380
|
+
return new Promise((resolve23, reject2) => {
|
|
21200
21381
|
const cleanup = () => {
|
|
21201
21382
|
signal?.removeEventListener("abort", abort);
|
|
21202
21383
|
const pending3 = this.waiters.get(runId);
|
|
@@ -21206,7 +21387,7 @@ var RunRegistry = class {
|
|
|
21206
21387
|
};
|
|
21207
21388
|
const done = () => {
|
|
21208
21389
|
cleanup();
|
|
21209
|
-
|
|
21390
|
+
resolve23();
|
|
21210
21391
|
};
|
|
21211
21392
|
const abort = () => {
|
|
21212
21393
|
cleanup();
|
|
@@ -21248,7 +21429,7 @@ var RunRegistry = class {
|
|
|
21248
21429
|
}
|
|
21249
21430
|
/** Interrupts every live run, giving each a moment to stop cleanly. */
|
|
21250
21431
|
async interruptAll(timeoutMs = 3e3) {
|
|
21251
|
-
await Promise.race([Promise.allSettled(this.activeRunIds().map((id2) => this.interrupt(id2))), new Promise((
|
|
21432
|
+
await Promise.race([Promise.allSettled(this.activeRunIds().map((id2) => this.interrupt(id2))), new Promise((resolve23) => setTimeout(resolve23, timeoutMs))]);
|
|
21252
21433
|
}
|
|
21253
21434
|
resolveApproval(runId, approvalId, decision) {
|
|
21254
21435
|
if (decision !== "approved" && decision !== "denied" && decision !== "native") throw new HarnessError("invalid_request", "Unknown decision.");
|
|
@@ -21277,15 +21458,22 @@ var RunRegistry = class {
|
|
|
21277
21458
|
}
|
|
21278
21459
|
validateAnswer(input, answer) {
|
|
21279
21460
|
const selected = answer.selected;
|
|
21280
|
-
const selectedLabels = new Set(selected);
|
|
21281
21461
|
const notes = {};
|
|
21282
21462
|
for (const [label, note] of Object.entries(answer.notes ?? {})) {
|
|
21283
21463
|
const normalized = typeof note === "string" ? note.trim() : "";
|
|
21284
21464
|
if (normalized) notes[label] = normalized;
|
|
21285
21465
|
}
|
|
21286
21466
|
const text11 = answer.text?.trim();
|
|
21287
|
-
|
|
21288
|
-
|
|
21467
|
+
const problem = questionAnswerProblem(input, { selected, ...text11 ? { text: text11 } : {}, notes });
|
|
21468
|
+
if (problem) {
|
|
21469
|
+
log("a question answer was refused", {
|
|
21470
|
+
summary: input.summary ?? null,
|
|
21471
|
+
field: problem.field,
|
|
21472
|
+
selected: selected.length,
|
|
21473
|
+
notes: Object.keys(notes).length,
|
|
21474
|
+
textLength: text11?.length ?? 0
|
|
21475
|
+
});
|
|
21476
|
+
throw new HarnessError("invalid_answer", problem.message);
|
|
21289
21477
|
}
|
|
21290
21478
|
return { selected, ...text11 ? { text: text11 } : {}, ...Object.keys(notes).length ? { notes } : {} };
|
|
21291
21479
|
}
|
|
@@ -21306,7 +21494,7 @@ var RunRegistry = class {
|
|
|
21306
21494
|
// src/sessions/sessions.ts
|
|
21307
21495
|
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
21308
21496
|
import { existsSync as existsSync28, mkdirSync as mkdirSync23 } from "node:fs";
|
|
21309
|
-
import { isAbsolute as isAbsolute9, join as
|
|
21497
|
+
import { isAbsolute as isAbsolute9, join as join64 } from "node:path";
|
|
21310
21498
|
init_errors();
|
|
21311
21499
|
init_fs();
|
|
21312
21500
|
|
|
@@ -21372,7 +21560,7 @@ ${text11}`);
|
|
|
21372
21560
|
async function waitForChildren(root, runId, signal) {
|
|
21373
21561
|
while (!signal?.aborted) {
|
|
21374
21562
|
if (settled2(root, runId)) return true;
|
|
21375
|
-
await new Promise((
|
|
21563
|
+
await new Promise((resolve23) => setTimeout(resolve23, 250));
|
|
21376
21564
|
}
|
|
21377
21565
|
return false;
|
|
21378
21566
|
}
|
|
@@ -21921,11 +22109,11 @@ var MAX_TEXT2 = 4e3;
|
|
|
21921
22109
|
var clip5 = (text11) => text11.length > MAX_TEXT2 ? `${text11.slice(0, MAX_TEXT2)}\u2026` : text11;
|
|
21922
22110
|
function dismissable(answer, fallback, options2) {
|
|
21923
22111
|
if (!options2?.signal && !options2?.timeout) return answer;
|
|
21924
|
-
return new Promise((
|
|
22112
|
+
return new Promise((resolve23) => {
|
|
21925
22113
|
let timer;
|
|
21926
22114
|
const done = (value) => {
|
|
21927
22115
|
if (timer) clearTimeout(timer);
|
|
21928
|
-
|
|
22116
|
+
resolve23(value);
|
|
21929
22117
|
};
|
|
21930
22118
|
if (options2.signal?.aborted) return done(fallback);
|
|
21931
22119
|
options2.signal?.addEventListener("abort", () => done(fallback), { once: true });
|
|
@@ -22006,6 +22194,217 @@ function parseInitialBashCommand(input) {
|
|
|
22006
22194
|
return outside ? { command: input.slice(outside[0].length).trim(), workspaceWriteScope: "outside" } : { command: input, workspaceWriteScope: "workspace" };
|
|
22007
22195
|
}
|
|
22008
22196
|
|
|
22197
|
+
// src/commands/promptLoader.ts
|
|
22198
|
+
import { readdirSync as readdirSync29, statSync as statSync22 } from "node:fs";
|
|
22199
|
+
import { dirname as dirname23, join as join63, resolve as resolve12 } from "node:path";
|
|
22200
|
+
|
|
22201
|
+
// src/commands/promptRoutes.ts
|
|
22202
|
+
import { resolve as resolve11, sep as sep10 } from "node:path";
|
|
22203
|
+
|
|
22204
|
+
// src/plugins/commandPrefix.ts
|
|
22205
|
+
import { basename as basename14, dirname as dirname22, extname as extname3 } from "node:path";
|
|
22206
|
+
var slug3 = (value) => {
|
|
22207
|
+
const normalized = value.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
22208
|
+
const prefix = /^[a-z]/.test(normalized) ? normalized : `plugin-${normalized || "command"}`;
|
|
22209
|
+
return prefix === "plugin" || prefix === "skill" ? `extension-${prefix}` : prefix;
|
|
22210
|
+
};
|
|
22211
|
+
function pluginCommandPrefix(source) {
|
|
22212
|
+
if (source.origin === "package" && source.baseDir) {
|
|
22213
|
+
const manifest = packageManifest(source.baseDir);
|
|
22214
|
+
return manifest.commandPrefix ?? slug3(manifest.name);
|
|
22215
|
+
}
|
|
22216
|
+
if (source.path.startsWith("builtin:")) return slug3(source.path.slice("builtin:".length));
|
|
22217
|
+
const synthetic = /^<[^:>]+:([^>]+)>$/.exec(source.path);
|
|
22218
|
+
if (synthetic) return slug3(synthetic[1]);
|
|
22219
|
+
const file2 = basename14(source.path, extname3(source.path));
|
|
22220
|
+
return slug3(file2 === "index" ? basename14(dirname22(source.path)) : file2);
|
|
22221
|
+
}
|
|
22222
|
+
|
|
22223
|
+
// src/commands/routes.ts
|
|
22224
|
+
function commandRoutes(commands) {
|
|
22225
|
+
const routes = /* @__PURE__ */ new Map();
|
|
22226
|
+
const byName = /* @__PURE__ */ new Map();
|
|
22227
|
+
const ownersByPrefix = /* @__PURE__ */ new Map();
|
|
22228
|
+
const builtinPrefixes = new Map(BUILTIN_PLUGIN_REGISTRY.commands().map((command) => [command.prefix, command.pluginId]));
|
|
22229
|
+
for (const command of commands) {
|
|
22230
|
+
const owners = ownersByPrefix.get(command.prefix) ?? /* @__PURE__ */ new Map();
|
|
22231
|
+
const paths2 = owners.get(command.owner) ?? /* @__PURE__ */ new Set();
|
|
22232
|
+
paths2.add(command.path);
|
|
22233
|
+
owners.set(command.owner, paths2);
|
|
22234
|
+
ownersByPrefix.set(command.prefix, owners);
|
|
22235
|
+
const route = `${command.prefix}:${command.name}`;
|
|
22236
|
+
routes.set(route, [...routes.get(route) ?? [], command]);
|
|
22237
|
+
byName.set(command.name, [...byName.get(command.name) ?? [], command]);
|
|
22238
|
+
}
|
|
22239
|
+
const conflictingPrefixes = /* @__PURE__ */ new Map();
|
|
22240
|
+
for (const [prefix, owners] of ownersByPrefix) {
|
|
22241
|
+
const builtin = builtinPrefixes.get(prefix);
|
|
22242
|
+
if (owners.size > 1 || builtin || prefix === "plugin" || prefix === "skill") {
|
|
22243
|
+
conflictingPrefixes.set(prefix, [
|
|
22244
|
+
...builtin ? [`built-in plugin ${builtin}`] : [],
|
|
22245
|
+
...[...owners.values()].flatMap((paths2) => [...paths2])
|
|
22246
|
+
]);
|
|
22247
|
+
}
|
|
22248
|
+
}
|
|
22249
|
+
return { routes, byName, ownersByPrefix, conflictingPrefixes };
|
|
22250
|
+
}
|
|
22251
|
+
function promptCommandAvailable(name, taken, extensionCommands = /* @__PURE__ */ new Set()) {
|
|
22252
|
+
return !name.includes(":") && !taken.has(name) && !extensionCommands.has(name);
|
|
22253
|
+
}
|
|
22254
|
+
|
|
22255
|
+
// src/commands/promptRoutes.ts
|
|
22256
|
+
function promptOwners(path4, found) {
|
|
22257
|
+
const target = resolve11(path4);
|
|
22258
|
+
return found.filter((item) => item.kind === "prompt" && item.packageRoot && (target === resolve11(item.path) || target.startsWith(`${resolve11(item.path)}${sep10}`))).sort((a, b) => b.path.length - a.path.length);
|
|
22259
|
+
}
|
|
22260
|
+
function promptOwner(path4, found) {
|
|
22261
|
+
return promptOwners(path4, found)[0];
|
|
22262
|
+
}
|
|
22263
|
+
function promptPackageRoot(prompt, found) {
|
|
22264
|
+
return promptOwner(prompt.filePath, found)?.packageRoot ?? (prompt.sourceInfo.origin === "package" ? prompt.sourceInfo.baseDir ?? null : null);
|
|
22265
|
+
}
|
|
22266
|
+
function namespacePrompts(prompts, found, extensions = []) {
|
|
22267
|
+
const loose = [];
|
|
22268
|
+
const routes = [];
|
|
22269
|
+
for (const prompt of prompts) {
|
|
22270
|
+
const owners = new Set(promptOwners(prompt.filePath, found).map((owner) => owner.packageRoot));
|
|
22271
|
+
if (owners.size > 1) continue;
|
|
22272
|
+
const packageRoot = promptPackageRoot(prompt, found);
|
|
22273
|
+
if (!packageRoot) {
|
|
22274
|
+
if (!prompt.name.includes(":")) loose.push(prompt);
|
|
22275
|
+
continue;
|
|
22276
|
+
}
|
|
22277
|
+
const attribution2 = promptOwner(prompt.filePath, found)?.commandOwner;
|
|
22278
|
+
const prefix = attribution2?.prefix ?? pluginCommandPrefix({ path: prompt.filePath, origin: "package", baseDir: packageRoot });
|
|
22279
|
+
const name = !attribution2 && packageManifest(packageRoot).defaultPrompt === prompt.name ? "" : prompt.name;
|
|
22280
|
+
if (name && !/^[a-z][a-z0-9-]*$/.test(name)) continue;
|
|
22281
|
+
routes.push({ prefix, name, owner: attribution2?.owner ?? `package:${packageRoot}`, path: prompt.filePath, prompt });
|
|
22282
|
+
}
|
|
22283
|
+
const index = commandRoutes([...extensions, ...routes]);
|
|
22284
|
+
const selected = routes.flatMap((route) => {
|
|
22285
|
+
const key = `${route.prefix}:${route.name}`;
|
|
22286
|
+
return !index.conflictingPrefixes.has(route.prefix) && index.routes.get(key)?.length === 1 ? [{ ...route.prompt, name: key }] : [];
|
|
22287
|
+
});
|
|
22288
|
+
return [...loose, ...selected];
|
|
22289
|
+
}
|
|
22290
|
+
|
|
22291
|
+
// src/commands/promptLoader.ts
|
|
22292
|
+
var originalGetters = /* @__PURE__ */ new WeakMap();
|
|
22293
|
+
async function wrapPromptLoader(pi, loader, found, paths2, cwd, agentDir, extensions = () => [], bare = /* @__PURE__ */ new Set()) {
|
|
22294
|
+
const scan = async (paths3) => {
|
|
22295
|
+
const files3 = paths3.flatMap((path4) => {
|
|
22296
|
+
try {
|
|
22297
|
+
return statSync22(path4).isDirectory() ? readdirSync29(path4).filter((name) => name.endsWith(".md") && !name.startsWith(".")).sort().map((name) => join63(path4, name)) : [path4];
|
|
22298
|
+
} catch {
|
|
22299
|
+
return [];
|
|
22300
|
+
}
|
|
22301
|
+
});
|
|
22302
|
+
const templates2 = [];
|
|
22303
|
+
for (const path4 of [...new Set(files3)]) {
|
|
22304
|
+
const scanner = new pi.DefaultResourceLoader({ cwd, agentDir, settingsManager: pi.SettingsManager.inMemory({}), noExtensions: true, noSkills: true, noPromptTemplates: true, noThemes: true, noContextFiles: true, additionalPromptTemplatePaths: [path4] });
|
|
22305
|
+
await scanner.reload();
|
|
22306
|
+
templates2.push(...scanner.getPrompts().prompts);
|
|
22307
|
+
}
|
|
22308
|
+
return templates2;
|
|
22309
|
+
};
|
|
22310
|
+
const templates = await scan(paths2);
|
|
22311
|
+
const discovered = /* @__PURE__ */ new Map();
|
|
22312
|
+
for (const extension of loader.getExtensions().extensions) {
|
|
22313
|
+
if (extension.hidden) continue;
|
|
22314
|
+
const handlers = extension.handlers.get("resources_discover");
|
|
22315
|
+
if (!handlers) continue;
|
|
22316
|
+
const source = extension.sourceInfo;
|
|
22317
|
+
extension.handlers.set("resources_discover", handlers.map((handler, index) => async (event, context) => {
|
|
22318
|
+
const result = await handler(event, context);
|
|
22319
|
+
const key = `${extension.resolvedPath}:${index}`;
|
|
22320
|
+
if (!result) discovered.delete(key);
|
|
22321
|
+
else {
|
|
22322
|
+
const paths3 = (result.promptPaths ?? []).map((path4) => resolve12(dirname23(extension.resolvedPath), path4));
|
|
22323
|
+
const isPackage = source.origin === "package" && !!source.baseDir;
|
|
22324
|
+
const root = isPackage ? source.baseDir : extension.resolvedPath;
|
|
22325
|
+
const commandOwner = isPackage ? void 0 : { prefix: pluginCommandPrefix(source), owner: `extension:${source.path}` };
|
|
22326
|
+
const owners = paths3.map((path4) => ({ kind: "prompt", path: path4, origin: isPackage ? "package" : "shared", packageRoot: root, source: source.source, project: source.scope === "project", enabled: true, ...commandOwner ? { commandOwner } : {} }));
|
|
22327
|
+
discovered.set(key, { owners, prompts: await scan(paths3) });
|
|
22328
|
+
}
|
|
22329
|
+
return result;
|
|
22330
|
+
}));
|
|
22331
|
+
}
|
|
22332
|
+
bare.clear();
|
|
22333
|
+
for (const prompt of templates) if (promptPackageRoot(prompt, found)) bare.add(prompt.name);
|
|
22334
|
+
const getPrompts = originalGetters.get(loader) ?? loader.getPrompts.bind(loader);
|
|
22335
|
+
originalGetters.set(loader, getPrompts);
|
|
22336
|
+
loader.getPrompts = () => {
|
|
22337
|
+
const base = getPrompts();
|
|
22338
|
+
const dynamic = [...discovered.values()];
|
|
22339
|
+
const owners = [...found, ...dynamic.flatMap((item) => item.owners)];
|
|
22340
|
+
const byPath = new Map(base.prompts.map((prompt) => [prompt.filePath, prompt]));
|
|
22341
|
+
for (const prompt of [...templates, ...dynamic.flatMap((item) => item.prompts)]) {
|
|
22342
|
+
if (byPath.has(prompt.filePath)) continue;
|
|
22343
|
+
if (promptPackageRoot(prompt, owners) || ![...byPath.values()].some((item) => item.name === prompt.name && !promptPackageRoot(item, owners))) byPath.set(prompt.filePath, prompt);
|
|
22344
|
+
}
|
|
22345
|
+
bare.clear();
|
|
22346
|
+
for (const prompt of byPath.values()) if (promptPackageRoot(prompt, owners)) bare.add(prompt.name);
|
|
22347
|
+
return { ...base, prompts: namespacePrompts([...byPath.values()], owners, extensions()) };
|
|
22348
|
+
};
|
|
22349
|
+
return bare;
|
|
22350
|
+
}
|
|
22351
|
+
|
|
22352
|
+
// src/commands/extensionOwners.ts
|
|
22353
|
+
import { resolve as resolve13, sep as sep11 } from "node:path";
|
|
22354
|
+
function applyExtensionOwners(loader, found) {
|
|
22355
|
+
for (const extension of loader.getExtensions().extensions) {
|
|
22356
|
+
if (extension.hidden) continue;
|
|
22357
|
+
const path4 = resolve13(extension.resolvedPath || extension.path);
|
|
22358
|
+
const owner = found.filter((item) => item.kind === "extension" && item.packageRoot && (path4 === resolve13(item.path) || path4.startsWith(`${resolve13(item.path)}${sep11}`))).sort((a, b) => b.path.length - a.path.length)[0];
|
|
22359
|
+
if (!owner?.packageRoot) continue;
|
|
22360
|
+
const sourceInfo = { ...extension.sourceInfo, origin: "package", baseDir: owner.packageRoot, source: owner.source ?? owner.packageRoot };
|
|
22361
|
+
extension.sourceInfo = sourceInfo;
|
|
22362
|
+
for (const command of extension.commands.values()) command.sourceInfo = sourceInfo;
|
|
22363
|
+
}
|
|
22364
|
+
}
|
|
22365
|
+
|
|
22366
|
+
// src/commands/runtimeRouter.ts
|
|
22367
|
+
init_errors();
|
|
22368
|
+
import { resolve as resolve14, sep as sep12 } from "node:path";
|
|
22369
|
+
function installPluginCommandRouter(session, found, barePrompts) {
|
|
22370
|
+
const registrations = () => session.extensionRunner.getRegisteredCommands().map((command) => {
|
|
22371
|
+
const source = command.sourceInfo;
|
|
22372
|
+
const path4 = resolve14(source.path);
|
|
22373
|
+
const owner = found.filter((item) => item.kind === "extension" && item.packageRoot && (path4 === resolve14(item.path) || path4.startsWith(`${resolve14(item.path)}${sep12}`))).sort((a, b) => b.path.length - a.path.length)[0];
|
|
22374
|
+
const prefix = pluginCommandPrefix(owner?.packageRoot ? { path: source.path, origin: "package", baseDir: owner.packageRoot } : source);
|
|
22375
|
+
return { ...command, prefix, owner: owner?.packageRoot ? `package:${owner.packageRoot}` : source.origin === "package" ? `package:${source.baseDir ?? source.source}` : `extension:${source.path}`, path: source.path };
|
|
22376
|
+
});
|
|
22377
|
+
const originalPrompt = session.prompt.bind(session);
|
|
22378
|
+
session.prompt = async (text11, options2) => {
|
|
22379
|
+
if (options2?.expandPromptTemplates === false) return originalPrompt(text11, options2);
|
|
22380
|
+
const match = /^\/([^\s]+)(?:\s+([\s\S]*))?$/.exec(text11.trim());
|
|
22381
|
+
if (match) {
|
|
22382
|
+
const name = match[1];
|
|
22383
|
+
const index = commandRoutes(registrations());
|
|
22384
|
+
const promptNames = new Set(session.resourceLoader.getPrompts().prompts.map((prompt) => prompt.name));
|
|
22385
|
+
if (name.startsWith("plugin:") || index.byName.has(name) || barePrompts.has(name) && !promptNames.has(name)) {
|
|
22386
|
+
throw new HarnessError("invalid_request", `Plugin command /${name} requires its owner prefix.`);
|
|
22387
|
+
}
|
|
22388
|
+
const separator = name.indexOf(":");
|
|
22389
|
+
const prefix = separator < 0 ? "" : name.slice(0, separator);
|
|
22390
|
+
if (separator > 0 && index.ownersByPrefix.has(prefix)) {
|
|
22391
|
+
if (index.conflictingPrefixes.has(prefix)) throw new HarnessError("invalid_request", `Plugin prefix /${prefix}: is ambiguous.`);
|
|
22392
|
+
const matches = index.routes.get(name) ?? [];
|
|
22393
|
+
if (matches.length > 1 || matches.length === 1 && promptNames.has(name)) throw new HarnessError("invalid_request", `Plugin command /${name} is ambiguous.`);
|
|
22394
|
+
if (matches.length === 1) {
|
|
22395
|
+
const command = session.extensionRunner.getCommand(matches[0].invocationName);
|
|
22396
|
+
if (!command) throw new HarnessError("invalid_request", `Plugin command /${name} is unavailable.`);
|
|
22397
|
+
await command.handler(match[2]?.trim() ?? "", session.extensionRunner.createCommandContext());
|
|
22398
|
+
return;
|
|
22399
|
+
}
|
|
22400
|
+
if (!promptNames.has(name)) throw new HarnessError("invalid_request", `Unknown plugin command /${name}.`);
|
|
22401
|
+
}
|
|
22402
|
+
}
|
|
22403
|
+
await originalPrompt(text11, options2);
|
|
22404
|
+
};
|
|
22405
|
+
return () => [...registrations().map((command) => `${command.prefix}:${command.name}`), ...session.resourceLoader.getPrompts().prompts.map((prompt) => prompt.name)];
|
|
22406
|
+
}
|
|
22407
|
+
|
|
22009
22408
|
// src/sessions/runner.ts
|
|
22010
22409
|
function summarize(input, toolName = "") {
|
|
22011
22410
|
if (input && typeof input === "object") {
|
|
@@ -22141,6 +22540,7 @@ async function runTurn(run2, session, params) {
|
|
|
22141
22540
|
let aborted = false;
|
|
22142
22541
|
const usage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costUsd: 0, requests: 0 };
|
|
22143
22542
|
let usageReported = false;
|
|
22543
|
+
let lastContextTokens;
|
|
22144
22544
|
let reasoningTokens;
|
|
22145
22545
|
let requestStartedAt = null;
|
|
22146
22546
|
let firstOutputAt = null;
|
|
@@ -22266,6 +22666,7 @@ async function runTurn(run2, session, params) {
|
|
|
22266
22666
|
...message4.stopReason ? { stopReason: message4.stopReason } : {}
|
|
22267
22667
|
};
|
|
22268
22668
|
requestStartedAt = null;
|
|
22669
|
+
if (reported2) lastContextTokens = (reported2.input ?? 0) + (reported2.cacheRead ?? 0) + (reported2.cacheWrite ?? 0);
|
|
22269
22670
|
const contextWindow = session.model?.contextWindow;
|
|
22270
22671
|
const { costUsd, ...tokens } = usage;
|
|
22271
22672
|
run2.emit("usage.updated", {
|
|
@@ -22349,16 +22750,18 @@ async function runTurn(run2, session, params) {
|
|
|
22349
22750
|
break;
|
|
22350
22751
|
}
|
|
22351
22752
|
case "compaction_start": {
|
|
22352
|
-
run2.emit("compaction.started", { reason: stringField(event, "reason", "context limit") });
|
|
22753
|
+
run2.emit("compaction.started", { reason: stringField(event, "reason", "context limit"), ...lastContextTokens === void 0 ? {} : { contextTokens: lastContextTokens }, ...session.model?.contextWindow ? { contextWindow: session.model.contextWindow } : {} });
|
|
22353
22754
|
break;
|
|
22354
22755
|
}
|
|
22355
22756
|
case "compaction_end": {
|
|
22356
22757
|
const compaction2 = event;
|
|
22758
|
+
const result = compaction2.result && typeof compaction2.result === "object" ? compaction2.result : null;
|
|
22357
22759
|
const summary4 = evidenceText(compaction2.result);
|
|
22358
22760
|
const boundedSummary = summary4 ? limitedText(summary4) : null;
|
|
22359
22761
|
run2.emit("compaction.ended", {
|
|
22360
22762
|
reason: stringField(compaction2, "reason", "context limit"),
|
|
22361
22763
|
aborted: compaction2.aborted === true,
|
|
22764
|
+
...typeof result?.tokensBefore === "number" && Number.isFinite(result.tokensBefore) ? { tokensBefore: result.tokensBefore } : {},
|
|
22362
22765
|
...typeof compaction2.willRetry === "boolean" ? { willRetry: compaction2.willRetry } : {},
|
|
22363
22766
|
...stringField(compaction2, "errorMessage") ? { error: stringField(compaction2, "errorMessage").slice(0, 1e3) } : {},
|
|
22364
22767
|
...boundedSummary ? { summary: boundedSummary.text, ...boundedSummary.truncated ? { summaryTruncated: true } : {} } : {}
|
|
@@ -22372,7 +22775,7 @@ async function runTurn(run2, session, params) {
|
|
|
22372
22775
|
if (params.command) {
|
|
22373
22776
|
const notice = await runCommand(session, params.command);
|
|
22374
22777
|
if (notice) run2.emit("extension.notice", { kind: "notify", level: "info", text: notice });
|
|
22375
|
-
if (aborted) run2.finish("interrupted");
|
|
22778
|
+
if (aborted) run2.finish("interrupted", void 0, void 0, "user_abort");
|
|
22376
22779
|
else run2.finish("completed");
|
|
22377
22780
|
return;
|
|
22378
22781
|
}
|
|
@@ -22381,8 +22784,7 @@ async function runTurn(run2, session, params) {
|
|
|
22381
22784
|
await session.sendCustomMessage({ customType: CONTEXT_MESSAGE_TYPE, content: params.update.content, display: false, details: { [CONTEXT_SNAPSHOT_KEY]: params.update.snapshot } }, { triggerTurn: false });
|
|
22382
22785
|
}
|
|
22383
22786
|
const command = /^\/(\S+)/.exec(params.text)?.[1];
|
|
22384
|
-
|
|
22385
|
-
if (params.preamble && extensionCommand) {
|
|
22787
|
+
if (params.preamble && command) {
|
|
22386
22788
|
if (!session.sendCustomMessage) throw new HarnessError("provider_error", "The Pi session cannot receive the command context.");
|
|
22387
22789
|
await session.sendCustomMessage({ customType: "xingular-preamble", content: params.preamble, display: false, details: {} }, { triggerTurn: false });
|
|
22388
22790
|
}
|
|
@@ -22409,9 +22811,15 @@ ${result}`;
|
|
|
22409
22811
|
...input.truncated ? { inputTruncated: true } : {}
|
|
22410
22812
|
});
|
|
22411
22813
|
let refusal = !command2 ? "Enter a Bash command after !outside." : !params.initialBashAvailable ? "Bash is not available in this run's capability snapshot." : !session.executeBash ? "This Pi session cannot execute Bash directly." : !params.gate ? "This run has no Bash policy gate." : null;
|
|
22814
|
+
if (refusal && !params.initialBashAvailable && command2) params.onRefusal?.(toolCallId, "bash", { code: "capability", reason: refusal });
|
|
22412
22815
|
if (!refusal) {
|
|
22413
22816
|
try {
|
|
22414
|
-
|
|
22817
|
+
const blocked = params.gate.checkDetailed ? await params.gate.checkDetailed("bash", args) : null;
|
|
22818
|
+
if (!params.gate.checkDetailed) refusal = await params.gate.check("bash", args);
|
|
22819
|
+
if (blocked) {
|
|
22820
|
+
params.onRefusal?.(toolCallId, "bash", blocked);
|
|
22821
|
+
refusal = blocked.reason;
|
|
22822
|
+
}
|
|
22415
22823
|
} catch (error) {
|
|
22416
22824
|
refusal = error instanceof Error ? error.message : "The Bash policy could not be checked.";
|
|
22417
22825
|
}
|
|
@@ -22445,7 +22853,7 @@ ${result}`;
|
|
|
22445
22853
|
await session.sendCustomMessage({ customType: "xingular-initial-bash", content: initialBashInstruction, display: false, details: {} }, { triggerTurn: false });
|
|
22446
22854
|
initialBashInstruction = void 0;
|
|
22447
22855
|
}
|
|
22448
|
-
const prompt = params.preamble && !
|
|
22856
|
+
const prompt = params.preamble && !command ? `${params.preamble}
|
|
22449
22857
|
|
|
22450
22858
|
${params.text}` : params.text;
|
|
22451
22859
|
const images = (params.images ?? []).map((image) => ({ type: "image", mimeType: image.mediaType, data: image.data }));
|
|
@@ -22475,14 +22883,14 @@ ${initialBashInstruction}` : prompt;
|
|
|
22475
22883
|
}
|
|
22476
22884
|
if (slice?.endedBy && !aborted && !lastError) {
|
|
22477
22885
|
if (!slice.checkpointWritten) run2.emit("checkpoint", derivedCheckpoint(slice, ledger));
|
|
22478
|
-
run2.finish("interrupted", void 0, "slice_complete");
|
|
22886
|
+
run2.finish("interrupted", void 0, "slice_complete", slice.endedBy === "budget" ? "slice_budget" : "compaction_thrash");
|
|
22479
22887
|
return;
|
|
22480
22888
|
}
|
|
22481
|
-
if (aborted) run2.finish("interrupted");
|
|
22889
|
+
if (aborted) run2.finish("interrupted", void 0, void 0, "user_abort");
|
|
22482
22890
|
else if (lastError) run2.finish("failed", { code: "provider_error", message: lastError });
|
|
22483
22891
|
else run2.finish("completed");
|
|
22484
22892
|
} catch (error) {
|
|
22485
|
-
if (aborted) run2.finish("interrupted");
|
|
22893
|
+
if (aborted) run2.finish("interrupted", void 0, void 0, "user_abort");
|
|
22486
22894
|
else run2.finish("failed", { code: "provider_error", message: error instanceof Error ? error.message : String(error) });
|
|
22487
22895
|
} finally {
|
|
22488
22896
|
if (heartbeat) clearInterval(heartbeat);
|
|
@@ -22509,6 +22917,7 @@ async function runCommand(session, command) {
|
|
|
22509
22917
|
}
|
|
22510
22918
|
}
|
|
22511
22919
|
}
|
|
22920
|
+
var promptRouting = /* @__PURE__ */ new WeakMap();
|
|
22512
22921
|
async function loadResources(pi, params, gate, automaticSettings) {
|
|
22513
22922
|
const loading = params.automatic ? null : await params.plugins?.loading() ?? null;
|
|
22514
22923
|
const resourceLoader = new pi.DefaultResourceLoader({
|
|
@@ -22530,11 +22939,37 @@ async function loadResources(pi, params, gate, automaticSettings) {
|
|
|
22530
22939
|
});
|
|
22531
22940
|
await resourceLoader.reload();
|
|
22532
22941
|
if (loading) {
|
|
22942
|
+
applyExtensionOwners(resourceLoader, loading.found);
|
|
22943
|
+
const extensions = () => registeredBy(resourceLoader).flatMap((record3) => {
|
|
22944
|
+
const owner = runtimeOwner(loading, record3.path);
|
|
22945
|
+
const prefix = pluginCommandPrefix(owner?.packageRoot ? { path: record3.path, origin: "package", baseDir: owner.packageRoot } : { path: record3.path });
|
|
22946
|
+
return record3.commands.map((name) => ({ prefix, name, owner: owner?.packageRoot ? `package:${owner.packageRoot}` : `extension:${record3.path}`, path: record3.path }));
|
|
22947
|
+
});
|
|
22533
22948
|
const records = registeredBy(resourceLoader).map((record3) => ({ ...record3, hash: registrationHash(loading, record3.path) }));
|
|
22534
22949
|
const publish = () => params.plugins?.loaded(records);
|
|
22535
22950
|
observeResources(pi, resourceLoader, loading, records, publish);
|
|
22951
|
+
const bare = await wrapPromptLoader(pi, resourceLoader, loading.found, loading.promptPaths, params.cwd, params.account.configDir, extensions);
|
|
22952
|
+
promptRouting.set(resourceLoader, { loading, bare });
|
|
22536
22953
|
publish();
|
|
22537
22954
|
}
|
|
22955
|
+
if (loading || params.automatic) {
|
|
22956
|
+
const extensions = resourceLoader.getExtensions().extensions;
|
|
22957
|
+
const ids2 = [];
|
|
22958
|
+
let known = true;
|
|
22959
|
+
for (const extension of extensions) {
|
|
22960
|
+
const path4 = extension.resolvedPath || extension.path;
|
|
22961
|
+
if (extension.hidden) {
|
|
22962
|
+
for (const id2 of [OUTPUT_TRIM, BUDGET_GUARD]) if (path4 === `<inline:${id2}>`) ids2.push(id2);
|
|
22963
|
+
} else if (params.variantExtensions?.some((entry2) => path4 === entry2 || path4.startsWith(`${entry2}/`))) {
|
|
22964
|
+
ids2.push("variant");
|
|
22965
|
+
} else {
|
|
22966
|
+
const owner = loading && runtimeOwner(loading, path4);
|
|
22967
|
+
if (owner) ids2.push(pluginIdOf(owner));
|
|
22968
|
+
else known = false;
|
|
22969
|
+
}
|
|
22970
|
+
}
|
|
22971
|
+
if (known) params.activatedPlugins?.([...new Set(ids2)]);
|
|
22972
|
+
}
|
|
22538
22973
|
return resourceLoader;
|
|
22539
22974
|
}
|
|
22540
22975
|
function registeredBy(loader) {
|
|
@@ -22581,11 +23016,20 @@ var piSessionFactory = async ({ params, allow, gate, tools: tools4, runtime: loa
|
|
|
22581
23016
|
authorized: false
|
|
22582
23017
|
};
|
|
22583
23018
|
} catch (error) {
|
|
22584
|
-
|
|
23019
|
+
const reason = error instanceof Error ? error.message : "The active project workspace could not be verified.";
|
|
23020
|
+
params.onRefusal?.(event.toolCallId, event.toolName, { code: "workspace", reason });
|
|
23021
|
+
return { block: true, reason };
|
|
22585
23022
|
}
|
|
22586
23023
|
}
|
|
22587
|
-
const
|
|
22588
|
-
if (
|
|
23024
|
+
const refusal = gate.checkDetailed ? await gate.checkDetailed(event.toolName, event.input) : null;
|
|
23025
|
+
if (!gate.checkDetailed) {
|
|
23026
|
+
const reason = await gate.check(event.toolName, event.input);
|
|
23027
|
+
if (reason) return { block: true, reason };
|
|
23028
|
+
}
|
|
23029
|
+
if (refusal) {
|
|
23030
|
+
params.onRefusal?.(event.toolCallId, event.toolName, refusal);
|
|
23031
|
+
return { block: true, reason: refusal.reason };
|
|
23032
|
+
}
|
|
22589
23033
|
if (authorization) {
|
|
22590
23034
|
authorization.authorized = true;
|
|
22591
23035
|
writeAuthorizations.set(event.toolCallId, authorization);
|
|
@@ -22638,7 +23082,9 @@ var piSessionFactory = async ({ params, allow, gate, tools: tools4, runtime: loa
|
|
|
22638
23082
|
});
|
|
22639
23083
|
}
|
|
22640
23084
|
const piSession = session;
|
|
22641
|
-
|
|
23085
|
+
const routing = promptRouting.get(resourceLoader);
|
|
23086
|
+
const routes = installPluginCommandRouter(session, routing?.loading.found ?? [], routing?.bare ?? /* @__PURE__ */ new Set());
|
|
23087
|
+
piSession.extensionCommands = routes();
|
|
22642
23088
|
supportsImageInput = piSession.model?.input.includes("image") ?? false;
|
|
22643
23089
|
const streamAgent = piSession.agent;
|
|
22644
23090
|
const originalStream = streamAgent.streamFunction;
|
|
@@ -22736,32 +23182,34 @@ var Sessions = class {
|
|
|
22736
23182
|
}
|
|
22737
23183
|
gate(run2, caller, gated, diagnostic = false, automatic = false) {
|
|
22738
23184
|
const { policy } = this.options;
|
|
22739
|
-
|
|
23185
|
+
const gate = {
|
|
23186
|
+
check: async (name, input) => (await gate.checkDetailed(name, input))?.reason ?? null,
|
|
22740
23187
|
ask: (names2) => {
|
|
22741
23188
|
for (const name of names2) gated.add(name);
|
|
22742
23189
|
},
|
|
22743
|
-
|
|
23190
|
+
checkDetailed: async (tool, args) => {
|
|
22744
23191
|
let record3;
|
|
22745
23192
|
let inspection;
|
|
22746
23193
|
try {
|
|
22747
23194
|
record3 = loadRun2(this.env.root, run2.runId);
|
|
22748
23195
|
inspection = inspectRunWorkspaceWrite(record3, tool, args);
|
|
22749
23196
|
} catch (error) {
|
|
22750
|
-
return error instanceof Error ? error.message : "The active project workspace could not be verified.";
|
|
23197
|
+
return { code: "workspace", reason: error instanceof Error ? error.message : "The active project workspace could not be verified." };
|
|
22751
23198
|
}
|
|
22752
23199
|
const call = { run: record3, caller, tool, args: inspection.input };
|
|
22753
23200
|
const hostScope = await policy.scope?.(call) ?? { allowed: true };
|
|
22754
23201
|
const scope = combineWorkspaceScope(hostScope, inspection);
|
|
22755
|
-
if (!scope.allowed && (scope.automatic || automatic && inspection.outside || !scope.requiresApproval)) return scope.reason ?? "Outside the approved work scope.";
|
|
23202
|
+
if (!scope.allowed && (scope.automatic || automatic && inspection.outside || !scope.requiresApproval)) return { code: inspection.outside ? "workspace" : "scope", reason: scope.reason ?? "Outside the approved work scope." };
|
|
22756
23203
|
try {
|
|
22757
23204
|
await policy.authorize?.(call);
|
|
22758
23205
|
} catch (error) {
|
|
22759
|
-
return error instanceof Error ? error.message : "Tool unavailable in the app settings.";
|
|
23206
|
+
return { code: "policy", reason: error instanceof Error ? error.message : "Tool unavailable in the app settings." };
|
|
22760
23207
|
}
|
|
22761
23208
|
if (scope.allowed && !scope.humanOnly && (scope.automatic || !gated.has(tool))) return null;
|
|
22762
|
-
if (diagnostic) return "This diagnostic call requires a separate per-call approval.";
|
|
22763
|
-
if (call.run.trigger === "probe" || call.run.trigger === "improver") return scope.reason ? `Refused in a refinement run: ${scope.reason}` : "Refused in a refinement run: this call needs a person to approve it.";
|
|
22764
|
-
if (call.run.trigger === "reflection") return scope.reason ? `Refused in a reflection run: ${scope.reason}` : "Refused in a reflection run: this call needs a person to approve it.";
|
|
23209
|
+
if (diagnostic) return { code: "unattended", reason: "This diagnostic call requires a separate per-call approval." };
|
|
23210
|
+
if (call.run.trigger === "probe" || call.run.trigger === "improver") return { code: "unattended", reason: scope.reason ? `Refused in a refinement run: ${scope.reason}` : "Refused in a refinement run: this call needs a person to approve it." };
|
|
23211
|
+
if (call.run.trigger === "reflection") return { code: "unattended", reason: scope.reason ? `Refused in a reflection run: ${scope.reason}` : "Refused in a reflection run: this call needs a person to approve it." };
|
|
23212
|
+
if (automatic) return { code: "unattended", reason: scope.reason ?? "Refused in an unattended run: this call needs a person to approve it." };
|
|
22765
23213
|
const boundaryAction = inspection.outside ? `${inspection.reason} ${summarize(call.args, tool)}` : summarize(call.args);
|
|
22766
23214
|
const decision = await run2.requestApproval({
|
|
22767
23215
|
target: tool,
|
|
@@ -22770,11 +23218,11 @@ var Sessions = class {
|
|
|
22770
23218
|
rememberable: scope.allowed && !scope.humanOnly,
|
|
22771
23219
|
standing: scope.humanOnly ? scope.allowGlobalApproval ? "switch" : "none" : void 0
|
|
22772
23220
|
});
|
|
22773
|
-
if (decision !== "approved") return "The user denied this action.
|
|
23221
|
+
if (decision !== "approved") return { code: "approval_denied", reason: "The user denied this action. Do not repeat it or ask for permission in a message; choose another approach or report what is blocked." };
|
|
22774
23222
|
try {
|
|
22775
23223
|
await policy.authorize?.(call);
|
|
22776
23224
|
} catch (error) {
|
|
22777
|
-
return error instanceof Error ? error.message : "Tool was disabled while waiting.";
|
|
23225
|
+
return { code: "policy", reason: error instanceof Error ? error.message : "Tool was disabled while waiting." };
|
|
22778
23226
|
}
|
|
22779
23227
|
return null;
|
|
22780
23228
|
},
|
|
@@ -22831,6 +23279,7 @@ ${formatBashCommandForApproval(operationArgs.command)}`,
|
|
|
22831
23279
|
}
|
|
22832
23280
|
}
|
|
22833
23281
|
};
|
|
23282
|
+
return gate;
|
|
22834
23283
|
}
|
|
22835
23284
|
/** Compatibility alias for the shared native service. */
|
|
22836
23285
|
spawnSubagent(request2) {
|
|
@@ -22985,6 +23434,7 @@ Attached saved file references:`);
|
|
|
22985
23434
|
id: runId,
|
|
22986
23435
|
projectId: input.run.projectId ?? caller.projectId ?? null,
|
|
22987
23436
|
mode: input.run.mode ?? "chat",
|
|
23437
|
+
effectiveMode: input.plan ? "plan" : "normal",
|
|
22988
23438
|
harness: input.run.harness ?? "pi",
|
|
22989
23439
|
sessionType: input.run.sessionType ?? "structured",
|
|
22990
23440
|
sessionId: input.run.sessionId,
|
|
@@ -22997,6 +23447,7 @@ Attached saved file references:`);
|
|
|
22997
23447
|
startedAt: now(),
|
|
22998
23448
|
endedAt: null,
|
|
22999
23449
|
status: "queued",
|
|
23450
|
+
loadedSkills: [],
|
|
23000
23451
|
exitCode: null,
|
|
23001
23452
|
handoffId: input.run.handoffId ?? null,
|
|
23002
23453
|
accountId: account.id,
|
|
@@ -23010,12 +23461,12 @@ Attached saved file references:`);
|
|
|
23010
23461
|
...input.effort ? { effort: input.effort } : {}
|
|
23011
23462
|
});
|
|
23012
23463
|
try {
|
|
23013
|
-
writeJson(
|
|
23464
|
+
writeJson(join64(runDir(root, runId), "caller.json"), caller);
|
|
23014
23465
|
} catch {
|
|
23015
23466
|
}
|
|
23016
23467
|
const fail3 = (error, code = "provider_unavailable") => {
|
|
23017
23468
|
const message4 = error instanceof Error ? error.message : String(error);
|
|
23018
|
-
updateRun(root, runId, (record3) => Object.assign(record3, { status: "failed", endedAt: now(), terminalReason: message4 }));
|
|
23469
|
+
updateRun(root, runId, (record3) => Object.assign(record3, { status: "failed", endedAt: now(), terminalReason: message4, terminalCode: stopCodeOf(code) }));
|
|
23019
23470
|
log(`session ${runId} did not start: ${message4}`, code);
|
|
23020
23471
|
};
|
|
23021
23472
|
const { plugins } = this.options;
|
|
@@ -23117,20 +23568,19 @@ Attached saved file references:`);
|
|
|
23117
23568
|
for (const tool of tools4) if (tool.planModeSafe) planModeSafety.set(tool.name, tool.planModeSafe);
|
|
23118
23569
|
const gate = (() => {
|
|
23119
23570
|
const baseGate = this.gate(run2, caller, new Set((input.tools.gated ?? []).filter((name) => capabilities.tools.includes(name))), false, input.automatic === true);
|
|
23120
|
-
|
|
23121
|
-
|
|
23122
|
-
|
|
23123
|
-
|
|
23124
|
-
const refusal = input.plan ? planRefusal(name, readOnly, args, planModeSafety.get(name)) : null;
|
|
23125
|
-
return refusal ? Promise.resolve(refusal) : baseGate.check(name, args);
|
|
23126
|
-
}
|
|
23571
|
+
const checkDetailed = (name, args) => {
|
|
23572
|
+
if (input.run.relation === "subagent" && !capabilities.tools.includes(name)) return Promise.resolve({ code: "capability", reason: "This tool is outside the child capability snapshot." });
|
|
23573
|
+
const refusal = input.plan ? planRefusal(name, readOnly, args, planModeSafety.get(name)) : null;
|
|
23574
|
+
return refusal ? Promise.resolve({ code: "plan_mode", reason: refusal }) : baseGate.checkDetailed(name, args);
|
|
23127
23575
|
};
|
|
23576
|
+
return { ...baseGate, checkDetailed, check: async (name, args) => (await checkDetailed(name, args))?.reason ?? null };
|
|
23128
23577
|
})();
|
|
23129
23578
|
const params = {
|
|
23130
23579
|
runId,
|
|
23131
23580
|
...input.initialBashCommand !== void 0 && input.initialBashCommand !== null ? { initialBashCommand: input.initialBashCommand, initialBashAvailable: capabilities.tools.includes("bash") } : {},
|
|
23132
23581
|
...input.initialBashContext !== void 0 && input.initialBashContext !== null ? { initialBashContext: input.initialBashContext } : {},
|
|
23133
23582
|
gate,
|
|
23583
|
+
onRefusal: (toolCallId, name, refusal) => run2.emit("tool.refused", { toolCallId, name, ...refusal, reason: refusal.reason.slice(0, 2e3) }),
|
|
23134
23584
|
restrictTools: input.run.relation === "subagent",
|
|
23135
23585
|
...input.plan ? { plan: true } : {},
|
|
23136
23586
|
...stateEligible && input.plan ? { repairPlan: true } : {},
|
|
@@ -23154,9 +23604,14 @@ Attached saved file references:`);
|
|
|
23154
23604
|
this.options.subagents.extension(runId),
|
|
23155
23605
|
{ name: "image-pruning", factory: imagePruning() },
|
|
23156
23606
|
{ name: COMPACTION_CHECKPOINT_EXTENSION, factory: compactionCheckpoint({ initialTodo: continuation?.todo, planInstructions: checkpointPlanInstructions }) },
|
|
23157
|
-
...sliceEnabled ? [{ name: WORK_SLICE_EXTENSION, factory: workSliceGuard({ policy: slicePolicy, state: sliceState, checkpointAvailable
|
|
23607
|
+
...sliceEnabled ? [{ name: WORK_SLICE_EXTENSION, factory: workSliceGuard({ policy: slicePolicy, state: sliceState, checkpointAvailable, onRefuse: (event, reason) => {
|
|
23608
|
+
if (typeof event.toolCallId === "string") run2.emit("tool.refused", { toolCallId: event.toolCallId, name: event.toolName, code: "work_slice", reason: reason.slice(0, 2e3) });
|
|
23609
|
+
} }) }] : []
|
|
23158
23610
|
],
|
|
23159
23611
|
...plugins && loadPlugins ? { plugins: { loading: loadPlugins, loaded: (extensions) => plugins.loaded(extensions) } } : {},
|
|
23612
|
+
activatedPlugins: (ids2) => updateRun(root, runId, (record3) => {
|
|
23613
|
+
record3.activatedPlugins = ids2;
|
|
23614
|
+
}),
|
|
23160
23615
|
system,
|
|
23161
23616
|
...sliceEnabled ? { slice: { state: sliceState, maxToolCalls: slicePolicy.maxToolCalls } } : {},
|
|
23162
23617
|
// A command sends no request of its own, so the context update waits for the next turn.
|
|
@@ -23218,7 +23673,7 @@ Attached saved file references:`);
|
|
|
23218
23673
|
// src/sessions/sideQuestions.ts
|
|
23219
23674
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
23220
23675
|
import { tmpdir as tmpdir8 } from "node:os";
|
|
23221
|
-
import { join as
|
|
23676
|
+
import { join as join65 } from "node:path";
|
|
23222
23677
|
import { z as z6 } from "zod";
|
|
23223
23678
|
init_errors();
|
|
23224
23679
|
init_fs();
|
|
@@ -23419,7 +23874,7 @@ ${note.body}`;
|
|
|
23419
23874
|
return `${reference.label ?? reference.path}: this selected reference is unavailable.`;
|
|
23420
23875
|
}
|
|
23421
23876
|
});
|
|
23422
|
-
const sessionDir = await mkdtemp(
|
|
23877
|
+
const sessionDir = await mkdtemp(join65(tmpdir8(), "xh-side-question-"));
|
|
23423
23878
|
this.sessionDirs.set(runId, sessionDir);
|
|
23424
23879
|
const caller = { ...input.caller, runId, sessionId };
|
|
23425
23880
|
try {
|
|
@@ -23493,13 +23948,13 @@ init_fs();
|
|
|
23493
23948
|
import { createHash as createHash14, randomBytes as randomBytes3 } from "node:crypto";
|
|
23494
23949
|
import { existsSync as existsSync30, mkdirSync as mkdirSync25, realpathSync as realpathSync7, rmSync as rmSync23 } from "node:fs";
|
|
23495
23950
|
import { tmpdir as tmpdir9 } from "node:os";
|
|
23496
|
-
import { basename as
|
|
23951
|
+
import { basename as basename15, dirname as dirname25, isAbsolute as isAbsolute10, join as join67, resolve as resolve15 } from "node:path";
|
|
23497
23952
|
|
|
23498
23953
|
// src/worktrees/copy.ts
|
|
23499
23954
|
init_errors();
|
|
23500
23955
|
init_fs();
|
|
23501
23956
|
import { copyFileSync as copyFileSync5, existsSync as existsSync29, lstatSync as lstatSync14, mkdirSync as mkdirSync24 } from "node:fs";
|
|
23502
|
-
import { dirname as
|
|
23957
|
+
import { dirname as dirname24, join as join66 } from "node:path";
|
|
23503
23958
|
|
|
23504
23959
|
// src/worktrees/git.ts
|
|
23505
23960
|
init_errors();
|
|
@@ -23507,7 +23962,7 @@ import { spawn as spawn5 } from "node:child_process";
|
|
|
23507
23962
|
var QUIET_FILTERS = ["-c", "filter.lfs.smudge=", "-c", "filter.lfs.process=", "-c", "filter.lfs.required=false", "-c", "core.hooksPath=/dev/null"];
|
|
23508
23963
|
var FILTER_KEYS = /^filter\.(.*)\.(?:clean|smudge|process|required)$/i;
|
|
23509
23964
|
function configuredFilters(cwd, env) {
|
|
23510
|
-
return new Promise((
|
|
23965
|
+
return new Promise((resolve23, reject2) => {
|
|
23511
23966
|
const child = spawn5("git", ["config", "--null", "--name-only", "--get-regexp", "^filter\\..*\\.(clean|smudge|process|required)$"], {
|
|
23512
23967
|
cwd,
|
|
23513
23968
|
env,
|
|
@@ -23530,7 +23985,7 @@ function configuredFilters(cwd, env) {
|
|
|
23530
23985
|
const match = FILTER_KEYS.exec(key);
|
|
23531
23986
|
if (match) filters.add(match[1]);
|
|
23532
23987
|
}
|
|
23533
|
-
|
|
23988
|
+
resolve23([...filters].flatMap((filter) => [
|
|
23534
23989
|
"-c",
|
|
23535
23990
|
`filter.${filter}.clean=`,
|
|
23536
23991
|
"-c",
|
|
@@ -23546,7 +24001,7 @@ function configuredFilters(cwd, env) {
|
|
|
23546
24001
|
async function git(cwd, args, options2 = {}) {
|
|
23547
24002
|
const env = { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_LFS_SKIP_SMUDGE: "1", LC_ALL: "C", ...options2.env };
|
|
23548
24003
|
const filters = await configuredFilters(cwd, env);
|
|
23549
|
-
return new Promise((
|
|
24004
|
+
return new Promise((resolve23, reject2) => {
|
|
23550
24005
|
const input = options2.input;
|
|
23551
24006
|
const hasInput = input !== void 0 && input.length > 0;
|
|
23552
24007
|
const child = spawn5("git", [...QUIET_FILTERS, ...filters, ...args], {
|
|
@@ -23565,7 +24020,7 @@ async function git(cwd, args, options2 = {}) {
|
|
|
23565
24020
|
reject2(new HarnessError("git_failed", `git ${args.join(" ")} failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`}`));
|
|
23566
24021
|
return;
|
|
23567
24022
|
}
|
|
23568
|
-
|
|
24023
|
+
resolve23(result);
|
|
23569
24024
|
});
|
|
23570
24025
|
if (hasInput && child.stdin) {
|
|
23571
24026
|
child.stdin.on("error", (error) => {
|
|
@@ -23598,8 +24053,8 @@ function copyFiles(from, to, paths2) {
|
|
|
23598
24053
|
} catch {
|
|
23599
24054
|
continue;
|
|
23600
24055
|
}
|
|
23601
|
-
const source =
|
|
23602
|
-
const target =
|
|
24056
|
+
const source = join66(from, relative10);
|
|
24057
|
+
const target = join66(to, relative10);
|
|
23603
24058
|
if (existsSync29(target)) {
|
|
23604
24059
|
report.skipped.push({ path: relative10, reason: "exists" });
|
|
23605
24060
|
continue;
|
|
@@ -23626,7 +24081,7 @@ function copyFiles(from, to, paths2) {
|
|
|
23626
24081
|
report.skipped.push({ path: relative10, reason: "over_total" });
|
|
23627
24082
|
continue;
|
|
23628
24083
|
}
|
|
23629
|
-
mkdirSync24(
|
|
24084
|
+
mkdirSync24(dirname24(target), { recursive: true });
|
|
23630
24085
|
copyFileSync5(source, target);
|
|
23631
24086
|
total += stat.size;
|
|
23632
24087
|
report.copied.push(relative10);
|
|
@@ -23652,7 +24107,7 @@ async function ignoredFiles(checkout) {
|
|
|
23652
24107
|
if (pathBytes > IGNORED_PATH_BYTES_CAP) throw ignoredFilesError("the ignored paths exceed the snapshot limit");
|
|
23653
24108
|
let stat;
|
|
23654
24109
|
try {
|
|
23655
|
-
stat = lstatSync14(
|
|
24110
|
+
stat = lstatSync14(join66(checkout, relative10));
|
|
23656
24111
|
} catch {
|
|
23657
24112
|
throw ignoredFilesError(`could not inspect ${relative10}`);
|
|
23658
24113
|
}
|
|
@@ -23667,7 +24122,7 @@ function ignoredFilesError(reason) {
|
|
|
23667
24122
|
return new HarnessError("worktree_has_work", `Ignored files cannot be safely preserved: ${reason}.`, "Move or remove those files, then try again.");
|
|
23668
24123
|
}
|
|
23669
24124
|
async function includedFiles(checkout) {
|
|
23670
|
-
const patterns2 =
|
|
24125
|
+
const patterns2 = join66(checkout, INCLUDE_FILE);
|
|
23671
24126
|
if (!existsSync29(patterns2)) return [];
|
|
23672
24127
|
const ignored = new Set(nulList((await git(checkout, ["ls-files", "--others", "--ignored", "--exclude-standard", "-z"])).stdout));
|
|
23673
24128
|
const matching = nulList((await git(checkout, ["ls-files", "--others", "--ignored", `--exclude-from=${patterns2}`, "-z"])).stdout);
|
|
@@ -23691,10 +24146,10 @@ function real(path4) {
|
|
|
23691
24146
|
try {
|
|
23692
24147
|
return realpathSync7(path4);
|
|
23693
24148
|
} catch {
|
|
23694
|
-
return
|
|
24149
|
+
return resolve15(path4);
|
|
23695
24150
|
}
|
|
23696
24151
|
}
|
|
23697
|
-
function
|
|
24152
|
+
function slug4(text11) {
|
|
23698
24153
|
return text11.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "repo";
|
|
23699
24154
|
}
|
|
23700
24155
|
function statusPaths(output) {
|
|
@@ -23716,11 +24171,11 @@ var Worktrees = class {
|
|
|
23716
24171
|
leaseCounts = /* @__PURE__ */ new Map();
|
|
23717
24172
|
chain = Promise.resolve();
|
|
23718
24173
|
constructor(stateDir, options2 = {}) {
|
|
23719
|
-
this.root = options2.root ??
|
|
24174
|
+
this.root = options2.root ?? join67(stateDir, "worktrees");
|
|
23720
24175
|
this.branchPrefix = options2.branchPrefix ?? "wt/";
|
|
23721
24176
|
this.keep = options2.keep ?? 15;
|
|
23722
|
-
this.registryFile =
|
|
23723
|
-
this.trust = new SetupTrust(
|
|
24177
|
+
this.registryFile = join67(stateDir, "worktrees.json");
|
|
24178
|
+
this.trust = new SetupTrust(join67(stateDir, "worktree-setup-trust.json"));
|
|
23724
24179
|
this.recover(true);
|
|
23725
24180
|
}
|
|
23726
24181
|
// ---- Registry -------------------------------------------------------------------------------
|
|
@@ -23785,7 +24240,7 @@ var Worktrees = class {
|
|
|
23785
24240
|
const top = await git(folder2, ["rev-parse", "--show-toplevel"], { allowFail: true });
|
|
23786
24241
|
if (top.code !== 0) throw new HarnessError("not_a_repository", `${folder2} is not inside a git repository.`, "Worktrees need a git repository with at least one commit.");
|
|
23787
24242
|
const gitDir = await gitText(folder2, ["rev-parse", "--absolute-git-dir"]);
|
|
23788
|
-
const common =
|
|
24243
|
+
const common = resolve15(top.stdout.trim(), await gitText(folder2, ["rev-parse", "--git-common-dir"]));
|
|
23789
24244
|
if (real(gitDir) === real(common)) return real(top.stdout.trim());
|
|
23790
24245
|
const main2 = (await gitText(folder2, ["worktree", "list", "--porcelain"])).split("\n").find((line2) => line2.startsWith("worktree "));
|
|
23791
24246
|
return real(main2 ? main2.slice("worktree ".length) : top.stdout.trim());
|
|
@@ -23808,19 +24263,19 @@ var Worktrees = class {
|
|
|
23808
24263
|
}
|
|
23809
24264
|
// ---- Create and adopt -----------------------------------------------------------------------
|
|
23810
24265
|
repoKey(repoRoot) {
|
|
23811
|
-
return `${
|
|
24266
|
+
return `${slug4(basename15(repoRoot))}-${createHash14("sha256").update(repoRoot).digest("hex").slice(0, 8)}`;
|
|
23812
24267
|
}
|
|
23813
24268
|
uniqueName(repoRoot, wanted) {
|
|
23814
24269
|
const taken = new Set(this.read().worktrees.filter((record3) => record3.repoRoot === repoRoot && !record3.removed).map((record3) => record3.name));
|
|
23815
|
-
const folder2 =
|
|
24270
|
+
const folder2 = join67(this.root, this.repoKey(repoRoot));
|
|
23816
24271
|
if (wanted !== void 0) {
|
|
23817
24272
|
if (!NAME2.test(wanted)) throw invalid("A worktree name uses lowercase letters, digits and dashes (at most 63).");
|
|
23818
|
-
if (taken.has(wanted) || existsSync30(
|
|
24273
|
+
if (taken.has(wanted) || existsSync30(join67(folder2, wanted))) throw new HarnessError("worktree_exists", `A worktree named ${wanted} already exists.`);
|
|
23819
24274
|
return wanted;
|
|
23820
24275
|
}
|
|
23821
24276
|
for (; ; ) {
|
|
23822
24277
|
const name = `wt-${randomBytes3(3).toString("hex")}`;
|
|
23823
|
-
if (!taken.has(name) && !existsSync30(
|
|
24278
|
+
if (!taken.has(name) && !existsSync30(join67(folder2, name))) return name;
|
|
23824
24279
|
}
|
|
23825
24280
|
}
|
|
23826
24281
|
async branchName(repoRoot, name) {
|
|
@@ -23842,8 +24297,8 @@ var Worktrees = class {
|
|
|
23842
24297
|
const baseCommit = (await git(repoRoot, ["rev-parse", "--verify", "-q", `${baseRef}^{commit}`], { allowFail: true })).stdout.trim();
|
|
23843
24298
|
if (!baseCommit) throw invalid(`${baseRef} does not name a commit.`);
|
|
23844
24299
|
const branch = input.branch ? await this.branchName(repoRoot, input.branch) : null;
|
|
23845
|
-
const path4 =
|
|
23846
|
-
mkdirSync25(
|
|
24300
|
+
const path4 = join67(this.root, this.repoKey(repoRoot), name);
|
|
24301
|
+
mkdirSync25(dirname25(path4), { recursive: true });
|
|
23847
24302
|
await git(repoRoot, ["worktree", "add", ...branch ? ["-b", branch] : ["--detach"], path4, baseCommit]);
|
|
23848
24303
|
const at = now();
|
|
23849
24304
|
const record3 = {
|
|
@@ -23872,7 +24327,7 @@ var Worktrees = class {
|
|
|
23872
24327
|
carried = copyFiles(repoRoot, path4, await untrackedFiles(repoRoot));
|
|
23873
24328
|
}
|
|
23874
24329
|
const included = copyFiles(repoRoot, path4, await includedFiles(repoRoot));
|
|
23875
|
-
const setup = input.setup === false ? { state: "none", hash: null, log: null } : await runSetup({ checkout: path4, source: repoRoot, name, repoRoot, logFile:
|
|
24330
|
+
const setup = input.setup === false ? { state: "none", hash: null, log: null } : await runSetup({ checkout: path4, source: repoRoot, name, repoRoot, logFile: join67(this.root, "logs", `${record3.id}.setup.log`), trust: this.trust });
|
|
23876
24331
|
return { worktree: record3, carried: { ...carried, patched }, included, setup };
|
|
23877
24332
|
});
|
|
23878
24333
|
}
|
|
@@ -23889,7 +24344,7 @@ var Worktrees = class {
|
|
|
23889
24344
|
const branch = (await git(folder2, ["symbolic-ref", "-q", "--short", "HEAD"], { allowFail: true })).stdout.trim() || null;
|
|
23890
24345
|
const head2 = await gitText(folder2, ["rev-parse", "HEAD"]);
|
|
23891
24346
|
const taken = new Set(this.read().worktrees.filter((record4) => record4.repoRoot === repoRoot && !record4.removed).map((record4) => record4.name));
|
|
23892
|
-
let name =
|
|
24347
|
+
let name = slug4(basename15(folder2)).slice(0, 56) || "adopted";
|
|
23893
24348
|
if (!/^[a-z0-9]/.test(name)) name = `w-${name}`;
|
|
23894
24349
|
for (let suffix = 2; taken.has(name); suffix += 1) name = `${name.replace(/-\d+$/, "")}-${suffix}`;
|
|
23895
24350
|
const at = now();
|
|
@@ -23979,7 +24434,7 @@ var Worktrees = class {
|
|
|
23979
24434
|
}
|
|
23980
24435
|
/** Records the worktree's commits and working changes under a snapshot ref. */
|
|
23981
24436
|
async snapshot(record3, ignored) {
|
|
23982
|
-
const index =
|
|
24437
|
+
const index = join67(tmpdir9(), `xharness-index-${record3.id}-${randomBytes3(4).toString("hex")}`);
|
|
23983
24438
|
const env = { ...IDENTITY, GIT_INDEX_FILE: index };
|
|
23984
24439
|
try {
|
|
23985
24440
|
await git(record3.path, ["read-tree", "HEAD"], { env });
|
|
@@ -24036,7 +24491,7 @@ var Worktrees = class {
|
|
|
24036
24491
|
const { snapshot: snapshot2, head: head2 } = record3.removed;
|
|
24037
24492
|
const start = snapshot2 ? await gitText(record3.repoRoot, ["rev-parse", `${snapshot2}^`]) : head2;
|
|
24038
24493
|
const onBranch = record3.branch && (await git(record3.repoRoot, ["rev-parse", "--verify", "-q", `refs/heads/${record3.branch}`], { allowFail: true })).stdout.trim() === start;
|
|
24039
|
-
mkdirSync25(
|
|
24494
|
+
mkdirSync25(dirname25(record3.path), { recursive: true });
|
|
24040
24495
|
await git(record3.repoRoot, ["worktree", "add", ...onBranch ? [record3.path, record3.branch] : ["--detach", record3.path, start]]);
|
|
24041
24496
|
if (snapshot2) {
|
|
24042
24497
|
await git(record3.path, ["read-tree", "-u", "--reset", snapshot2]);
|
|
@@ -24094,7 +24549,7 @@ var Worktrees = class {
|
|
|
24094
24549
|
if (dirty) throw new HarnessError("handoff_target_dirty", `The ${direction === "toSource" ? "main checkout" : "worktree"} has uncommitted changes.`, "Commit or stash them, then hand off again.");
|
|
24095
24550
|
const patch = (await git(origin3, ["diff", "--binary", direction === "toSource" ? record3.baseCommit : "HEAD"])).stdout;
|
|
24096
24551
|
const untracked = await untrackedFiles(origin3);
|
|
24097
|
-
const collisions = untracked.filter((path4) => existsSync30(
|
|
24552
|
+
const collisions = untracked.filter((path4) => existsSync30(join67(target, path4)));
|
|
24098
24553
|
if (collisions.length) throw new HarnessError("handoff_conflict", `${collisions.length} untracked file(s) already exist in the target, for example ${collisions[0]}.`);
|
|
24099
24554
|
if (patch.trim()) {
|
|
24100
24555
|
const check2 = await git(target, ["apply", "--check", "--binary", "--whitespace=nowarn"], { input: patch, allowFail: true });
|
|
@@ -25246,16 +25701,16 @@ var UsageLimits = class {
|
|
|
25246
25701
|
// src/annotations/capture.ts
|
|
25247
25702
|
import { spawn as spawn6 } from "node:child_process";
|
|
25248
25703
|
import { randomUUID as randomUUID18 } from "node:crypto";
|
|
25249
|
-
import { existsSync as existsSync32, readdirSync as
|
|
25704
|
+
import { existsSync as existsSync32, readdirSync as readdirSync30, statSync as statSync23 } from "node:fs";
|
|
25250
25705
|
import { createServer as createServer5 } from "node:net";
|
|
25251
|
-
import { basename as
|
|
25706
|
+
import { basename as basename16, join as join69 } from "node:path";
|
|
25252
25707
|
|
|
25253
25708
|
// src/annotations/bridge.ts
|
|
25254
25709
|
init_fs();
|
|
25255
25710
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
25256
25711
|
import { chmodSync as chmodSync14, existsSync as existsSync31 } from "node:fs";
|
|
25257
25712
|
import { createServer as createServer4 } from "node:http";
|
|
25258
|
-
import { join as
|
|
25713
|
+
import { join as join68 } from "node:path";
|
|
25259
25714
|
var POLL_MS = 2e4;
|
|
25260
25715
|
var GONE_MS = 45e3;
|
|
25261
25716
|
var CALL_MS = 1e4;
|
|
@@ -25308,7 +25763,7 @@ var NotesBridge = class {
|
|
|
25308
25763
|
nextWindow = 1;
|
|
25309
25764
|
nextCall = 1;
|
|
25310
25765
|
settingsFile() {
|
|
25311
|
-
return
|
|
25766
|
+
return join68(this.options.stateDir, "notes-bridge.json");
|
|
25312
25767
|
}
|
|
25313
25768
|
/** Starts the bridge (once) on the port and token kept from last time, so the app's snippet stays valid. */
|
|
25314
25769
|
start() {
|
|
@@ -25329,13 +25784,13 @@ var NotesBridge = class {
|
|
|
25329
25784
|
const token = typeof saved?.token === "string" && /^[a-f0-9]{48}$/.test(saved.token) ? saved.token : randomBytes4(24).toString("hex");
|
|
25330
25785
|
const wanted = this.options.port ?? (Number.isSafeInteger(saved?.port) ? saved.port : 0);
|
|
25331
25786
|
const server = createServer4((request2, response) => void this.handle(request2, response, token));
|
|
25332
|
-
const port = await new Promise((
|
|
25787
|
+
const port = await new Promise((resolve23, reject2) => {
|
|
25333
25788
|
const attempt2 = (port2, retry) => {
|
|
25334
25789
|
server.once("error", (error) => {
|
|
25335
25790
|
if (retry && error.code === "EADDRINUSE") attempt2(0, false);
|
|
25336
25791
|
else reject2(error);
|
|
25337
25792
|
});
|
|
25338
|
-
server.listen(port2, "127.0.0.1", () =>
|
|
25793
|
+
server.listen(port2, "127.0.0.1", () => resolve23(server.address().port));
|
|
25339
25794
|
};
|
|
25340
25795
|
attempt2(wanted, wanted !== 0);
|
|
25341
25796
|
});
|
|
@@ -25361,12 +25816,12 @@ var NotesBridge = class {
|
|
|
25361
25816
|
this.sweep();
|
|
25362
25817
|
const live = this.windows.get(windowId);
|
|
25363
25818
|
if (!live) return Promise.reject(new Error("That app window is no longer connected."));
|
|
25364
|
-
return new Promise((
|
|
25819
|
+
return new Promise((resolve23, reject2) => {
|
|
25365
25820
|
const id2 = String(this.nextCall++);
|
|
25366
25821
|
const pending2 = {
|
|
25367
25822
|
id: id2,
|
|
25368
25823
|
operation,
|
|
25369
|
-
resolve:
|
|
25824
|
+
resolve: resolve23,
|
|
25370
25825
|
reject: reject2,
|
|
25371
25826
|
timer: setTimeout(() => {
|
|
25372
25827
|
live.sent.delete(id2);
|
|
@@ -25487,7 +25942,7 @@ var NotesBridge = class {
|
|
|
25487
25942
|
this.server = null;
|
|
25488
25943
|
this.info = null;
|
|
25489
25944
|
if (server) {
|
|
25490
|
-
const closed = new Promise((
|
|
25945
|
+
const closed = new Promise((resolve23) => server.close(() => resolve23()));
|
|
25491
25946
|
server.closeAllConnections();
|
|
25492
25947
|
await closed;
|
|
25493
25948
|
}
|
|
@@ -25555,24 +26010,24 @@ var spawnApp = (command, args) => {
|
|
|
25555
26010
|
child.on("error", () => void 0);
|
|
25556
26011
|
child.unref();
|
|
25557
26012
|
};
|
|
25558
|
-
var freePort = () => new Promise((
|
|
26013
|
+
var freePort = () => new Promise((resolve23, reject2) => {
|
|
25559
26014
|
const server = createServer5();
|
|
25560
26015
|
server.unref();
|
|
25561
26016
|
server.on("error", reject2);
|
|
25562
26017
|
server.listen(0, "127.0.0.1", () => {
|
|
25563
26018
|
const address = server.address();
|
|
25564
|
-
server.close(() => typeof address === "object" && address ?
|
|
26019
|
+
server.close(() => typeof address === "object" && address ? resolve23(address.port) : reject2(new Error("No free port.")));
|
|
25565
26020
|
});
|
|
25566
26021
|
});
|
|
25567
26022
|
function appExecutable(path4) {
|
|
25568
26023
|
if (!/\.app\/?$/.test(path4)) return path4;
|
|
25569
|
-
const folder2 =
|
|
26024
|
+
const folder2 = join69(path4, "Contents", "MacOS");
|
|
25570
26025
|
if (!existsSync32(folder2)) throw new Error(`${path4} has no Contents/MacOS folder.`);
|
|
25571
|
-
const programs =
|
|
25572
|
-
const named2 =
|
|
26026
|
+
const programs = readdirSync30(folder2).filter((name) => !name.startsWith(".") && statSync23(join69(folder2, name)).isFile());
|
|
26027
|
+
const named2 = basename16(path4.replace(/\/$/, ""), ".app");
|
|
25573
26028
|
const program = programs.find((name) => name === named2) ?? programs[0];
|
|
25574
26029
|
if (!program) throw new Error(`${path4} has no program in Contents/MacOS.`);
|
|
25575
|
-
return
|
|
26030
|
+
return join69(folder2, program);
|
|
25576
26031
|
}
|
|
25577
26032
|
function describeApp(port, version2, list7) {
|
|
25578
26033
|
const agent = typeof version2["User-Agent"] === "string" ? version2["User-Agent"] : "";
|
|
@@ -25704,8 +26159,8 @@ var AnnotationCapture = class {
|
|
|
25704
26159
|
if (existing instanceof WebTarget) return existing;
|
|
25705
26160
|
const session = new BrowserSession({
|
|
25706
26161
|
key: `notes-${key}`,
|
|
25707
|
-
profileDir:
|
|
25708
|
-
downloadsDir:
|
|
26162
|
+
profileDir: join69(this.options.stateDir, "browser", "profiles", "person"),
|
|
26163
|
+
downloadsDir: join69(this.options.stateDir, "browser", "downloads", fileName(`notes-${key}`)),
|
|
25709
26164
|
// Always a window the person can see and select in.
|
|
25710
26165
|
config: () => ({ ...readBrowserConfig(this.options.stateDir), headed: true }),
|
|
25711
26166
|
network: this.network,
|
|
@@ -25790,10 +26245,10 @@ var AnnotationCapture = class {
|
|
|
25790
26245
|
const deadline = Date.now() + (this.options.launchTimeoutMs ?? 2e4);
|
|
25791
26246
|
for (; ; ) {
|
|
25792
26247
|
if (await probe(`${endpoint}/json/version`, PROBE_MS)) break;
|
|
25793
|
-
if (Date.now() > deadline) throw new Error(`${
|
|
25794
|
-
await new Promise((
|
|
26248
|
+
if (Date.now() > deadline) throw new Error(`${basename16(path4)} did not open a DevTools port. Only Chromium and Electron apps can be inspected this way.`);
|
|
26249
|
+
await new Promise((resolve23) => setTimeout(resolve23, 250));
|
|
25795
26250
|
}
|
|
25796
|
-
const name =
|
|
26251
|
+
const name = basename16(path4.trim().replace(/\/$/, "")).replace(/\.app$/, "");
|
|
25797
26252
|
const attached2 = await this.attach(key, { kind: "cdp", endpoint, name });
|
|
25798
26253
|
return { kind: "cdp", app: attached2.app, windows: attached2.windows, endpoint };
|
|
25799
26254
|
}
|
|
@@ -25936,8 +26391,8 @@ function compose(notes, prompt) {
|
|
|
25936
26391
|
|
|
25937
26392
|
// src/annotations/delivery.ts
|
|
25938
26393
|
var RETRY_DELAYS = [250, 1e3, 3e3, 8e3];
|
|
25939
|
-
var wait = (ms) => new Promise((
|
|
25940
|
-
const timer = setTimeout(
|
|
26394
|
+
var wait = (ms) => new Promise((resolve23) => {
|
|
26395
|
+
const timer = setTimeout(resolve23, ms);
|
|
25941
26396
|
timer.unref?.();
|
|
25942
26397
|
});
|
|
25943
26398
|
var NoteDeliveries = class {
|
|
@@ -26140,7 +26595,7 @@ var Harness = class {
|
|
|
26140
26595
|
this.browserEnabled = options2.browser !== false;
|
|
26141
26596
|
this.stateDir = options2.stateDir;
|
|
26142
26597
|
mkdirSync26(this.stateDir, { recursive: true });
|
|
26143
|
-
isolateProcess(
|
|
26598
|
+
isolateProcess(join70(this.stateDir, "accounts"));
|
|
26144
26599
|
this.env = new Env(options2.dataRoot || null, options2.directory ?? EMPTY_DIRECTORY, options2.productName ?? DEFAULT_PRODUCT_NAME, this.bus);
|
|
26145
26600
|
this.hub = new Hub(this, options2.hub);
|
|
26146
26601
|
this.policy = options2.policy ?? {};
|
|
@@ -26148,8 +26603,8 @@ var Harness = class {
|
|
|
26148
26603
|
configureBuiltins(options2.builtins ?? []);
|
|
26149
26604
|
this.trust = new VariantTrust(this.stateDir);
|
|
26150
26605
|
this.refineHost = options2.refineHost ?? null;
|
|
26151
|
-
const secrets = options2.secrets ?? fileSecretStore(
|
|
26152
|
-
this.accounts = new Accounts({ dir:
|
|
26606
|
+
const secrets = options2.secrets ?? fileSecretStore(join70(this.stateDir, "secrets.json"));
|
|
26607
|
+
this.accounts = new Accounts({ dir: join70(this.stateDir, "accounts"), secrets, bus: this.bus });
|
|
26153
26608
|
this.approvals = new Approvals(this.stateDir, this.bus);
|
|
26154
26609
|
this.usageLimits = new UsageLimits({ accounts: this.accounts, root: () => this.env.dataRoot });
|
|
26155
26610
|
this.accounts.observeModels = (models, accountId) => this.usageLimits.observeModels(models, accountId);
|
|
@@ -26164,7 +26619,7 @@ var Harness = class {
|
|
|
26164
26619
|
ended: (runId, sessionId) => this.apps.revokeRun(runId, sessionId)
|
|
26165
26620
|
});
|
|
26166
26621
|
this.apps = new ConnectedApps({ env: this.env, stateDir: this.stateDir, secrets, bus: this.bus, runs: this.runs, policy: this.policy, oauthClients: options2.oauthClients });
|
|
26167
|
-
this.plugins = new Plugins({ stateDir: this.stateDir, bus: this.bus, resolver: options2.resolver, attachments: () => this.env.dataRoot ? load3(this.env.dataRoot) : null, packagesDir: () => this.env.dataRoot ?
|
|
26622
|
+
this.plugins = new Plugins({ stateDir: this.stateDir, bus: this.bus, resolver: options2.resolver, attachments: () => this.env.dataRoot ? load3(this.env.dataRoot) : null, packagesDir: () => this.env.dataRoot ? join70(this.env.dataRoot, "plugins") : null });
|
|
26168
26623
|
this.hooks = new Hooks({
|
|
26169
26624
|
stateDir: this.stateDir,
|
|
26170
26625
|
bus: this.bus,
|
|
@@ -26172,9 +26627,9 @@ var Harness = class {
|
|
|
26172
26627
|
// The shared folder stands in for an account: packages are the same for all of them.
|
|
26173
26628
|
wanted: async () => {
|
|
26174
26629
|
if (!this.env.dataRoot) return [];
|
|
26175
|
-
const shared = ensureSharedDir(
|
|
26176
|
-
const named2 = readJsonOr(
|
|
26177
|
-
if (!packageFolders(
|
|
26630
|
+
const shared = ensureSharedDir(join70(this.stateDir, "accounts"));
|
|
26631
|
+
const named2 = readJsonOr(join70(shared, "settings.json"), {}).packages;
|
|
26632
|
+
if (!packageFolders(join70(this.env.dataRoot, "plugins")).length && !(Array.isArray(named2) && named2.length)) return [];
|
|
26178
26633
|
return (await this.plugins.loading({ cwd: this.stateDir, agentDir: shared, caller: {} })).hooks;
|
|
26179
26634
|
},
|
|
26180
26635
|
call: (connection, tool, args) => this.apps.callUnattended(connection, tool, args ?? {}),
|
|
@@ -26259,7 +26714,7 @@ var Harness = class {
|
|
|
26259
26714
|
const root = dataRoot;
|
|
26260
26715
|
this.hooks.watch(wikiDir(root));
|
|
26261
26716
|
void this.hooks.reload();
|
|
26262
|
-
for (const folder2 of ["registry/skills", "registry/prompts", "registry/rules", "config", "runs"]) mkdirSync26(
|
|
26717
|
+
for (const folder2 of ["registry/skills", "registry/prompts", "registry/rules", "config", "runs"]) mkdirSync26(join70(root, folder2), { recursive: true });
|
|
26263
26718
|
let migrationError = null;
|
|
26264
26719
|
try {
|
|
26265
26720
|
const moved = migrateVariantLayout(root);
|
|
@@ -26268,7 +26723,7 @@ var Harness = class {
|
|
|
26268
26723
|
migrationError = error instanceof Error ? error.message : String(error);
|
|
26269
26724
|
log("variant layout", migrationError);
|
|
26270
26725
|
}
|
|
26271
|
-
mkdirSync26(
|
|
26726
|
+
mkdirSync26(join70(root, "variants"), { recursive: true });
|
|
26272
26727
|
let legacyWiki = null;
|
|
26273
26728
|
try {
|
|
26274
26729
|
legacyWiki = moveLegacy(root);
|
|
@@ -26365,7 +26820,7 @@ function createHarness(options2) {
|
|
|
26365
26820
|
|
|
26366
26821
|
// src/plugins/cli.ts
|
|
26367
26822
|
import { homedir as homedir6 } from "node:os";
|
|
26368
|
-
import { join as
|
|
26823
|
+
import { join as join71, resolve as resolve16 } from "node:path";
|
|
26369
26824
|
import { createInterface } from "node:readline/promises";
|
|
26370
26825
|
var PLUGINS_USAGE = `Usage: xagent plugins <command> [options]
|
|
26371
26826
|
|
|
@@ -26431,10 +26886,10 @@ async function runPluginsCli(argv, dependencies = {}) {
|
|
|
26431
26886
|
return command ? 0 : 2;
|
|
26432
26887
|
}
|
|
26433
26888
|
const base = dependencies.cwd ?? process.cwd();
|
|
26434
|
-
const root =
|
|
26435
|
-
const options2 = { dataRoot: parsed.values.data ?
|
|
26889
|
+
const root = join71(dependencies.home ?? homedir6(), ".xingular-harness");
|
|
26890
|
+
const options2 = { dataRoot: parsed.values.data ? resolve16(base, parsed.values.data) : join71(root, "data"), stateDir: parsed.values.state ? resolve16(base, parsed.values.state) : join71(root, "state"), productName: DEFAULT_PRODUCT_NAME };
|
|
26436
26891
|
const harness = dependencies.createHarness ? dependencies.createHarness(options2) : new Harness(options2);
|
|
26437
|
-
const scope = { cwd: parsed.values.cwd ?
|
|
26892
|
+
const scope = { cwd: parsed.values.cwd ? resolve16(base, parsed.values.cwd) : null };
|
|
26438
26893
|
const need = (what) => {
|
|
26439
26894
|
if (!target) throw new Error(`${command} needs ${what}.`);
|
|
26440
26895
|
return target;
|
|
@@ -26545,7 +27000,7 @@ function terminalAsk() {
|
|
|
26545
27000
|
|
|
26546
27001
|
// src/hub/cli.ts
|
|
26547
27002
|
import { homedir as homedir7 } from "node:os";
|
|
26548
|
-
import { join as
|
|
27003
|
+
import { join as join72, resolve as resolve17 } from "node:path";
|
|
26549
27004
|
var HUB_USAGE = `Usage: xagent hub <list | show | install> [id@version] [options]
|
|
26550
27005
|
|
|
26551
27006
|
list [--refresh] Browse the curated catalog and locally installed versions
|
|
@@ -26571,7 +27026,7 @@ async function runHubCli(argv, dependencies = {}) {
|
|
|
26571
27026
|
`);
|
|
26572
27027
|
return 2;
|
|
26573
27028
|
}
|
|
26574
|
-
values2[arg.slice(2)] =
|
|
27029
|
+
values2[arg.slice(2)] = resolve17(value);
|
|
26575
27030
|
} else if (arg.startsWith("--")) {
|
|
26576
27031
|
write4(`Unknown option ${arg}.
|
|
26577
27032
|
`);
|
|
@@ -26587,8 +27042,8 @@ async function runHubCli(argv, dependencies = {}) {
|
|
|
26587
27042
|
write4(HUB_USAGE);
|
|
26588
27043
|
return 2;
|
|
26589
27044
|
}
|
|
26590
|
-
const root =
|
|
26591
|
-
const options2 = { dataRoot: values2.data ??
|
|
27045
|
+
const root = join72(dependencies.home ?? homedir7(), ".xingular-harness");
|
|
27046
|
+
const options2 = { dataRoot: values2.data ?? join72(root, "data"), stateDir: values2.state ?? join72(root, "state") };
|
|
26592
27047
|
const harness = dependencies.createHarness?.(options2) ?? new Harness(options2);
|
|
26593
27048
|
try {
|
|
26594
27049
|
const overview2 = await harness.hub.list(refresh);
|
|
@@ -26634,13 +27089,13 @@ Delivery: ${JSON.stringify(delivery)}
|
|
|
26634
27089
|
init_fs();
|
|
26635
27090
|
import { mkdtempSync as mkdtempSync10, readFileSync as readFileSync35 } from "node:fs";
|
|
26636
27091
|
import { homedir as homedir8, tmpdir as tmpdir11 } from "node:os";
|
|
26637
|
-
import { basename as
|
|
27092
|
+
import { basename as basename17, join as join75, resolve as resolve18 } from "node:path";
|
|
26638
27093
|
|
|
26639
27094
|
// src/refine/demo.ts
|
|
26640
27095
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
26641
|
-
import { cpSync as cpSync5, existsSync as existsSync33, mkdirSync as mkdirSync27, mkdtempSync as mkdtempSync9, readdirSync as
|
|
27096
|
+
import { cpSync as cpSync5, existsSync as existsSync33, mkdirSync as mkdirSync27, mkdtempSync as mkdtempSync9, readdirSync as readdirSync31, readFileSync as readFileSync33, writeFileSync as writeFileSync12 } from "node:fs";
|
|
26642
27097
|
import { tmpdir as tmpdir10 } from "node:os";
|
|
26643
|
-
import { join as
|
|
27098
|
+
import { join as join73 } from "node:path";
|
|
26644
27099
|
var DEMO_SCRIPT = `import { readFileSync } from 'node:fs'
|
|
26645
27100
|
|
|
26646
27101
|
const rows = readFileSync(process.argv[2], 'utf8').trim().split('\\n').slice(1)
|
|
@@ -26663,11 +27118,11 @@ var DEMO_LEDGERS = {
|
|
|
26663
27118
|
};
|
|
26664
27119
|
function demoTasks(root, scope = "demo") {
|
|
26665
27120
|
return Object.entries(DEMO_LEDGERS).map(([id2, amounts]) => {
|
|
26666
|
-
const sourceDir =
|
|
27121
|
+
const sourceDir = join73(root, "sources", id2);
|
|
26667
27122
|
mkdirSync27(sourceDir, { recursive: true });
|
|
26668
|
-
writeFileSync12(
|
|
27123
|
+
writeFileSync12(join73(sourceDir, "ledger.csv"), `amount
|
|
26669
27124
|
${amounts}`);
|
|
26670
|
-
writeFileSync12(
|
|
27125
|
+
writeFileSync12(join73(sourceDir, "reference_answer.txt"), "never shown to a model");
|
|
26671
27126
|
return {
|
|
26672
27127
|
id: id2,
|
|
26673
27128
|
scope,
|
|
@@ -26687,24 +27142,24 @@ var DemoExecutor = class {
|
|
|
26687
27142
|
executions = 0;
|
|
26688
27143
|
async execute(task, variantDir, label, runId) {
|
|
26689
27144
|
this.executions++;
|
|
26690
|
-
const dir2 =
|
|
27145
|
+
const dir2 = join73(this.root, "executions", runId);
|
|
26691
27146
|
mkdirSync27(dir2, { recursive: true });
|
|
26692
|
-
const script =
|
|
26693
|
-
const result = spawnSync3(process.execPath, [script,
|
|
27147
|
+
const script = join73(variantDir ?? "", "scripts", "summarize.mjs");
|
|
27148
|
+
const result = spawnSync3(process.execPath, [script, join73(task.sourceDir ?? "", "ledger.csv")], { encoding: "utf8", timeout: 1e4 });
|
|
26694
27149
|
if (result.status !== 0) {
|
|
26695
27150
|
return { label, runId, dir: dir2, ok: false, delivered: false, costUsd: 0, wallMs: 0, error: result.stderr || "script failed" };
|
|
26696
27151
|
}
|
|
26697
|
-
writeFileSync12(
|
|
26698
|
-
writeFileSync12(
|
|
27152
|
+
writeFileSync12(join73(dir2, "answer.txt"), result.stdout);
|
|
27153
|
+
writeFileSync12(join73(dir2, "trajectory.md"), `[ACTION] Run scripts/summarize.mjs on ledger.csv
|
|
26699
27154
|
[OBSERVATION]
|
|
26700
27155
|
${result.stdout}`);
|
|
26701
27156
|
return { label, runId, dir: dir2, ok: true, delivered: true, costUsd: 0, wallMs: 0, error: "" };
|
|
26702
27157
|
}
|
|
26703
27158
|
trajectory(rollout) {
|
|
26704
|
-
return readFileSync33(
|
|
27159
|
+
return readFileSync33(join73(rollout.dir, "trajectory.md"), "utf8");
|
|
26705
27160
|
}
|
|
26706
27161
|
deliverableFiles(rollout) {
|
|
26707
|
-
const path4 =
|
|
27162
|
+
const path4 = join73(rollout.dir, "answer.txt");
|
|
26708
27163
|
return existsSync33(path4) ? { "answer.txt": readFileSync33(path4, "utf8") } : {};
|
|
26709
27164
|
}
|
|
26710
27165
|
validateVariant(_task, variant) {
|
|
@@ -26723,15 +27178,15 @@ var DemoImprover = class {
|
|
|
26723
27178
|
let workspace = null;
|
|
26724
27179
|
let text11;
|
|
26725
27180
|
if (input.label.startsWith("select.")) {
|
|
26726
|
-
const expected = `Total: ${signedTotal(readFileSync33(
|
|
26727
|
-
const ids2 =
|
|
26728
|
-
const chosen = ids2.find((name) => readFileSync33(
|
|
27181
|
+
const expected = `Total: ${signedTotal(readFileSync33(join73(context, "source", "ledger.csv"), "utf8"))}`;
|
|
27182
|
+
const ids2 = readdirSync31(join73(context, "candidates")).filter((name) => name.endsWith(".md")).sort();
|
|
27183
|
+
const chosen = ids2.find((name) => readFileSync33(join73(context, "candidates", name), "utf8").includes(expected))?.replace(/\.md$/, "") ?? ids2[0].replace(/\.md$/, "");
|
|
26729
27184
|
text11 = `<why>The signed total matches the source ledger.</why>
|
|
26730
27185
|
\`\`\`choice
|
|
26731
27186
|
${chosen}
|
|
26732
27187
|
\`\`\``;
|
|
26733
27188
|
} else if (input.label.startsWith("experience.")) {
|
|
26734
|
-
const changed2 = readFileSync33(
|
|
27189
|
+
const changed2 = readFileSync33(join73(context, "harness", "scripts", "summarize.mjs"), "utf8") !== readFileSync33(join73(context, "base_harness", "scripts", "summarize.mjs"), "utf8");
|
|
26735
27190
|
text11 = `<edits>
|
|
26736
27191
|
component: scripts/summarize.mjs
|
|
26737
27192
|
candidates: inv1
|
|
@@ -26742,9 +27197,9 @@ verdict: ${changed2 ? "helped" : "no observed effect"}
|
|
|
26742
27197
|
|
|
26743
27198
|
` + (changed2 ? "<experience>\ncue: When a ledger includes credits and debits\nlesson: Include both signs when summing in scripts/summarize.mjs.\nevidence: The scripts/summarize.mjs row -- removing the positive-only filter changed the probe total to include the credits omitted by the base trajectory.\nconfidence: high for this observed arithmetic contrast\n</experience>" : "NO_ENTRY");
|
|
26744
27199
|
} else {
|
|
26745
|
-
workspace = mkdtempSync9(
|
|
26746
|
-
cpSync5(
|
|
26747
|
-
const script =
|
|
27200
|
+
workspace = mkdtempSync9(join73(tmpdir10(), "harness-demo-"));
|
|
27201
|
+
cpSync5(join73(context, "harness"), workspace, { recursive: true });
|
|
27202
|
+
const script = join73(workspace, "scripts", "summarize.mjs");
|
|
26748
27203
|
writeFileSync12(script, readFileSync33(script, "utf8").replace(".filter((amount) => amount > 0)", ""));
|
|
26749
27204
|
text11 = "<notes>Keep signed amounts: the base total omitted credits in the source ledger.</notes>";
|
|
26750
27205
|
}
|
|
@@ -26756,10 +27211,10 @@ verdict: ${changed2 ? "helped" : "no observed effect"}
|
|
|
26756
27211
|
init_fs();
|
|
26757
27212
|
import { createHash as createHash15 } from "node:crypto";
|
|
26758
27213
|
import { existsSync as existsSync34, readFileSync as readFileSync34 } from "node:fs";
|
|
26759
|
-
import { dirname as
|
|
27214
|
+
import { dirname as dirname26, join as join74 } from "node:path";
|
|
26760
27215
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
26761
27216
|
function matrixDir(root, id2) {
|
|
26762
|
-
return
|
|
27217
|
+
return join74(root, "refine", safeId(id2));
|
|
26763
27218
|
}
|
|
26764
27219
|
var packageVersionMemo = null;
|
|
26765
27220
|
function packageVersion() {
|
|
@@ -26768,8 +27223,8 @@ function packageVersion() {
|
|
|
26768
27223
|
try {
|
|
26769
27224
|
let dir2 = fileURLToPath3(import.meta.url);
|
|
26770
27225
|
for (let up = 0; up < 6; up++) {
|
|
26771
|
-
dir2 =
|
|
26772
|
-
const manifest =
|
|
27226
|
+
dir2 = dirname26(dir2);
|
|
27227
|
+
const manifest = join74(dir2, "package.json");
|
|
26773
27228
|
if (existsSync34(manifest)) {
|
|
26774
27229
|
const json2 = JSON.parse(readFileSync34(manifest, "utf8"));
|
|
26775
27230
|
if (json2.version) packageVersionMemo = json2.version;
|
|
@@ -26797,7 +27252,7 @@ function stage(input) {
|
|
|
26797
27252
|
return input.versions.map((entry2) => {
|
|
26798
27253
|
try {
|
|
26799
27254
|
assertVariantHasNoGraderVerdicts(entry2.variant);
|
|
26800
|
-
return { label: entry2.label, variant: entry2.variant, fingerprint: fingerprint(entry2.variant), dir: writeVariant(PI_SURFACE, entry2.variant,
|
|
27255
|
+
return { label: entry2.label, variant: entry2.variant, fingerprint: fingerprint(entry2.variant), dir: writeVariant(PI_SURFACE, entry2.variant, join74(matrixDir(input.root, input.id), "variants", entry2.label)), error: "" };
|
|
26801
27256
|
} catch (error) {
|
|
26802
27257
|
return { label: entry2.label, variant: entry2.variant, fingerprint: fingerprint(entry2.variant), dir: "", error: error instanceof Error ? error.message : String(error) };
|
|
26803
27258
|
}
|
|
@@ -26824,10 +27279,10 @@ async function runMatrix(input) {
|
|
|
26824
27279
|
};
|
|
26825
27280
|
const save3 = () => {
|
|
26826
27281
|
summary4.usd = Math.round(summary4.rows.reduce((sum, row) => sum + row.costUsd, 0) * 1e6) / 1e6;
|
|
26827
|
-
writeJson(
|
|
27282
|
+
writeJson(join74(matrixDir(root, id2), "matrix.json"), summary4);
|
|
26828
27283
|
emit({ type: "summary", summary: structuredClone(summary4) });
|
|
26829
27284
|
};
|
|
26830
|
-
writeJson(
|
|
27285
|
+
writeJson(join74(matrixDir(root, id2), "suite.json"), { id: id2, versions: staged.map(({ label, fingerprint: fingerprint2 }) => ({ label, fingerprint: fingerprint2 })), tasks });
|
|
26831
27286
|
save3();
|
|
26832
27287
|
const pairs = staged.flatMap((version2) => tasks.map((task) => ({ version: version2, task })));
|
|
26833
27288
|
await mapLimit(pairs, input.rolloutWidth ?? 4, async ({ version: version2, task }) => {
|
|
@@ -26851,7 +27306,7 @@ async function runMatrix(input) {
|
|
|
26851
27306
|
try {
|
|
26852
27307
|
rollout = await executor.execute(task, version2.dir, runId, runId);
|
|
26853
27308
|
} catch (error) {
|
|
26854
|
-
rollout = { label: runId, runId, dir:
|
|
27309
|
+
rollout = { label: runId, runId, dir: join74(matrixDir(root, id2), "missing", version2.label, task.id), ok: false, delivered: false, costUsd: 0, wallMs: 0, error: error instanceof Error ? error.message : String(error) };
|
|
26855
27310
|
}
|
|
26856
27311
|
emit({ type: "execution.finished", version: version2.label, task: task.id, ok: rollout.ok, delivered: rollout.delivered, error: rollout.error });
|
|
26857
27312
|
row = rowOf(version2, task, runId, executorName, rollout);
|
|
@@ -26953,7 +27408,7 @@ async function runRefineCli(argv, io = { write: (text11) => void process.stdout.
|
|
|
26953
27408
|
const { command, values: values2 } = parse3(argv);
|
|
26954
27409
|
try {
|
|
26955
27410
|
if (command === "demo") {
|
|
26956
|
-
const root = values2.out ?
|
|
27411
|
+
const root = values2.out ? resolve18(values2.out) : mkdtempSync10(join75(tmpdir11(), "harness-refine-demo-"));
|
|
26957
27412
|
const k = Number(values2.k ?? 1);
|
|
26958
27413
|
seed(root, "demo", demoBaseVariant(), "demo base harness");
|
|
26959
27414
|
const executor = new DemoExecutor(root);
|
|
@@ -26963,7 +27418,7 @@ async function runRefineCli(argv, io = { write: (text11) => void process.stdout.
|
|
|
26963
27418
|
describe3(summary4, io.write);
|
|
26964
27419
|
}
|
|
26965
27420
|
io.write(`library: ${new Library(root, "demo", "accounting").entries().join(", ") || "(empty)"}
|
|
26966
|
-
records: ${
|
|
27421
|
+
records: ${join75(root, "refine")}
|
|
26967
27422
|
`);
|
|
26968
27423
|
return 0;
|
|
26969
27424
|
}
|
|
@@ -26972,15 +27427,15 @@ records: ${join74(root, "refine")}
|
|
|
26972
27427
|
io.write(REFINE_USAGE);
|
|
26973
27428
|
return 2;
|
|
26974
27429
|
}
|
|
26975
|
-
const base =
|
|
26976
|
-
const harness = createHarness({ dataRoot:
|
|
27430
|
+
const base = join75(homedir8(), ".xingular-harness");
|
|
27431
|
+
const harness = createHarness({ dataRoot: resolve18(values2.data ?? join75(base, "data")), stateDir: resolve18(values2.state ?? join75(base, "state")) });
|
|
26977
27432
|
try {
|
|
26978
|
-
const scope = values2.scope ??
|
|
26979
|
-
const variant = readVariant(PI_SURFACE,
|
|
27433
|
+
const scope = values2.scope ?? basename17(resolve18(values2.variant)).replace(/[^A-Za-z0-9_-]/g, "-");
|
|
27434
|
+
const variant = readVariant(PI_SURFACE, resolve18(values2.variant));
|
|
26980
27435
|
commit(harness.env.root, scope, variant, { kind: "import", note: `from ${values2.variant}` });
|
|
26981
|
-
const prompt = readFileSync35(
|
|
27436
|
+
const prompt = readFileSync35(resolve18(values2.task), "utf8");
|
|
26982
27437
|
const { id: id2 } = harness.refine.start({
|
|
26983
|
-
task: { id:
|
|
27438
|
+
task: { id: basename17(values2.task).replace(/\.md$/, "").replace(/[^A-Za-z0-9_-]/g, "-"), prompt, scope, sourceDir: values2.source ? resolve18(values2.source) : null },
|
|
26984
27439
|
k: Number(values2.k ?? 1),
|
|
26985
27440
|
mode: mode(values2.mode),
|
|
26986
27441
|
improver: values2.model ? { model: values2.model } : {},
|
|
@@ -27004,7 +27459,7 @@ records: ${join74(root, "refine")}
|
|
|
27004
27459
|
}
|
|
27005
27460
|
}
|
|
27006
27461
|
if (command === "matrix") {
|
|
27007
|
-
const root =
|
|
27462
|
+
const root = resolve18(values2.data ?? join75(homedir8(), ".xingular-harness", "data"));
|
|
27008
27463
|
let versions2 = [];
|
|
27009
27464
|
let tasks;
|
|
27010
27465
|
if (values2.demo) {
|
|
@@ -27016,10 +27471,10 @@ records: ${join74(root, "refine")}
|
|
|
27016
27471
|
io.write(REFINE_USAGE);
|
|
27017
27472
|
return 2;
|
|
27018
27473
|
}
|
|
27019
|
-
const suite = JSON.parse(readFileSync35(
|
|
27474
|
+
const suite = JSON.parse(readFileSync35(resolve18(values2.suite), "utf8"));
|
|
27020
27475
|
if (suite.executor && suite.executor !== "demo") throw new Error(`Executor '${suite.executor}' needs a model; this command runs without one. Use 'demo'.`);
|
|
27021
27476
|
for (const entry2 of suite.versions) {
|
|
27022
|
-
if (entry2.dir) versions2.push({ label: entry2.label, variant: readVariant(PI_SURFACE,
|
|
27477
|
+
if (entry2.dir) versions2.push({ label: entry2.label, variant: readVariant(PI_SURFACE, resolve18(entry2.dir)) });
|
|
27023
27478
|
else if (entry2.scope) {
|
|
27024
27479
|
const fingerprint2 = entry2.version ?? head(root, entry2.scope)?.fingerprint;
|
|
27025
27480
|
if (!fingerprint2) throw new Error(`The harness '${entry2.scope}' has no head version to evaluate.`);
|
|
@@ -27030,7 +27485,7 @@ records: ${join74(root, "refine")}
|
|
|
27030
27485
|
}
|
|
27031
27486
|
const executor = new DemoExecutor(root);
|
|
27032
27487
|
const summary4 = await runMatrix({ root, id: values2.id ?? "matrix", versions: versions2, tasks, executor, rolloutWidth: values2.width ? Number(values2.width) : void 0 });
|
|
27033
|
-
if (values2.out) writeJson(
|
|
27488
|
+
if (values2.out) writeJson(resolve18(values2.out), summary4);
|
|
27034
27489
|
describeMatrix(summary4, io.write);
|
|
27035
27490
|
return summary4.status === "ok" || summary4.status === "partial" ? 0 : 1;
|
|
27036
27491
|
}
|
|
@@ -27045,7 +27500,7 @@ records: ${join74(root, "refine")}
|
|
|
27045
27500
|
|
|
27046
27501
|
// src/world/cli.ts
|
|
27047
27502
|
import { homedir as homedir9 } from "node:os";
|
|
27048
|
-
import { join as
|
|
27503
|
+
import { join as join76, resolve as resolve19 } from "node:path";
|
|
27049
27504
|
var WEB_USAGE = `Usage: xagent web <status | key <provider> [--remove] | keyless on|off> [--state <dir>] [--data <dir>]
|
|
27050
27505
|
|
|
27051
27506
|
status Which search services are ready
|
|
@@ -27071,11 +27526,11 @@ var BROWSER_USAGE = `Usage: xagent browser [status | set <options>] [--state <di
|
|
|
27071
27526
|
`;
|
|
27072
27527
|
function stateOf2(argv, home) {
|
|
27073
27528
|
const args = [];
|
|
27074
|
-
let stateDir =
|
|
27075
|
-
let dataRoot =
|
|
27529
|
+
let stateDir = join76(home, ".xingular-harness", "state");
|
|
27530
|
+
let dataRoot = join76(home, ".xingular-harness", "data");
|
|
27076
27531
|
for (let index = 0; index < argv.length; index += 1) {
|
|
27077
|
-
if (argv[index] === "--state") stateDir =
|
|
27078
|
-
else if (argv[index] === "--data") dataRoot =
|
|
27532
|
+
if (argv[index] === "--state") stateDir = resolve19(argv[++index] ?? stateDir);
|
|
27533
|
+
else if (argv[index] === "--data") dataRoot = resolve19(argv[++index] ?? dataRoot);
|
|
27079
27534
|
else args.push(argv[index]);
|
|
27080
27535
|
}
|
|
27081
27536
|
return { args, stateDir, dataRoot };
|
|
@@ -27090,7 +27545,7 @@ async function readStdin2() {
|
|
|
27090
27545
|
async function runWebCli(argv, dependencies = {}) {
|
|
27091
27546
|
const write4 = dependencies.write ?? ((text11) => void process.stdout.write(text11));
|
|
27092
27547
|
const { args, stateDir, dataRoot } = stateOf2(argv, dependencies.home ?? homedir9());
|
|
27093
|
-
const secrets = dependencies.secrets ?? fileSecretStore(
|
|
27548
|
+
const secrets = dependencies.secrets ?? fileSecretStore(join76(stateDir, "secrets.json"));
|
|
27094
27549
|
const [command, name] = args;
|
|
27095
27550
|
if (!command || command === "help") {
|
|
27096
27551
|
write4(WEB_USAGE);
|
|
@@ -27324,7 +27779,7 @@ var RpcPeer = class {
|
|
|
27324
27779
|
/** Asks the host to perform a named operation. */
|
|
27325
27780
|
callHost(call, params, timeoutMs = 3e4) {
|
|
27326
27781
|
const id2 = `s${++this.nextCallId}`;
|
|
27327
|
-
return new Promise((
|
|
27782
|
+
return new Promise((resolve23, reject2) => {
|
|
27328
27783
|
const timer = setTimeout(() => {
|
|
27329
27784
|
this.pendingCalls.delete(id2);
|
|
27330
27785
|
reject2(new HarnessError("host_timeout", `The host did not answer ${call}.`));
|
|
@@ -27332,7 +27787,7 @@ var RpcPeer = class {
|
|
|
27332
27787
|
this.pendingCalls.set(id2, {
|
|
27333
27788
|
resolve: (value) => {
|
|
27334
27789
|
clearTimeout(timer);
|
|
27335
|
-
|
|
27790
|
+
resolve23(value);
|
|
27336
27791
|
},
|
|
27337
27792
|
reject: (error) => {
|
|
27338
27793
|
clearTimeout(timer);
|
|
@@ -27508,25 +27963,6 @@ function ensureDir(dir2) {
|
|
|
27508
27963
|
return dir2;
|
|
27509
27964
|
}
|
|
27510
27965
|
|
|
27511
|
-
// src/plugins/commandPrefix.ts
|
|
27512
|
-
import { basename as basename17, dirname as dirname25, extname as extname3 } from "node:path";
|
|
27513
|
-
var slug4 = (value) => {
|
|
27514
|
-
const normalized = value.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
27515
|
-
const prefix = /^[a-z]/.test(normalized) ? normalized : `plugin-${normalized || "command"}`;
|
|
27516
|
-
return prefix === "plugin" || prefix === "skill" ? `extension-${prefix}` : prefix;
|
|
27517
|
-
};
|
|
27518
|
-
function pluginCommandPrefix(source) {
|
|
27519
|
-
if (source.origin === "package" && source.baseDir) {
|
|
27520
|
-
const manifest = packageManifest(source.baseDir);
|
|
27521
|
-
return manifest.commandPrefix ?? slug4(manifest.name);
|
|
27522
|
-
}
|
|
27523
|
-
if (source.path.startsWith("builtin:")) return slug4(source.path.slice("builtin:".length));
|
|
27524
|
-
const synthetic = /^<[^:>]+:([^>]+)>$/.exec(source.path);
|
|
27525
|
-
if (synthetic) return slug4(synthetic[1]);
|
|
27526
|
-
const file2 = basename17(source.path, extname3(source.path));
|
|
27527
|
-
return slug4(file2 === "index" ? basename17(dirname25(source.path)) : file2);
|
|
27528
|
-
}
|
|
27529
|
-
|
|
27530
27966
|
// src/commands/registry.ts
|
|
27531
27967
|
var CORE_COMMANDS = [
|
|
27532
27968
|
{ name: "help", description: "List the commands", group: "Core", kind: "local" },
|
|
@@ -27571,38 +28007,6 @@ var CORE_COMMANDS = [
|
|
|
27571
28007
|
];
|
|
27572
28008
|
var ALIASES = new Map(CORE_COMMANDS.flatMap((command) => ("aliases" in command ? command.aliases : []).map((alias) => [alias, command.name])));
|
|
27573
28009
|
|
|
27574
|
-
// src/commands/routes.ts
|
|
27575
|
-
function commandRoutes(commands) {
|
|
27576
|
-
const routes = /* @__PURE__ */ new Map();
|
|
27577
|
-
const byName = /* @__PURE__ */ new Map();
|
|
27578
|
-
const ownersByPrefix = /* @__PURE__ */ new Map();
|
|
27579
|
-
const builtinPrefixes = new Map(BUILTIN_PLUGIN_REGISTRY.commands().map((command) => [command.prefix, command.pluginId]));
|
|
27580
|
-
for (const command of commands) {
|
|
27581
|
-
const owners = ownersByPrefix.get(command.prefix) ?? /* @__PURE__ */ new Map();
|
|
27582
|
-
const paths2 = owners.get(command.owner) ?? /* @__PURE__ */ new Set();
|
|
27583
|
-
paths2.add(command.path);
|
|
27584
|
-
owners.set(command.owner, paths2);
|
|
27585
|
-
ownersByPrefix.set(command.prefix, owners);
|
|
27586
|
-
const route = `${command.prefix}:${command.name}`;
|
|
27587
|
-
routes.set(route, [...routes.get(route) ?? [], command]);
|
|
27588
|
-
byName.set(command.name, [...byName.get(command.name) ?? [], command]);
|
|
27589
|
-
}
|
|
27590
|
-
const conflictingPrefixes = /* @__PURE__ */ new Map();
|
|
27591
|
-
for (const [prefix, owners] of ownersByPrefix) {
|
|
27592
|
-
const builtin = builtinPrefixes.get(prefix);
|
|
27593
|
-
if (owners.size > 1 || builtin || prefix === "plugin" || prefix === "skill") {
|
|
27594
|
-
conflictingPrefixes.set(prefix, [
|
|
27595
|
-
...builtin ? [`built-in plugin ${builtin}`] : [],
|
|
27596
|
-
...[...owners.values()].flatMap((paths2) => [...paths2])
|
|
27597
|
-
]);
|
|
27598
|
-
}
|
|
27599
|
-
}
|
|
27600
|
-
return { routes, byName, ownersByPrefix, conflictingPrefixes };
|
|
27601
|
-
}
|
|
27602
|
-
function promptCommandAvailable(name, taken, extensionCommands = /* @__PURE__ */ new Set()) {
|
|
27603
|
-
return !name.includes(":") && !taken.has(name) && !extensionCommands.has(name);
|
|
27604
|
-
}
|
|
27605
|
-
|
|
27606
28010
|
// src/commands/list.ts
|
|
27607
28011
|
function listCommands(plugins) {
|
|
27608
28012
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -27656,8 +28060,24 @@ function listCommands(plugins) {
|
|
|
27656
28060
|
});
|
|
27657
28061
|
}
|
|
27658
28062
|
}
|
|
27659
|
-
if (contribution2.kind === "command" && contribution2.path
|
|
27660
|
-
|
|
28063
|
+
if (contribution2.kind === "command" && contribution2.path) {
|
|
28064
|
+
const packageRoot = plugin.source === "package" || plugin.id.startsWith("package:") ? plugin.path : null;
|
|
28065
|
+
const producer = plugin.contributions.find((item) => item.kind === "code" && item.state === "on" && item.path && item.registered?.resources?.some((resource) => resource.kind === "command" && resource.path === contribution2.path));
|
|
28066
|
+
if (packageRoot || producer?.path) {
|
|
28067
|
+
const prefix = pluginCommandPrefix(packageRoot ? { path: contribution2.path, origin: "package", baseDir: packageRoot } : { path: producer.path });
|
|
28068
|
+
const name = packageRoot && packageManifest(packageRoot).defaultPrompt === contribution2.name ? "" : contribution2.name;
|
|
28069
|
+
if (/^[a-z][a-z0-9-]*$/.test(name) || name === "") {
|
|
28070
|
+
extensions.push({
|
|
28071
|
+
prefix,
|
|
28072
|
+
name,
|
|
28073
|
+
owner: packageRoot ? `package:${packageRoot}` : `extension:${producer.path}`,
|
|
28074
|
+
path: contribution2.path,
|
|
28075
|
+
listed: { name: `${prefix}:${name}`, description: contribution2.description, group: "Prompts", source: "prompt", owner: plugin.id }
|
|
28076
|
+
});
|
|
28077
|
+
}
|
|
28078
|
+
} else if (promptCommandAvailable(contribution2.name, taken, extensionCommandNames) && !out.has(contribution2.name)) {
|
|
28079
|
+
out.set(contribution2.name, { name: contribution2.name, description: contribution2.description, group: "Prompts", source: "prompt", owner: plugin.id });
|
|
28080
|
+
}
|
|
27661
28081
|
}
|
|
27662
28082
|
if (contribution2.kind === "skill") {
|
|
27663
28083
|
const name = `skill:${contribution2.name}`;
|
|
@@ -27673,8 +28093,8 @@ function listCommands(plugins) {
|
|
|
27673
28093
|
}
|
|
27674
28094
|
|
|
27675
28095
|
// src/variant/seed.ts
|
|
27676
|
-
import { existsSync as existsSync36, readdirSync as
|
|
27677
|
-
import { join as
|
|
28096
|
+
import { existsSync as existsSync36, readdirSync as readdirSync32, readFileSync as readFileSync36, statSync as statSync24 } from "node:fs";
|
|
28097
|
+
import { join as join77 } from "node:path";
|
|
27678
28098
|
function body(text11) {
|
|
27679
28099
|
return (parse(text11)?.body ?? text11).trim();
|
|
27680
28100
|
}
|
|
@@ -27687,15 +28107,15 @@ function read4(path4) {
|
|
|
27687
28107
|
}
|
|
27688
28108
|
function folders2(dir2) {
|
|
27689
28109
|
try {
|
|
27690
|
-
return
|
|
28110
|
+
return readdirSync32(dir2).filter((name) => !name.startsWith(".")).sort();
|
|
27691
28111
|
} catch {
|
|
27692
28112
|
return [];
|
|
27693
28113
|
}
|
|
27694
28114
|
}
|
|
27695
28115
|
function blocksDir(root) {
|
|
27696
|
-
if (existsSync36(
|
|
27697
|
-
const moved = folders2(
|
|
27698
|
-
for (const name of moved) if (existsSync36(
|
|
28116
|
+
if (existsSync36(join77(root, "wiki", "blocks"))) return join77(root, "wiki", "blocks");
|
|
28117
|
+
const moved = folders2(join77(root, "legacy")).filter((name) => name.startsWith("wiki-")).reverse();
|
|
28118
|
+
for (const name of moved) if (existsSync36(join77(root, "legacy", name, "blocks"))) return join77(root, "legacy", name, "blocks");
|
|
27699
28119
|
return null;
|
|
27700
28120
|
}
|
|
27701
28121
|
function skillName2(stem) {
|
|
@@ -27706,20 +28126,20 @@ function legacyPieces(root, input) {
|
|
|
27706
28126
|
const scope = { agentId: input.agentId ?? null, projectId: input.projectId ?? null };
|
|
27707
28127
|
const on = (id2) => attached(choices, [id2], scope, false);
|
|
27708
28128
|
const rules = [];
|
|
27709
|
-
const rulesDir =
|
|
28129
|
+
const rulesDir = join77(root, "registry", "rules");
|
|
27710
28130
|
for (const name of folders2(rulesDir)) {
|
|
27711
28131
|
const stem = name.replace(/\.md$/, "");
|
|
27712
|
-
const path4 = name.endsWith(".md") ?
|
|
28132
|
+
const path4 = name.endsWith(".md") ? join77(rulesDir, name) : join77(rulesDir, name, "SKILL.md");
|
|
27713
28133
|
const text11 = read4(path4);
|
|
27714
28134
|
if (text11 && on(`rules/${stem}`)) rules.push(body(text11));
|
|
27715
28135
|
}
|
|
27716
28136
|
const skills = {};
|
|
27717
|
-
const skillsDir =
|
|
28137
|
+
const skillsDir = join77(root, "registry", "skills");
|
|
27718
28138
|
for (const name of folders2(skillsDir)) {
|
|
27719
28139
|
const stem = name.replace(/\.md$/, "");
|
|
27720
|
-
let path4 =
|
|
28140
|
+
let path4 = join77(skillsDir, name);
|
|
27721
28141
|
try {
|
|
27722
|
-
if (
|
|
28142
|
+
if (statSync24(path4).isDirectory()) path4 = join77(path4, "SKILL.md");
|
|
27723
28143
|
} catch {
|
|
27724
28144
|
continue;
|
|
27725
28145
|
}
|
|
@@ -27738,13 +28158,13 @@ ${body(text11)}
|
|
|
27738
28158
|
const memory = [];
|
|
27739
28159
|
const blocks = blocksDir(root);
|
|
27740
28160
|
if (blocks) {
|
|
27741
|
-
const user = read4(
|
|
28161
|
+
const user = read4(join77(blocks, "shared", "user.md"));
|
|
27742
28162
|
if (user && body(user)) memory.push(`## About the person
|
|
27743
28163
|
|
|
27744
28164
|
${body(user)}`);
|
|
27745
28165
|
if (input.agentId && /^[A-Za-z0-9_-]+$/.test(input.agentId)) {
|
|
27746
28166
|
for (const [file2, title2] of [["persona.md", "Persona"], ["notes.md", "Notes"]]) {
|
|
27747
|
-
const text11 = read4(
|
|
28167
|
+
const text11 = read4(join77(blocks, "agents", input.agentId, file2));
|
|
27748
28168
|
if (text11 && body(text11)) memory.push(`## ${title2}
|
|
27749
28169
|
|
|
27750
28170
|
${body(text11)}`);
|
|
@@ -27942,6 +28362,7 @@ function methods(harness) {
|
|
|
27942
28362
|
return params.render ? { pack, text: render(env, pack) } : pack;
|
|
27943
28363
|
},
|
|
27944
28364
|
"context.runPack": (params) => load2(root(), text7(params, "runId")),
|
|
28365
|
+
"context.runDetails": (params) => runDetails(root(), text7(params, "runId")),
|
|
27945
28366
|
// What the request was made of: one row per contributor, reconciled to the provider's figure.
|
|
27946
28367
|
"context.attribution": (params) => attribution(env, text7(params, "runId")),
|
|
27947
28368
|
// ---- Plugins: everything that shapes a session, and the trust a working folder's code needs ---
|
|
@@ -28079,12 +28500,12 @@ import { spawn as nodeSpawn } from "node:child_process";
|
|
|
28079
28500
|
import { createHash as createHash16, randomUUID as randomUUID19 } from "node:crypto";
|
|
28080
28501
|
import { appendFileSync as appendFileSync2 } from "node:fs";
|
|
28081
28502
|
import { homedir as homedir11 } from "node:os";
|
|
28082
|
-
import { isAbsolute as isAbsolute13, join as
|
|
28503
|
+
import { isAbsolute as isAbsolute13, join as join79, resolve as resolve20 } from "node:path";
|
|
28083
28504
|
|
|
28084
28505
|
// src/core/interactiveProfile.ts
|
|
28085
|
-
import { closeSync as closeSync7, fsyncSync as fsyncSync3, linkSync as linkSync2, mkdirSync as mkdirSync29, openSync as openSync7, readFileSync as readFileSync37, rmSync as rmSync24, statSync as
|
|
28506
|
+
import { closeSync as closeSync7, fsyncSync as fsyncSync3, linkSync as linkSync2, mkdirSync as mkdirSync29, openSync as openSync7, readFileSync as readFileSync37, rmSync as rmSync24, statSync as statSync25, writeFileSync as writeFileSync14 } from "node:fs";
|
|
28086
28507
|
import { homedir as homedir10 } from "node:os";
|
|
28087
|
-
import { dirname as
|
|
28508
|
+
import { dirname as dirname27, isAbsolute as isAbsolute12, join as join78 } from "node:path";
|
|
28088
28509
|
var fail2 = () => {
|
|
28089
28510
|
throw new Error("Invalid interactive profile configuration.");
|
|
28090
28511
|
};
|
|
@@ -28125,7 +28546,7 @@ function validateInteractiveProfile(value) {
|
|
|
28125
28546
|
};
|
|
28126
28547
|
}
|
|
28127
28548
|
function interactiveProfilePath(home = homedir10()) {
|
|
28128
|
-
return
|
|
28549
|
+
return join78(absolute(home), ".xingular-harness", "profile.json");
|
|
28129
28550
|
}
|
|
28130
28551
|
function selectedConfigPath(location = {}) {
|
|
28131
28552
|
return location.configPath === void 0 ? interactiveProfilePath(location.home) : absolute(location.configPath);
|
|
@@ -28133,7 +28554,7 @@ function selectedConfigPath(location = {}) {
|
|
|
28133
28554
|
function readInteractiveProfile(location = {}) {
|
|
28134
28555
|
const path4 = selectedConfigPath(location);
|
|
28135
28556
|
try {
|
|
28136
|
-
const info =
|
|
28557
|
+
const info = statSync25(path4);
|
|
28137
28558
|
if (!info.isFile() || info.size > 16384) return fail2();
|
|
28138
28559
|
return validateInteractiveProfile(JSON.parse(readFileSync37(path4, "utf8")));
|
|
28139
28560
|
} catch (error) {
|
|
@@ -28149,16 +28570,16 @@ function overrides(value) {
|
|
|
28149
28570
|
}
|
|
28150
28571
|
function resolveInteractiveProfile(options2 = {}) {
|
|
28151
28572
|
const home = absolute(options2.home ?? homedir10());
|
|
28152
|
-
const root =
|
|
28573
|
+
const root = join78(home, ".xingular-harness");
|
|
28153
28574
|
const selected = readInteractiveProfile(options2);
|
|
28154
28575
|
const legacy = overrides(options2.legacyDefaults);
|
|
28155
28576
|
const explicit = overrides(options2.overrides);
|
|
28156
|
-
const dataRoot = explicit.dataRoot ?? selected?.dataRoot ?? legacy.dataRoot ??
|
|
28157
|
-
const stateDir = explicit.stateDir ?? selected?.stateDir ?? legacy.stateDir ??
|
|
28577
|
+
const dataRoot = explicit.dataRoot ?? selected?.dataRoot ?? legacy.dataRoot ?? join78(root, "data");
|
|
28578
|
+
const stateDir = explicit.stateDir ?? selected?.stateDir ?? legacy.stateDir ?? join78(root, "state");
|
|
28158
28579
|
const defaultVariantScope = explicit.defaultVariantScope ?? selected?.defaultVariantScope ?? legacy.defaultVariantScope ?? "default";
|
|
28159
28580
|
const secretBackend = explicit.secretBackend ?? selected?.secretBackend ?? legacy.secretBackend ?? {
|
|
28160
28581
|
kind: "file",
|
|
28161
|
-
path:
|
|
28582
|
+
path: join78(stateDir, "secrets.json")
|
|
28162
28583
|
};
|
|
28163
28584
|
return validateInteractiveProfile({ version: 1, dataRoot, stateDir, defaultVariantScope, secretBackend });
|
|
28164
28585
|
}
|
|
@@ -28209,27 +28630,27 @@ function resolveStandaloneOptions(argv, home = homedir11(), cwd = process.cwd())
|
|
|
28209
28630
|
if (argument !== "--data" && argument !== "--state") throw new Error(`Unknown standalone option: ${argument}`);
|
|
28210
28631
|
const value = argv[index + 1];
|
|
28211
28632
|
if (!value || value.startsWith("--")) throw new Error(`${argument} needs a directory path.`);
|
|
28212
|
-
if (argument === "--data") overrides2.dataRoot =
|
|
28213
|
-
else overrides2.stateDir =
|
|
28633
|
+
if (argument === "--data") overrides2.dataRoot = resolve20(cwd, value);
|
|
28634
|
+
else overrides2.stateDir = resolve20(cwd, value);
|
|
28214
28635
|
given.add(argument);
|
|
28215
28636
|
index += 1;
|
|
28216
28637
|
}
|
|
28217
28638
|
if (!isAbsolute13(home) && !(given.has("--data") && given.has("--state"))) {
|
|
28218
28639
|
throw new Error(`The home folder "${home}" is not an absolute path, so the default data and state folders cannot be placed. Set HOME to your home folder, or pass --data and --state with absolute folders.`);
|
|
28219
28640
|
}
|
|
28220
|
-
const devRoot = isAbsolute13(home) ?
|
|
28221
|
-
const devData = devRoot ?
|
|
28222
|
-
const devState = devRoot ?
|
|
28641
|
+
const devRoot = isAbsolute13(home) ? join79(home, ".xingular-harness-dev") : null;
|
|
28642
|
+
const devData = devRoot ? join79(devRoot, "data") : null;
|
|
28643
|
+
const devState = devRoot ? join79(devRoot, "state") : null;
|
|
28223
28644
|
const development = !!devRoot && (overrides2.dataRoot === devData || overrides2.stateDir === devState);
|
|
28224
28645
|
const resolvedProfile = isAbsolute13(home) ? resolveInteractiveProfile({
|
|
28225
28646
|
home,
|
|
28226
28647
|
...development ? {
|
|
28227
|
-
configPath:
|
|
28648
|
+
configPath: join79(devRoot, "profile.json"),
|
|
28228
28649
|
legacyDefaults: {
|
|
28229
28650
|
dataRoot: devData,
|
|
28230
28651
|
stateDir: devState,
|
|
28231
28652
|
defaultVariantScope: "default",
|
|
28232
|
-
secretBackend: { kind: "file", path:
|
|
28653
|
+
secretBackend: { kind: "file", path: join79(devState, "secrets.json") }
|
|
28233
28654
|
}
|
|
28234
28655
|
} : {},
|
|
28235
28656
|
overrides: overrides2
|
|
@@ -28238,9 +28659,9 @@ function resolveStandaloneOptions(argv, home = homedir11(), cwd = process.cwd())
|
|
|
28238
28659
|
dataRoot: overrides2.dataRoot,
|
|
28239
28660
|
stateDir: overrides2.stateDir,
|
|
28240
28661
|
defaultVariantScope: "default",
|
|
28241
|
-
secretBackend: { kind: "file", path:
|
|
28662
|
+
secretBackend: { kind: "file", path: join79(overrides2.stateDir, "secrets.json") }
|
|
28242
28663
|
};
|
|
28243
|
-
const profile = development ? { ...resolvedProfile, secretBackend: { kind: "file", path:
|
|
28664
|
+
const profile = development ? { ...resolvedProfile, secretBackend: { kind: "file", path: join79(resolvedProfile.stateDir, "secrets.json") } } : resolvedProfile;
|
|
28244
28665
|
return {
|
|
28245
28666
|
profile,
|
|
28246
28667
|
paths: {
|
|
@@ -28321,14 +28742,14 @@ async function runStandaloneTerminal(argv, dependencies = {}) {
|
|
|
28321
28742
|
if (!(dependencies.stdinIsTTY ?? process.stdin.isTTY) || !(dependencies.stdoutIsTTY ?? process.stdout.isTTY)) {
|
|
28322
28743
|
throw new Error("The interactive Harness terminal needs a TTY. Run it from a terminal, or use `xagent serve` for a host process.");
|
|
28323
28744
|
}
|
|
28324
|
-
let cwd =
|
|
28745
|
+
let cwd = resolve20(dependencies.cwd ?? process.cwd());
|
|
28325
28746
|
const { paths: paths2, profile } = resolveStandaloneOptions(argv, dependencies.home, cwd);
|
|
28326
28747
|
const secrets = await interactiveProfileSecretStore(profile, dependencies.hostSecretStore);
|
|
28327
28748
|
const makeHarness = dependencies.createHarness ?? createHarness;
|
|
28328
28749
|
const harness = makeHarness({ dataRoot: paths2.dataRoot, stateDir: paths2.stateDir, productName: DEFAULT_PRODUCT_NAME, secrets });
|
|
28329
28750
|
const newSessionId = dependencies.createSessionId ?? randomUUID19;
|
|
28330
28751
|
let current2 = { sessionId: newSessionId(), accountId: null, nativeSessionId: null };
|
|
28331
|
-
const logFile =
|
|
28752
|
+
const logFile = join79(paths2.stateDir, "harness.log");
|
|
28332
28753
|
setLogSink((text11) => {
|
|
28333
28754
|
try {
|
|
28334
28755
|
appendFileSync2(logFile, `${(/* @__PURE__ */ new Date()).toISOString()} ${text11}
|
|
@@ -28350,7 +28771,7 @@ async function runStandaloneTerminal(argv, dependencies = {}) {
|
|
|
28350
28771
|
worktreeId = worktree.id;
|
|
28351
28772
|
}
|
|
28352
28773
|
for (; ; ) {
|
|
28353
|
-
const sessionFolder =
|
|
28774
|
+
const sessionFolder = join79(paths2.dataRoot, "sessions", "terminal", createHash16("sha256").update(cwd).digest("hex"));
|
|
28354
28775
|
const launch = await harness.terminals.prepare({ ...current2, cwd, sessionFolder, variant: { scope: variantScope }, ...worktreeId ? { run: { worktreeId } } : {} });
|
|
28355
28776
|
const code = await spawnFrontend(paths2.remoteControl ? { ...launch, env: { ...launch.env, [REMOTE_CONTROL_ENV]: "1" } } : launch, cwd, dependencies.spawn);
|
|
28356
28777
|
const next = harness.terminals.takeRelaunch(current2.sessionId);
|
|
@@ -28374,7 +28795,7 @@ async function runStandaloneTerminal(argv, dependencies = {}) {
|
|
|
28374
28795
|
|
|
28375
28796
|
// src/worktrees/cli.ts
|
|
28376
28797
|
import { homedir as homedir12 } from "node:os";
|
|
28377
|
-
import { join as
|
|
28798
|
+
import { join as join80, resolve as resolve21 } from "node:path";
|
|
28378
28799
|
var WORKTREE_USAGE = `Usage: xagent worktree <command> [--state <dir>]
|
|
28379
28800
|
|
|
28380
28801
|
list Worktrees of the repository you are in
|
|
@@ -28409,11 +28830,11 @@ async function findWorktree(worktrees, cwd, key, removed = false) {
|
|
|
28409
28830
|
}
|
|
28410
28831
|
async function runWorktreeCli(argv, dependencies = {}) {
|
|
28411
28832
|
const write4 = dependencies.write ?? ((text11) => void process.stdout.write(text11));
|
|
28412
|
-
const cwd =
|
|
28833
|
+
const cwd = resolve21(dependencies.cwd ?? process.cwd());
|
|
28413
28834
|
const args = [];
|
|
28414
|
-
let stateDir =
|
|
28835
|
+
let stateDir = join80(dependencies.home ?? homedir12(), ".xingular-harness", "state");
|
|
28415
28836
|
for (let index = 0; index < argv.length; index += 1) {
|
|
28416
|
-
if (argv[index] === "--state") stateDir =
|
|
28837
|
+
if (argv[index] === "--state") stateDir = resolve21(argv[++index] ?? stateDir);
|
|
28417
28838
|
else args.push(argv[index]);
|
|
28418
28839
|
}
|
|
28419
28840
|
const [command, ...rest] = args;
|
|
@@ -28497,7 +28918,7 @@ ${status.changes} changed path(s), ${status.ahead} commit(s) since it started.
|
|
|
28497
28918
|
return result.failed.length ? 1 : 0;
|
|
28498
28919
|
}
|
|
28499
28920
|
case "adopt": {
|
|
28500
|
-
const record3 = await worktrees.adopt(
|
|
28921
|
+
const record3 = await worktrees.adopt(resolve21(cwd, positional[0] ?? ""));
|
|
28501
28922
|
write4(`${describe5(record3)}
|
|
28502
28923
|
`);
|
|
28503
28924
|
return 0;
|
|
@@ -28579,11 +29000,11 @@ function serveCommand(argv) {
|
|
|
28579
29000
|
process.stderr.write("serve needs --state <dir>.\n");
|
|
28580
29001
|
return Promise.resolve(2);
|
|
28581
29002
|
}
|
|
28582
|
-
const stateDir =
|
|
29003
|
+
const stateDir = resolve22(stateArgument);
|
|
28583
29004
|
const peer = new RpcPeer(stdioTransport());
|
|
28584
29005
|
const options2 = {
|
|
28585
29006
|
stateDir,
|
|
28586
|
-
dataRoot: values2.data?.[0] ?
|
|
29007
|
+
dataRoot: values2.data?.[0] ? resolve22(values2.data[0]) : null,
|
|
28587
29008
|
productName: values2.name?.[0],
|
|
28588
29009
|
builtins: values2.builtins,
|
|
28589
29010
|
...switches.has("host-secrets") ? { secrets: remoteSecrets(peer) } : {},
|
|
@@ -28593,12 +29014,12 @@ function serveCommand(argv) {
|
|
|
28593
29014
|
const harness = createHarness(options2);
|
|
28594
29015
|
if (switches.has("host-tools")) harness.sessions.provide(remoteTools(peer));
|
|
28595
29016
|
attach3(harness, peer);
|
|
28596
|
-
return new Promise((
|
|
29017
|
+
return new Promise((resolve23) => {
|
|
28597
29018
|
let closing = false;
|
|
28598
29019
|
const shutdown = () => {
|
|
28599
29020
|
if (closing) return;
|
|
28600
29021
|
closing = true;
|
|
28601
|
-
void Promise.race([harness.close(), new Promise((done) => setTimeout(done, 5e3))]).then(() =>
|
|
29022
|
+
void Promise.race([harness.close(), new Promise((done) => setTimeout(done, 5e3))]).then(() => resolve23(0));
|
|
28602
29023
|
};
|
|
28603
29024
|
peer.handle("harness.shutdown", () => void setImmediate(shutdown));
|
|
28604
29025
|
process.stdin.on("end", shutdown);
|