@sema-agent/core 1.451.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/agent-transcript-tool.d.ts +15 -0
- package/dist/agents/agent-transcript-tool.js +122 -0
- package/dist/agents/cascade.js +5 -8
- package/dist/agents/observer.js +3 -0
- package/dist/agents/repair-loop.d.ts +1 -1
- package/dist/agents/repair-loop.js +1 -1
- package/dist/agents/retain-ledger.d.ts +60 -0
- package/dist/agents/retain-ledger.js +225 -0
- package/dist/agents/roster-store.d.ts +1 -0
- package/dist/agents/roster-store.js +9 -0
- package/dist/agents/send-message-tool.d.ts +41 -0
- package/dist/agents/send-message-tool.js +469 -0
- package/dist/agents/subagent.d.ts +33 -108
- package/dist/agents/subagent.js +40 -813
- package/dist/agents/teacher.d.ts +0 -1
- package/dist/agents/teacher.js +0 -9
- package/dist/bin/sema-tb.js +3 -3
- package/dist/brain/anthropic.js +15 -40
- package/dist/brain/openai.js +15 -40
- package/dist/brain/reasoning.d.ts +1 -1
- package/dist/brain/stream-engine.js +2 -2
- package/dist/brain/stream-shared.d.ts +17 -0
- package/dist/brain/stream-shared.js +50 -0
- package/dist/brain/timeout.d.ts +1 -1
- package/dist/brain/timeout.js +1 -1
- package/dist/config/catalog.js +1 -3
- package/dist/config/defaults.d.ts +6 -0
- package/dist/config/defaults.js +6 -0
- package/dist/core/ask-question.d.ts +1 -1
- package/dist/core/ask-question.js +5 -5
- package/dist/core/background-agent-store.d.ts +2 -0
- package/dist/core/background-agent-store.js +5 -1
- package/dist/core/background-shell.d.ts +1 -1
- package/dist/core/checkpoint-store.d.ts +0 -1
- package/dist/core/exec-gate.d.ts +1 -1
- package/dist/core/exec-output-tail.d.ts +19 -1
- package/dist/core/exec-output-tail.js +44 -5
- package/dist/core/file-snapshot-store.d.ts +1 -1
- package/dist/core/fs-write-gate-policy.d.ts +1 -1
- package/dist/core/git-worktree-env.d.ts +1 -1
- package/dist/core/hooks.d.ts +1 -1
- package/dist/core/image-downsample.d.ts +44 -0
- package/dist/core/image-downsample.js +97 -0
- package/dist/core/lsp-session.d.ts +1 -0
- package/dist/core/lsp-session.js +24 -6
- package/dist/core/lsp.d.ts +11 -1
- package/dist/core/lsp.js +63 -6
- package/dist/core/mailbox-store.d.ts +3 -2
- package/dist/core/mailbox-store.js +19 -4
- package/dist/core/mcp.d.ts +3 -45
- package/dist/core/mcp.js +2 -97
- package/dist/core/media-byte-cap.d.ts +1 -1
- package/dist/core/memory-recall.d.ts +0 -12
- package/dist/core/memory-recall.js +0 -222
- package/dist/core/memory.d.ts +0 -6
- package/dist/core/memory.js +0 -88
- package/dist/core/message-utils.d.ts +1 -1
- package/dist/core/oracle-isolation.d.ts +1 -1
- package/dist/core/remote-env.d.ts +1 -1
- package/dist/core/roles.d.ts +1 -1
- package/dist/core/runner/active-skill-scope.d.ts +1 -1
- package/dist/core/runner/assemble-result.d.ts +0 -2
- package/dist/core/runner/memory-consolidation.d.ts +1 -1
- package/dist/core/runner/prepare-memory.d.ts +16 -0
- package/dist/core/runner/prepare-memory.js +191 -0
- package/dist/core/runner/prepare-task.d.ts +4 -28
- package/dist/core/runner/prepare-task.js +29 -208
- package/dist/core/runner/prompt-suggestions.d.ts +1 -1
- package/dist/core/runner/runtask.d.ts +2 -3
- package/dist/core/runner/runtask.js +337 -529
- package/dist/core/runner/session-rule-policy.d.ts +1 -1
- package/dist/core/runner/synthetic-tools.d.ts +5 -5
- package/dist/core/runner/synthetic-tools.js +4 -4
- package/dist/core/runner/tool-disclosure.d.ts +3 -3
- package/dist/core/runner/tool-disclosure.js +2 -2
- package/dist/core/runner/tool-output-projection.d.ts +6 -0
- package/dist/core/runner/tool-output-projection.js +81 -0
- package/dist/core/runtime.d.ts +1 -1
- package/dist/core/scheduler.d.ts +1 -1
- package/dist/core/sensitive-path-policy.d.ts +1 -1
- package/dist/core/session-reconcile.d.ts +1 -1
- package/dist/core/session-reconcile.js +5 -2
- package/dist/core/session-store.d.ts +2 -1
- package/dist/core/session-store.js +2 -1
- package/dist/core/session.d.ts +0 -1
- package/dist/core/task-notification.d.ts +1 -0
- package/dist/core/task-registry-agent.d.ts +115 -0
- package/dist/core/task-registry-agent.js +1163 -0
- package/dist/core/task-registry-monitor.d.ts +13 -0
- package/dist/core/task-registry-monitor.js +347 -0
- package/dist/core/task-registry-shared.d.ts +294 -0
- package/dist/core/task-registry-shared.js +271 -0
- package/dist/core/task-registry-workflow.d.ts +5 -0
- package/dist/core/task-registry-workflow.js +143 -0
- package/dist/core/task-registry.d.ts +54 -213
- package/dist/core/task-registry.js +190 -1766
- package/dist/core/task-tool-shape.d.ts +32 -0
- package/dist/core/task-tool-shape.js +83 -0
- package/dist/core/tool-policy.d.ts +1 -2
- package/dist/core/tool-result-budget.d.ts +1 -1
- package/dist/core/tool-result-store.d.ts +2 -2
- package/dist/core/tool-result-store.js +3 -3
- package/dist/core/tools.d.ts +7 -3
- package/dist/core/tools.js +3 -8
- package/dist/core/trace.d.ts +2 -2
- package/dist/core/types.d.ts +1 -29
- package/dist/core/workflow-journal-store.d.ts +2 -0
- package/dist/core/workflow-journal-store.js +14 -0
- package/dist/core/workflow-run-store-contract.d.ts +1 -1
- package/dist/core/workflow-run-store-contract.js +19 -19
- package/dist/engine/execution-env/node-execution-env.d.ts +14 -0
- package/dist/engine/execution-env/node-execution-env.js +184 -44
- package/dist/engine/lsp/frame-decoder.d.ts +1 -1
- package/dist/engine/lsp/frame-decoder.js +1 -1
- package/dist/engine/lsp/node-lsp-manager.d.ts +4 -2
- package/dist/engine/lsp/node-lsp-manager.js +22 -5
- package/dist/engine/lsp/stdio-lsp-transport.d.ts +1 -1
- package/dist/engine/lsp/stdio-lsp-transport.js +19 -8
- package/dist/index.d.ts +18 -16
- package/dist/index.js +15 -13
- package/dist/internal/harness-types.d.ts +7 -0
- package/dist/internal/harness-types.js +1 -0
- package/dist/internal/harness.d.ts +1 -7
- package/dist/internal/harness.js +1 -0
- package/dist/orchestration/goal.js +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +2 -1
- package/dist/orchestration/run-workflow-tool.js +29 -6
- package/dist/orchestration/workflow-types.d.ts +193 -0
- package/dist/orchestration/workflow-types.js +54 -0
- package/dist/orchestration/workflow.d.ts +3 -184
- package/dist/orchestration/workflow.js +87 -64
- package/dist/prompt-assembly/assemble.d.ts +3 -3
- package/dist/prompt-assembly/assemble.js +0 -16
- package/dist/prompt-assembly/explain.d.ts +1 -1
- package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
- package/dist/prompts/default.d.ts +1 -6
- package/dist/prompts/default.js +3 -11
- package/dist/scenarios/env.d.ts +1 -1
- package/dist/scenarios/full-body.d.ts +0 -3
- package/dist/scenarios/full-body.js +0 -2
- package/dist/stores/cc/mailbox-store.js +6 -1
- package/dist/stores/file/background-agent-store.d.ts +2 -15
- package/dist/stores/file/background-agent-store.js +33 -102
- package/dist/stores/file/checkpoint-store.d.ts +1 -14
- package/dist/stores/file/checkpoint-store.js +47 -119
- package/dist/stores/file/file-snapshot-store.d.ts +1 -1
- package/dist/stores/file/mailbox-store.d.ts +1 -1
- package/dist/stores/file/mailbox-store.js +9 -7
- package/dist/stores/file/shared-ledger.d.ts +35 -0
- package/dist/stores/file/shared-ledger.js +94 -0
- package/dist/stores/file/workflow-run-store.d.ts +1 -14
- package/dist/stores/file/workflow-run-store.js +24 -93
- package/dist/tools/fs/bash-readonly-classifier.d.ts +8 -0
- package/dist/tools/fs/bash-readonly-classifier.js +166 -0
- package/dist/tools/fs/encoding.d.ts +5 -0
- package/dist/tools/fs/encoding.js +6 -0
- package/dist/tools/fs/fs-bash.d.ts +31 -0
- package/dist/tools/fs/fs-bash.js +672 -0
- package/dist/tools/fs/fs-pdf.d.ts +22 -0
- package/dist/tools/fs/fs-pdf.js +236 -0
- package/dist/tools/fs/fs-read.d.ts +8 -0
- package/dist/tools/fs/fs-read.js +286 -0
- package/dist/tools/fs/fs-search-tools.d.ts +5 -0
- package/dist/tools/fs/fs-search-tools.js +173 -0
- package/dist/tools/fs/fs-shared.d.ts +55 -0
- package/dist/tools/fs/fs-shared.js +144 -0
- package/dist/tools/fs/fs-write.d.ts +8 -0
- package/dist/tools/fs/fs-write.js +423 -0
- package/dist/tools/fs/index.d.ts +12 -65
- package/dist/tools/fs/index.js +26 -2026
- package/dist/tools/fs/notebook.d.ts +43 -0
- package/dist/tools/fs/notebook.js +141 -0
- package/dist/tools/fs/pdf.d.ts +1 -1
- package/dist/tools/fs/repo-map.d.ts +3 -3
- package/dist/tools/fs/repo-map.js +3 -3
- package/dist/tools/fs/safety.d.ts +1 -1
- package/dist/tools/fs/search.d.ts +1 -1
- package/dist/tools/fs/search.js +141 -12
- package/dist/tools/gitea-issue.js +4 -2
- package/dist/tools/monitor.d.ts +1 -1
- package/dist/tools/monitor.js +12 -8
- package/dist/tools/scheduler-tools.d.ts +1 -1
- package/dist/tools/scheduler-tools.js +16 -16
- package/dist/tools/task-list.js +34 -12
- package/dist/tools/web.d.ts +4 -2
- package/dist/tools/web.js +106 -20
- package/dist/tools/worktree.d.ts +1 -1
- package/dist/tools/worktree.js +14 -14
- package/package.json +1 -1
- package/dist/core/dynamic-recall.d.ts +0 -32
- package/dist/core/dynamic-recall.js +0 -76
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
-
import { defineTool } from "../core/tools.js";
|
|
2
|
+
import { defineTool, errorResult } from "../core/tools.js";
|
|
3
3
|
import { hasScheduler, isValidCronExpr, } from "../core/scheduler.js";
|
|
4
4
|
export const SCHEDULE_WAKEUP_TOOL_NAME = "ScheduleWakeup";
|
|
5
5
|
export const AUTONOMOUS_LOOP_SENTINEL = "<<autonomous-loop>>";
|
|
@@ -210,33 +210,33 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
210
210
|
execute: async (args) => {
|
|
211
211
|
const a = args;
|
|
212
212
|
if (a.schedule !== undefined && a.cron !== undefined) {
|
|
213
|
-
return "Error (CronCreate): pass exactly one of `schedule` (object form) or `cron` (string form), not both.";
|
|
213
|
+
return errorResult("Error (CronCreate): pass exactly one of `schedule` (object form) or `cron` (string form), not both.");
|
|
214
214
|
}
|
|
215
215
|
if (a.schedule === undefined && a.cron === undefined) {
|
|
216
|
-
return "Error (CronCreate): a schedule is required — pass `schedule` (kinds cron/at/delay) or the shorthand `cron` string.";
|
|
216
|
+
return errorResult("Error (CronCreate): a schedule is required — pass `schedule` (kinds cron/at/delay) or the shorthand `cron` string.");
|
|
217
217
|
}
|
|
218
218
|
if (a.prompt === undefined) {
|
|
219
|
-
return "Error (CronCreate): `prompt` is required — the task runs unattended, so it needs self-contained instructions.";
|
|
219
|
+
return errorResult("Error (CronCreate): `prompt` is required — the task runs unattended, so it needs self-contained instructions.");
|
|
220
220
|
}
|
|
221
221
|
const when = a.schedule ?? { kind: "cron", expr: a.cron };
|
|
222
222
|
if (when.kind === "cron") {
|
|
223
223
|
if (!isValidCronExpr(when.expr)) {
|
|
224
|
-
return `Error (CronCreate): invalid cron expression "${when.expr}" — only 5/6 space-separated cron fields (digits and * / , -) are allowed
|
|
224
|
+
return errorResult(`Error (CronCreate): invalid cron expression "${when.expr}" — only 5/6 space-separated cron fields (digits and * / , -) are allowed.`);
|
|
225
225
|
}
|
|
226
226
|
const deepErr = cronScheduleError(when.expr);
|
|
227
227
|
if (deepErr)
|
|
228
|
-
return `Error (CronCreate): cron expression "${when.expr}" — ${deepErr}
|
|
228
|
+
return errorResult(`Error (CronCreate): cron expression "${when.expr}" — ${deepErr}`);
|
|
229
229
|
}
|
|
230
230
|
else if (a.recurring !== undefined) {
|
|
231
|
-
return `Error (CronCreate): \`recurring\` only applies to cron schedules — "${when.kind}" fires once by nature; omit \`recurring
|
|
231
|
+
return errorResult(`Error (CronCreate): \`recurring\` only applies to cron schedules — "${when.kind}" fires once by nature; omit \`recurring\`.`);
|
|
232
232
|
}
|
|
233
233
|
const durable = a.durable ?? a.schedule !== undefined;
|
|
234
234
|
if (!durable) {
|
|
235
235
|
if (sched.schedulerCapabilities.supportsSessionLifetime !== true) {
|
|
236
|
-
return "Error (CronCreate): this scheduler backend does not support session-scoped jobs (they could not be cleaned up when the session ends). Pass `durable: true` to schedule a persistent job instead.";
|
|
236
|
+
return errorResult("Error (CronCreate): this scheduler backend does not support session-scoped jobs (they could not be cleaned up when the session ends). Pass `durable: true` to schedule a persistent job instead.");
|
|
237
237
|
}
|
|
238
238
|
if (ctx.sessionId === undefined) {
|
|
239
|
-
return "Error (CronCreate): session-scoped jobs need a session to bind to, and this run has none. Pass `durable: true` to schedule a persistent job instead.";
|
|
239
|
+
return errorResult("Error (CronCreate): session-scoped jobs need a session to bind to, and this run has none. Pass `durable: true` to schedule a persistent job instead.");
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
const intent = {
|
|
@@ -248,7 +248,7 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
248
248
|
};
|
|
249
249
|
const r = await sched.schedule(intent, schedCtx);
|
|
250
250
|
if (!r.ok)
|
|
251
|
-
return `Error (CronCreate): ${r.error.message}
|
|
251
|
+
return errorResult(`Error (CronCreate): ${r.error.message}`);
|
|
252
252
|
const humanSchedule = when.kind === "cron"
|
|
253
253
|
? cronToHuman(when.expr)
|
|
254
254
|
: when.kind === "delay"
|
|
@@ -278,7 +278,7 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
278
278
|
if (!r.ok) {
|
|
279
279
|
if (r.error.code === "not_found")
|
|
280
280
|
return `CronDelete: no scheduled task "${id}" (already gone or not yours).`;
|
|
281
|
-
return `Error (CronDelete): ${r.error.message}
|
|
281
|
+
return errorResult(`Error (CronDelete): ${r.error.message}`);
|
|
282
282
|
}
|
|
283
283
|
return { content: `Cancelled scheduled task ${id}.`, details: { type: "cron-delete", id } };
|
|
284
284
|
},
|
|
@@ -292,7 +292,7 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
292
292
|
execute: async () => {
|
|
293
293
|
const r = await sched.list(schedCtx);
|
|
294
294
|
if (!r.ok)
|
|
295
|
-
return `Error (CronList): ${r.error.message}
|
|
295
|
+
return errorResult(`Error (CronList): ${r.error.message}`);
|
|
296
296
|
if (r.value.length === 0) {
|
|
297
297
|
return { content: "No scheduled tasks.", details: { type: "cron-list", jobs: [] } };
|
|
298
298
|
}
|
|
@@ -367,7 +367,7 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
367
367
|
if (a.stop === true) {
|
|
368
368
|
const pending = await listPendingWakeups();
|
|
369
369
|
if ("err" in pending)
|
|
370
|
-
return `Error (${SCHEDULE_WAKEUP_TOOL_NAME}): ${pending.err}
|
|
370
|
+
return errorResult(`Error (${SCHEDULE_WAKEUP_TOOL_NAME}): ${pending.err}`);
|
|
371
371
|
const cancelledWakeups = await cancelWakeups(pending.ids);
|
|
372
372
|
return {
|
|
373
373
|
content: `Loop stopped. Cancelled ${cancelledWakeups} pending wakeup(s); recurring cron tasks are unaffected.`,
|
|
@@ -375,10 +375,10 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
375
375
|
};
|
|
376
376
|
}
|
|
377
377
|
if (a.delaySeconds === undefined || a.reason === undefined || a.prompt === undefined) {
|
|
378
|
-
return `Error (${SCHEDULE_WAKEUP_TOOL_NAME}): delaySeconds, reason and prompt are required unless \`stop\` is true
|
|
378
|
+
return errorResult(`Error (${SCHEDULE_WAKEUP_TOOL_NAME}): delaySeconds, reason and prompt are required unless \`stop\` is true.`);
|
|
379
379
|
}
|
|
380
380
|
if (sched.schedulerCapabilities.supportsSessionWakeup === false) {
|
|
381
|
-
return `Error (${SCHEDULE_WAKEUP_TOOL_NAME}): this environment has no resident scheduler that can honor a session wakeup — the wakeup would never fire. Wait in the foreground instead, or start the work in a self-detaching form
|
|
381
|
+
return errorResult(`Error (${SCHEDULE_WAKEUP_TOOL_NAME}): this environment has no resident scheduler that can honor a session wakeup — the wakeup would never fire. Wait in the foreground instead, or start the work in a self-detaching form.`);
|
|
382
382
|
}
|
|
383
383
|
const clampedDelaySeconds = Math.min(3600, Math.max(60, Math.round(a.delaySeconds)));
|
|
384
384
|
const wasClamped = clampedDelaySeconds !== a.delaySeconds;
|
|
@@ -387,7 +387,7 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
387
387
|
await cancelWakeups(pending.ids);
|
|
388
388
|
const r = await sched.schedule({ prompt: a.prompt, when: { kind: "delay", delaySec: clampedDelaySeconds }, label: WAKEUP_LABEL, mode: "session-wakeup" }, schedCtx);
|
|
389
389
|
if (!r.ok)
|
|
390
|
-
return `Error (${SCHEDULE_WAKEUP_TOOL_NAME}): ${r.error.message}
|
|
390
|
+
return errorResult(`Error (${SCHEDULE_WAKEUP_TOOL_NAME}): ${r.error.message}`);
|
|
391
391
|
const scheduledFor = Date.now() + clampedDelaySeconds * 1000;
|
|
392
392
|
const hhmmss = new Date(scheduledFor).toTimeString().slice(0, 8);
|
|
393
393
|
const clampNote = wasClamped ? ` (clamped to ${clampedDelaySeconds}s from your requested value)` : "";
|
package/dist/tools/task-list.js
CHANGED
|
@@ -326,22 +326,41 @@ export function createTaskListTools(store) {
|
|
|
326
326
|
t.metadata[k] = v;
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
|
+
const ignoredDeps = [];
|
|
330
|
+
let linkedBlocks = false;
|
|
331
|
+
let linkedBlockedBy = false;
|
|
329
332
|
for (const b of a.addBlocks ?? []) {
|
|
330
|
-
if (!t.blocks.includes(b))
|
|
331
|
-
t.blocks.push(b);
|
|
332
333
|
const other = b === id ? t : await tx.get(b);
|
|
333
|
-
if (
|
|
334
|
+
if (!other) {
|
|
335
|
+
if (!ignoredDeps.includes(b))
|
|
336
|
+
ignoredDeps.push(b);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (!t.blocks.includes(b)) {
|
|
340
|
+
t.blocks.push(b);
|
|
341
|
+
linkedBlocks = true;
|
|
342
|
+
}
|
|
343
|
+
if (!other.blockedBy.includes(id)) {
|
|
334
344
|
other.blockedBy.push(id);
|
|
345
|
+
linkedBlocks = true;
|
|
335
346
|
if (other !== t)
|
|
336
347
|
await tx.set(b, other);
|
|
337
348
|
}
|
|
338
349
|
}
|
|
339
350
|
for (const b of a.addBlockedBy ?? []) {
|
|
340
|
-
if (!t.blockedBy.includes(b))
|
|
341
|
-
t.blockedBy.push(b);
|
|
342
351
|
const other = b === id ? t : await tx.get(b);
|
|
343
|
-
if (
|
|
352
|
+
if (!other) {
|
|
353
|
+
if (!ignoredDeps.includes(b))
|
|
354
|
+
ignoredDeps.push(b);
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
if (!t.blockedBy.includes(b)) {
|
|
358
|
+
t.blockedBy.push(b);
|
|
359
|
+
linkedBlockedBy = true;
|
|
360
|
+
}
|
|
361
|
+
if (!other.blocks.includes(id)) {
|
|
344
362
|
other.blocks.push(id);
|
|
363
|
+
linkedBlockedBy = true;
|
|
345
364
|
if (other !== t)
|
|
346
365
|
await tx.set(b, other);
|
|
347
366
|
}
|
|
@@ -354,10 +373,13 @@ export function createTaskListTools(store) {
|
|
|
354
373
|
a.activeForm !== undefined ? "activeForm" : "",
|
|
355
374
|
a.owner !== undefined ? "owner" : "",
|
|
356
375
|
a.metadata ? "metadata" : "",
|
|
357
|
-
|
|
358
|
-
|
|
376
|
+
linkedBlocks ? "blocks" : "",
|
|
377
|
+
linkedBlockedBy ? "blockedBy" : "",
|
|
359
378
|
].filter(Boolean);
|
|
360
|
-
|
|
379
|
+
const ignoredNote = ignoredDeps.length
|
|
380
|
+
? ` (ignored: no such task ${ignoredDeps.map((x) => `#${x}`).join(", ")})`
|
|
381
|
+
: "";
|
|
382
|
+
return { content: `Updated task #${id}${changed.length ? ` ${changed.join(", ")}` : ""}${ignoredNote}`, details: { type: "task", task: snapTask(t) } };
|
|
361
383
|
}),
|
|
362
384
|
};
|
|
363
385
|
const taskList = {
|
|
@@ -393,12 +415,12 @@ export function createTaskListTools(store) {
|
|
|
393
415
|
if (all.length === 0) {
|
|
394
416
|
return { content: "The task list is empty.", details: { type: "task-list", tasks: [] } };
|
|
395
417
|
}
|
|
396
|
-
const
|
|
418
|
+
const openIds = new Set(all.filter((x) => x.status !== "completed").map((x) => x.id));
|
|
397
419
|
const lines = all.map((t) => {
|
|
398
|
-
const liveBlockers = t.blockedBy.filter((b) =>
|
|
420
|
+
const liveBlockers = t.blockedBy.filter((b) => openIds.has(b));
|
|
399
421
|
return `#${t.id} [${t.status}] ${t.subject}${t.owner ? ` (owner: ${t.owner})` : ""}${liveBlockers.length ? ` (blocked by: ${liveBlockers.join(", ")})` : ""}`;
|
|
400
422
|
});
|
|
401
|
-
const open =
|
|
423
|
+
const open = openIds.size;
|
|
402
424
|
return {
|
|
403
425
|
content: `${lines.join("\n")}\n(${all.length} total, ${open} open)`,
|
|
404
426
|
details: { type: "task-list", tasks: all.map(snapTask) },
|
package/dist/tools/web.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentTool } from "../internal/harness.js";
|
|
1
|
+
import type { AgentTool } from "../internal/harness-types.js";
|
|
2
2
|
import type { Brain, ToolSpec } from "../core/types.js";
|
|
3
3
|
import type { Model } from "../internal/llm.js";
|
|
4
4
|
export interface WebFetchConfig {
|
|
@@ -14,7 +14,7 @@ export declare function webFetchToolSpec(config?: WebFetchConfig): ToolSpec;
|
|
|
14
14
|
export declare function createWebFetchTool(config?: WebFetchConfig): AgentTool;
|
|
15
15
|
export declare const WEBFETCH_SUMMARY_MAX_CONTENT = 100000;
|
|
16
16
|
export declare const WEBFETCH_SUMMARY_GUIDELINES: string;
|
|
17
|
-
export declare function
|
|
17
|
+
export declare function createWebFetchSummarizer(brain: Brain, model: Model): (content: string, prompt: string, signal?: AbortSignal) => Promise<string>;
|
|
18
18
|
export interface WebSearchConfig {
|
|
19
19
|
search: (query: string, signal?: AbortSignal, opts?: {
|
|
20
20
|
allowedDomains?: string[];
|
|
@@ -25,5 +25,7 @@ export interface WebSearchConfig {
|
|
|
25
25
|
snippet: string;
|
|
26
26
|
}>>;
|
|
27
27
|
maxResults?: number;
|
|
28
|
+
timeoutMs?: number;
|
|
28
29
|
}
|
|
30
|
+
export declare function clipCodePoints(s: string, max: number): string;
|
|
29
31
|
export declare function createWebSearchTool(config: WebSearchConfig): ToolSpec;
|
package/dist/tools/web.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
-
import { defineTool } from "../core/tools.js";
|
|
2
|
+
import { defineTool, errorResult } from "../core/tools.js";
|
|
3
3
|
import { delimitUntrusted, inlineUntrusted } from "../core/untrusted-text.js";
|
|
4
|
+
import { redactSecrets } from "../core/untrusted-egress.js";
|
|
4
5
|
import { isPrivateHost } from "../core/runner/image.js";
|
|
5
6
|
const MAX_REDIRECTS = 10;
|
|
6
7
|
const MAX_URL_LENGTH = 2048;
|
|
@@ -229,10 +230,7 @@ export function webFetchToolSpec(config = {}) {
|
|
|
229
230
|
const { url, prompt } = args;
|
|
230
231
|
const startedAt = Date.now();
|
|
231
232
|
let urlForDetails = url.length > MAX_URL_LENGTH ? url.slice(0, MAX_URL_LENGTH) : url;
|
|
232
|
-
const failure = (message, extra = {}) => ({
|
|
233
|
-
content: message,
|
|
234
|
-
details: { type: "web-fetch", url: urlForDetails, bytes: 0, durationMs: Date.now() - startedAt, ...extra },
|
|
235
|
-
});
|
|
233
|
+
const failure = (message, extra = {}) => errorResult(message, { type: "web-fetch", url: urlForDetails, bytes: 0, durationMs: Date.now() - startedAt, ...extra });
|
|
236
234
|
if (url.length > MAX_URL_LENGTH) {
|
|
237
235
|
return failure(`Error (WebFetch): URL is ${url.length} characters — the limit is ${MAX_URL_LENGTH}.`);
|
|
238
236
|
}
|
|
@@ -381,6 +379,7 @@ export function webFetchToolSpec(config = {}) {
|
|
|
381
379
|
return {
|
|
382
380
|
content: `Error (WebFetch): the request was interrupted (aborted) before completion${finalResponse ? " while reading the response body" : ""}. No content was retrieved.`,
|
|
383
381
|
details: { ...errDetails, aborted: true },
|
|
382
|
+
isError: true,
|
|
384
383
|
};
|
|
385
384
|
}
|
|
386
385
|
if (timedOut) {
|
|
@@ -391,11 +390,13 @@ export function webFetchToolSpec(config = {}) {
|
|
|
391
390
|
? `Error (WebFetch): the server responded (HTTP ${connectedMeta.status}) to a redirect hop, but the request stalled while following the redirect — timed out after ${timeoutMs}ms. The host IS reachable; retry only if the stall was likely transient, or fetch the redirect target directly.`
|
|
392
391
|
: `Error (WebFetch): timed out after ${timeoutMs}ms while connecting — the host did not respond or the connection stalled before any response arrived. Do not retry the same URL; continue without it or try a different source.`,
|
|
393
392
|
details: { ...errDetails, timedOut: true },
|
|
393
|
+
isError: true,
|
|
394
394
|
};
|
|
395
395
|
}
|
|
396
396
|
return {
|
|
397
397
|
content: `Error (WebFetch): request failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
398
398
|
details: errDetails,
|
|
399
|
+
isError: true,
|
|
399
400
|
};
|
|
400
401
|
}
|
|
401
402
|
finally {
|
|
@@ -447,6 +448,7 @@ export function webFetchToolSpec(config = {}) {
|
|
|
447
448
|
durationMs: Date.now() - startedAt,
|
|
448
449
|
...transferStateDetails,
|
|
449
450
|
},
|
|
451
|
+
isError: true,
|
|
450
452
|
};
|
|
451
453
|
}
|
|
452
454
|
if (bodyBytes) {
|
|
@@ -474,6 +476,7 @@ export function webFetchToolSpec(config = {}) {
|
|
|
474
476
|
durationMs: Date.now() - startedAt,
|
|
475
477
|
...transferStateDetails,
|
|
476
478
|
},
|
|
479
|
+
isError: true,
|
|
477
480
|
};
|
|
478
481
|
}
|
|
479
482
|
}
|
|
@@ -547,7 +550,7 @@ export const WEBFETCH_SUMMARY_GUIDELINES = `Provide a concise response based onl
|
|
|
547
550
|
` - Use quotation marks for exact language from articles; any language outside of the quotation should never be word-for-word the same.\n` +
|
|
548
551
|
` - You are not a lawyer and never comment on the legality of your own prompts and responses.\n` +
|
|
549
552
|
` - Never produce or reproduce exact song lyrics.\n`;
|
|
550
|
-
export function
|
|
553
|
+
export function createWebFetchSummarizer(brain, model) {
|
|
551
554
|
return async (content, prompt, signal) => {
|
|
552
555
|
const truncated = content.length > WEBFETCH_SUMMARY_MAX_CONTENT
|
|
553
556
|
? content.slice(0, WEBFETCH_SUMMARY_MAX_CONTENT) + "\n\n[Content truncated due to length...]"
|
|
@@ -571,20 +574,36 @@ export function makeWebFetchSummarizer(brain, model) {
|
|
|
571
574
|
return text;
|
|
572
575
|
};
|
|
573
576
|
}
|
|
577
|
+
const DEFAULT_SEARCH_TIMEOUT_MS = 30_000;
|
|
578
|
+
const SEARCH_ERROR_EXCERPT_CHARS = 2048;
|
|
579
|
+
const SEARCH_TITLE_MAX_CHARS = 300;
|
|
580
|
+
const SEARCH_SNIPPET_MAX_CHARS = 1_000;
|
|
581
|
+
const SEARCH_BODY_MAX_CHARS = 100_000;
|
|
582
|
+
export function clipCodePoints(s, max) {
|
|
583
|
+
const cps = [...s];
|
|
584
|
+
return cps.length > max ? cps.slice(0, max).join("") + "…" : s;
|
|
585
|
+
}
|
|
574
586
|
function hostMatchesDomain(host, domain) {
|
|
575
|
-
const
|
|
576
|
-
|
|
587
|
+
const norm = (s) => s.toLowerCase().replace(/^\*\./, "").replace(/^\.+|\.+$/g, "");
|
|
588
|
+
const h = norm(host);
|
|
589
|
+
const d = norm(domain);
|
|
590
|
+
if (!d || !h)
|
|
577
591
|
return false;
|
|
578
|
-
return
|
|
592
|
+
return h === d || h.endsWith("." + d);
|
|
579
593
|
}
|
|
580
594
|
function webSearchResultAllowed(url, allowed, blocked) {
|
|
581
595
|
let host;
|
|
582
596
|
try {
|
|
583
|
-
|
|
597
|
+
const u = new URL(url);
|
|
598
|
+
if (u.protocol !== "http:" && u.protocol !== "https:")
|
|
599
|
+
return false;
|
|
600
|
+
host = u.hostname.toLowerCase();
|
|
584
601
|
}
|
|
585
602
|
catch {
|
|
586
|
-
return
|
|
603
|
+
return false;
|
|
587
604
|
}
|
|
605
|
+
if (!host)
|
|
606
|
+
return false;
|
|
588
607
|
if (blocked && blocked.some((d) => hostMatchesDomain(host, d)))
|
|
589
608
|
return false;
|
|
590
609
|
if (allowed && allowed.length && !allowed.some((d) => hostMatchesDomain(host, d)))
|
|
@@ -612,27 +631,94 @@ export function createWebSearchTool(config) {
|
|
|
612
631
|
effect: "read",
|
|
613
632
|
execute: async (args, ctx) => {
|
|
614
633
|
const { query, allowed_domains, blocked_domains } = args;
|
|
634
|
+
const startedAt = Date.now();
|
|
635
|
+
const failCard = (extra = {}) => ({
|
|
636
|
+
type: "web-search",
|
|
637
|
+
query,
|
|
638
|
+
results: [],
|
|
639
|
+
durationSeconds: (Date.now() - startedAt) / 1000,
|
|
640
|
+
searchCount: 0,
|
|
641
|
+
...extra,
|
|
642
|
+
});
|
|
615
643
|
if (allowed_domains?.length && blocked_domains?.length) {
|
|
616
|
-
return "Error: Cannot specify both allowed_domains and blocked_domains in the same request";
|
|
644
|
+
return errorResult("Error: Cannot specify both allowed_domains and blocked_domains in the same request", failCard());
|
|
617
645
|
}
|
|
618
|
-
const
|
|
646
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_SEARCH_TIMEOUT_MS;
|
|
647
|
+
const ac = new AbortController();
|
|
648
|
+
const timer = setTimeout(() => ac.abort(), timeoutMs);
|
|
649
|
+
const onOuterAbort = () => ac.abort();
|
|
650
|
+
if (ctx.signal?.aborted)
|
|
651
|
+
ac.abort();
|
|
652
|
+
else
|
|
653
|
+
ctx.signal?.addEventListener("abort", onOuterAbort, { once: true });
|
|
654
|
+
const TIMED_OUT = Symbol("websearch-timeout");
|
|
655
|
+
const abortedFrame = () => errorResult("Error (WebSearch): the search was interrupted (aborted) before completion. No results were retrieved.", failCard({ aborted: true }));
|
|
656
|
+
const timedOutFrame = () => errorResult(`Error (WebSearch): the search backend did not respond within ${timeoutMs}ms. The tool stopped waiting; the backend may still be executing the request.`, failCard({ timedOut: true }));
|
|
619
657
|
let results;
|
|
620
658
|
try {
|
|
621
|
-
|
|
659
|
+
const work = Promise.resolve(config.search(query, ac.signal, { allowedDomains: allowed_domains, blockedDomains: blocked_domains }));
|
|
660
|
+
work.catch(() => { });
|
|
661
|
+
let fireDeadline;
|
|
662
|
+
const deadline = new Promise((res) => {
|
|
663
|
+
fireDeadline = () => res(TIMED_OUT);
|
|
664
|
+
});
|
|
665
|
+
const deadlineTimer = setTimeout(() => fireDeadline(), timeoutMs);
|
|
666
|
+
deadlineTimer.unref?.();
|
|
667
|
+
try {
|
|
668
|
+
const raced = await Promise.race([work, deadline]);
|
|
669
|
+
if (raced === TIMED_OUT) {
|
|
670
|
+
return ctx.signal?.aborted ? abortedFrame() : timedOutFrame();
|
|
671
|
+
}
|
|
672
|
+
results = raced;
|
|
673
|
+
}
|
|
674
|
+
finally {
|
|
675
|
+
clearTimeout(deadlineTimer);
|
|
676
|
+
}
|
|
622
677
|
}
|
|
623
678
|
catch (e) {
|
|
624
|
-
|
|
679
|
+
if (ctx.signal?.aborted)
|
|
680
|
+
return abortedFrame();
|
|
681
|
+
if (ac.signal.aborted)
|
|
682
|
+
return timedOutFrame();
|
|
683
|
+
const msg = redactSecrets(e instanceof Error ? e.message : String(e)).trim();
|
|
684
|
+
const headline = "Error (WebSearch): the search backend failed.";
|
|
685
|
+
return errorResult(msg
|
|
686
|
+
? `${headline} The backend's error text follows:\n\n${delimitUntrusted("WebSearch backend error", msg, SEARCH_ERROR_EXCERPT_CHARS)}`
|
|
687
|
+
: headline, failCard());
|
|
625
688
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
689
|
+
finally {
|
|
690
|
+
clearTimeout(timer);
|
|
691
|
+
ctx.signal?.removeEventListener("abort", onOuterAbort);
|
|
692
|
+
}
|
|
693
|
+
const usable = results.filter((r) => webSearchResultAllowed(r.url));
|
|
694
|
+
const droppedUnusable = results.length - usable.length;
|
|
695
|
+
results = usable.filter((r) => webSearchResultAllowed(r.url, allowed_domains, blocked_domains)).slice(0, max);
|
|
696
|
+
let clipped = false;
|
|
697
|
+
const shown = results.map((r) => {
|
|
698
|
+
const title = clipCodePoints(r.title, SEARCH_TITLE_MAX_CHARS);
|
|
699
|
+
const url = clipCodePoints(r.url, MAX_URL_LENGTH);
|
|
700
|
+
const snippet = clipCodePoints(r.snippet, SEARCH_SNIPPET_MAX_CHARS);
|
|
701
|
+
if (title !== r.title || url !== r.url || snippet !== r.snippet)
|
|
702
|
+
clipped = true;
|
|
703
|
+
return { title, url, snippet };
|
|
704
|
+
});
|
|
705
|
+
const body = shown.map((r, i) => `${i + 1}. ${r.title}\n ${r.url}\n ${r.snippet}`).join("\n");
|
|
706
|
+
if (body.length > SEARCH_BODY_MAX_CHARS)
|
|
707
|
+
clipped = true;
|
|
708
|
+
const fenced = delimitUntrusted(`WebSearch ${JSON.stringify(query)}`, body || "(no results)", SEARCH_BODY_MAX_CHARS);
|
|
709
|
+
const dropNote = droppedUnusable > 0
|
|
710
|
+
? `\n\n[WebSearch: ${droppedUnusable} result(s) returned by the backend were dropped — their URL was not a usable http(s) link]`
|
|
711
|
+
: "";
|
|
712
|
+
const clipNote = clipped
|
|
713
|
+
? "\n\n[WebSearch: one or more result fields exceeded the display limits and were truncated — treat the text above as an excerpt, not the backend's full result.]"
|
|
714
|
+
: "";
|
|
715
|
+
const modelText = `${fenced}${dropNote}${clipNote}\n\nREMINDER: You MUST include the sources above in your response to the user using markdown hyperlinks.`;
|
|
630
716
|
return {
|
|
631
717
|
content: modelText,
|
|
632
718
|
details: {
|
|
633
719
|
type: "web-search",
|
|
634
720
|
query,
|
|
635
|
-
results: [{ tool_use_id: ctx.toolCallId, content:
|
|
721
|
+
results: [{ tool_use_id: ctx.toolCallId, content: shown.map((r) => ({ title: r.title, url: r.url })) }],
|
|
636
722
|
durationSeconds: (Date.now() - startedAt) / 1000,
|
|
637
723
|
searchCount: 1,
|
|
638
724
|
},
|
package/dist/tools/worktree.d.ts
CHANGED
package/dist/tools/worktree.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { defineTool } from "../core/tools.js";
|
|
3
|
+
import { defineTool, errorResult } from "../core/tools.js";
|
|
4
4
|
import { addWorktree, pruneWorktrees, WORKTREE_PARENT } from "../core/git-worktree-env.js";
|
|
5
5
|
const ENTER_DESCRIPTION = "Use this tool ONLY when explicitly instructed to work in a worktree — either by the user directly, or by " +
|
|
6
6
|
"project instructions. This tool creates an isolated git worktree and switches the current session into it.\n" +
|
|
@@ -113,20 +113,20 @@ export function createWorktreeTools(env, opts) {
|
|
|
113
113
|
execute: async (args) => {
|
|
114
114
|
const { name, path, baseRef } = args;
|
|
115
115
|
if (path !== undefined && name !== undefined) {
|
|
116
|
-
return "Error (EnterWorktree): `name` and `path` are mutually exclusive — `path` enters an existing worktree, `name` creates a new one.";
|
|
116
|
+
return errorResult("Error (EnterWorktree): `name` and `path` are mutually exclusive — `path` enters an existing worktree, `name` creates a new one.");
|
|
117
117
|
}
|
|
118
118
|
if (path !== undefined && baseRef !== undefined) {
|
|
119
|
-
return "Error (EnterWorktree): `baseRef` only applies when creating a new worktree — an existing worktree entered via `path` keeps whatever state it has.";
|
|
119
|
+
return errorResult("Error (EnterWorktree): `baseRef` only applies when creating a new worktree — an existing worktree entered via `path` keeps whatever state it has.");
|
|
120
120
|
}
|
|
121
121
|
if (session.current && path === undefined) {
|
|
122
|
-
return (`Error (EnterWorktree): already in a worktree session (${session.current.worktreeDir}). ` +
|
|
122
|
+
return errorResult(`Error (EnterWorktree): already in a worktree session (${session.current.worktreeDir}). ` +
|
|
123
123
|
"Use ExitWorktree to leave it before creating a new worktree, or pass `path` to switch into an " +
|
|
124
124
|
"existing worktree of this repository.");
|
|
125
125
|
}
|
|
126
126
|
const probe = await env.exec("git rev-parse --is-inside-work-tree", { cwd: repoRoot });
|
|
127
127
|
if (!probe.ok || probe.value.exitCode !== 0) {
|
|
128
128
|
const detail = probe.ok ? (probe.value.stderr || probe.value.stdout).trim().split("\n")[0] : String(probe.error);
|
|
129
|
-
return (`Error (EnterWorktree): the working directory ${repoRoot} is not a git repository` +
|
|
129
|
+
return errorResult(`Error (EnterWorktree): the working directory ${repoRoot} is not a git repository` +
|
|
130
130
|
`${detail ? ` (${detail})` : ""}. This deployment has no VCS-agnostic worktree extension point, ` +
|
|
131
131
|
"so worktree isolation is unavailable here.");
|
|
132
132
|
}
|
|
@@ -134,31 +134,31 @@ export function createWorktreeTools(env, opts) {
|
|
|
134
134
|
const candidate = resolve(cwdRef.current, path);
|
|
135
135
|
const top = await env.exec("git rev-parse --show-toplevel", { cwd: candidate });
|
|
136
136
|
if (!top.ok || top.value.exitCode !== 0) {
|
|
137
|
-
return `Error (EnterWorktree): ${candidate} is not an accessible git worktree (cannot resolve its top-level directory)
|
|
137
|
+
return errorResult(`Error (EnterWorktree): ${candidate} is not an accessible git worktree (cannot resolve its top-level directory).`);
|
|
138
138
|
}
|
|
139
139
|
const target = top.value.stdout.trim();
|
|
140
140
|
const list = await env.exec("git worktree list --porcelain", { cwd: repoRoot });
|
|
141
141
|
if (!list.ok || list.value.exitCode !== 0) {
|
|
142
|
-
return "Error (EnterWorktree): could not read `git worktree list` for this repository.";
|
|
142
|
+
return errorResult("Error (EnterWorktree): could not read `git worktree list` for this repository.");
|
|
143
143
|
}
|
|
144
144
|
const registered = list.value.stdout
|
|
145
145
|
.split("\n")
|
|
146
146
|
.filter((l) => l.startsWith("worktree "))
|
|
147
147
|
.map((l) => l.slice("worktree ".length).trim());
|
|
148
148
|
if (!registered.includes(target)) {
|
|
149
|
-
return (`Error (EnterWorktree): ${target} is not a registered worktree of this repository ` +
|
|
149
|
+
return errorResult(`Error (EnterWorktree): ${target} is not a registered worktree of this repository ` +
|
|
150
150
|
"(it does not appear in `git worktree list`).");
|
|
151
151
|
}
|
|
152
152
|
if (target === registered[0]) {
|
|
153
|
-
return `Error (EnterWorktree): ${target} is the main working tree, not a linked worktree — nothing to enter
|
|
153
|
+
return errorResult(`Error (EnterWorktree): ${target} is the main working tree, not a linked worktree — nothing to enter.`);
|
|
154
154
|
}
|
|
155
155
|
if (target !== repoRoot && !target.startsWith(repoRoot.endsWith("/") ? repoRoot : `${repoRoot}/`)) {
|
|
156
|
-
return (`Error (EnterWorktree): the worktree ${target} lives outside the task root ${repoRoot}; ` +
|
|
156
|
+
return errorResult(`Error (EnterWorktree): the worktree ${target} lives outside the task root ${repoRoot}; ` +
|
|
157
157
|
"file containment stays anchored at the task root, so it cannot be entered from this session.");
|
|
158
158
|
}
|
|
159
159
|
const head = await env.exec("git rev-parse HEAD", { cwd: target });
|
|
160
160
|
if (!head.ok || head.value.exitCode !== 0) {
|
|
161
|
-
return `Error (EnterWorktree): cannot resolve HEAD of the worktree ${target}
|
|
161
|
+
return errorResult(`Error (EnterWorktree): cannot resolve HEAD of the worktree ${target}.`);
|
|
162
162
|
}
|
|
163
163
|
const previous = session.current;
|
|
164
164
|
const originalCwd = previous ? previous.originalCwd : cwdRef.current;
|
|
@@ -172,7 +172,7 @@ export function createWorktreeTools(env, opts) {
|
|
|
172
172
|
}
|
|
173
173
|
const base = await resolveBaseCommit(env, repoRoot, baseRef ?? "fresh");
|
|
174
174
|
if ("error" in base)
|
|
175
|
-
return `Error (EnterWorktree): ${base.error}
|
|
175
|
+
return errorResult(`Error (EnterWorktree): ${base.error}.`);
|
|
176
176
|
let createdDir;
|
|
177
177
|
try {
|
|
178
178
|
const { worktreeDir } = await addWorktree(env, {
|
|
@@ -187,7 +187,7 @@ export function createWorktreeTools(env, opts) {
|
|
|
187
187
|
createdDir = worktreeDir;
|
|
188
188
|
}
|
|
189
189
|
catch (e) {
|
|
190
|
-
return `Error (EnterWorktree): ${e instanceof Error ? e.message : String(e)}
|
|
190
|
+
return errorResult(`Error (EnterWorktree): ${e instanceof Error ? e.message : String(e)}`);
|
|
191
191
|
}
|
|
192
192
|
session.current = {
|
|
193
193
|
worktreeDir: createdDir,
|
|
@@ -227,7 +227,7 @@ export function createWorktreeTools(env, opts) {
|
|
|
227
227
|
const discardChanges = raw.discardChanges ?? raw.discard_changes;
|
|
228
228
|
const wantsRemove = raw.action === "remove";
|
|
229
229
|
if (keep === true && discardChanges === true) {
|
|
230
|
-
return "Error (ExitWorktree): `keep: true` and `discardChanges: true` contradict each other — pick one.";
|
|
230
|
+
return errorResult("Error (ExitWorktree): `keep: true` and `discardChanges: true` contradict each other — pick one.");
|
|
231
231
|
}
|
|
232
232
|
if (!session.current) {
|
|
233
233
|
return ("No-op: there is no active EnterWorktree session to exit. This tool only operates on the " +
|
package/package.json
CHANGED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { AgentMessage } from "../internal/harness.js";
|
|
2
|
-
import { type MemorySelector, encodeSurfacedKey } from "./memory-recall.js";
|
|
3
|
-
import type { MemoryStore } from "./memory.js";
|
|
4
|
-
export interface DynamicRecallConfig {
|
|
5
|
-
enabled?: boolean;
|
|
6
|
-
recentMessageWindow?: number;
|
|
7
|
-
maxQueryChars?: number;
|
|
8
|
-
}
|
|
9
|
-
export declare const DEFAULT_RECENT_MESSAGE_WINDOW = 6;
|
|
10
|
-
export declare const DEFAULT_MAX_QUERY_CHARS = 4000;
|
|
11
|
-
export declare function buildRecallQuery(messages: readonly AgentMessage[], window?: number, maxChars?: number): string;
|
|
12
|
-
export interface DynamicRecallOutcome {
|
|
13
|
-
block: string | undefined;
|
|
14
|
-
surfacedIds: readonly string[];
|
|
15
|
-
surfacedKeys: readonly string[];
|
|
16
|
-
}
|
|
17
|
-
export interface DynamicRecallInput {
|
|
18
|
-
store: MemoryStore;
|
|
19
|
-
scopes: readonly string[];
|
|
20
|
-
selector: MemorySelector;
|
|
21
|
-
query: string;
|
|
22
|
-
recentTools?: string[];
|
|
23
|
-
maxSelected?: number;
|
|
24
|
-
maxLinked?: number;
|
|
25
|
-
timeoutMs?: number;
|
|
26
|
-
signal?: AbortSignal;
|
|
27
|
-
nowMs?: number;
|
|
28
|
-
priorSurfacedIds?: ReadonlySet<string>;
|
|
29
|
-
priorSurfacedKeys?: ReadonlySet<string>;
|
|
30
|
-
}
|
|
31
|
-
export declare function runDynamicRecall(input: DynamicRecallInput): Promise<DynamicRecallOutcome>;
|
|
32
|
-
export { encodeSurfacedKey };
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { encodeSurfacedKey, selectAndComposeLayeredMemory, selectAndComposeMemory, } from "./memory-recall.js";
|
|
2
|
-
export const DEFAULT_RECENT_MESSAGE_WINDOW = 6;
|
|
3
|
-
export const DEFAULT_MAX_QUERY_CHARS = 4_000;
|
|
4
|
-
export function buildRecallQuery(messages, window = DEFAULT_RECENT_MESSAGE_WINDOW, maxChars = DEFAULT_MAX_QUERY_CHARS) {
|
|
5
|
-
if (window <= 0 || maxChars <= 0)
|
|
6
|
-
return "";
|
|
7
|
-
const recent = messages.slice(-window);
|
|
8
|
-
const parts = [];
|
|
9
|
-
for (const m of recent) {
|
|
10
|
-
const text = messageText(m);
|
|
11
|
-
if (text)
|
|
12
|
-
parts.push(text);
|
|
13
|
-
}
|
|
14
|
-
const joined = parts.join("\n").trim();
|
|
15
|
-
if (joined.length <= maxChars)
|
|
16
|
-
return joined;
|
|
17
|
-
return joined.slice(joined.length - maxChars);
|
|
18
|
-
}
|
|
19
|
-
function messageText(m) {
|
|
20
|
-
const content = m.content;
|
|
21
|
-
if (typeof content === "string")
|
|
22
|
-
return content.trim();
|
|
23
|
-
if (Array.isArray(content)) {
|
|
24
|
-
const out = [];
|
|
25
|
-
for (const c of content) {
|
|
26
|
-
if (typeof c.text === "string" && c.text.length > 0)
|
|
27
|
-
out.push(c.text);
|
|
28
|
-
}
|
|
29
|
-
return out.join(" ").trim();
|
|
30
|
-
}
|
|
31
|
-
return "";
|
|
32
|
-
}
|
|
33
|
-
const EMPTY_OUTCOME = { block: undefined, surfacedIds: [], surfacedKeys: [] };
|
|
34
|
-
export async function runDynamicRecall(input) {
|
|
35
|
-
if (input.scopes.length === 0 || input.query.trim().length === 0)
|
|
36
|
-
return EMPTY_OUTCOME;
|
|
37
|
-
if (input.scopes.length === 1) {
|
|
38
|
-
const res = await selectAndComposeMemory({
|
|
39
|
-
store: input.store,
|
|
40
|
-
scope: input.scopes[0],
|
|
41
|
-
objective: input.query,
|
|
42
|
-
selector: input.selector,
|
|
43
|
-
recentTools: input.recentTools,
|
|
44
|
-
maxSelected: input.maxSelected,
|
|
45
|
-
maxLinked: input.maxLinked,
|
|
46
|
-
timeoutMs: input.timeoutMs,
|
|
47
|
-
signal: input.signal,
|
|
48
|
-
nowMs: input.nowMs,
|
|
49
|
-
priorSurfacedIds: input.priorSurfacedIds,
|
|
50
|
-
});
|
|
51
|
-
if ("degrade" in res)
|
|
52
|
-
return EMPTY_OUTCOME;
|
|
53
|
-
if (res.block === undefined || res.surfacedIds.length === 0)
|
|
54
|
-
return EMPTY_OUTCOME;
|
|
55
|
-
return { block: res.block, surfacedIds: res.surfacedIds, surfacedKeys: [] };
|
|
56
|
-
}
|
|
57
|
-
const res = await selectAndComposeLayeredMemory({
|
|
58
|
-
store: input.store,
|
|
59
|
-
scopes: [...input.scopes],
|
|
60
|
-
objective: input.query,
|
|
61
|
-
selector: input.selector,
|
|
62
|
-
recentTools: input.recentTools,
|
|
63
|
-
maxSelected: input.maxSelected,
|
|
64
|
-
maxLinked: input.maxLinked,
|
|
65
|
-
timeoutMs: input.timeoutMs,
|
|
66
|
-
signal: input.signal,
|
|
67
|
-
nowMs: input.nowMs,
|
|
68
|
-
priorSurfacedKeys: input.priorSurfacedKeys,
|
|
69
|
-
});
|
|
70
|
-
if ("degrade" in res)
|
|
71
|
-
return EMPTY_OUTCOME;
|
|
72
|
-
if (res.block === undefined || res.surfacedKeys.length === 0)
|
|
73
|
-
return EMPTY_OUTCOME;
|
|
74
|
-
return { block: res.block, surfacedIds: [], surfacedKeys: res.surfacedKeys };
|
|
75
|
-
}
|
|
76
|
-
export { encodeSurfacedKey };
|