@sema-agent/core 1.450.0 → 1.452.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/observer.js +3 -0
- package/dist/agents/subagent.js +51 -12
- 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/exec-output-tail.d.ts +18 -1
- package/dist/core/exec-output-tail.js +38 -5
- package/dist/core/lsp-session.d.ts +1 -0
- package/dist/core/lsp-session.js +24 -6
- package/dist/core/lsp.d.ts +10 -0
- 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/memory.js +1 -1
- package/dist/core/runner/prepare-task.js +11 -1
- package/dist/core/runner/runtask.js +1 -1
- package/dist/core/session-reconcile.js +5 -2
- package/dist/core/task-notification.d.ts +1 -0
- package/dist/core/task-registry.d.ts +15 -9
- package/dist/core/task-registry.js +290 -53
- package/dist/core/tool-result-store.js +2 -2
- package/dist/core/tools.d.ts +5 -0
- package/dist/core/tools.js +3 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/core/workflow-journal-store.d.ts +2 -0
- package/dist/core/workflow-journal-store.js +14 -0
- package/dist/engine/execution-env/node-execution-env.d.ts +1 -0
- package/dist/engine/execution-env/node-execution-env.js +130 -20
- package/dist/engine/lsp/node-lsp-manager.d.ts +3 -1
- 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 +17 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +2 -0
- package/dist/orchestration/run-workflow-tool.js +35 -6
- package/dist/orchestration/workflow.d.ts +9 -0
- package/dist/orchestration/workflow.js +80 -5
- package/dist/stores/cc/mailbox-store.js +6 -1
- package/dist/stores/file/background-agent-store.js +3 -2
- package/dist/stores/file/mailbox-store.d.ts +1 -1
- package/dist/stores/file/mailbox-store.js +9 -7
- package/dist/tools/fs/encoding.d.ts +5 -0
- package/dist/tools/fs/encoding.js +6 -0
- package/dist/tools/fs/index.js +184 -120
- package/dist/tools/fs/notebook.d.ts +43 -0
- package/dist/tools/fs/notebook.js +141 -0
- package/dist/tools/fs/repo-map.js +2 -2
- package/dist/tools/fs/search.js +141 -12
- package/dist/tools/gitea-issue.js +4 -2
- package/dist/tools/monitor.js +12 -8
- package/dist/tools/scheduler-tools.js +16 -16
- package/dist/tools/task-list.js +34 -12
- package/dist/tools/web.d.ts +2 -0
- package/dist/tools/web.js +105 -19
- package/dist/tools/worktree.js +14 -14
- package/package.json +1 -1
package/dist/core/lsp-session.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { uriToPath, buildRequest, callHierarchyMethod, parseResult, pathToUri } from "./lsp-protocol.js";
|
|
2
|
+
import { lspFailureOf } from "./lsp.js";
|
|
2
3
|
export class TransportLspSession {
|
|
3
4
|
transport;
|
|
4
5
|
languageId;
|
|
@@ -37,7 +38,7 @@ export class TransportLspSession {
|
|
|
37
38
|
async request(op, params, signal) {
|
|
38
39
|
const built = buildRequest(op, params);
|
|
39
40
|
if (!built)
|
|
40
|
-
return { kind: "none" };
|
|
41
|
+
return { kind: "none", reason: "unsupported_operation" };
|
|
41
42
|
await this.syncOpenedFiles(params.filePath, signal);
|
|
42
43
|
if (!this.warmupBarrierDone && this.warmup.windowMs > 0) {
|
|
43
44
|
this.warmupBarrierDone = true;
|
|
@@ -49,18 +50,23 @@ export class TransportLspSession {
|
|
|
49
50
|
}
|
|
50
51
|
for (let attempt = 0;; attempt++) {
|
|
51
52
|
let raw;
|
|
53
|
+
let noHierarchyItem = false;
|
|
52
54
|
try {
|
|
53
55
|
raw = await this.transport.request(built.method, built.params, signal);
|
|
54
56
|
if (op === "incomingCalls" || op === "outgoingCalls") {
|
|
55
57
|
const items = Array.isArray(raw) ? raw : raw != null ? [raw] : [];
|
|
56
|
-
|
|
58
|
+
noHierarchyItem = items.length === 0;
|
|
59
|
+
raw = noHierarchyItem ? null : await this.transport.request(callHierarchyMethod(op), { item: items[0] }, signal);
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
catch (e) {
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
const f = lspFailureOf(e) ?? TransportLspSession.classifyUnbranded(signal, this.transport);
|
|
64
|
+
this.log("lsp_op_degraded", { op, file: params.filePath, reason: f.reason, err: e instanceof Error ? e.message : String(e) });
|
|
65
|
+
return { kind: "none", reason: f.reason, ...(f.detail !== undefined ? { detail: f.detail } : {}) };
|
|
62
66
|
}
|
|
63
|
-
const result =
|
|
67
|
+
const result = noHierarchyItem
|
|
68
|
+
? { kind: "none", reason: "no_call_hierarchy_item" }
|
|
69
|
+
: parseResult(op, raw);
|
|
64
70
|
if (result.kind === "none" && this.warmup.windowMs > 0 && Date.now() - this.createdAt < this.warmup.windowMs && !signal?.aborted) {
|
|
65
71
|
await new Promise((r) => setTimeout(r, this.warmup.retryMs));
|
|
66
72
|
continue;
|
|
@@ -92,7 +98,12 @@ export class TransportLspSession {
|
|
|
92
98
|
try {
|
|
93
99
|
text = await this.readText(filePath, signal);
|
|
94
100
|
}
|
|
95
|
-
catch {
|
|
101
|
+
catch (e) {
|
|
102
|
+
this.log("lsp_sync_skipped", {
|
|
103
|
+
file: filePath,
|
|
104
|
+
error: e instanceof Error ? e.message : String(e),
|
|
105
|
+
opened: this.opened.has(filePath),
|
|
106
|
+
});
|
|
96
107
|
return;
|
|
97
108
|
}
|
|
98
109
|
const cur = this.opened.get(filePath);
|
|
@@ -115,6 +126,13 @@ export class TransportLspSession {
|
|
|
115
126
|
this.syncing.set(filePath, job);
|
|
116
127
|
return job.finally(() => this.syncing.delete(filePath));
|
|
117
128
|
}
|
|
129
|
+
static classifyUnbranded(signal, transport) {
|
|
130
|
+
if (signal?.aborted)
|
|
131
|
+
return { reason: "cancelled" };
|
|
132
|
+
if (transport.closed === true)
|
|
133
|
+
return { reason: "server_terminated" };
|
|
134
|
+
return { reason: "server_error" };
|
|
135
|
+
}
|
|
118
136
|
get closed() {
|
|
119
137
|
return this.transport.closed === true;
|
|
120
138
|
}
|
package/dist/core/lsp.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface LspSymbolInfo {
|
|
|
14
14
|
line: number;
|
|
15
15
|
character: number;
|
|
16
16
|
}
|
|
17
|
+
export type LspNoneReason = "unsupported_operation" | "server_error" | "timeout" | "cancelled" | "server_terminated" | "no_call_hierarchy_item";
|
|
17
18
|
export type LspResult = {
|
|
18
19
|
kind: "locations";
|
|
19
20
|
locations: LspLocation[];
|
|
@@ -25,7 +26,15 @@ export type LspResult = {
|
|
|
25
26
|
contents: string;
|
|
26
27
|
} | {
|
|
27
28
|
kind: "none";
|
|
29
|
+
reason?: LspNoneReason;
|
|
30
|
+
detail?: string;
|
|
28
31
|
};
|
|
32
|
+
export declare const LSP_FAILURE_BRAND = "lspFailureReason";
|
|
33
|
+
export declare function brandLspFailure<E extends Error>(e: E, reason: LspNoneReason, detail?: string): E;
|
|
34
|
+
export declare function lspFailureOf(e: unknown): {
|
|
35
|
+
reason: LspNoneReason;
|
|
36
|
+
detail?: string;
|
|
37
|
+
} | undefined;
|
|
29
38
|
export interface LspRequestParams {
|
|
30
39
|
filePath: string;
|
|
31
40
|
line?: number;
|
|
@@ -43,6 +52,7 @@ export interface LspTransport {
|
|
|
43
52
|
readonly closed?: boolean;
|
|
44
53
|
}
|
|
45
54
|
export type LspReadText = (filePath: string, signal?: AbortSignal) => Promise<string>;
|
|
55
|
+
export declare const MAX_LSP_FILE_BYTES: number;
|
|
46
56
|
export interface LspServerManager {
|
|
47
57
|
sessionFor(filePath: string, signal?: AbortSignal, env?: ExecutionEnv): Promise<LspSession | undefined>;
|
|
48
58
|
readonly diagnostics?: import("./lsp-diagnostics.js").LspDiagnosticsRegistry;
|
package/dist/core/lsp.js
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
import { uriToPath } from "./lsp-protocol.js";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { defineTool } from "./tools.js";
|
|
3
|
+
import { defineTool, errorResult } from "./tools.js";
|
|
4
4
|
export const LSP_OPERATIONS = [
|
|
5
5
|
"goToDefinition", "findReferences", "hover", "documentSymbol", "workspaceSymbol",
|
|
6
6
|
"goToImplementation", "prepareCallHierarchy", "incomingCalls", "outgoingCalls",
|
|
7
7
|
];
|
|
8
|
-
|
|
8
|
+
export const LSP_FAILURE_BRAND = "lspFailureReason";
|
|
9
|
+
const LSP_NONE_REASONS = new Set([
|
|
10
|
+
"unsupported_operation", "server_error", "timeout", "cancelled", "server_terminated", "no_call_hierarchy_item",
|
|
11
|
+
]);
|
|
12
|
+
export function brandLspFailure(e, reason, detail) {
|
|
13
|
+
const carrier = e;
|
|
14
|
+
carrier[LSP_FAILURE_BRAND] = reason;
|
|
15
|
+
if (detail !== undefined)
|
|
16
|
+
carrier.lspFailureDetail = detail;
|
|
17
|
+
return e;
|
|
18
|
+
}
|
|
19
|
+
export function lspFailureOf(e) {
|
|
20
|
+
const r = e?.[LSP_FAILURE_BRAND];
|
|
21
|
+
if (typeof r !== "string" || !LSP_NONE_REASONS.has(r))
|
|
22
|
+
return undefined;
|
|
23
|
+
const d = e.lspFailureDetail;
|
|
24
|
+
return { reason: r, ...(typeof d === "string" ? { detail: d } : {}) };
|
|
25
|
+
}
|
|
26
|
+
export const MAX_LSP_FILE_BYTES = 10 * 1000 * 1000;
|
|
27
|
+
function fmt(result, ctx) {
|
|
9
28
|
switch (result.kind) {
|
|
10
29
|
case "hover":
|
|
11
30
|
return result.contents.trim() || "(no hover information)";
|
|
@@ -20,9 +39,30 @@ function fmt(result) {
|
|
|
20
39
|
: `Found ${result.symbols.length} symbol(s):\n` +
|
|
21
40
|
result.symbols.map((s) => `- ${s.name}${s.kind ? ` (${s.kind})` : ""} — ${s.uri}:${s.line}:${s.character}`).join("\n");
|
|
22
41
|
case "none":
|
|
42
|
+
return fmtNone(result, ctx);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function fmtNone(result, ctx) {
|
|
46
|
+
const { operation, filePath } = ctx;
|
|
47
|
+
const detail = result.detail;
|
|
48
|
+
switch (result.reason) {
|
|
49
|
+
case undefined:
|
|
23
50
|
return "No results.";
|
|
51
|
+
case "no_call_hierarchy_item":
|
|
52
|
+
return "No call hierarchy item found at this position";
|
|
53
|
+
case "unsupported_operation":
|
|
54
|
+
return `The language server for "${filePath}" does not support ${operation}${detail ? ` (${detail})` : ""}. Fall back to Grep/Read for this lookup.`;
|
|
55
|
+
case "timeout":
|
|
56
|
+
return `Error performing ${operation}: the language server did not respond in time. This is NOT an empty result — do not conclude the symbol has no ${operation === "findReferences" ? "references" : "results"}; retry or fall back to Grep/Read.`;
|
|
57
|
+
case "server_terminated":
|
|
58
|
+
return `Error performing ${operation}: the language server exited before answering. This is NOT an empty result — retry (a fresh server is spawned on the next call) or fall back to Grep/Read.`;
|
|
59
|
+
case "cancelled":
|
|
60
|
+
return `Error performing ${operation}: the request was cancelled before the language server answered. This is NOT an empty result.`;
|
|
61
|
+
case "server_error":
|
|
62
|
+
return `Error performing ${operation}: ${detail ?? "the language server returned an error"}. This is NOT an empty result — fall back to Grep/Read.`;
|
|
24
63
|
}
|
|
25
64
|
}
|
|
65
|
+
const LSP_ERROR_REASONS = new Set(["server_error", "timeout", "cancelled", "server_terminated"]);
|
|
26
66
|
async function filterIgnored(result, isIgnored) {
|
|
27
67
|
if (!isIgnored)
|
|
28
68
|
return result;
|
|
@@ -80,14 +120,29 @@ export function createLspTool(manager, opts = {}) {
|
|
|
80
120
|
execute: async (args, ctx) => {
|
|
81
121
|
const { operation, filePath, line, character, query, symbol } = args;
|
|
82
122
|
if (POSITION_OPS.has(operation) && (line === undefined || character === undefined)) {
|
|
83
|
-
return `Error (LSP): the "${operation}" operation needs both line and character
|
|
123
|
+
return errorResult(`Error (LSP): the "${operation}" operation needs both line and character.`);
|
|
84
124
|
}
|
|
85
125
|
if (POSITION_OPS.has(operation) && (!Number.isInteger(line) || line < 1 || !Number.isInteger(character) || character < 1)) {
|
|
86
|
-
return `Error (LSP): line and character are 1-based integers (as shown in editors); got line=${line}, character=${character}
|
|
126
|
+
return errorResult(`Error (LSP): line and character are 1-based integers (as shown in editors); got line=${line}, character=${character}.`);
|
|
87
127
|
}
|
|
88
128
|
const wsQuery = query ?? symbol;
|
|
89
129
|
if (operation === "workspaceSymbol" && wsQuery === undefined) {
|
|
90
|
-
return `Error (LSP): the "workspaceSymbol" operation needs a query
|
|
130
|
+
return errorResult(`Error (LSP): the "workspaceSymbol" operation needs a query.`);
|
|
131
|
+
}
|
|
132
|
+
if (opts.env) {
|
|
133
|
+
const isAbsForm = filePath.startsWith("/") || /^[A-Za-z]:[\\/]/.test(filePath);
|
|
134
|
+
const cwd = opts.env.cwd;
|
|
135
|
+
const probe = isAbsForm || !cwd ? filePath : cwd.replace(/[\\/]+$/, "") + (cwd.includes("\\") ? "\\" : "/") + filePath;
|
|
136
|
+
const info = await opts.env.fileInfo(probe, ctx.signal);
|
|
137
|
+
if (!info.ok && info.error.code === "not_found")
|
|
138
|
+
return errorResult(`Error (LSP): File does not exist: ${filePath}`);
|
|
139
|
+
if (!info.ok && info.error.code === "invalid")
|
|
140
|
+
return errorResult(`Error (LSP): Path is not a file: ${filePath}`);
|
|
141
|
+
if (info.ok && info.value.kind === "directory")
|
|
142
|
+
return errorResult(`Error (LSP): Path is not a file: ${filePath}`);
|
|
143
|
+
if (info.ok && info.value.kind === "file" && info.value.size > MAX_LSP_FILE_BYTES) {
|
|
144
|
+
return `File too large for LSP analysis (${Math.ceil(info.value.size / 1e6)}MB exceeds 10MB limit)`;
|
|
145
|
+
}
|
|
91
146
|
}
|
|
92
147
|
const session = await manager.sessionFor(filePath, ctx.signal, opts.env);
|
|
93
148
|
if (!session) {
|
|
@@ -95,7 +150,9 @@ export function createLspTool(manager, opts = {}) {
|
|
|
95
150
|
}
|
|
96
151
|
const seamCharacter = character !== undefined ? character - 1 : undefined;
|
|
97
152
|
const result = await session.request(operation, { filePath, line, character: seamCharacter, symbol: wsQuery }, ctx.signal);
|
|
98
|
-
|
|
153
|
+
const final = toModelBase(await filterIgnored(result, opts.isPathIgnored));
|
|
154
|
+
const text = fmt(final, { operation, filePath });
|
|
155
|
+
return final.kind === "none" && final.reason !== undefined && LSP_ERROR_REASONS.has(final.reason) ? errorResult(text) : text;
|
|
99
156
|
},
|
|
100
157
|
});
|
|
101
158
|
}
|
|
@@ -15,7 +15,7 @@ export interface MailboxStore {
|
|
|
15
15
|
sentAt: number;
|
|
16
16
|
}): Promise<number>;
|
|
17
17
|
claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
|
|
18
|
-
ack(scope: string, handle: string, upToSeq: number): Promise<void>;
|
|
18
|
+
ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<void>;
|
|
19
19
|
releaseLease(scope: string, handle: string, owner: string): Promise<void>;
|
|
20
20
|
peekCount(scope: string, handle: string): Promise<number>;
|
|
21
21
|
drop(scope: string, handle: string): Promise<void>;
|
|
@@ -23,6 +23,7 @@ export interface MailboxStore {
|
|
|
23
23
|
maxAgeMs?: number;
|
|
24
24
|
}): Promise<number>;
|
|
25
25
|
}
|
|
26
|
+
export declare function newestSentAt(messages: readonly MailboxMessage[]): number | undefined;
|
|
26
27
|
export declare class InMemoryMailboxStore implements MailboxStore {
|
|
27
28
|
private boxes;
|
|
28
29
|
private key;
|
|
@@ -33,7 +34,7 @@ export declare class InMemoryMailboxStore implements MailboxStore {
|
|
|
33
34
|
sentAt: number;
|
|
34
35
|
}): Promise<number>;
|
|
35
36
|
claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
|
|
36
|
-
ack(scope: string, handle: string, upToSeq: number): Promise<void>;
|
|
37
|
+
ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<void>;
|
|
37
38
|
releaseLease(scope: string, handle: string, owner: string): Promise<void>;
|
|
38
39
|
peekCount(scope: string, handle: string): Promise<number>;
|
|
39
40
|
drop(scope: string, handle: string): Promise<void>;
|
|
@@ -1,6 +1,19 @@
|
|
|
1
|
+
export function newestSentAt(messages) {
|
|
2
|
+
let newest;
|
|
3
|
+
for (const m of messages) {
|
|
4
|
+
if (!Number.isFinite(m.sentAt))
|
|
5
|
+
return undefined;
|
|
6
|
+
if (newest === undefined || m.sentAt > newest)
|
|
7
|
+
newest = m.sentAt;
|
|
8
|
+
}
|
|
9
|
+
return newest;
|
|
10
|
+
}
|
|
1
11
|
export class InMemoryMailboxStore {
|
|
2
12
|
boxes = new Map();
|
|
3
13
|
key(scope, handle) {
|
|
14
|
+
if (handle.includes("\u0000")) {
|
|
15
|
+
throw new Error(`MailboxStore: unsafe handle ${JSON.stringify(handle)} — a NUL byte makes the composite key ambiguous`);
|
|
16
|
+
}
|
|
4
17
|
return `${scope}\u0000${handle.toLowerCase()}`;
|
|
5
18
|
}
|
|
6
19
|
box(scope, handle) {
|
|
@@ -30,12 +43,14 @@ export class InMemoryMailboxStore {
|
|
|
30
43
|
b.lease = { owner, expiresAt: now + ttlMs, maxSeq };
|
|
31
44
|
return { messages: b.messages.map((m) => ({ ...m })), maxSeq };
|
|
32
45
|
}
|
|
33
|
-
async ack(scope, handle, upToSeq) {
|
|
46
|
+
async ack(scope, handle, owner, upToSeq) {
|
|
34
47
|
const b = this.boxes.get(this.key(scope, handle));
|
|
35
48
|
if (!b)
|
|
36
49
|
return;
|
|
50
|
+
if (b.lease === undefined || b.lease.owner !== owner)
|
|
51
|
+
return;
|
|
37
52
|
b.messages = b.messages.filter((m) => m.seq > upToSeq);
|
|
38
|
-
if (b.lease
|
|
53
|
+
if (b.lease.maxSeq <= upToSeq)
|
|
39
54
|
b.lease = undefined;
|
|
40
55
|
}
|
|
41
56
|
async releaseLease(scope, handle, owner) {
|
|
@@ -56,8 +71,8 @@ export class InMemoryMailboxStore {
|
|
|
56
71
|
for (const b of this.boxes.values()) {
|
|
57
72
|
if (b.scope !== scope)
|
|
58
73
|
continue;
|
|
59
|
-
const newest = b.messages
|
|
60
|
-
if (newest !== undefined && newest
|
|
74
|
+
const newest = newestSentAt(b.messages);
|
|
75
|
+
if (newest !== undefined && newest < now - opts.maxAgeMs) {
|
|
61
76
|
b.messages = [];
|
|
62
77
|
b.lease = undefined;
|
|
63
78
|
dropped++;
|
package/dist/core/memory.js
CHANGED
|
@@ -486,7 +486,7 @@ export function createRememberTool(store, scope, onRemembered) {
|
|
|
486
486
|
const a = args;
|
|
487
487
|
const note = String(a.note ?? "").trim();
|
|
488
488
|
if (!note) {
|
|
489
|
-
return { content: "Nothing remembered (empty note).", details: { ok: false } };
|
|
489
|
+
return { content: "Nothing remembered (empty note).", details: { ok: false }, isError: true };
|
|
490
490
|
}
|
|
491
491
|
const oneLine = (v, cap) => {
|
|
492
492
|
if (typeof v !== "string")
|
|
@@ -892,6 +892,7 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
|
|
|
892
892
|
parentModel: () => harnessRef.current?.getModel(),
|
|
893
893
|
...(spec.getApiKeyAndHeaders !== undefined ? { parentGetApiKeyAndHeaders: spec.getApiKeyAndHeaders } : {}),
|
|
894
894
|
principal: spec.principal,
|
|
895
|
+
oneShot: spec.oneShot,
|
|
895
896
|
workflowDepth: internals?.workflowDepth,
|
|
896
897
|
parentCwd: taskRootPath,
|
|
897
898
|
parentThinking: () => harnessRef.current?.getThinkingLevel() ?? thinking,
|
|
@@ -998,9 +999,12 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
|
|
|
998
999
|
const results = await mcp.refresh(typeof serverArg === "string" && serverArg.length > 0 ? serverArg : undefined);
|
|
999
1000
|
const lines = [];
|
|
1000
1001
|
let changed = false;
|
|
1002
|
+
let anyActiveFailure = false;
|
|
1001
1003
|
for (const r of results) {
|
|
1002
1004
|
if (r.status !== "refreshed" || r.tools === undefined) {
|
|
1003
1005
|
lines.push(`${r.server}: ${r.status}${r.error !== undefined ? ` (${r.error})` : ""}`);
|
|
1006
|
+
if (r.status === "failed")
|
|
1007
|
+
anyActiveFailure = true;
|
|
1004
1008
|
continue;
|
|
1005
1009
|
}
|
|
1006
1010
|
const excludedSet = new Set(toolFaceSnapshot.exclude ?? []);
|
|
@@ -1011,6 +1015,7 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
|
|
|
1011
1015
|
}
|
|
1012
1016
|
catch (foldErr) {
|
|
1013
1017
|
lines.push(`${r.server}: failed (${foldErr instanceof Error ? foldErr.message : String(foldErr)})`);
|
|
1018
|
+
anyActiveFailure = true;
|
|
1014
1019
|
continue;
|
|
1015
1020
|
}
|
|
1016
1021
|
for (let i = tools.length - 1; i >= 0; i--) {
|
|
@@ -1040,7 +1045,12 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
|
|
|
1040
1045
|
lines.push("WARNING: refreshed tools declare irreversible/egress safety hints, but this task started with no approval gate registered (no policy/hooks and no gated tools at start) — these hints cannot arm a gate mid-task (RB-46); the refreshed tools run ungated on this deployment shape.");
|
|
1041
1046
|
}
|
|
1042
1047
|
const text = lines.length === 0 ? "No connected MCP servers to refresh." : lines.join("\n");
|
|
1043
|
-
return {
|
|
1048
|
+
return {
|
|
1049
|
+
content: [{ type: "text", text }],
|
|
1050
|
+
details: { results },
|
|
1051
|
+
terminate: false,
|
|
1052
|
+
...(anyActiveFailure && !changed ? { isError: true } : {}),
|
|
1053
|
+
};
|
|
1044
1054
|
},
|
|
1045
1055
|
});
|
|
1046
1056
|
toolEffects.set("RefreshMcpTools", "read");
|
|
@@ -1243,7 +1243,7 @@ export class Runner {
|
|
|
1243
1243
|
return { output: raw, truncated: true, totalChars };
|
|
1244
1244
|
};
|
|
1245
1245
|
const CC_DETAIL_TYPES = new Set([
|
|
1246
|
-
"edit", "multiedit", "create", "update", "bash", "notebook-edit", "text", "grep", "glob", "mcp",
|
|
1246
|
+
"edit", "multiedit", "create", "update", "bash", "notebook-edit", "notebook", "text", "grep", "glob", "mcp",
|
|
1247
1247
|
"agent", "task", "task-list", "task-output", "memory-saved", "workflow-run",
|
|
1248
1248
|
"web-fetch", "web-search", "todo", "cron-create", "cron-delete", "cron-list", "image",
|
|
1249
1249
|
"task-stop", "tool-search", "memory-recall", "repo-map", "fork", "enter-plan-mode", "exit-plan-mode",
|
|
@@ -6,6 +6,9 @@ const INTERRUPTED_UNKNOWN = "[INTERRUPTED] The previous run ended before this to
|
|
|
6
6
|
const INTERRUPTED_SAFE = "[INTERRUPTED] The previous run ended before this tool call's result was recorded. This tool is " +
|
|
7
7
|
"read-only/idempotent, so its outcome carries no risk: simply call it again if you still need " +
|
|
8
8
|
"the result.";
|
|
9
|
+
const INTERRUPTED_IDEMPOTENT = "[INTERRUPTED] The previous run ended before this tool call's result was recorded. The call may or " +
|
|
10
|
+
"may not have taken effect. It is safe to REPLAY: re-issuing the same call converges the state to " +
|
|
11
|
+
"what you intended. If you decide not to re-issue it, verify the current state first.";
|
|
9
12
|
const INTERRUPTED_NEVER_STARTED = "[INTERRUPTED] The run was aborted before this tool call started. It was never executed and had " +
|
|
10
13
|
"no side effects — it is safe to re-issue this call if you still need it.";
|
|
11
14
|
export function findOrphanToolCalls(messages, suspendedBatch) {
|
|
@@ -50,9 +53,9 @@ export async function reconcileInterruptedSession(session, toolEffects, suspende
|
|
|
50
53
|
const recovered = [];
|
|
51
54
|
for (const orphan of orphans) {
|
|
52
55
|
const effect = toolEffects?.get(canonicalToolName(orphan.toolName)) ?? "write";
|
|
53
|
-
const
|
|
56
|
+
const effectText = effect === "read" ? INTERRUPTED_SAFE : effect === "idempotent" ? INTERRUPTED_IDEMPOTENT : INTERRUPTED_UNKNOWN;
|
|
54
57
|
const neverStarted = startedToolCallIds !== undefined && !startedToolCallIds.has(orphan.toolCallId);
|
|
55
|
-
const text = neverStarted ? INTERRUPTED_NEVER_STARTED :
|
|
58
|
+
const text = neverStarted ? INTERRUPTED_NEVER_STARTED : effectText;
|
|
56
59
|
const entryId = await session.appendMessage({
|
|
57
60
|
role: "toolResult",
|
|
58
61
|
toolCallId: orphan.toolCallId,
|
|
@@ -20,6 +20,7 @@ export interface TaskNotificationPayload {
|
|
|
20
20
|
recentSteps?: import("../agents/subagent-steps.js").SubagentStep[];
|
|
21
21
|
editedFiles?: import("../agents/subagent-steps.js").SubagentEditedFile[];
|
|
22
22
|
resumable?: boolean;
|
|
23
|
+
completionId?: string;
|
|
23
24
|
}
|
|
24
25
|
export interface ExternalNotificationInput {
|
|
25
26
|
task_id: string;
|
|
@@ -27,8 +27,14 @@ export interface UnifiedTaskOutput {
|
|
|
27
27
|
stoppedBy?: StopSource;
|
|
28
28
|
seq?: number;
|
|
29
29
|
partial_result?: boolean;
|
|
30
|
+
completionId?: string;
|
|
30
31
|
details?: unknown;
|
|
31
32
|
}
|
|
33
|
+
type UnifiedTaskResult = {
|
|
34
|
+
content: string;
|
|
35
|
+
details: UnifiedTaskOutput;
|
|
36
|
+
isError?: boolean;
|
|
37
|
+
};
|
|
32
38
|
export interface SemaTaskHandle {
|
|
33
39
|
id: string;
|
|
34
40
|
type: SemaTaskType;
|
|
@@ -41,6 +47,7 @@ export interface SemaTaskHandle {
|
|
|
41
47
|
updatedAt: number;
|
|
42
48
|
outputFile?: string;
|
|
43
49
|
outputOffset?: number;
|
|
50
|
+
completionId?: string;
|
|
44
51
|
}
|
|
45
52
|
export interface TaskKind {
|
|
46
53
|
type: SemaTaskType;
|
|
@@ -257,6 +264,7 @@ export declare class TaskRegistry {
|
|
|
257
264
|
markStopSourceByShellId(shellId: string, source: StopSource, env?: unknown): void;
|
|
258
265
|
clearPendingStopSourceByShellId(shellId: string, source: StopSource, env?: unknown): void;
|
|
259
266
|
getStopAttribution(id: string): StopSource | undefined;
|
|
267
|
+
getCompletionId(id: string): string | undefined;
|
|
260
268
|
parkBackgroundAgent(id: string, park: {
|
|
261
269
|
checkpointToken: string;
|
|
262
270
|
seq?: number;
|
|
@@ -323,18 +331,16 @@ export declare class TaskRegistry {
|
|
|
323
331
|
timeoutResidentShellIds(env: unknown): BackgroundShellId[];
|
|
324
332
|
retainedShellIds(env: unknown): BackgroundShellId[];
|
|
325
333
|
registerMonitor(input: RegisterMonitorInput): string;
|
|
334
|
+
private absorbMonitorPoll;
|
|
335
|
+
private emitMonitorEvent;
|
|
326
336
|
private startMonitorWatcher;
|
|
327
337
|
registerWorkflow(input: RegisterWorkflowInput): string;
|
|
328
|
-
pollTask(id: string, access: TaskAccess, opts?: TaskPollOptions): Promise<
|
|
329
|
-
content: string;
|
|
330
|
-
details: UnifiedTaskOutput;
|
|
331
|
-
}>;
|
|
338
|
+
pollTask(id: string, access: TaskAccess, opts?: TaskPollOptions): Promise<UnifiedTaskResult>;
|
|
332
339
|
private serveDurableAgentRow;
|
|
333
|
-
stopTask(id: string, access: TaskAccess, opts?: TaskStopOptions): Promise<
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
resolveBackgroundAgentByName(name: string, access: TaskAccess): {
|
|
340
|
+
stopTask(id: string, access: TaskAccess, opts?: TaskStopOptions): Promise<UnifiedTaskResult>;
|
|
341
|
+
resolveBackgroundAgentByName(name: string, access: TaskAccess, opts?: {
|
|
342
|
+
preferRunning?: boolean;
|
|
343
|
+
}): {
|
|
338
344
|
status: "found";
|
|
339
345
|
handle: BackgroundAgentTaskHandle;
|
|
340
346
|
} | {
|