@sema-agent/core 2.9.0 → 2.10.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/subagent.js +76 -1
- package/dist/config/defaults.d.ts +1 -0
- package/dist/config/defaults.js +1 -0
- package/dist/core/runner/assemble-result.d.ts +1 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/prepare-task.d.ts +2 -1
- package/dist/core/runner/prepare-task.js +37 -12
- package/dist/core/runner/runtask.js +18 -5
- package/dist/core/runner/tool-disclosure.d.ts +1 -1
- package/dist/core/runner/tool-disclosure.js +18 -3
- package/dist/core/task-registry-agent.js +3 -0
- package/dist/core/types.d.ts +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/orchestration/workflow.js +18 -10
- package/dist/tools/web.d.ts +15 -0
- package/dist/tools/web.js +42 -0
- package/package.json +1 -1
package/dist/agents/subagent.js
CHANGED
|
@@ -17,7 +17,7 @@ import { addWorktree } from "../core/git-worktree-env.js";
|
|
|
17
17
|
import { shellQuote } from "../tools/fs/search.js";
|
|
18
18
|
import { BG_AGENT_REAP_STOP_ERROR } from "../core/task-registry.js";
|
|
19
19
|
import { extractErrorCode } from "../brain/errors.js";
|
|
20
|
-
import { FORK_DEFAULT_MAX_TURNS, SESSION_BG_DEFAULT_TIMEOUT_SEC, RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX } from "../config/defaults.js";
|
|
20
|
+
import { FORK_DEFAULT_MAX_TURNS, SESSION_BG_DEFAULT_TIMEOUT_SEC, RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX, RUNNING_AGENT_OBSERVE_EVERY_BEATS } from "../config/defaults.js";
|
|
21
21
|
export { FORK_DEFAULT_MAX_TURNS, SESSION_BG_DEFAULT_TIMEOUT_SEC, RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX };
|
|
22
22
|
import { SUBAGENT_RESUME_CAP, SubagentRetainLedger, getOrCreateSessionRetainLedger, ensureSessionReapHook, createResumePrompt, } from "./retain-ledger.js";
|
|
23
23
|
import { recordRosterSpawn } from "./roster-store.js";
|
|
@@ -227,6 +227,21 @@ function errorKindClause(c) {
|
|
|
227
227
|
const FAILED_SESSION_RETAIN_TTL_MS = 15 * 60 * 1000;
|
|
228
228
|
const PARTIAL_FINDINGS_MAX_CHARS = 1200;
|
|
229
229
|
const BG_NOTIFY_DRAIN_WINDOW_MS = 2_000;
|
|
230
|
+
function createBgActivityBeat(parentToolCallId, emitTick) {
|
|
231
|
+
let beats = 0;
|
|
232
|
+
let starts = 0;
|
|
233
|
+
return (e) => {
|
|
234
|
+
if (e.type !== "tool_start" && e.type !== "tool_end")
|
|
235
|
+
return;
|
|
236
|
+
if (parentToolCallId === undefined || e.parentToolCallId !== parentToolCallId)
|
|
237
|
+
return;
|
|
238
|
+
if (e.type === "tool_start")
|
|
239
|
+
starts += 1;
|
|
240
|
+
beats += 1;
|
|
241
|
+
if (beats === 1 || beats % RUNNING_AGENT_OBSERVE_EVERY_BEATS === 0)
|
|
242
|
+
emitTick(starts);
|
|
243
|
+
};
|
|
244
|
+
}
|
|
230
245
|
function markerFragment() {
|
|
231
246
|
return uuidv7().replace(/-/g, "").slice(-12);
|
|
232
247
|
}
|
|
@@ -351,6 +366,25 @@ export function createSubagentResume(deps) {
|
|
|
351
366
|
}
|
|
352
367
|
}
|
|
353
368
|
reviveStartedAt = Date.now();
|
|
369
|
+
const reviveActivityBeat = createBgActivityBeat(deps.parentToolCallId, (toolStarts) => {
|
|
370
|
+
if (reviveEmit === undefined)
|
|
371
|
+
return;
|
|
372
|
+
const currentAction = resumeStepRecorder.currentAction();
|
|
373
|
+
const currentTool = resumeStepRecorder.currentActionStructured();
|
|
374
|
+
reviveEmit({
|
|
375
|
+
kind: "tick",
|
|
376
|
+
taskId: deps.taskId,
|
|
377
|
+
sessionScoped: deps.sessionScoped === true,
|
|
378
|
+
...(deps.rowAgentType !== undefined ? { agentType: deps.rowAgentType } : {}),
|
|
379
|
+
transcriptId: entry.childSessionId,
|
|
380
|
+
sessionId: entry.childSessionId,
|
|
381
|
+
...(deps.parentToolCallId !== undefined ? { parentToolCallId: deps.parentToolCallId } : {}),
|
|
382
|
+
progressTaskId: entry.childSessionId,
|
|
383
|
+
...(currentAction !== undefined ? { currentAction } : {}),
|
|
384
|
+
...(currentTool !== undefined ? { currentTool } : {}),
|
|
385
|
+
usage: { toolUses: toolStarts },
|
|
386
|
+
});
|
|
387
|
+
});
|
|
354
388
|
stream = childRunner.runTaskStream(resumeSpec, undefined, {
|
|
355
389
|
...entry.internalsSnapshot,
|
|
356
390
|
...(true
|
|
@@ -362,6 +396,7 @@ export function createSubagentResume(deps) {
|
|
|
362
396
|
}
|
|
363
397
|
catch {
|
|
364
398
|
}
|
|
399
|
+
reviveActivityBeat(e);
|
|
365
400
|
if (reviveEmit !== undefined && e.type === "task_progress") {
|
|
366
401
|
reviveEmit({
|
|
367
402
|
kind: "tick",
|
|
@@ -1698,6 +1733,23 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1698
1733
|
const s2ForkNotifyReady = (inject) => {
|
|
1699
1734
|
bg.registry.attachAgentNotify(taskId, inject);
|
|
1700
1735
|
};
|
|
1736
|
+
const bgForkActivityBeat = createBgActivityBeat(ctx.toolCallId, (toolStarts) => {
|
|
1737
|
+
const currentAction = stepRecorder.currentAction();
|
|
1738
|
+
const currentTool = stepRecorder.currentActionStructured();
|
|
1739
|
+
sinkEmit({
|
|
1740
|
+
kind: "tick",
|
|
1741
|
+
taskId,
|
|
1742
|
+
sessionScoped: sessionScopedBg === true,
|
|
1743
|
+
transcriptId: forkedId,
|
|
1744
|
+
sessionId: forkedId,
|
|
1745
|
+
parentToolCallId: ctx.toolCallId,
|
|
1746
|
+
progressTaskId: forkedId,
|
|
1747
|
+
agentType: spawnAgentType,
|
|
1748
|
+
...(currentAction !== undefined ? { currentAction } : {}),
|
|
1749
|
+
...(currentTool !== undefined ? { currentTool } : {}),
|
|
1750
|
+
usage: { toolUses: toolStarts },
|
|
1751
|
+
});
|
|
1752
|
+
});
|
|
1701
1753
|
const bgForkInternals = bgSink
|
|
1702
1754
|
? {
|
|
1703
1755
|
...forkInternals,
|
|
@@ -1708,6 +1760,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1708
1760
|
}
|
|
1709
1761
|
catch {
|
|
1710
1762
|
}
|
|
1763
|
+
bgForkActivityBeat(e);
|
|
1711
1764
|
if (e.type === "task_progress") {
|
|
1712
1765
|
const currentAction = stepRecorder.currentAction();
|
|
1713
1766
|
const currentTool = stepRecorder.currentActionStructured();
|
|
@@ -1764,6 +1817,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1764
1817
|
? classifySubagentError({ status: "failed", ...(child.errorCode !== undefined ? { errorCode: child.errorCode } : {}), ...(child.errorMessage !== undefined ? { errorMessage: child.errorMessage } : {}) })
|
|
1765
1818
|
: undefined;
|
|
1766
1819
|
const settledBg = bg.registry.settleBackgroundAgent(taskId, {
|
|
1820
|
+
cycle: 0,
|
|
1767
1821
|
status: okBg ? "completed" : reapedBg ? "killed" : "failed",
|
|
1768
1822
|
...resultSettleFields(child.result),
|
|
1769
1823
|
...(!okBg ? { error: reapedBg ? (collateralBg ? BG_AGENT_COLLATERAL_REAP_REASON : BG_AGENT_REAP_STOP_ERROR) : child.errorMessage ?? String(child.status) } : {}),
|
|
@@ -1835,6 +1889,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1835
1889
|
const errCodeForkReject = killed ? undefined : extractErrorCode(msgFork);
|
|
1836
1890
|
const errClassForkReject = killed ? undefined : classifySubagentError({ status: "failed", errorMessage: msgFork });
|
|
1837
1891
|
const settledBg = bg.registry.settleBackgroundAgent(taskId, {
|
|
1892
|
+
cycle: 0,
|
|
1838
1893
|
status: killed ? "killed" : "failed",
|
|
1839
1894
|
error: killed ? BG_AGENT_REAP_STOP_ERROR : msgFork,
|
|
1840
1895
|
...(errCodeForkReject !== undefined ? { errorCode: errCodeForkReject } : {}),
|
|
@@ -2157,6 +2212,23 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2157
2212
|
bg.registry.finalizeParkedResume(taskId);
|
|
2158
2213
|
reviveAttachedResolve?.();
|
|
2159
2214
|
};
|
|
2215
|
+
const bgActivityBeat = createBgActivityBeat(ctx.toolCallId, (toolStarts) => {
|
|
2216
|
+
const currentAction = stepRecorder.currentAction();
|
|
2217
|
+
const currentTool = stepRecorder.currentActionStructured();
|
|
2218
|
+
sinkEmit({
|
|
2219
|
+
kind: "tick",
|
|
2220
|
+
taskId,
|
|
2221
|
+
sessionScoped: sessionScopedBg === true,
|
|
2222
|
+
transcriptId: bgChildSessionId,
|
|
2223
|
+
sessionId: bgChildSessionId,
|
|
2224
|
+
parentToolCallId: ctx.toolCallId,
|
|
2225
|
+
progressTaskId: bgChildSessionId,
|
|
2226
|
+
agentType: spawnAgentType,
|
|
2227
|
+
...(currentAction !== undefined ? { currentAction } : {}),
|
|
2228
|
+
...(currentTool !== undefined ? { currentTool } : {}),
|
|
2229
|
+
usage: { toolUses: toolStarts },
|
|
2230
|
+
});
|
|
2231
|
+
});
|
|
2160
2232
|
const bgInternals = bgSink
|
|
2161
2233
|
? {
|
|
2162
2234
|
...childInternals,
|
|
@@ -2167,6 +2239,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2167
2239
|
}
|
|
2168
2240
|
catch {
|
|
2169
2241
|
}
|
|
2242
|
+
bgActivityBeat(e);
|
|
2170
2243
|
if (e.type === "task_progress") {
|
|
2171
2244
|
const currentAction = stepRecorder.currentAction();
|
|
2172
2245
|
const currentTool = stepRecorder.currentActionStructured();
|
|
@@ -2400,6 +2473,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2400
2473
|
? classifySubagentError({ status: "failed", ...(child.errorCode !== undefined ? { errorCode: child.errorCode } : {}), ...(child.errorMessage !== undefined ? { errorMessage: child.errorMessage } : {}) })
|
|
2401
2474
|
: undefined;
|
|
2402
2475
|
const settled = bg.registry.settleBackgroundAgent(taskId, {
|
|
2476
|
+
cycle: 0,
|
|
2403
2477
|
status: ok ? "completed" : reaped ? "killed" : "failed",
|
|
2404
2478
|
seq: seqAtSettle ?? 1,
|
|
2405
2479
|
...resultSettleFields(child.result),
|
|
@@ -2496,6 +2570,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2496
2570
|
const errCodeBgReject = killed ? undefined : extractErrorCode(msg);
|
|
2497
2571
|
const errClassBgReject = killed ? undefined : classifySubagentError({ status: "failed", errorMessage: msg });
|
|
2498
2572
|
const settled = bg.registry.settleBackgroundAgent(taskId, {
|
|
2573
|
+
cycle: 0,
|
|
2499
2574
|
status: killed ? "killed" : "failed",
|
|
2500
2575
|
error: msg,
|
|
2501
2576
|
seq: seqAtSettle ?? 1,
|
|
@@ -4,3 +4,4 @@ export declare const SESSION_BG_DEFAULT_TIMEOUT_SEC: number;
|
|
|
4
4
|
export declare const RETAIN_DEFAULT_TTL_MS: number;
|
|
5
5
|
export declare const RETAIN_DEFAULT_MAX = 16;
|
|
6
6
|
export declare const SESSION_DEFAULT_TTL_DAYS = 7;
|
|
7
|
+
export declare const RUNNING_AGENT_OBSERVE_EVERY_BEATS = 4;
|
package/dist/config/defaults.js
CHANGED
|
@@ -143,5 +143,5 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
143
143
|
void _internalCompaction;
|
|
144
144
|
if (flags.unpricedSpend)
|
|
145
145
|
delete publicStats.costMicroUsd;
|
|
146
|
-
return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, checkpointToken, checkpointGate, stats: publicStats };
|
|
146
|
+
return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, checkpointToken, checkpointGate, ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), stats: publicStats };
|
|
147
147
|
}
|
|
@@ -17,7 +17,7 @@ import { type CwdRef } from "../../tools/fs/index.js";
|
|
|
17
17
|
import type { Runner } from "./runtask.js";
|
|
18
18
|
import { type CheckpointGate, type CheckpointState, type CheckpointToken, type ResourceLedger, type ResourceLimitReason } from "../checkpoint-store.js";
|
|
19
19
|
import type { AgentMessage, AgentTool, ExecutionEnv } from "../../internal/harness.js";
|
|
20
|
-
import type { RunnerDeps, TaskEvent, TaskSpec, ToolActivity, ToolEffect } from "../types.js";
|
|
20
|
+
import type { RunnerDeps, TaskEvent, TaskResult, TaskSpec, ToolActivity, ToolEffect } from "../types.js";
|
|
21
21
|
import type { RepairBundle } from "../../agents/repair-loop.js";
|
|
22
22
|
export declare const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible";
|
|
23
23
|
export declare function checkpointScopeOf(spec: {
|
|
@@ -48,6 +48,7 @@ export interface Prepared {
|
|
|
48
48
|
tasks: number;
|
|
49
49
|
costMicroUsd: number;
|
|
50
50
|
};
|
|
51
|
+
rewindNotes?: NonNullable<TaskResult["rewindNotes"]>;
|
|
51
52
|
cwdRef?: CwdRef;
|
|
52
53
|
worktreeSessionRef?: {
|
|
53
54
|
current?: {
|
|
@@ -616,8 +616,37 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
616
616
|
catch {
|
|
617
617
|
}
|
|
618
618
|
}
|
|
619
|
-
|
|
619
|
+
const rewindNotes = [];
|
|
620
|
+
const rewindCaptureRequested = spec.rewindFiles === true;
|
|
621
|
+
let rewindTarget = spec.resumeAt !== undefined ? (rewindCaptureRequested ? spec.resumeAt : undefined) : spec.rewindFilesTo;
|
|
620
622
|
const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
|
|
623
|
+
if (spec.resumeAt !== undefined && !rewindCaptureRequested) {
|
|
624
|
+
rewindNotes.push({
|
|
625
|
+
code: "conversation_only",
|
|
626
|
+
message: `the conversation was branched at entry "${spec.resumeAt}" but the working tree was NOT rewound — this task set resumeAt without rewindFiles, so files remain at their current state`,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
if (!deps.fileSnapshotStore) {
|
|
630
|
+
if (rewindTarget !== undefined) {
|
|
631
|
+
const e = new Error(`rewind-files: restoring the working tree to entry "${rewindTarget}" requires a snapshot backend, but this deployment wired no RunnerDeps.fileSnapshotStore — no snapshot was ever captured, so the files were NOT rewound`);
|
|
632
|
+
e.code = "rewind.store_unconfigured";
|
|
633
|
+
throw e;
|
|
634
|
+
}
|
|
635
|
+
if (rewindCaptureRequested) {
|
|
636
|
+
rewindNotes.push({
|
|
637
|
+
code: "snapshot_store_unconfigured",
|
|
638
|
+
message: "rewindFiles was requested but no RunnerDeps.fileSnapshotStore is wired — no working-tree snapshot was captured for this turn, so it cannot be rewound to later",
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
else if (!handsEnabled) {
|
|
643
|
+
if (rewindTarget !== undefined || rewindCaptureRequested) {
|
|
644
|
+
rewindNotes.push({
|
|
645
|
+
code: "files_env_unsupported",
|
|
646
|
+
message: "the file side of rewind was inert: this deployment mounts no filesystem-capable ExecutionEnv, so no working-tree snapshot was captured or restored",
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
}
|
|
621
650
|
if (rewindTarget !== undefined && deps.fileSnapshotStore && handsEnabled) {
|
|
622
651
|
if (rewindBefore) {
|
|
623
652
|
let anchor;
|
|
@@ -642,18 +671,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
642
671
|
const restoreSignal = spec.signal ? AbortSignal.any([abortController.signal, spec.signal]) : abortController.signal;
|
|
643
672
|
const restored = await deps.fileSnapshotStore.restore(sessionId, rewindTarget, executionEnv, restoreRoot, restoreSignal);
|
|
644
673
|
if (!restored.ok) {
|
|
645
|
-
if (restored.error.code === "not_found"
|
|
646
|
-
const e = new Error(
|
|
674
|
+
if (restored.error.code === "not_found") {
|
|
675
|
+
const e = new Error(rewindBefore
|
|
676
|
+
? `rewind-files: the resolved "before" snapshot anchor "${rewindTarget}" disappeared before restore — files were NOT rewound`
|
|
677
|
+
: `rewind-files: no file snapshot exists for entry "${rewindTarget}" on session "${sessionId}" — the working tree was NOT rewound (only a COMPLETED turn that ran with rewindFiles is snapshotted, and snapshots are keyed by that turn's END leaf; earlier turns that ran without rewindFiles, mid-turn entries, and reaped snapshots have none)`);
|
|
647
678
|
e.code = "rewind_snapshot.unresolvable";
|
|
648
679
|
throw e;
|
|
649
680
|
}
|
|
650
|
-
if (restored.error.code === "not_found") {
|
|
651
|
-
try {
|
|
652
|
-
deps.onError?.(new Error(`rewind-files: no snapshot for entry "${rewindTarget}" — files left unchanged`), { phase: "rewind", sessionId });
|
|
653
|
-
}
|
|
654
|
-
catch {
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
681
|
else {
|
|
658
682
|
const e = new Error(`rewind-files restore failed (${restored.error.code}): ${restored.error.message}`);
|
|
659
683
|
e.code = "rewind.restore_failed";
|
|
@@ -1852,7 +1876,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1852
1876
|
...(executionMode !== undefined ? { executionMode } : {}),
|
|
1853
1877
|
activate: async () => {
|
|
1854
1878
|
if (activeTools.has(name))
|
|
1855
|
-
return;
|
|
1879
|
+
return undefined;
|
|
1856
1880
|
activeTools.add(name);
|
|
1857
1881
|
try {
|
|
1858
1882
|
await rematerialize(activeTools);
|
|
@@ -1861,6 +1885,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1861
1885
|
activeTools.delete(name);
|
|
1862
1886
|
throw e;
|
|
1863
1887
|
}
|
|
1888
|
+
return listingRideRef.current?.([name]);
|
|
1864
1889
|
},
|
|
1865
1890
|
};
|
|
1866
1891
|
};
|
|
@@ -3357,7 +3382,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3357
3382
|
: undefined;
|
|
3358
3383
|
overheadState.promptChars = systemPrompt.length;
|
|
3359
3384
|
const preparedHolder = {};
|
|
3360
|
-
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), ownedEnv, suspendRef, reviewRef, reviewRequestRef, suspendLoopRef, suspendForResource, ...(callCapRef ? { callCapRef } : {}), ...(cutKills ? { cutKills } : {}), ...(callCapRef ? { callIssuedAtRef } : {}), suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, promptOverheadTokens, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
3385
|
+
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), ownedEnv, suspendRef, reviewRef, reviewRequestRef, suspendLoopRef, suspendForResource, ...(callCapRef ? { callCapRef } : {}), ...(cutKills ? { cutKills } : {}), ...(callCapRef ? { callIssuedAtRef } : {}), suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, promptOverheadTokens, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
3361
3386
|
const prepared = buildPrepared();
|
|
3362
3387
|
preparedHolder.current = prepared;
|
|
3363
3388
|
return prepared;
|
|
@@ -2764,6 +2764,7 @@ export class Runner {
|
|
|
2764
2764
|
threw,
|
|
2765
2765
|
model: prepared.model.id,
|
|
2766
2766
|
unpricedSpend: rs.telemetry.unpricedSpend,
|
|
2767
|
+
rewindNotes: prepared.rewindNotes,
|
|
2767
2768
|
abortedForTimeout: timeout.fired,
|
|
2768
2769
|
abortedForTurns: rs.limits.turnsExceeded,
|
|
2769
2770
|
abortedLive,
|
|
@@ -3045,10 +3046,19 @@ export class Runner {
|
|
|
3045
3046
|
if (!leafId)
|
|
3046
3047
|
return;
|
|
3047
3048
|
const root = prepared.taskRootPath;
|
|
3048
|
-
const
|
|
3049
|
-
if (
|
|
3050
|
-
if (Date.now() - refusedAt < SNAPSHOT_TOO_LARGE_TTL_MS)
|
|
3049
|
+
const refusal = this.snapshotTooLargeRoots.get(root);
|
|
3050
|
+
if (refusal !== undefined) {
|
|
3051
|
+
if (Date.now() - refusal.refusedAt < SNAPSHOT_TOO_LARGE_TTL_MS) {
|
|
3052
|
+
if (!refusal.skipAnnounced) {
|
|
3053
|
+
refusal.skipAnnounced = true;
|
|
3054
|
+
try {
|
|
3055
|
+
this.deps.onError?.(new Error(`rewind-files snapshot skipped for this working-tree root: a too_large refusal is still inside its ${SNAPSHOT_TOO_LARGE_TTL_MS / 60_000}-minute cooldown (re-probed at ${new Date(refusal.refusedAt + SNAPSHOT_TOO_LARGE_TTL_MS).toISOString()}) — turns completed during the cooldown capture no snapshot and cannot be rewound to. Announced once per cooldown window.`), { phase: "rewind", sessionId: prepared.sessionId, classification: "too_large" });
|
|
3056
|
+
}
|
|
3057
|
+
catch {
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3051
3060
|
return;
|
|
3061
|
+
}
|
|
3052
3062
|
this.snapshotTooLargeRoots.delete(root);
|
|
3053
3063
|
}
|
|
3054
3064
|
const ac = new AbortController();
|
|
@@ -3071,11 +3081,14 @@ export class Runner {
|
|
|
3071
3081
|
}
|
|
3072
3082
|
if (!r.ok) {
|
|
3073
3083
|
const tooLarge = r.error.code === "too_large";
|
|
3084
|
+
const refusedAt = Date.now();
|
|
3074
3085
|
if (tooLarge)
|
|
3075
|
-
this.snapshotTooLargeRoots.set(root,
|
|
3086
|
+
this.snapshotTooLargeRoots.set(root, { refusedAt, skipAnnounced: false });
|
|
3076
3087
|
try {
|
|
3077
3088
|
this.deps.onError?.(new Error(`rewind-files snapshot failed (${r.error.code}): ${r.error.message}` +
|
|
3078
|
-
(tooLarge
|
|
3089
|
+
(tooLarge
|
|
3090
|
+
? ` — skipping further snapshots for this root (this process) during a ${SNAPSHOT_TOO_LARGE_TTL_MS / 60_000}-minute cooldown, until ${new Date(refusedAt + SNAPSHOT_TOO_LARGE_TTL_MS).toISOString()}, when the tree is re-probed automatically. Raise the store's snapshotBounds to accept a tree this size; shrinking the tree also re-enables rewind, but only at that re-probe — not immediately`
|
|
3091
|
+
: "")), { phase: "rewind", sessionId: prepared.sessionId, ...(tooLarge ? { classification: "too_large" } : {}) });
|
|
3079
3092
|
}
|
|
3080
3093
|
catch {
|
|
3081
3094
|
}
|
|
@@ -31,7 +31,7 @@ export declare function buildDeferredRegistry(deferred: ReadonlySet<string>, too
|
|
|
31
31
|
export interface PlaceholderDirectCall {
|
|
32
32
|
resolveReal: () => PlaceholderDirectTarget | undefined;
|
|
33
33
|
executionMode?: ToolExecutionMode;
|
|
34
|
-
activate: () => Promise<
|
|
34
|
+
activate: () => Promise<string | undefined>;
|
|
35
35
|
}
|
|
36
36
|
export interface PlaceholderDirectTarget {
|
|
37
37
|
parameters: TSchema;
|
|
@@ -79,8 +79,11 @@ export function createPlaceholderTool(info, direct) {
|
|
|
79
79
|
execute: async (toolCallId, params, signal, onUpdate) => {
|
|
80
80
|
const real = direct.resolveReal();
|
|
81
81
|
if (real !== undefined && Value.Check(real.parameters, params)) {
|
|
82
|
-
await direct.activate();
|
|
83
|
-
|
|
82
|
+
const ride = await direct.activate();
|
|
83
|
+
const result = await real.invoke(toolCallId, params, signal, onUpdate);
|
|
84
|
+
if (ride === undefined || ride === "")
|
|
85
|
+
return result;
|
|
86
|
+
return { ...result, content: [...result.content, { type: "text", text: ride }] };
|
|
84
87
|
}
|
|
85
88
|
return teachingRejection();
|
|
86
89
|
},
|
|
@@ -168,15 +171,27 @@ export function resolveToolSearch(args, registry) {
|
|
|
168
171
|
}
|
|
169
172
|
export function extractDiscoveredToolNames(messages, registry) {
|
|
170
173
|
const names = new Set();
|
|
174
|
+
const pendingDirect = new Map();
|
|
171
175
|
for (const m of messages) {
|
|
176
|
+
if (m.role === "toolResult") {
|
|
177
|
+
const name = pendingDirect.get(m.toolCallId);
|
|
178
|
+
if (name !== undefined && m.isError !== true)
|
|
179
|
+
names.add(name);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
172
182
|
if (m.role !== "assistant")
|
|
173
183
|
continue;
|
|
174
184
|
for (const part of m.content) {
|
|
175
|
-
if (part.type
|
|
185
|
+
if (part.type !== "toolCall")
|
|
186
|
+
continue;
|
|
187
|
+
if (part.name === TOOL_SEARCH_NAME) {
|
|
176
188
|
for (const n of resolveToolSearch(part.arguments, registry)) {
|
|
177
189
|
names.add(n);
|
|
178
190
|
}
|
|
179
191
|
}
|
|
192
|
+
else if (registry.has(part.name)) {
|
|
193
|
+
pendingDirect.set(part.id, part.name);
|
|
194
|
+
}
|
|
180
195
|
}
|
|
181
196
|
}
|
|
182
197
|
return [...names];
|
|
@@ -652,6 +652,9 @@ export function settleBackgroundAgentLane(core, id, outcome) {
|
|
|
652
652
|
const handle = core.handles.get(id);
|
|
653
653
|
if (!handle || handle.type !== "background_agent")
|
|
654
654
|
return undefined;
|
|
655
|
+
if (outcome.cycle === undefined && (handle.reviveCycle ?? 0) !== 0) {
|
|
656
|
+
throw new Error(`settleBackgroundAgent("${id}"): the row has been revived (cycle ${handle.reviveCycle}) — pass the cycle this settle speaks for, or use settleRevivedAgent`);
|
|
657
|
+
}
|
|
655
658
|
if ((outcome.cycle ?? 0) !== (handle.reviveCycle ?? 0))
|
|
656
659
|
return undefined;
|
|
657
660
|
if (handle.status !== "running") {
|
package/dist/core/types.d.ts
CHANGED
|
@@ -390,6 +390,10 @@ export interface TaskResult {
|
|
|
390
390
|
atTurn: number;
|
|
391
391
|
};
|
|
392
392
|
structuredOutput?: unknown;
|
|
393
|
+
rewindNotes?: Array<{
|
|
394
|
+
code: "conversation_only" | "files_env_unsupported" | "snapshot_store_unconfigured";
|
|
395
|
+
message: string;
|
|
396
|
+
}>;
|
|
393
397
|
stats: {
|
|
394
398
|
turns: number;
|
|
395
399
|
tokens: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { createSkillsFromDirectory, type SkillsDirectoryOptions, type SkillsDire
|
|
|
7
7
|
export { REPORT_FINDINGS_TOOL_NAME, type ReportedFinding } from "./core/runner/synthetic-tools.js";
|
|
8
8
|
export { formatToolError, formatZodValidationError, formatValidationPath, truncateError, errorClassOf } from "./core/tool-errors.js";
|
|
9
9
|
export type { WorkerErrorClass } from "./core/tool-errors.js";
|
|
10
|
-
export { createWebFetchTool, webFetchToolSpec, htmlToText, type WebFetchConfig, createWebSearchTool, type WebSearchConfig, createWebFetchSummarizer, WEBFETCH_SUMMARY_MAX_CONTENT, WEBFETCH_SUMMARY_GUIDELINES, } from "./tools/web.js";
|
|
10
|
+
export { createWebFetchTool, webFetchToolSpec, htmlToText, type WebFetchConfig, createWebSearchTool, type WebSearchConfig, createSearxngSearchBackend, probeSearchBackend, type SearxngBackendOptions, createWebFetchSummarizer, WEBFETCH_SUMMARY_MAX_CONTENT, WEBFETCH_SUMMARY_GUIDELINES, } from "./tools/web.js";
|
|
11
11
|
export { createTodoWriteTool } from "./tools/todo.js";
|
|
12
12
|
export { createTaskListTools, createMemoryTaskListStore, assertJsonMetadata, type TaskListItem, type TaskListStore } from "./tools/task-list.js";
|
|
13
13
|
export { assembleCodeTools, type CodeToolsConfig, CODE_ROLE } from "./scenarios/full-body.js";
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export { SKILL_TOOL_NAME } from "./core/runner/synthetic-tools.js";
|
|
|
5
5
|
export { createSkillsFromDirectory, } from "./core/skills-directory.js";
|
|
6
6
|
export { REPORT_FINDINGS_TOOL_NAME } from "./core/runner/synthetic-tools.js";
|
|
7
7
|
export { formatToolError, formatZodValidationError, formatValidationPath, truncateError, errorClassOf } from "./core/tool-errors.js";
|
|
8
|
-
export { createWebFetchTool, webFetchToolSpec, htmlToText, createWebSearchTool, createWebFetchSummarizer, WEBFETCH_SUMMARY_MAX_CONTENT, WEBFETCH_SUMMARY_GUIDELINES, } from "./tools/web.js";
|
|
8
|
+
export { createWebFetchTool, webFetchToolSpec, htmlToText, createWebSearchTool, createSearxngSearchBackend, probeSearchBackend, createWebFetchSummarizer, WEBFETCH_SUMMARY_MAX_CONTENT, WEBFETCH_SUMMARY_GUIDELINES, } from "./tools/web.js";
|
|
9
9
|
export { createTodoWriteTool } from "./tools/todo.js";
|
|
10
10
|
export { createTaskListTools, createMemoryTaskListStore, assertJsonMetadata } from "./tools/task-list.js";
|
|
11
11
|
export { assembleCodeTools, CODE_ROLE } from "./scenarios/full-body.js";
|
|
@@ -9,6 +9,7 @@ import { combinePolicies, createAllowDenyPolicy } from "../core/tool-policy.js";
|
|
|
9
9
|
import { callKeyOrdinal, oversizeJournalResult, journalOversizeTombstone, JOURNAL_OVERSIZE_ERROR_CODE, MAX_JOURNAL_RESULT_BYTES } from "../core/workflow-journal-store.js";
|
|
10
10
|
import { isWorkflowRunActive, closeWorkflowChannel, markWorkflowActive, publishWorkflowEvent } from "./workflow-observe.js";
|
|
11
11
|
import { isDurablePause, mapNestedSuspend } from "../agents/suspend-guard.js";
|
|
12
|
+
import { RUNNING_AGENT_OBSERVE_EVERY_BEATS } from "../config/defaults.js";
|
|
12
13
|
import { boundInputHashOf } from "../core/canonical-json.js";
|
|
13
14
|
import { boundedRedactedSummary } from "../core/untrusted-egress.js";
|
|
14
15
|
import { delimitUntrusted } from "../core/untrusted-text.js";
|
|
@@ -20,7 +21,6 @@ const MAX_TRANSCRIPT_CHARS = 4000;
|
|
|
20
21
|
const WORKFLOW_RESULT_MAX = 4000;
|
|
21
22
|
const WORKFLOW_RESULT_FULL_MAX = 200_000;
|
|
22
23
|
const MAX_ACTIVITY = 30;
|
|
23
|
-
const RUNNING_AGENT_PERSIST_EVERY_BEATS = 4;
|
|
24
24
|
function workflowModelLabel(spec) {
|
|
25
25
|
const model = spec.model;
|
|
26
26
|
if (model === undefined)
|
|
@@ -453,16 +453,17 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
453
453
|
};
|
|
454
454
|
const waIdOf = (callKey) => `wa${createHash("sha256").update(`${runId}:${callKey}`).digest("hex").slice(0, 16)}`;
|
|
455
455
|
const bceLive = new Map();
|
|
456
|
-
const bceSpawn = (callKey, label, agentType, replayed) => {
|
|
456
|
+
const bceSpawn = (callKey, label, agentType, replayed, sessionId) => {
|
|
457
457
|
if (!bceSink)
|
|
458
458
|
return;
|
|
459
459
|
const id = waIdOf(callKey);
|
|
460
|
-
bceLive.set(id, { callKey, label, ...(agentType !== undefined ? { agentType } : {}) });
|
|
460
|
+
bceLive.set(id, { callKey, label, ...(agentType !== undefined ? { agentType } : {}), ...(sessionId !== undefined ? { sessionId } : {}) });
|
|
461
461
|
bceEmit({
|
|
462
462
|
kind: "spawn",
|
|
463
463
|
taskId: id,
|
|
464
464
|
sessionScoped: false,
|
|
465
465
|
owner: runId,
|
|
466
|
+
...(sessionId !== undefined ? { sessionId, transcriptId: sessionId } : {}),
|
|
466
467
|
...(scope !== undefined ? { scope } : {}),
|
|
467
468
|
description: replayed ? `${label} (replayed)` : label,
|
|
468
469
|
agentType: agentType ?? "workflow-agent",
|
|
@@ -474,6 +475,11 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
474
475
|
startedAt: Date.now(),
|
|
475
476
|
});
|
|
476
477
|
};
|
|
478
|
+
const bceBindSession = (callKey, sessionId) => {
|
|
479
|
+
const row = bceLive.get(waIdOf(callKey));
|
|
480
|
+
if (row !== undefined)
|
|
481
|
+
row.sessionId = sessionId;
|
|
482
|
+
};
|
|
477
483
|
const bceTick = (callKey, e) => {
|
|
478
484
|
if (!bceSink)
|
|
479
485
|
return;
|
|
@@ -489,6 +495,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
489
495
|
workflowRunId: runId,
|
|
490
496
|
...(scope !== undefined ? { scope } : {}),
|
|
491
497
|
...(row.agentType !== undefined ? { agentType: row.agentType } : { agentType: "workflow-agent" }),
|
|
498
|
+
...(row.sessionId !== undefined ? { sessionId: row.sessionId, transcriptId: row.sessionId } : {}),
|
|
492
499
|
name: e.name ?? row.label,
|
|
493
500
|
progressTaskId: e.taskId,
|
|
494
501
|
...(e.parentTaskId !== undefined ? { progressParentTaskId: e.parentTaskId } : {}),
|
|
@@ -635,7 +642,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
635
642
|
rec.toolCalls = (rec.toolCalls ?? 0) + 1;
|
|
636
643
|
rec.activity = tail;
|
|
637
644
|
beatCount += 1;
|
|
638
|
-
if (beatCount === 1 || beatCount %
|
|
645
|
+
if (beatCount === 1 || beatCount % RUNNING_AGENT_OBSERVE_EVERY_BEATS === 0) {
|
|
639
646
|
void persist("update");
|
|
640
647
|
}
|
|
641
648
|
};
|
|
@@ -768,7 +775,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
768
775
|
agentPhaseOf.set(replayRec, phaseInstance);
|
|
769
776
|
emit({ type: "agent_start", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), callKey, prompt, ...(model !== undefined ? { model } : {}), replayed: true, ts: at });
|
|
770
777
|
emit({ type: "agent_end", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), status: replayRec.status, output: cachedOutput, ...(rs.toolCalls !== undefined ? { toolCalls: rs.toolCalls } : {}), replayed: true, ts: at });
|
|
771
|
-
bceSpawn(callKey, label, agentOpts.agentType, true);
|
|
778
|
+
bceSpawn(callKey, label, agentOpts.agentType, true, r.sessionId || undefined);
|
|
772
779
|
bceTerminal(callKey, replayRec.status === "completed" ? "completed" : "failed", cachedOutput, r.sessionId || undefined, replayRec.stats);
|
|
773
780
|
accumulateStats(r, false);
|
|
774
781
|
void persist("update");
|
|
@@ -810,7 +817,8 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
810
817
|
if (finalized)
|
|
811
818
|
throw new Error("workflow run already finalized — ctx.agent cannot spawn after the run ended");
|
|
812
819
|
rec.startedAt = now();
|
|
813
|
-
|
|
820
|
+
const bornChildSessionId = resolveChildSessionIdAtSpawn(spec);
|
|
821
|
+
bceSpawn(callKey, label, agentOpts.agentType, false, bornChildSessionId);
|
|
814
822
|
const typedSpec0 = applyWorkflowAgentType(spec, agentOpts.agentType, agentRegistry);
|
|
815
823
|
const typedSpec = typedSpec0.model === undefined && inheritedModelSnap !== undefined ? { ...typedSpec0, model: inheritedModelSnap } : typedSpec0;
|
|
816
824
|
const framedSpec = withWorkflowChildPersona(typedSpec, agentOpts.schema ?? typedSpec.outputSchema);
|
|
@@ -852,10 +860,11 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
852
860
|
const onCallerAbort = () => attemptCtl.abort(new Error("workflow aborted"));
|
|
853
861
|
effectiveSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
854
862
|
armWatchdog();
|
|
855
|
-
const attemptSessionId = resolveChildSessionIdAtSpawn(runSpec);
|
|
863
|
+
const attemptSessionId = attempts === 1 ? bornChildSessionId : resolveChildSessionIdAtSpawn(runSpec);
|
|
856
864
|
const attemptSpec = attemptSessionId !== undefined ? { ...runSpec, sessionId: attemptSessionId, signal: attemptCtl.signal } : { ...runSpec, signal: attemptCtl.signal };
|
|
857
865
|
if (attemptSessionId !== undefined && !finalized) {
|
|
858
866
|
rec.sessionId = attemptSessionId;
|
|
867
|
+
bceBindSession(callKey, attemptSessionId);
|
|
859
868
|
void persist("update");
|
|
860
869
|
}
|
|
861
870
|
const attemptInternals = {
|
|
@@ -1143,9 +1152,9 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1143
1152
|
throw new Error(finalized ? "workflow run already finalized — ctx.agentStream cannot spawn after the run ended" : "workflow aborted");
|
|
1144
1153
|
}
|
|
1145
1154
|
rec.startedAt = now();
|
|
1146
|
-
|
|
1155
|
+
const childSessionId = resolveChildSessionIdAtSpawn(spec);
|
|
1156
|
+
bceSpawn(callKey, label, agentOpts.agentType, false, childSessionId);
|
|
1147
1157
|
let stream;
|
|
1148
|
-
let childSessionId;
|
|
1149
1158
|
try {
|
|
1150
1159
|
const typedSpec0 = applyWorkflowAgentType(spec, agentOpts.agentType, agentRegistry);
|
|
1151
1160
|
const typedSpec = typedSpec0.model === undefined && inheritedModelSnap !== undefined ? { ...typedSpec0, model: inheritedModelSnap } : typedSpec0;
|
|
@@ -1154,7 +1163,6 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1154
1163
|
const baseRunSpec = agentOpts.schema
|
|
1155
1164
|
? { ...framedSpec, ...authInherit, signal: effectiveSignal, outputSchema: agentOpts.schema }
|
|
1156
1165
|
: { ...framedSpec, ...authInherit, signal: effectiveSignal };
|
|
1157
|
-
childSessionId = resolveChildSessionIdAtSpawn(baseRunSpec);
|
|
1158
1166
|
const runSpec = childSessionId !== undefined ? { ...baseRunSpec, sessionId: childSessionId } : baseRunSpec;
|
|
1159
1167
|
const enrichedForwardS = opts.onForwardEvent !== undefined
|
|
1160
1168
|
? (e) => {
|
package/dist/tools/web.d.ts
CHANGED
|
@@ -29,3 +29,18 @@ export interface WebSearchConfig {
|
|
|
29
29
|
}
|
|
30
30
|
export declare function clipCodePoints(s: string, max: number): string;
|
|
31
31
|
export declare function createWebSearchTool(config: WebSearchConfig): ToolSpec;
|
|
32
|
+
export interface SearxngBackendOptions {
|
|
33
|
+
fetchImpl?: typeof fetch;
|
|
34
|
+
timeoutMs?: number;
|
|
35
|
+
extraParams?: Record<string, string>;
|
|
36
|
+
}
|
|
37
|
+
export declare function createSearxngSearchBackend(baseUrl: string, options?: SearxngBackendOptions): WebSearchConfig["search"];
|
|
38
|
+
export declare function probeSearchBackend(search: WebSearchConfig["search"], options?: {
|
|
39
|
+
timeoutMs?: number;
|
|
40
|
+
}): Promise<{
|
|
41
|
+
ok: true;
|
|
42
|
+
results: number;
|
|
43
|
+
} | {
|
|
44
|
+
ok: false;
|
|
45
|
+
error: string;
|
|
46
|
+
}>;
|
package/dist/tools/web.js
CHANGED
|
@@ -798,3 +798,45 @@ export function createWebSearchTool(config) {
|
|
|
798
798
|
},
|
|
799
799
|
};
|
|
800
800
|
}
|
|
801
|
+
export function createSearxngSearchBackend(baseUrl, options = {}) {
|
|
802
|
+
const doFetch = options.fetchImpl ?? globalThis.fetch;
|
|
803
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
804
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
805
|
+
return async (query, signal, opts) => {
|
|
806
|
+
const q = opts?.allowedDomains && opts.allowedDomains.length > 0
|
|
807
|
+
? `${opts.allowedDomains.map((d) => `site:${d}`).join(" OR ")} ${query}`
|
|
808
|
+
: query;
|
|
809
|
+
const url = new URL(`${base}/search`);
|
|
810
|
+
url.searchParams.set("q", q);
|
|
811
|
+
url.searchParams.set("format", "json");
|
|
812
|
+
for (const [k, v] of Object.entries(options.extraParams ?? {}))
|
|
813
|
+
url.searchParams.set(k, v);
|
|
814
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
815
|
+
const res = await doFetch(url, { signal: signal ? AbortSignal.any([signal, timeout]) : timeout, headers: { accept: "application/json" } });
|
|
816
|
+
if (!res.ok)
|
|
817
|
+
throw new Error(`SearXNG ${res.status} ${res.statusText} from ${base}/search`);
|
|
818
|
+
const body = (await res.json());
|
|
819
|
+
if (!Array.isArray(body.results))
|
|
820
|
+
throw new Error(`SearXNG returned no results array from ${base}/search — is format=json enabled on this instance?`);
|
|
821
|
+
return body.results
|
|
822
|
+
.filter((r) => typeof r.url === "string" && r.url !== "")
|
|
823
|
+
.map((r) => ({
|
|
824
|
+
title: typeof r.title === "string" && r.title !== "" ? r.title : r.url,
|
|
825
|
+
url: r.url,
|
|
826
|
+
snippet: typeof r.content === "string" ? r.content : "",
|
|
827
|
+
}));
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
export async function probeSearchBackend(search, options) {
|
|
831
|
+
const budget = options?.timeoutMs ?? 15_000;
|
|
832
|
+
try {
|
|
833
|
+
const results = await Promise.race([
|
|
834
|
+
search("connectivity probe", AbortSignal.timeout(budget)),
|
|
835
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`probe timed out after ${budget}ms`)), budget)),
|
|
836
|
+
]);
|
|
837
|
+
return { ok: true, results: results.length };
|
|
838
|
+
}
|
|
839
|
+
catch (e) {
|
|
840
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
841
|
+
}
|
|
842
|
+
}
|