@sema-agent/core 2.12.0 → 3.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 +1 -0
- package/dist/agents/agent-transcript-tool.js +1 -1
- package/dist/agents/cascade.d.ts +4 -0
- package/dist/agents/cascade.js +54 -9
- package/dist/agents/repair-loop.d.ts +2 -0
- package/dist/agents/repair-loop.js +101 -2
- package/dist/agents/retain-ledger.d.ts +18 -2
- package/dist/agents/retain-ledger.js +37 -8
- package/dist/agents/roster-store.d.ts +4 -0
- package/dist/agents/roster-store.js +22 -1
- package/dist/agents/teacher.js +4 -2
- package/dist/agents/team.js +7 -4
- package/dist/agents/verify.d.ts +15 -2
- package/dist/agents/verify.js +36 -1
- package/dist/bin/sema-tb.js +2 -2
- package/dist/brain/circuit-breaker.d.ts +1 -0
- package/dist/brain/circuit-breaker.js +26 -9
- package/dist/brain/degrading.js +13 -1
- package/dist/brain/failover.js +2 -0
- package/dist/brain/repetition.js +10 -1
- package/dist/brain/routing.js +5 -1
- package/dist/brain/status-sink.js +5 -1
- package/dist/brain/stream-engine.js +4 -1
- package/dist/brain/tool-call-repair.js +32 -14
- package/dist/core/auto-compaction.js +17 -7
- package/dist/core/auto-promote.js +2 -1
- package/dist/core/checkpoint-store.d.ts +1 -0
- package/dist/core/checkpoint-store.js +28 -10
- package/dist/core/consolidate-scope.js +4 -3
- package/dist/core/context-edit.js +3 -0
- package/dist/core/context-guard.js +3 -0
- package/dist/core/git-worktree-env.d.ts +6 -1
- package/dist/core/git-worktree-env.js +17 -1
- package/dist/core/hooks.d.ts +1 -0
- package/dist/core/hooks.js +40 -2
- package/dist/core/lsp-diagnostics.js +13 -7
- package/dist/core/lsp-protocol.d.ts +1 -1
- package/dist/core/lsp-protocol.js +20 -8
- package/dist/core/lsp-session.d.ts +12 -2
- package/dist/core/lsp-session.js +114 -49
- package/dist/core/lsp.d.ts +13 -1
- package/dist/core/lsp.js +64 -9
- package/dist/core/mcp.d.ts +1 -0
- package/dist/core/mcp.js +4 -3
- package/dist/core/memory-engine/data-plane.js +4 -0
- package/dist/core/memory-engine/engine.d.ts +2 -1
- package/dist/core/memory-engine/engine.js +82 -7
- package/dist/core/memory-engine/file-backend.d.ts +1 -0
- package/dist/core/memory-engine/file-backend.js +13 -3
- package/dist/core/memory-engine/layout.js +12 -1
- package/dist/core/memory.js +3 -0
- package/dist/core/permission-rules.d.ts +2 -1
- package/dist/core/permission-rules.js +24 -3
- package/dist/core/runner/active-skill-scope.js +34 -6
- package/dist/core/runner/assemble-result.d.ts +1 -1
- package/dist/core/runner/assemble-result.js +0 -1
- package/dist/core/runner/prepare-task.js +23 -3
- package/dist/core/runner/runtask.js +48 -30
- package/dist/core/runner/tool-disclosure.d.ts +1 -0
- package/dist/core/runner/tool-disclosure.js +26 -7
- package/dist/core/runner/usage-accounting.d.ts +6 -0
- package/dist/core/runner/usage-accounting.js +9 -8
- package/dist/core/secret-env.js +5 -1
- package/dist/core/sensitive-path-policy.js +9 -3
- package/dist/core/session-store.js +15 -4
- package/dist/core/skill-tool-specifier.d.ts +8 -0
- package/dist/core/skill-tool-specifier.js +58 -0
- package/dist/core/skills-directory.d.ts +1 -1
- package/dist/core/skills-directory.js +16 -4
- package/dist/core/store-contracts/session-repo-contract.js +40 -0
- package/dist/core/task-registry-monitor.js +7 -1
- package/dist/core/tool-policy.js +65 -17
- package/dist/core/trace.d.ts +1 -0
- package/dist/core/types.d.ts +3 -5
- package/dist/core/with-retry.js +0 -1
- package/dist/engine/compaction/compaction.d.ts +1 -0
- package/dist/engine/compaction/compaction.js +14 -5
- package/dist/engine/lsp/frame-decoder.js +6 -3
- package/dist/engine/lsp/node-lsp-manager.d.ts +2 -0
- package/dist/engine/lsp/node-lsp-manager.js +103 -26
- package/dist/engine/lsp/stdio-lsp-transport.js +16 -7
- package/dist/index.d.ts +1 -1
- package/dist/internal/harness.d.ts +1 -1
- package/dist/internal/harness.js +1 -1
- package/dist/internal/llm.d.ts +1 -1
- package/dist/orchestration/workflow-governance.js +43 -9
- package/dist/orchestration/workflow-sandbox-conformance.js +85 -51
- package/dist/orchestration/workflow-script-store.js +34 -4
- package/dist/orchestration/workflow.js +10 -6
- package/dist/prompt-assembly/artifact-store.d.ts +2 -0
- package/dist/prompt-assembly/artifact-store.js +39 -24
- package/dist/prompt-assembly/assemble.js +38 -7
- package/dist/prompt-assembly/epoch.js +19 -2
- package/dist/prompt-assembly/event-registry.js +3 -3
- package/dist/prompt-assembly/explain.js +1 -1
- package/dist/prompt-assembly/tool-catalog.js +16 -1
- package/dist/prompt-assembly/turn-snapshot.js +4 -1
- package/dist/stores/file/fs-atomic.d.ts +3 -0
- package/dist/stores/file/fs-atomic.js +53 -8
- package/dist/stores/file/index.d.ts +1 -0
- package/dist/stores/file/index.js +7 -0
- package/dist/stores/file/session-store.d.ts +2 -0
- package/dist/stores/file/session-store.js +28 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -0
- package/dist/tools/fs/bash-readonly-classifier.js +57 -8
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +93 -5
- package/dist/tools/loop-tick.js +1 -1
- package/dist/tools/monitor.js +55 -9
- package/dist/tools/scheduler-tools.js +9 -3
- package/dist/tools/web.d.ts +8 -2
- package/dist/tools/web.js +46 -17
- package/dist/tools/worktree.d.ts +2 -0
- package/dist/tools/worktree.js +233 -143
- package/package.json +5 -2
package/dist/tools/monitor.js
CHANGED
|
@@ -29,6 +29,16 @@ const MONITOR_DESCRIPTION = "Run a shell command in the background and watch its
|
|
|
29
29
|
'- Wrong: `until grep -q "BUILD SUCCEEDED" build.log; do sleep 5; done` — if the build fails, this never exits and you wait until the watch times out.\n' +
|
|
30
30
|
'- Right: `until grep -qE "BUILD (SUCCEEDED|FAILED)" build.log; do sleep 5; done` — every terminal state exits.\n' +
|
|
31
31
|
"Before arming, ask: *if this process crashed right now, would my filter emit anything?* If not, widen it.";
|
|
32
|
+
function envBackgroundWallSec(caps, requestedSec) {
|
|
33
|
+
const positive = (v) => (typeof v === "number" && Number.isFinite(v) && v > 0 ? v : undefined);
|
|
34
|
+
const cap = positive(caps.maxBgTimeoutSec);
|
|
35
|
+
const base = requestedSec ?? positive(caps.defaultBgTimeoutSec);
|
|
36
|
+
if (base === undefined)
|
|
37
|
+
return cap;
|
|
38
|
+
if (cap === undefined)
|
|
39
|
+
return base;
|
|
40
|
+
return Math.min(base, cap);
|
|
41
|
+
}
|
|
32
42
|
export function createMonitorTool(env, opts) {
|
|
33
43
|
return defineTool({
|
|
34
44
|
name: "Monitor",
|
|
@@ -56,9 +66,10 @@ export function createMonitorTool(env, opts) {
|
|
|
56
66
|
}
|
|
57
67
|
const isPersistent = persistent === true;
|
|
58
68
|
const timeoutMs = Math.min(MONITOR_MAX_TIMEOUT_MS, Math.max(1, Math.floor(timeout_ms ?? MONITOR_DEFAULT_TIMEOUT_MS)));
|
|
69
|
+
const spawnTimeoutSec = isPersistent ? undefined : Math.ceil(timeoutMs / 1000) + 5;
|
|
59
70
|
const spawn = await env.spawnBackground(command, {
|
|
60
71
|
...(opts.cwdRef !== undefined ? { cwd: opts.cwdRef.current } : {}),
|
|
61
|
-
...(
|
|
72
|
+
...(spawnTimeoutSec !== undefined ? { timeout: spawnTimeoutSec } : {}),
|
|
62
73
|
});
|
|
63
74
|
if (!spawn.ok)
|
|
64
75
|
return errorResult(`Error (Monitor): ${spawn.error.message}`);
|
|
@@ -87,18 +98,50 @@ export function createMonitorTool(env, opts) {
|
|
|
87
98
|
});
|
|
88
99
|
}
|
|
89
100
|
catch (e) {
|
|
90
|
-
|
|
91
|
-
|
|
101
|
+
const why = e instanceof Error ? e.message : String(e);
|
|
102
|
+
let killDetail;
|
|
103
|
+
try {
|
|
104
|
+
const kill = await env.killBackground(spawn.value.shellId);
|
|
105
|
+
if (!kill.ok)
|
|
106
|
+
killDetail = `${kill.error.code}: ${kill.error.message}`;
|
|
107
|
+
}
|
|
108
|
+
catch (killErr) {
|
|
109
|
+
killDetail = killErr instanceof Error ? killErr.message : String(killErr);
|
|
110
|
+
}
|
|
111
|
+
return errorResult(killDetail === undefined
|
|
112
|
+
? `Error (Monitor): the watch process was started but could not be registered (${why}); it has been terminated.`
|
|
113
|
+
: `Error (Monitor): the watch process was started but could not be registered (${why}), and it could NOT be terminated either (${killDetail}). ` +
|
|
114
|
+
`It may still be running as background shell ${spawn.value.shellId}; no task id was minted, so TaskOutput/TaskStop cannot reach it — ` +
|
|
115
|
+
"stop it out of band (the environment's own background time budget and its teardown sweep are the remaining backstops).");
|
|
92
116
|
}
|
|
93
|
-
const bgTimeout = env.backgroundCapabilities
|
|
117
|
+
const bgTimeout = envBackgroundWallSec(env.backgroundCapabilities, spawnTimeoutSec);
|
|
94
118
|
const wasClamped = timeout_ms !== undefined && timeout_ms !== timeoutMs;
|
|
95
119
|
const clampNote = !isPersistent && wasClamped ? ` (clamped to ${timeoutMs}ms from your requested ${timeout_ms}ms)` : "";
|
|
96
120
|
const timeoutIgnoredNote = isPersistent && timeout_ms !== undefined ? ` (the timeout_ms you passed is ignored because persistent is true)` : "";
|
|
121
|
+
const envWallMs = bgTimeout !== undefined ? bgTimeout * 1000 : undefined;
|
|
122
|
+
const envWallWins = !isPersistent && envWallMs !== undefined && envWallMs < timeoutMs;
|
|
123
|
+
const persistentBudget = bgTimeout !== undefined
|
|
124
|
+
? `the execution environment kills background processes after ${bgTimeout}s`
|
|
125
|
+
: `the execution environment's background time budget still applies`;
|
|
126
|
+
const retainedByEnv = env.backgroundCapabilities.retainBackgroundProcesses === true;
|
|
127
|
+
const persistentAnchor = sessionScoped
|
|
128
|
+
? retainedByEnv
|
|
129
|
+
? ". This environment declares retained background processes, so at the run teardown the watch is marked retained and " +
|
|
130
|
+
"survives the session's release: only your TaskStop, the command's own exit, or that budget end it."
|
|
131
|
+
: "; it runs until then, until you stop it, or until the session ends."
|
|
132
|
+
: retainedByEnv
|
|
133
|
+
? ". This mount carries no session identity, so the watch is NOT session-resident despite persistent: true; and because this " +
|
|
134
|
+
"environment declares retained background processes it survives the session's release rather than being settled with the run — " +
|
|
135
|
+
"nothing but your TaskStop, the command's own exit, or that budget will end it, and no later turn can reach it " +
|
|
136
|
+
"(stop it explicitly with TaskStop before you finish)."
|
|
137
|
+
: ". This mount carries no session identity, so the watch is NOT session-resident despite persistent: true — " +
|
|
138
|
+
"it belongs to this task alone and the run teardown settles it, so do not count on it outliving this turn " +
|
|
139
|
+
"(stop it explicitly with TaskStop when you are done).";
|
|
97
140
|
const lifetime = isPersistent
|
|
98
|
-
?
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
141
|
+
? `It is persistent: no watch timeout of its own${timeoutIgnoredNote}, but ${persistentBudget}${persistentAnchor}`
|
|
142
|
+
: envWallWins
|
|
143
|
+
? `It will be killed after ${envWallMs}ms if still running — the execution environment's background time budget (${bgTimeout}s) is shorter than the ${timeoutMs}ms watch timeout${clampNote}, and the shorter of the two wins.`
|
|
144
|
+
: `It will be killed after ${timeoutMs}ms${clampNote} if still running.`;
|
|
102
145
|
const content = onNotify !== undefined
|
|
103
146
|
? `Monitoring in background; task_id=${taskId}. Each stdout line becomes a notification event (lines within ` +
|
|
104
147
|
`~200ms are batched); you'll get a final notification with the exit code when it ends — do not poll. ` +
|
|
@@ -111,8 +154,11 @@ export function createMonitorTool(env, opts) {
|
|
|
111
154
|
type: "monitor-start",
|
|
112
155
|
taskId,
|
|
113
156
|
persistent: isPersistent,
|
|
157
|
+
...(bgTimeout !== undefined ? { envBudgetSec: bgTimeout } : {}),
|
|
158
|
+
...(envWallWins && envWallMs !== undefined ? { effectiveTimeoutMs: envWallMs } : {}),
|
|
114
159
|
...(isPersistent
|
|
115
|
-
?
|
|
160
|
+
?
|
|
161
|
+
{ timeoutIgnored: timeout_ms !== undefined, sessionScoped, ...(retainedByEnv ? { retainedByEnv: true } : {}) }
|
|
116
162
|
: { timeoutMs, ...(timeout_ms !== undefined ? { requestedTimeoutMs: timeout_ms } : {}), wasClamped }),
|
|
117
163
|
},
|
|
118
164
|
};
|
|
@@ -158,6 +158,12 @@ export function createSchedulerTools(env, ctx) {
|
|
|
158
158
|
return [];
|
|
159
159
|
const sched = env;
|
|
160
160
|
const schedCtx = toSchedulerContext(ctx);
|
|
161
|
+
let cronCreateChain = Promise.resolve();
|
|
162
|
+
const serializedCronCreateOp = (fn) => {
|
|
163
|
+
const next = cronCreateChain.then(fn, fn);
|
|
164
|
+
cronCreateChain = next.then(() => undefined, () => undefined);
|
|
165
|
+
return next;
|
|
166
|
+
};
|
|
161
167
|
const cronCreateDescription = `Schedule a prompt to be enqueued at a future time. Use for both recurring schedules and one-shot reminders. The task runs UNATTENDED when it fires, so write a fully self-contained \`prompt\` (it will not see this conversation). Use CronList to see what you've scheduled, CronDelete to remove one.
|
|
162
168
|
|
|
163
169
|
## One-shot tasks (schedule kind "delay" or "at")
|
|
@@ -210,7 +216,7 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
210
216
|
label: Type.Optional(Type.String({ description: "Short label (also a dedup key with the schedule)." })),
|
|
211
217
|
}),
|
|
212
218
|
effect: "write",
|
|
213
|
-
execute: async (args) => {
|
|
219
|
+
execute: async (args) => serializedCronCreateOp(async () => {
|
|
214
220
|
const a = args;
|
|
215
221
|
if (a.schedule !== undefined && a.cron !== undefined) {
|
|
216
222
|
return errorResult("Error (CronCreate): pass exactly one of `schedule` (object form) or `cron` (string form), not both.");
|
|
@@ -224,7 +230,7 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
224
230
|
const when = a.schedule ?? { kind: "cron", expr: a.cron };
|
|
225
231
|
if (when.kind === "cron") {
|
|
226
232
|
if (!isValidCronExpr(when.expr)) {
|
|
227
|
-
return errorResult(`Error (CronCreate): invalid cron expression "${when.expr}" — only 5
|
|
233
|
+
return errorResult(`Error (CronCreate): invalid cron expression "${when.expr}" — only 5 space-separated cron fields (digits and * / , -) are allowed (a 6th leading seconds field is parsed but always rejected next — this scheduler has no seconds hand).`);
|
|
228
234
|
}
|
|
229
235
|
const deepErr = cronScheduleError(when.expr);
|
|
230
236
|
if (deepErr)
|
|
@@ -277,7 +283,7 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
277
283
|
...(replaced !== undefined ? { replaced } : {}),
|
|
278
284
|
},
|
|
279
285
|
};
|
|
280
|
-
},
|
|
286
|
+
}),
|
|
281
287
|
});
|
|
282
288
|
const cronCancel = defineTool({
|
|
283
289
|
name: "CronDelete",
|
package/dist/tools/web.d.ts
CHANGED
|
@@ -6,7 +6,10 @@ export interface WebFetchConfig {
|
|
|
6
6
|
fetchImpl?: typeof fetch;
|
|
7
7
|
maxBytes?: number;
|
|
8
8
|
timeoutMs?: number;
|
|
9
|
-
summarize?: (content: string, prompt: string, signal?: AbortSignal) => Promise<string
|
|
9
|
+
summarize?: (content: string, prompt: string, signal?: AbortSignal) => Promise<string | {
|
|
10
|
+
text: string;
|
|
11
|
+
truncated?: boolean;
|
|
12
|
+
}>;
|
|
10
13
|
userAgent?: string;
|
|
11
14
|
}
|
|
12
15
|
export declare function htmlToText(html: string): string;
|
|
@@ -14,7 +17,10 @@ export declare function webFetchToolSpec(config?: WebFetchConfig): ToolSpec;
|
|
|
14
17
|
export declare function createWebFetchTool(config?: WebFetchConfig): AgentTool;
|
|
15
18
|
export declare const WEBFETCH_SUMMARY_MAX_CONTENT = 100000;
|
|
16
19
|
export declare const WEBFETCH_SUMMARY_GUIDELINES: string;
|
|
17
|
-
export declare function createWebFetchSummarizer(brain: Brain, model: Model): (content: string, prompt: string, signal?: AbortSignal) => Promise<string
|
|
20
|
+
export declare function createWebFetchSummarizer(brain: Brain, model: Model): (content: string, prompt: string, signal?: AbortSignal) => Promise<string | {
|
|
21
|
+
text: string;
|
|
22
|
+
truncated?: boolean;
|
|
23
|
+
}>;
|
|
18
24
|
export interface WebSearchConfig {
|
|
19
25
|
search: (query: string, signal?: AbortSignal, opts?: {
|
|
20
26
|
allowedDomains?: string[];
|
package/dist/tools/web.js
CHANGED
|
@@ -495,6 +495,7 @@ export function webFetchToolSpec(config = {}) {
|
|
|
495
495
|
}
|
|
496
496
|
const text = /html/i.test(contentType) || /^\s*</.test(raw) ? htmlToText(raw) : raw;
|
|
497
497
|
let out = text;
|
|
498
|
+
let summaryTruncated = false;
|
|
498
499
|
let note;
|
|
499
500
|
if (prompt && bodyCut) {
|
|
500
501
|
note =
|
|
@@ -503,7 +504,17 @@ export function webFetchToolSpec(config = {}) {
|
|
|
503
504
|
}
|
|
504
505
|
else if (prompt && config.summarize) {
|
|
505
506
|
try {
|
|
506
|
-
|
|
507
|
+
const summarized = await config.summarize(text, prompt, ctx.signal);
|
|
508
|
+
if (typeof summarized === "string") {
|
|
509
|
+
out = summarized;
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
out = summarized.text;
|
|
513
|
+
if (summarized.truncated) {
|
|
514
|
+
summaryTruncated = true;
|
|
515
|
+
note = `[note: the summary below is INCOMPLETE — the summarizer hit its output limit before finishing]`;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
507
518
|
}
|
|
508
519
|
catch (e) {
|
|
509
520
|
out = text;
|
|
@@ -534,7 +545,7 @@ export function webFetchToolSpec(config = {}) {
|
|
|
534
545
|
bytes: bodyBytes ? bodyBytes.length : raw.length,
|
|
535
546
|
result: out.length > RESULT_PREVIEW_CHARS ? `${out.slice(0, RESULT_PREVIEW_CHARS)}\n…[${out.length - RESULT_PREVIEW_CHARS} chars truncated — full text in the tool output]` : out,
|
|
536
547
|
durationMs: Date.now() - startedAt,
|
|
537
|
-
...(truncationNote ? { truncated: true } : {}),
|
|
548
|
+
...(truncationNote || summaryTruncated ? { truncated: true } : {}),
|
|
538
549
|
...transferStateDetails,
|
|
539
550
|
},
|
|
540
551
|
};
|
|
@@ -560,18 +571,17 @@ export function createWebFetchSummarizer(brain, model) {
|
|
|
560
571
|
const msg = brain.complete
|
|
561
572
|
? await brain.complete(model, context, { signal })
|
|
562
573
|
: await (await Promise.resolve(brain.stream(model, context, { signal }))).result();
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
throw new Error(am.errorMessage ?? `summarizer stopped with ${am.stopReason}`);
|
|
574
|
+
if (msg.stopReason === "error" || msg.stopReason === "aborted") {
|
|
575
|
+
throw new Error(msg.errorMessage ?? `summarizer stopped with ${msg.stopReason}`);
|
|
566
576
|
}
|
|
567
|
-
const text =
|
|
568
|
-
.filter((b) => b
|
|
577
|
+
const text = msg.content
|
|
578
|
+
.filter((b) => b.type === "text" && typeof b.text === "string")
|
|
569
579
|
.map((b) => b.text)
|
|
570
580
|
.join("\n")
|
|
571
581
|
.trim();
|
|
572
582
|
if (!text)
|
|
573
583
|
throw new Error("summarizer returned no text");
|
|
574
|
-
return text;
|
|
584
|
+
return msg.stopReason === "length" || msg.partialFinalized === true ? { text, truncated: true } : text;
|
|
575
585
|
};
|
|
576
586
|
}
|
|
577
587
|
const DEFAULT_SEARCH_TIMEOUT_MS = 30_000;
|
|
@@ -801,25 +811,27 @@ export function createWebSearchTool(config) {
|
|
|
801
811
|
export function createSearxngSearchBackend(baseUrl, options = {}) {
|
|
802
812
|
const doFetch = options.fetchImpl ?? globalThis.fetch;
|
|
803
813
|
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
804
|
-
const
|
|
814
|
+
const parsedBase = new URL(baseUrl);
|
|
815
|
+
parsedBase.hash = "";
|
|
816
|
+
parsedBase.pathname = `${parsedBase.pathname.replace(/\/+$/, "")}/search`;
|
|
805
817
|
return async (query, signal, opts) => {
|
|
806
818
|
const q = opts?.allowedDomains && opts.allowedDomains.length > 0
|
|
807
819
|
? `${opts.allowedDomains.map((d) => `site:${d}`).join(" OR ")} ${query}`
|
|
808
820
|
: query;
|
|
809
|
-
const url = new URL(
|
|
810
|
-
url.searchParams.set("q", q);
|
|
811
|
-
url.searchParams.set("format", "json");
|
|
821
|
+
const url = new URL(parsedBase);
|
|
812
822
|
for (const [k, v] of Object.entries(options.extraParams ?? {}))
|
|
813
823
|
url.searchParams.set(k, v);
|
|
824
|
+
url.searchParams.set("q", q);
|
|
825
|
+
url.searchParams.set("format", "json");
|
|
814
826
|
const timeout = AbortSignal.timeout(timeoutMs);
|
|
815
827
|
const res = await doFetch(url, { signal: signal ? AbortSignal.any([signal, timeout]) : timeout, headers: { accept: "application/json" } });
|
|
816
828
|
if (!res.ok)
|
|
817
|
-
throw new Error(`SearXNG ${res.status} ${res.statusText} from ${
|
|
829
|
+
throw new Error(`SearXNG ${res.status} ${res.statusText} from ${url.origin}${url.pathname}`);
|
|
818
830
|
const body = (await res.json());
|
|
819
831
|
if (!Array.isArray(body.results))
|
|
820
|
-
throw new Error(`SearXNG returned no results array from ${
|
|
832
|
+
throw new Error(`SearXNG returned no results array from ${url.origin}${url.pathname} — is format=json enabled on this instance?`);
|
|
821
833
|
return body.results
|
|
822
|
-
.filter((r) => typeof r.url === "string" && r.url !== "")
|
|
834
|
+
.filter((r) => typeof r === "object" && r !== null && typeof r.url === "string" && r.url !== "")
|
|
823
835
|
.map((r) => ({
|
|
824
836
|
title: typeof r.title === "string" && r.title !== "" ? r.title : r.url,
|
|
825
837
|
url: r.url,
|
|
@@ -829,14 +841,31 @@ export function createSearxngSearchBackend(baseUrl, options = {}) {
|
|
|
829
841
|
}
|
|
830
842
|
export async function probeSearchBackend(search, options) {
|
|
831
843
|
const budget = options?.timeoutMs ?? 15_000;
|
|
844
|
+
let timer;
|
|
832
845
|
try {
|
|
833
846
|
const results = await Promise.race([
|
|
834
847
|
search("connectivity probe", AbortSignal.timeout(budget)),
|
|
835
|
-
new Promise((_, reject) =>
|
|
848
|
+
new Promise((_, reject) => {
|
|
849
|
+
timer = setTimeout(() => reject(new Error(`probe timed out after ${budget}ms`)), budget);
|
|
850
|
+
}),
|
|
836
851
|
]);
|
|
837
852
|
return { ok: true, results: results.length };
|
|
838
853
|
}
|
|
839
854
|
catch (e) {
|
|
840
|
-
|
|
855
|
+
let text;
|
|
856
|
+
if (e instanceof Error)
|
|
857
|
+
text = e.message;
|
|
858
|
+
else {
|
|
859
|
+
try {
|
|
860
|
+
text = String(e);
|
|
861
|
+
}
|
|
862
|
+
catch {
|
|
863
|
+
text = Object.prototype.toString.call(e);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
return { ok: false, error: text };
|
|
867
|
+
}
|
|
868
|
+
finally {
|
|
869
|
+
clearTimeout(timer);
|
|
841
870
|
}
|
|
842
871
|
}
|
package/dist/tools/worktree.d.ts
CHANGED