@xingular/harness 1.4.0-unstable → 1.5.0-unstable
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 +41 -1
- package/NOTICE.md +9 -7
- package/dist/cli.js +160 -456
- package/dist/sdk/prompt/compile.d.ts +4 -2
- package/dist/sdk/prompt/compile.js +10 -8
- package/dist/sdk/prompt/reporting.d.ts +2 -2
- package/dist/sdk/prompt/reporting.js +40 -250
- package/dist/sdk/prompt/standard.d.ts +4 -1
- package/dist/sdk/prompt/standard.js +6 -2
- package/dist/sdk/sessions/completionCheck.d.ts +4 -2
- package/dist/sdk/sessions/completionCheck.js +19 -1
- package/dist/sdk/sessions/contracts.js +2 -2
- package/dist/sdk/sessions/goalStop.d.ts +27 -0
- package/dist/sdk/sessions/goalStop.js +41 -0
- package/dist/sdk/sessions/runner.d.ts +0 -3
- package/dist/sdk/sessions/runner.js +16 -9
- package/dist/sdk/sessions/sessions.js +6 -36
- package/dist/sdk/sessions/workSlice.js +1 -1
- package/dist/sdk/terminal/piExtension.js +18 -31
- package/dist/sdk/terminal/terminals.js +1 -1
- package/dist/sdk/tools/askUser.js +1 -1
- package/dist/sdk/tools/todo.js +4 -3
- package/dist/terminal-opentui.js +108 -200
- package/dist/terminal-pi.js +262 -338
- package/docs/architecture/02-sessions-and-runs.md +13 -15
- package/docs/architecture/04-prompt.md +41 -35
- package/docs/architecture/07-tools-and-gate.md +2 -2
- package/docs/architecture/10-server-protocol.md +6 -5
- package/docs/architecture/11-terminal.md +7 -7
- package/docs/decisions/ADR-0008-flexible-reporting-guidance.md +2 -2
- package/docs/decisions/ADR-0069-stop-gate-and-finish.md +8 -2
- package/docs/decisions/ADR-0070-terminal-slice-continuation.md +4 -1
- package/docs/decisions/ADR-0072-codex-prompts-and-goal.md +3 -0
- package/docs/decisions/ADR-0073-large-reporting-prompt.md +6 -1
- package/docs/decisions/ADR-0074-report-content-not-report-blocks.md +90 -0
- package/docs/decisions/ADR-0075-no-todo-gate.md +84 -0
- package/docs/decisions/ADR-0076-codex-final-answer-verbatim.md +93 -0
- package/docs/releases/1.4.0-unstable.md +9 -2
- package/docs/releases/1.5.0-unstable.md +23 -0
- package/docs/study/STUDY-0004-turn-end-controls.md +7 -0
- package/docs/study/STUDY-0006-reporting-prompts.md +48 -10
- package/package.json +1 -1
- package/dist/sdk/sessions/stopGate.d.ts +0 -78
- package/dist/sdk/sessions/stopGate.js +0 -143
package/dist/terminal-opentui.js
CHANGED
|
@@ -1429,7 +1429,7 @@ function isActiveStatus(status) {
|
|
|
1429
1429
|
|
|
1430
1430
|
// src/tools/todo.ts
|
|
1431
1431
|
var TODO_WRITE = "todo_write";
|
|
1432
|
-
var TODO_GUIDANCE = "If the work has several distinct steps
|
|
1432
|
+
var TODO_GUIDANCE = "If the work has several distinct steps, call todo_write before your first other tool call with the steps you know so far, and keep the list current as steps start and finish. " + 'The list shows the person your progress; it is not the scope of the work. The request, and any document it points to, is the scope: add steps as the work shows them, and list the steps of a handoff or plan instead of folding them into one "remaining work" item. ' + "Keep the list true: mark a step completed only once it is done and checked, and never mark unfinished work completed. Before you end the turn, compare the request, not the list, with what is done and checked; if a step stays open, say why in the reply. " + "When a login blocks a step, leave it open and give the exact command the person must run in the reply. " + "The person follows your work from it, and a subagent\u2019s todo list shows as its progress in the person\u2019s list of subagents.";
|
|
1433
1433
|
var TODO_EVENTS = new Set(["todo.updated", "checklist.updated"]);
|
|
1434
1434
|
var MARK = { pending: "[ ]", in_progress: "[~]", completed: "[x]" };
|
|
1435
1435
|
function describeTodo(items) {
|
|
@@ -1666,7 +1666,7 @@ function askUserTool(run) {
|
|
|
1666
1666
|
return {
|
|
1667
1667
|
name: ASK_USER,
|
|
1668
1668
|
label: "Ask the user",
|
|
1669
|
-
description: "Ask the user one to five related questions in one request and wait until they answer the batch. Give each question a concise 1\u20132 word label and aim to keep each question to 25 words or fewer. " + "Every question must offer 1\u20136 short choices, each with a helpful description of 25 words or fewer, and at least one choice must be marked recommended: true. Recommendations are visual only: they never preselect an answer, and submitted choices keep their original labels. Mark at most one recommended choice for a single-choice question, or several when multiSelect is true. Every question also includes a free-text \u201CSomething else\u201D answer. Set multiSelect when several can apply. When a UI diagram helps compare choices, set uiSketch: true and give every choice a detailed, bounded ASCII wireframe. Use ASCII tables and panel borders to show the affected element with relevant neighboring UI, readable labels and spacing. Keep each panel\u2019s outer `+` corners in fixed columns, include side edges at those same columns on every content row, and run horizontal borders corner to corner; if a title sits in a top border, leave at least two dashes on both sides. Keep framing consistent across alternatives and concentrate detail on what changes. Do not substitute a symbolic logo or one-line illustration. The preview supplies its outer border; draw internal borders only for actual UI panels. Use it when you need a decision the request and the files do not answer, not for small talk and never to ask permission for work inside the request. If no answer arrives, continue with your best judgment instead of asking again or treating the turn as blocked.",
|
|
1669
|
+
description: "Ask the user one to five related questions in one request and wait until they answer the batch. Give each question a concise 1\u20132 word label and aim to keep each question to 25 words or fewer. " + "Every question must offer 1\u20136 short choices, each with a helpful description of 25 words or fewer, and at least one choice must be marked recommended: true. Recommendations are visual only: they never preselect an answer, and submitted choices keep their original labels. Mark at most one recommended choice for a single-choice question, or several when multiSelect is true. Every question also includes a free-text \u201CSomething else\u201D answer. Set multiSelect when several can apply. When a UI diagram helps compare choices, set uiSketch: true and give every choice a detailed, bounded ASCII wireframe. Use ASCII tables and panel borders to show the affected element with relevant neighboring UI, readable labels and spacing. Keep each panel\u2019s outer `+` corners in fixed columns, include side edges at those same columns on every content row, and run horizontal borders corner to corner; if a title sits in a top border, leave at least two dashes on both sides. Keep framing consistent across alternatives and concentrate detail on what changes. Do not substitute a symbolic logo or one-line illustration. The preview supplies its outer border; draw internal borders only for actual UI panels. Use it when you need a decision the request and the files do not answer, not for small talk and never to ask permission for work inside the request. If no answer arrives, continue with your best judgment instead of asking again or treating the turn as blocked. Never use it to ask for a login, a secret or a credential: give the exact command in the reply instead.",
|
|
1670
1670
|
parameters: (Type) => Type.Object({
|
|
1671
1671
|
questions: Type.Array(Type.Object({
|
|
1672
1672
|
summary: Type.String({ description: "A concise label of one or two words" }),
|
|
@@ -17361,73 +17361,36 @@ import { readFileSync as readFileSync9 } from "fs";
|
|
|
17361
17361
|
import { createBashTool, createEditTool, createEditToolDefinition, createPowerShellTool, createWriteTool, createWriteToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
17362
17362
|
import { Type as Type2 } from "typebox";
|
|
17363
17363
|
|
|
17364
|
-
// src/sessions/
|
|
17365
|
-
|
|
17366
|
-
|
|
17367
|
-
|
|
17368
|
-
|
|
17369
|
-
const
|
|
17370
|
-
|
|
17371
|
-
|
|
17372
|
-
|
|
17373
|
-
|
|
17374
|
-
status: item?.status
|
|
17375
|
-
});
|
|
17376
|
-
if (!parsed.success)
|
|
17377
|
-
return null;
|
|
17378
|
-
items.push(parsed.data);
|
|
17364
|
+
// src/sessions/goalStop.ts
|
|
17365
|
+
function openItems(todo) {
|
|
17366
|
+
return todo.filter((item) => item.status !== "completed");
|
|
17367
|
+
}
|
|
17368
|
+
function goalStopOutcome(todo, goal) {
|
|
17369
|
+
const open = openItems(todo);
|
|
17370
|
+
if (goal?.status === "blocked")
|
|
17371
|
+
return { code: "needs_person", reason: goal.lastReason || "The goal is blocked.", open };
|
|
17372
|
+
if (goal && (goal.status === "budget_limited" || goal.status === "usage_limited" || goal.status === "paused")) {
|
|
17373
|
+
return { code: "unfinished", reason: goal.lastReason || `The goal is ${goal.status.replace("_", " ")}.`, open };
|
|
17379
17374
|
}
|
|
17380
|
-
return
|
|
17375
|
+
return null;
|
|
17381
17376
|
}
|
|
17382
|
-
function
|
|
17383
|
-
|
|
17384
|
-
|
|
17385
|
-
|
|
17386
|
-
|
|
17387
|
-
|
|
17388
|
-
|
|
17389
|
-
goal = prompt;
|
|
17390
|
-
todo = validatedTodo(options.initialTodo ?? []) ?? [];
|
|
17391
|
-
baseline = undefined;
|
|
17392
|
-
checkpoint = undefined;
|
|
17393
|
-
};
|
|
17394
|
-
pi.on("before_agent_start", (event) => {
|
|
17395
|
-
if (event.messageType !== "user")
|
|
17396
|
-
return;
|
|
17397
|
-
reset(event.prompt);
|
|
17398
|
-
});
|
|
17399
|
-
pi.on("tool_result", (event) => {
|
|
17400
|
-
if (event.toolName !== TODO_WRITE || event.isError)
|
|
17401
|
-
return;
|
|
17402
|
-
const items = validatedTodo(event.input.items);
|
|
17403
|
-
if (items)
|
|
17404
|
-
todo = items;
|
|
17405
|
-
});
|
|
17406
|
-
pi.on("context", (event) => {
|
|
17407
|
-
const index = event.messages.findLastIndex((message) => message.role === "compactionSummary");
|
|
17408
|
-
const summary = index >= 0 ? event.messages[index] : undefined;
|
|
17409
|
-
const key = summary ? `${summary.timestamp}:${summary.tokensBefore}` : null;
|
|
17410
|
-
if (!baseline) {
|
|
17411
|
-
baseline = { key };
|
|
17412
|
-
return;
|
|
17413
|
-
}
|
|
17414
|
-
if (!key || key === baseline.key)
|
|
17415
|
-
return;
|
|
17416
|
-
if (checkpoint?.key !== key) {
|
|
17417
|
-
const content = renderCompactionCheckpoint({ goal, todo, planInstructions: options.planInstructions?.() ?? null });
|
|
17418
|
-
checkpoint = {
|
|
17419
|
-
key,
|
|
17420
|
-
message: content ? { role: "custom", customType: COMPACTION_CHECKPOINT_EXTENSION, content, display: false, timestamp: Date.now() } : null
|
|
17421
|
-
};
|
|
17422
|
-
}
|
|
17423
|
-
if (!checkpoint.message)
|
|
17424
|
-
return;
|
|
17425
|
-
return { messages: [...event.messages.slice(0, index + 1), checkpoint.message, ...event.messages.slice(index + 1)] };
|
|
17426
|
-
});
|
|
17427
|
-
pi.on("agent_settled", () => reset());
|
|
17428
|
-
pi.on("session_tree", () => reset());
|
|
17377
|
+
function handoffFields(todo, goal) {
|
|
17378
|
+
const next = openItems(todo)[0]?.text || "";
|
|
17379
|
+
const blocked = goal?.status === "blocked" ? (goal.lastReason || "The goal is blocked.").slice(0, 500) : "";
|
|
17380
|
+
return {
|
|
17381
|
+
done: todo.filter((item) => item.status === "completed").slice(0, MAX_CHECKPOINT_DONE).map((item) => ({ item: item.text.slice(0, 300), evidence: [] })),
|
|
17382
|
+
next: next.slice(0, 500),
|
|
17383
|
+
...blocked ? { blocked } : {}
|
|
17429
17384
|
};
|
|
17430
17385
|
}
|
|
17386
|
+
var stopCheckpointReason = (outcome) => outcome.code === "needs_person" ? "requested" : "stuck";
|
|
17387
|
+
function stopSummary(outcome) {
|
|
17388
|
+
if (outcome.code === "needs_person")
|
|
17389
|
+
return `The reply stopped for the person: ${outcome.reason}`.slice(0, 1200);
|
|
17390
|
+
if (outcome.reason)
|
|
17391
|
+
return `The reply ended with work left: ${outcome.reason}`.slice(0, 1200);
|
|
17392
|
+
return `The reply ended with ${outcome.open.length} open todo item${outcome.open.length === 1 ? "" : "s"}.`;
|
|
17393
|
+
}
|
|
17431
17394
|
|
|
17432
17395
|
// src/sessions/completionCheck.ts
|
|
17433
17396
|
var MAX_CHECK_TRANSCRIPT = 24000;
|
|
@@ -17555,121 +17518,6 @@ async function runCompletionCheck(ask, input, options = {}) {
|
|
|
17555
17518
|
function answerText(message) {
|
|
17556
17519
|
return textOf(message?.content);
|
|
17557
17520
|
}
|
|
17558
|
-
function launchSwitchOff(name) {
|
|
17559
|
-
const value = process.env[name]?.trim().toLowerCase();
|
|
17560
|
-
return value === "off" || value === "0" || value === "false";
|
|
17561
|
-
}
|
|
17562
|
-
|
|
17563
|
-
// src/sessions/stopGate.ts
|
|
17564
|
-
var STOP_GATE_EXTENSION = "stop-gate";
|
|
17565
|
-
var STOP_GATE_CUSTOM_TYPE = "xingular-stop-gate";
|
|
17566
|
-
var MAX_STOP_GATE_NUDGES = 3;
|
|
17567
|
-
var BOOKKEEPING_TOOLS = new Set([TODO_WRITE, "session_name"]);
|
|
17568
|
-
function createStopGateState(initialTodo = []) {
|
|
17569
|
-
return { todo: validatedTodo(initialTodo) ?? [], request: "", nudges: 0, callsSinceNudge: 0, callsInReply: 0 };
|
|
17570
|
-
}
|
|
17571
|
-
function resetStopGateState(state, initialTodo = [], request = "") {
|
|
17572
|
-
state.todo = validatedTodo(initialTodo) ?? [];
|
|
17573
|
-
state.request = request;
|
|
17574
|
-
state.nudges = 0;
|
|
17575
|
-
state.callsSinceNudge = 0;
|
|
17576
|
-
state.callsInReply = 0;
|
|
17577
|
-
}
|
|
17578
|
-
function openItems(todo) {
|
|
17579
|
-
return todo.filter((item) => item.status !== "completed");
|
|
17580
|
-
}
|
|
17581
|
-
function stopOutcome(state, goal) {
|
|
17582
|
-
const open = openItems(state.todo);
|
|
17583
|
-
if (goal?.status === "blocked")
|
|
17584
|
-
return { code: "needs_person", reason: goal.lastReason || "The goal is blocked.", open };
|
|
17585
|
-
if (goal && (goal.status === "budget_limited" || goal.status === "usage_limited" || goal.status === "paused")) {
|
|
17586
|
-
return { code: "unfinished", reason: goal.lastReason || `The goal is ${goal.status.replace("_", " ")}.`, open };
|
|
17587
|
-
}
|
|
17588
|
-
if (open.length)
|
|
17589
|
-
return { code: "unfinished", reason: "", open };
|
|
17590
|
-
return null;
|
|
17591
|
-
}
|
|
17592
|
-
function handoffFields(state, goal) {
|
|
17593
|
-
const todo = state?.todo ?? [];
|
|
17594
|
-
const next = openItems(todo)[0]?.text || "";
|
|
17595
|
-
const blocked = goal?.status === "blocked" ? (goal.lastReason || "The goal is blocked.").slice(0, 500) : "";
|
|
17596
|
-
return {
|
|
17597
|
-
done: todo.filter((item) => item.status === "completed").slice(0, MAX_CHECKPOINT_DONE).map((item) => ({ item: item.text.slice(0, 300), evidence: [] })),
|
|
17598
|
-
next: next.slice(0, 500),
|
|
17599
|
-
...blocked ? { blocked } : {}
|
|
17600
|
-
};
|
|
17601
|
-
}
|
|
17602
|
-
var stopCheckpointReason = (outcome) => outcome.code === "needs_person" ? "requested" : "stuck";
|
|
17603
|
-
function stopSummary(outcome) {
|
|
17604
|
-
if (outcome.code === "needs_person")
|
|
17605
|
-
return `The reply stopped for the person: ${outcome.reason}`.slice(0, 1200);
|
|
17606
|
-
if (outcome.reason)
|
|
17607
|
-
return `The reply ended with work left: ${outcome.reason}`.slice(0, 1200);
|
|
17608
|
-
return `The reply ended with ${outcome.open.length} open todo item${outcome.open.length === 1 ? "" : "s"}.`;
|
|
17609
|
-
}
|
|
17610
|
-
function nudgeText(count, max, open) {
|
|
17611
|
-
const list = open.slice(0, 12).map((item) => `- ${item.text}`).join(`
|
|
17612
|
-
`);
|
|
17613
|
-
const more = open.length > 12 ? `
|
|
17614
|
-
- \u2026and ${open.length - 12} more` : "";
|
|
17615
|
-
return [
|
|
17616
|
-
`Harness stop gate (${count}/${max}): your reply ended with ${open.length} open todo item${open.length === 1 ? "" : "s"}:`,
|
|
17617
|
-
`${list}${more}`,
|
|
17618
|
-
"Continue with the next open item now. Mark an item completed only after it is done and checked.",
|
|
17619
|
-
"The list shows progress; it is not the limit of the work. If the request needs more than the list says, add it and do it.",
|
|
17620
|
-
"Decisions inside the request are yours. Stop only for something only the person can do (a deploy, a secret, remote data, money, missing access), and then say exactly what it is."
|
|
17621
|
-
].join(`
|
|
17622
|
-
`);
|
|
17623
|
-
}
|
|
17624
|
-
function stopGateNotice(event) {
|
|
17625
|
-
return `Stop gate ${event.count}: the reply ended with ${event.open.length} open todo item${event.open.length === 1 ? "" : "s"}; continuing.`;
|
|
17626
|
-
}
|
|
17627
|
-
function stopGate(options) {
|
|
17628
|
-
const { state } = options;
|
|
17629
|
-
const max = options.max ?? MAX_STOP_GATE_NUDGES;
|
|
17630
|
-
return (pi) => {
|
|
17631
|
-
pi.on("before_agent_start", (event) => {
|
|
17632
|
-
if (event.messageType !== undefined && event.messageType !== "user")
|
|
17633
|
-
return;
|
|
17634
|
-
resetStopGateState(state, options.initialTodo, options.request?.() || (typeof event.prompt === "string" ? event.prompt : ""));
|
|
17635
|
-
});
|
|
17636
|
-
pi.on("tool_call", (event) => {
|
|
17637
|
-
if (BOOKKEEPING_TOOLS.has(event.toolName))
|
|
17638
|
-
return;
|
|
17639
|
-
state.callsSinceNudge += 1;
|
|
17640
|
-
state.callsInReply += 1;
|
|
17641
|
-
});
|
|
17642
|
-
pi.on("tool_result", (event) => {
|
|
17643
|
-
if (event.toolName !== TODO_WRITE || event.isError)
|
|
17644
|
-
return;
|
|
17645
|
-
const items = validatedTodo(event.input?.items);
|
|
17646
|
-
if (items)
|
|
17647
|
-
state.todo = items;
|
|
17648
|
-
});
|
|
17649
|
-
pi.on("agent_before_settle", (event, ctx) => {
|
|
17650
|
-
if (event.outcome !== "completed" || event.continue || ctx?.signal?.aborted)
|
|
17651
|
-
return;
|
|
17652
|
-
if (options.active && !options.active())
|
|
17653
|
-
return;
|
|
17654
|
-
if (options.sliceEnded?.() || options.failed?.())
|
|
17655
|
-
return;
|
|
17656
|
-
if (state.nudges >= max)
|
|
17657
|
-
return;
|
|
17658
|
-
if (state.nudges > 0 && state.callsSinceNudge === 0)
|
|
17659
|
-
return;
|
|
17660
|
-
const open = openItems(state.todo);
|
|
17661
|
-
if (!open.length)
|
|
17662
|
-
return;
|
|
17663
|
-
state.nudges += 1;
|
|
17664
|
-
state.callsSinceNudge = 0;
|
|
17665
|
-
options.onEvent?.({ kind: "todo", count: state.nudges, open });
|
|
17666
|
-
return {
|
|
17667
|
-
entries: [...event.entries, { type: "custom_message", customType: STOP_GATE_CUSTOM_TYPE, content: nudgeText(state.nudges, max, open), display: true }],
|
|
17668
|
-
continue: true
|
|
17669
|
-
};
|
|
17670
|
-
});
|
|
17671
|
-
};
|
|
17672
|
-
}
|
|
17673
17521
|
function sessionCheckModel(ctx) {
|
|
17674
17522
|
const session = ctx ?? {};
|
|
17675
17523
|
const model = session.model;
|
|
@@ -17686,6 +17534,10 @@ function sessionCheckModel(ctx) {
|
|
|
17686
17534
|
return { text: answerText(message), message };
|
|
17687
17535
|
};
|
|
17688
17536
|
}
|
|
17537
|
+
function launchSwitchOff(name) {
|
|
17538
|
+
const value = process.env[name]?.trim().toLowerCase();
|
|
17539
|
+
return value === "off" || value === "0" || value === "false";
|
|
17540
|
+
}
|
|
17689
17541
|
|
|
17690
17542
|
// src/sessions/goalLoop.ts
|
|
17691
17543
|
var GOAL_EXTENSION = "goal";
|
|
@@ -17813,6 +17665,7 @@ function goalNotice(event) {
|
|
|
17813
17665
|
}
|
|
17814
17666
|
|
|
17815
17667
|
// src/sessions/workSlice.ts
|
|
17668
|
+
var WORK_SLICE_EXTENSION = "work-slice";
|
|
17816
17669
|
var DEFAULT_SLICE_POLICY = {
|
|
17817
17670
|
enabled: true,
|
|
17818
17671
|
maxToolCalls: 320,
|
|
@@ -18056,6 +17909,74 @@ async function withCommandBudget(run, abort, seconds) {
|
|
|
18056
17909
|
}
|
|
18057
17910
|
}
|
|
18058
17911
|
|
|
17912
|
+
// src/sessions/compactionCheckpoint.ts
|
|
17913
|
+
var COMPACTION_CHECKPOINT_EXTENSION = "compaction-checkpoint";
|
|
17914
|
+
function validatedTodo(value) {
|
|
17915
|
+
if (!Array.isArray(value) || value.length > MAX_TODO_ITEMS)
|
|
17916
|
+
return null;
|
|
17917
|
+
const items = [];
|
|
17918
|
+
for (const valueItem of value) {
|
|
17919
|
+
const item = valueItem;
|
|
17920
|
+
const parsed = todoItemSchema.safeParse({
|
|
17921
|
+
text: typeof item?.text === "string" ? item.text.trim() : item?.text,
|
|
17922
|
+
status: item?.status
|
|
17923
|
+
});
|
|
17924
|
+
if (!parsed.success)
|
|
17925
|
+
return null;
|
|
17926
|
+
items.push(parsed.data);
|
|
17927
|
+
}
|
|
17928
|
+
return items;
|
|
17929
|
+
}
|
|
17930
|
+
function compactionCheckpoint(options = {}) {
|
|
17931
|
+
return (pi) => {
|
|
17932
|
+
let goal = "";
|
|
17933
|
+
let todo = validatedTodo(options.initialTodo ?? []) ?? [];
|
|
17934
|
+
let baseline;
|
|
17935
|
+
let checkpoint;
|
|
17936
|
+
const reset = (prompt = "") => {
|
|
17937
|
+
goal = prompt;
|
|
17938
|
+
todo = validatedTodo(options.initialTodo ?? []) ?? [];
|
|
17939
|
+
baseline = undefined;
|
|
17940
|
+
checkpoint = undefined;
|
|
17941
|
+
};
|
|
17942
|
+
pi.on("before_agent_start", (event) => {
|
|
17943
|
+
if (event.messageType !== "user")
|
|
17944
|
+
return;
|
|
17945
|
+
reset(event.prompt);
|
|
17946
|
+
});
|
|
17947
|
+
pi.on("tool_result", (event) => {
|
|
17948
|
+
if (event.toolName !== TODO_WRITE || event.isError)
|
|
17949
|
+
return;
|
|
17950
|
+
const items = validatedTodo(event.input.items);
|
|
17951
|
+
if (items)
|
|
17952
|
+
todo = items;
|
|
17953
|
+
});
|
|
17954
|
+
pi.on("context", (event) => {
|
|
17955
|
+
const index = event.messages.findLastIndex((message) => message.role === "compactionSummary");
|
|
17956
|
+
const summary = index >= 0 ? event.messages[index] : undefined;
|
|
17957
|
+
const key = summary ? `${summary.timestamp}:${summary.tokensBefore}` : null;
|
|
17958
|
+
if (!baseline) {
|
|
17959
|
+
baseline = { key };
|
|
17960
|
+
return;
|
|
17961
|
+
}
|
|
17962
|
+
if (!key || key === baseline.key)
|
|
17963
|
+
return;
|
|
17964
|
+
if (checkpoint?.key !== key) {
|
|
17965
|
+
const content = renderCompactionCheckpoint({ goal, todo, planInstructions: options.planInstructions?.() ?? null });
|
|
17966
|
+
checkpoint = {
|
|
17967
|
+
key,
|
|
17968
|
+
message: content ? { role: "custom", customType: COMPACTION_CHECKPOINT_EXTENSION, content, display: false, timestamp: Date.now() } : null
|
|
17969
|
+
};
|
|
17970
|
+
}
|
|
17971
|
+
if (!checkpoint.message)
|
|
17972
|
+
return;
|
|
17973
|
+
return { messages: [...event.messages.slice(0, index + 1), checkpoint.message, ...event.messages.slice(index + 1)] };
|
|
17974
|
+
});
|
|
17975
|
+
pi.on("agent_settled", () => reset());
|
|
17976
|
+
pi.on("session_tree", () => reset());
|
|
17977
|
+
};
|
|
17978
|
+
}
|
|
17979
|
+
|
|
18059
17980
|
// src/world/images.ts
|
|
18060
17981
|
var KEEP_IMAGES = 6;
|
|
18061
17982
|
var PRUNE_STEP = 4;
|
|
@@ -19025,7 +18946,6 @@ async function piExtension_default(pi, planRun, getSession, goal) {
|
|
|
19025
18946
|
console.error(`${error instanceof Error ? error.message : String(error)} Using the default work slices.`);
|
|
19026
18947
|
}
|
|
19027
18948
|
const sliceState = createSliceState(slicePolicy);
|
|
19028
|
-
const gateState = createStopGateState();
|
|
19029
18949
|
const recordCheckUsage = (raw) => {
|
|
19030
18950
|
const message = raw;
|
|
19031
18951
|
const reported = message.usage;
|
|
@@ -19305,18 +19225,6 @@ async function piExtension_default(pi, planRun, getSession, goal) {
|
|
|
19305
19225
|
continue: true
|
|
19306
19226
|
};
|
|
19307
19227
|
});
|
|
19308
|
-
if (!launchSwitchOff("XINGULAR_STOP_GATE"))
|
|
19309
|
-
stopGate({
|
|
19310
|
-
state: gateState,
|
|
19311
|
-
active: () => !planRun?.active,
|
|
19312
|
-
sliceEnded: () => sliceState.endedBy !== null,
|
|
19313
|
-
failed: () => Boolean(failure),
|
|
19314
|
-
onEvent: (event) => {
|
|
19315
|
-
terminalCall("notice", { kind: "notify", level: "info", extension: STOP_GATE_EXTENSION, text: stopGateNotice(event) }).catch(() => {
|
|
19316
|
-
return;
|
|
19317
|
-
});
|
|
19318
|
-
}
|
|
19319
|
-
})(pi);
|
|
19320
19228
|
if (goal) {
|
|
19321
19229
|
for (const spec of goalTools(() => goal))
|
|
19322
19230
|
pi.registerTool({
|
|
@@ -19375,13 +19283,13 @@ async function piExtension_default(pi, planRun, getSession, goal) {
|
|
|
19375
19283
|
failed: () => Boolean(failure),
|
|
19376
19284
|
blocked: () => goal?.get()?.status === "blocked",
|
|
19377
19285
|
goalActive: () => goal?.active() === true,
|
|
19378
|
-
todo: () =>
|
|
19379
|
-
next: () => handoffFields(
|
|
19286
|
+
todo: () => lastTodo,
|
|
19287
|
+
next: () => handoffFields(lastTodo, goal?.get() ?? null).next,
|
|
19380
19288
|
onContinue: (ended) => {
|
|
19381
|
-
terminalCall("checkpoint", { summary: `Work slice ${ended.slice} reached its limit after ${ended.steps} steps; the reply continues.`, ...handoffFields(
|
|
19289
|
+
terminalCall("checkpoint", { summary: `Work slice ${ended.slice} reached its limit after ${ended.steps} steps; the reply continues.`, ...handoffFields(lastTodo, goal?.get() ?? null), files: [], checks: [], decisions: [], slice: ended.slice, reason: "budget" }).catch(() => {
|
|
19382
19290
|
return;
|
|
19383
19291
|
});
|
|
19384
|
-
terminalCall("notice", { kind: "notify", level: "info", extension:
|
|
19292
|
+
terminalCall("notice", { kind: "notify", level: "info", extension: WORK_SLICE_EXTENSION, text: `Work slice ${ended.slice} reached ${ended.limit ?? "its limit"}; continuing with slice ${ended.slice + 1} of up to ${slicePolicy.maxSlices}.` }).catch(() => {
|
|
19385
19293
|
return;
|
|
19386
19294
|
});
|
|
19387
19295
|
}
|
|
@@ -19585,13 +19493,13 @@ async function piExtension_default(pi, planRun, getSession, goal) {
|
|
|
19585
19493
|
}
|
|
19586
19494
|
subagentParent = false;
|
|
19587
19495
|
const aborted = event.aborted || messageAborted;
|
|
19588
|
-
const stop = !sliceState.endedBy && !failure && !aborted && !planRun?.active ?
|
|
19496
|
+
const stop = !sliceState.endedBy && !failure && !aborted && !planRun?.active ? goalStopOutcome(lastTodo, goal?.get() ?? null) : null;
|
|
19589
19497
|
if (stop) {
|
|
19590
|
-
await terminalCall("checkpoint", { summary: stopSummary(stop), ...handoffFields(
|
|
19498
|
+
await terminalCall("checkpoint", { summary: stopSummary(stop), ...handoffFields(lastTodo, goal?.get() ?? null), files: [], checks: [], decisions: [], slice: sliceState.slice, reason: stopCheckpointReason(stop) }).catch(() => {
|
|
19591
19499
|
return;
|
|
19592
19500
|
});
|
|
19593
19501
|
} else if (sliceState.endedBy && !failure && !aborted) {
|
|
19594
|
-
await terminalCall("checkpoint", { summary: `The reply stopped after ${sliceState.steps} steps at its work-slice boundary.`, ...handoffFields(
|
|
19502
|
+
await terminalCall("checkpoint", { summary: `The reply stopped after ${sliceState.steps} steps at its work-slice boundary.`, ...handoffFields(lastTodo, goal?.get() ?? null), files: [], checks: [], decisions: [], slice: sliceState.slice, reason: sliceState.endedBy }).catch(() => {
|
|
19595
19503
|
return;
|
|
19596
19504
|
});
|
|
19597
19505
|
}
|