@sema-agent/core 4.0.0 → 5.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/CHANGELOG.md +37 -0
- package/dist/agents/cascade.d.ts +1 -0
- package/dist/agents/cascade.js +1 -1
- package/dist/agents/repair-loop.d.ts +2 -0
- package/dist/agents/repair-loop.js +21 -5
- package/dist/agents/roster-store.d.ts +1 -0
- package/dist/agents/roster-store.js +1 -1
- package/dist/agents/send-message-tool.d.ts +1 -0
- package/dist/agents/send-message-tool.js +2 -0
- package/dist/agents/subagent.d.ts +3 -1
- package/dist/agents/subagent.js +15 -32
- package/dist/agents/tool-filter.js +6 -7
- package/dist/agents/verify.d.ts +1 -0
- package/dist/agents/verify.js +1 -1
- package/dist/core/arg-summary.d.ts +21 -1
- package/dist/core/arg-summary.js +61 -14
- package/dist/core/auto-compaction.d.ts +1 -0
- package/dist/core/auto-compaction.js +1 -1
- package/dist/core/fs-write-gate-policy.js +2 -3
- package/dist/core/hooks.d.ts +1 -0
- package/dist/core/hooks.js +1 -1
- package/dist/core/mcp.js +0 -6
- package/dist/core/permission-rules.js +2 -3
- package/dist/core/runner/active-skill-scope.js +1 -2
- package/dist/core/runner/prepare-task.js +16 -8
- package/dist/core/runner/runtask.js +19 -16
- package/dist/core/runner/session-rule-policy.js +3 -4
- package/dist/core/sensitive-path-policy.js +2 -3
- package/dist/core/session-reconcile.js +1 -2
- package/dist/core/skill-tool-specifier.js +2 -3
- package/dist/core/skills-directory.js +2 -3
- package/dist/core/task-registry-shared.d.ts +1 -1
- package/dist/core/task-registry.d.ts +5 -1
- package/dist/core/task-registry.js +12 -30
- package/dist/core/task-tool-shape.d.ts +0 -2
- package/dist/core/task-tool-shape.js +2 -5
- package/dist/core/tool-name-aliases.d.ts +1 -2
- package/dist/core/tool-name-aliases.js +42 -60
- package/dist/core/tool-policy.js +11 -12
- package/dist/core/trace.d.ts +7 -0
- package/dist/core/untrusted-egress.d.ts +4 -2
- package/dist/core/untrusted-egress.js +21 -9
- package/dist/engine/execution-env/node-execution-env.js +1 -1
- package/dist/engine/loop/agent-loop.js +3 -12
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -2
- package/dist/orchestration/run-spec.js +2 -3
- package/dist/orchestration/run-workflow-tool.js +0 -1
- package/dist/orchestration/workflow-governance.js +2 -1
- package/dist/orchestration/workflow.d.ts +1 -0
- package/dist/orchestration/workflow.js +1 -1
- package/dist/prompt-assembly/packs/sema-default.js +1 -3
- package/dist/prompts/default.js +1 -3
- package/dist/prompts/simple-sections.d.ts +0 -1
- package/dist/prompts/simple-sections.js +0 -1
- package/dist/tools/fs/fs-bash.js +6 -8
- package/dist/tools/fs/fs-write.js +0 -1
- package/package.json +1 -1
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { canonicalToolName } from "./tool-name-aliases.js";
|
|
2
1
|
export const BASH_GENERIC_PARAMS = new Set(["command", "timeout", "description", "run_in_background"]);
|
|
3
2
|
const DEFAULT_CAPS = {
|
|
4
3
|
maxRules: 256,
|
|
@@ -142,7 +141,7 @@ function compile(rules, caps, primaryFieldGeneric) {
|
|
|
142
141
|
continue;
|
|
143
142
|
}
|
|
144
143
|
}
|
|
145
|
-
const toolName =
|
|
144
|
+
const toolName = parsed.toolName;
|
|
146
145
|
if (toolName.startsWith("mcp__") && (parsed.ruleContent !== undefined || indexOfUnescaped(text, "(") !== -1)) {
|
|
147
146
|
bad(text, "unsupported.mcp_paren", "MCP rules do not support patterns in parentheses (CC parity); use the toolAxes system");
|
|
148
147
|
continue;
|
|
@@ -262,7 +261,7 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
262
261
|
const defaultAction = opts?.defaultAction ?? "allow";
|
|
263
262
|
return {
|
|
264
263
|
check(req) {
|
|
265
|
-
const entry = byTool.get(
|
|
264
|
+
const entry = byTool.get(req.toolName);
|
|
266
265
|
if (entry) {
|
|
267
266
|
if (entry.bare.deny) {
|
|
268
267
|
return { action: "deny", reason: ruleMessage("denied", entry.bare.deny.ruleText, entry.bare.deny.source) };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { canonicalizeTarget, fileArgPath } from "../../tools/fs/safety.js";
|
|
2
2
|
import { PATH_WRITE_TOOLS, isWithin } from "./session-rule-policy.js";
|
|
3
|
-
import { canonicalToolName } from "../tool-name-aliases.js";
|
|
4
3
|
import { parseSkillToolEntry, skillSpecifierRejection } from "../skill-tool-specifier.js";
|
|
5
4
|
export class ActiveSkillScope {
|
|
6
5
|
frames = [];
|
|
@@ -21,7 +20,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
21
20
|
const { scope, env, rootPath, toolEffects } = opts;
|
|
22
21
|
return {
|
|
23
22
|
async check(req, signal) {
|
|
24
|
-
const toolName =
|
|
23
|
+
const toolName = req.toolName;
|
|
25
24
|
const frames = scope.active();
|
|
26
25
|
if (frames.length === 0)
|
|
27
26
|
return { action: "allow" };
|
|
@@ -29,7 +29,7 @@ import { reconcileInterruptedSession } from "../session-reconcile.js";
|
|
|
29
29
|
import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-detector.js";
|
|
30
30
|
import { reservedCollisions, reservedFor } from "../../brain/request-params.js";
|
|
31
31
|
import { defineTool, isDefineToolProduct } from "../tools.js";
|
|
32
|
-
import {
|
|
32
|
+
import { RETIRED_TOOL_NAMES } from "../tool-name-aliases.js";
|
|
33
33
|
import { pathToUri } from "../lsp-protocol.js";
|
|
34
34
|
import { DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, createOffloadPersist, firstPartyOffloadPolicy, InMemoryToolResultStore, RunnerSharedToolResultStore, ScopedToolResultStore, isVolatileOffloadStore, OFFLOAD_TOOL_NAME, createReadToolResultTool, withToolResultOffload, } from "../tool-result-store.js";
|
|
35
35
|
import { OUTPUT_TOOL_NAME, REPORT_FINDINGS_TOOL_NAME, SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createOutputTool, createReportBlockedTool, createReportFindingsTool, createSkillTool, normalizeSkills } from "./synthetic-tools.js";
|
|
@@ -2018,9 +2018,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2018
2018
|
};
|
|
2019
2019
|
}
|
|
2020
2020
|
if (spec.agents !== undefined && spec.agents.length > 0) {
|
|
2021
|
-
const known = new Set(tools.flatMap((t) => [
|
|
2021
|
+
const known = new Set(tools.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
|
|
2022
2022
|
for (const def of spec.agents) {
|
|
2023
|
-
const unknownAllow = (def.allowTools ?? []).filter((n) => n !== "*" && !known.has(
|
|
2023
|
+
const unknownAllow = (def.allowTools ?? []).filter((n) => n !== "*" && !known.has(n));
|
|
2024
2024
|
if (unknownAllow.length > 0) {
|
|
2025
2025
|
try {
|
|
2026
2026
|
deps.onError?.(new Error(`TaskSpec.agents: agent "${def.name}" allows tool(s) ${unknownAllow.join(", ")} not present in this task's assembled roster — likely a typo (the entry would be item-filtered at spawn; the agent stays usable). Advisory only: the delegation pool can differ from this roster, so a tool mounted only on the delegation tool or produced by per-spawn extraTools makes this spurious.`), { phase: "config", sessionId });
|
|
@@ -2031,7 +2031,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2031
2031
|
for (const n of def.denyTools ?? []) {
|
|
2032
2032
|
if (n === "*")
|
|
2033
2033
|
continue;
|
|
2034
|
-
if (!known.has(
|
|
2034
|
+
if (!known.has(n)) {
|
|
2035
2035
|
const e = new Error(`TaskSpec.agents: agent "${def.name}" declares tool "${n}" in its denied tools, but no such tool exists in this deployment — fix the agent's tools list or mount the tool.`);
|
|
2036
2036
|
e.code = "config.agent.unknown_tool";
|
|
2037
2037
|
throw e;
|
|
@@ -2193,15 +2193,22 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2193
2193
|
{
|
|
2194
2194
|
const nameGroups = toolPolicyNameSets(policy);
|
|
2195
2195
|
if (nameGroups.length > 0) {
|
|
2196
|
-
const known = new Set(tools.flatMap((t) => [
|
|
2196
|
+
const known = new Set(tools.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
|
|
2197
2197
|
for (const t of harnessTools)
|
|
2198
|
-
known.add(
|
|
2198
|
+
known.add(t.name);
|
|
2199
2199
|
const unmatched = new Set();
|
|
2200
2200
|
for (const g of nameGroups) {
|
|
2201
2201
|
for (const [kind, list] of [["deny", g.deny], ["ask", g.ask], ["allow", g.allow]]) {
|
|
2202
2202
|
for (const n of list ?? []) {
|
|
2203
|
-
if (
|
|
2204
|
-
|
|
2203
|
+
if (known.has(n))
|
|
2204
|
+
continue;
|
|
2205
|
+
const retired = RETIRED_TOOL_NAMES.get(n);
|
|
2206
|
+
if (retired !== undefined) {
|
|
2207
|
+
const err = new Error(`tool policy ${kind}-list entry "${n}" is a RETIRED tool name (${retired}) and matches nothing in this run's roster — legacy-name normalization was removed (RB-476-A), so this entry would silently guard nothing. Update the deployment's rule to the current name.`);
|
|
2208
|
+
err.code = "config.legacy_tool_name";
|
|
2209
|
+
throw err;
|
|
2210
|
+
}
|
|
2211
|
+
unmatched.add(`"${n}" (${kind})`);
|
|
2205
2212
|
}
|
|
2206
2213
|
}
|
|
2207
2214
|
}
|
|
@@ -3160,6 +3167,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3160
3167
|
let result;
|
|
3161
3168
|
try {
|
|
3162
3169
|
result = await runToolGate({
|
|
3170
|
+
onNotifyError: (f) => emitTrace(deps.tracer, () => ({ kind: "observer.notify_failed", version: 1, taskId: spec.taskId ?? sessionId, site: f.site, message: f.error.message, ts: Date.now() })),
|
|
3163
3171
|
event: e,
|
|
3164
3172
|
preToolUse: hooks?.preToolUse,
|
|
3165
3173
|
adjudicate,
|
|
@@ -36,7 +36,6 @@ import { delimitUntrusted, inlineUntrusted, sanitizeUntrustedText } from "../unt
|
|
|
36
36
|
import { reconcileInterruptedSession } from "../session-reconcile.js";
|
|
37
37
|
import { RunnerSharedToolResultStore } from "../tool-result-store.js";
|
|
38
38
|
import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
|
|
39
|
-
import { canonicalToolName } from "../tool-name-aliases.js";
|
|
40
39
|
import { toolPolicyNameSets } from "../tool-policy.js";
|
|
41
40
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
42
41
|
import { discloseDroppedPending, PendingSessionNotifications, renderTaskNotificationXml, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
|
|
@@ -748,6 +747,13 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
748
747
|
function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
749
748
|
const { spec, queue, internals, buildFinalizeText, ident, nudgeSchedule, parentToolCallId, subagentName, pushContent, emitCommitted, startedToolCallIds, toolStartAt, turnToolSpan, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs } = deps;
|
|
750
749
|
let lastWorkspaceCwd = prepared.cwdRef?.current;
|
|
750
|
+
const announceWorkspaceMove = () => {
|
|
751
|
+
const cwdNow = prepared.cwdRef?.current;
|
|
752
|
+
if (cwdNow !== undefined && cwdNow !== lastWorkspaceCwd) {
|
|
753
|
+
lastWorkspaceCwd = cwdNow;
|
|
754
|
+
queue.push({ type: "workspace_changed", cwd: cwdNow, ...ident() });
|
|
755
|
+
}
|
|
756
|
+
};
|
|
751
757
|
const onMessageUpdate = (event) => {
|
|
752
758
|
const ev = event.assistantMessageEvent;
|
|
753
759
|
if (rs.turn.callStartAt === undefined)
|
|
@@ -1000,7 +1006,7 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1000
1006
|
}));
|
|
1001
1007
|
internals?.onActivity?.({ phase: "end", toolCallId: event.toolCallId, toolName: event.toolName, isError: event.isError, at: toolNow });
|
|
1002
1008
|
if (prepared.lspDiagnostics && !event.isError) {
|
|
1003
|
-
const name =
|
|
1009
|
+
const name = event.toolName;
|
|
1004
1010
|
if (name === "Edit" || name === "Write" || name === "NotebookEdit") {
|
|
1005
1011
|
const d = event.result?.details;
|
|
1006
1012
|
const p = typeof d?.filePath === "string" ? d.filePath : typeof d?.notebookPath === "string" ? d.notebookPath : undefined;
|
|
@@ -1026,11 +1032,7 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1026
1032
|
})(),
|
|
1027
1033
|
...ident(),
|
|
1028
1034
|
});
|
|
1029
|
-
|
|
1030
|
-
if (cwdNow !== undefined && cwdNow !== lastWorkspaceCwd) {
|
|
1031
|
-
lastWorkspaceCwd = cwdNow;
|
|
1032
|
-
queue.push({ type: "workspace_changed", cwd: cwdNow, ...ident() });
|
|
1033
|
-
}
|
|
1035
|
+
announceWorkspaceMove();
|
|
1034
1036
|
if (spec.outputSchema && event.toolName === OUTPUT_TOOL_NAME) {
|
|
1035
1037
|
if (event.isError) {
|
|
1036
1038
|
rs.degrade.outputErrorStreak += 1;
|
|
@@ -1108,7 +1110,7 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1108
1110
|
}
|
|
1109
1111
|
}
|
|
1110
1112
|
};
|
|
1111
|
-
return { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd };
|
|
1113
|
+
return { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, announceWorkspaceMove };
|
|
1112
1114
|
}
|
|
1113
1115
|
export class Runner {
|
|
1114
1116
|
deps;
|
|
@@ -1914,18 +1916,18 @@ export class Runner {
|
|
|
1914
1916
|
}
|
|
1915
1917
|
}
|
|
1916
1918
|
rs.attach.attachmentsInjected = 0;
|
|
1917
|
-
const todoToolMounted = rs.attach.attachState !== undefined && prepared.tools.some((t) =>
|
|
1918
|
-
const taskToolsMounted = rs.attach.attachState !== undefined && prepared.tools.some((t) =>
|
|
1919
|
+
const todoToolMounted = rs.attach.attachState !== undefined && prepared.tools.some((t) => t.name === "TodoWrite");
|
|
1920
|
+
const taskToolsMounted = rs.attach.attachState !== undefined && prepared.tools.some((t) => t.name === "TaskCreate");
|
|
1919
1921
|
const writeFamilyByName = new Map();
|
|
1920
1922
|
if (rs.attach.attachState !== undefined) {
|
|
1921
1923
|
for (const t of prepared.tools) {
|
|
1922
|
-
const family = writeFamilyOfCanonical(
|
|
1924
|
+
const family = writeFamilyOfCanonical(t.name);
|
|
1923
1925
|
if (family !== undefined)
|
|
1924
1926
|
for (const n of [t.name, ...(t.aliases ?? [])])
|
|
1925
1927
|
writeFamilyByName.set(n, family);
|
|
1926
1928
|
}
|
|
1927
1929
|
}
|
|
1928
|
-
const writeFamilyOf = (name) => writeFamilyByName.get(name) ?? writeFamilyOfCanonical(
|
|
1930
|
+
const writeFamilyOf = (name) => writeFamilyByName.get(name) ?? writeFamilyOfCanonical(name);
|
|
1929
1931
|
const toolStartAt = new Map();
|
|
1930
1932
|
const turnToolSpan = {};
|
|
1931
1933
|
const startedToolCallIds = new Set();
|
|
@@ -2091,7 +2093,7 @@ export class Runner {
|
|
|
2091
2093
|
const postToolBatchHook = (spec.hooks ?? this.deps.hooks)?.postToolBatch;
|
|
2092
2094
|
const batchArgs = postToolBatchHook ? new Map() : undefined;
|
|
2093
2095
|
rs.turn.toolBatch = [];
|
|
2094
|
-
const { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd } = makeHarnessHandlers(prepared, stats, rs, {
|
|
2096
|
+
const { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, announceWorkspaceMove } = makeHarnessHandlers(prepared, stats, rs, {
|
|
2095
2097
|
spec, queue, internals, buildFinalizeText, ident, nudgeSchedule, parentToolCallId, subagentName,
|
|
2096
2098
|
pushContent, emitCommitted, startedToolCallIds, toolStartAt, turnToolSpan, writeFamilyOf,
|
|
2097
2099
|
toolLabels, postToolBatchHook, batchArgs,
|
|
@@ -2461,6 +2463,7 @@ export class Runner {
|
|
|
2461
2463
|
});
|
|
2462
2464
|
if (resume.outcome.gate === "policy_ask")
|
|
2463
2465
|
resume.decisionDelivered = true;
|
|
2466
|
+
announceWorkspaceMove();
|
|
2464
2467
|
}
|
|
2465
2468
|
if (walltimeExhaustedResume) {
|
|
2466
2469
|
timeout.fired = true;
|
|
@@ -3290,7 +3293,7 @@ export class Runner {
|
|
|
3290
3293
|
return;
|
|
3291
3294
|
if (resume.outcome.gate === "policy_ask" &&
|
|
3292
3295
|
resume.outcome.decision === "allow" &&
|
|
3293
|
-
!prepared.tools.some((t) => t.name ===
|
|
3296
|
+
!prepared.tools.some((t) => t.name === pendingAction.toolName)) {
|
|
3294
3297
|
const e = new Error(`the approved tool "${pendingAction.toolName}" is no longer available on resume — refusing to continue as if it ran; the checkpoint is reopened for a retry with the tool present`);
|
|
3295
3298
|
e.code = "resume.tool_unavailable";
|
|
3296
3299
|
throw e;
|
|
@@ -3355,7 +3358,7 @@ export class Runner {
|
|
|
3355
3358
|
return;
|
|
3356
3359
|
}
|
|
3357
3360
|
}
|
|
3358
|
-
const tool = prepared.tools.find((t) => t.name ===
|
|
3361
|
+
const tool = prepared.tools.find((t) => t.name === pendingAction.toolName);
|
|
3359
3362
|
if (!tool) {
|
|
3360
3363
|
const e = new Error(`the approved tool "${pendingAction.toolName}" is no longer available on resume`);
|
|
3361
3364
|
e.code = "resume.tool_unavailable";
|
|
@@ -3375,7 +3378,7 @@ export class Runner {
|
|
|
3375
3378
|
}
|
|
3376
3379
|
const executedIsError = res.isError === true;
|
|
3377
3380
|
emitEnd(executedIsError);
|
|
3378
|
-
onResolvedToolSuccess?.(
|
|
3381
|
+
onResolvedToolSuccess?.(pendingAction.toolName, executedIsError ? undefined : res.details);
|
|
3379
3382
|
const eid = await prepared.session.appendMessage({
|
|
3380
3383
|
role: "toolResult",
|
|
3381
3384
|
toolCallId: pendingAction.toolCallId,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { canonicalizeTarget, fileArgPath } from "../../tools/fs/safety.js";
|
|
2
2
|
import { isWinFormPath } from "../../tools/fs/safety.js";
|
|
3
3
|
import { createCoarseCommandNamePolicy } from "../tool-policy.js";
|
|
4
|
-
import { canonicalToolName } from "../tool-name-aliases.js";
|
|
5
4
|
export const PATH_WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit"]);
|
|
6
5
|
export function isWithin(root, p) {
|
|
7
6
|
if (!root)
|
|
@@ -22,8 +21,8 @@ export function isWithin(root, p) {
|
|
|
22
21
|
const deny = (reason) => ({ action: "deny", reason, decisionReason: "rule" });
|
|
23
22
|
export function createSessionRulePolicy(rules, opts) {
|
|
24
23
|
const { env, rootPath, toolEffects } = opts;
|
|
25
|
-
const toolDeny = new Set(
|
|
26
|
-
const toolAllow = rules.toolAllow ? new Set(rules.toolAllow
|
|
24
|
+
const toolDeny = new Set(rules.toolDeny ?? []);
|
|
25
|
+
const toolAllow = rules.toolAllow ? new Set(rules.toolAllow) : undefined;
|
|
27
26
|
const cmdPolicy = rules.commandAllow || rules.commandDeny
|
|
28
27
|
? createCoarseCommandNamePolicy({
|
|
29
28
|
...(rules.commandAllow ? { allow: rules.commandAllow } : {}),
|
|
@@ -34,7 +33,7 @@ export function createSessionRulePolicy(rules, opts) {
|
|
|
34
33
|
const allowDirs = rules.allowDirs && rules.allowDirs.length > 0 ? rules.allowDirs : undefined;
|
|
35
34
|
return {
|
|
36
35
|
async check(req, signal) {
|
|
37
|
-
const toolName =
|
|
36
|
+
const toolName = req.toolName;
|
|
38
37
|
if (toolDeny.has(toolName))
|
|
39
38
|
return deny(`tool "${req.toolName}" is denied by a session rule`);
|
|
40
39
|
if (toolAllow && !toolAllow.has(toolName)) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { canonicalizeTarget, writeTargetPath } from "../tools/fs/safety.js";
|
|
2
|
-
import { canonicalToolName } from "./tool-name-aliases.js";
|
|
3
2
|
const DEFAULT_GUARDED_TOOLS = ["Write", "Edit", "MultiEdit", "NotebookEdit"];
|
|
4
3
|
export const RECOMMENDED_SENSITIVE_PATTERNS = [
|
|
5
4
|
".env",
|
|
@@ -56,10 +55,10 @@ function matchSensitive(canonicalKey, compiled) {
|
|
|
56
55
|
}
|
|
57
56
|
export function createSensitivePathPolicy(opts) {
|
|
58
57
|
const compiled = compilePatterns(opts.patterns);
|
|
59
|
-
const guarded = new Set(
|
|
58
|
+
const guarded = new Set(opts.tools ?? DEFAULT_GUARDED_TOOLS);
|
|
60
59
|
return {
|
|
61
60
|
async check(req, signal) {
|
|
62
|
-
const canonical =
|
|
61
|
+
const canonical = req.toolName;
|
|
63
62
|
if (compiled.length === 0 || !guarded.has(canonical))
|
|
64
63
|
return { action: "allow" };
|
|
65
64
|
const path = writeTargetPath(canonical, req.args);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { canonicalToolName } from "./tool-name-aliases.js";
|
|
2
1
|
const INTERRUPTED_UNKNOWN = "[INTERRUPTED] The previous run ended before this tool call's result was recorded. Its outcome " +
|
|
3
2
|
"is UNKNOWN — the action may have completed, partially completed, or never run. Do not assume " +
|
|
4
3
|
"success or failure. Before relying on it: if it is safe to read, query the current state to " +
|
|
@@ -72,7 +71,7 @@ export async function reconcileInterruptedSession(session, toolEffects, suspende
|
|
|
72
71
|
const orphans = findOrphanToolCalls(messages, suspendedBatch).filter((o) => o.kind !== "result");
|
|
73
72
|
const recovered = [];
|
|
74
73
|
for (const orphan of orphans) {
|
|
75
|
-
const effect = toolEffects?.get(
|
|
74
|
+
const effect = toolEffects?.get(orphan.toolName) ?? "write";
|
|
76
75
|
const effectText = effect === "read" ? INTERRUPTED_SAFE : effect === "idempotent" ? INTERRUPTED_IDEMPOTENT : INTERRUPTED_UNKNOWN;
|
|
77
76
|
const neverStarted = startedToolCallIds !== undefined && !startedToolCallIds.has(orphan.toolCallId);
|
|
78
77
|
const text = neverStarted ? INTERRUPTED_NEVER_STARTED : effectText;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { parsePermissionRule, wildcardMatch } from "./permission-rules.js";
|
|
2
|
-
import { canonicalToolName } from "./tool-name-aliases.js";
|
|
3
2
|
import { COARSE_SHELL_TOOLS } from "./tool-policy.js";
|
|
4
3
|
import { parseLeadingCommandName } from "../tools/fs/bash-readonly-classifier.js";
|
|
5
|
-
const SPECIFIER_ENFORCED_TOOLS = new Set(COARSE_SHELL_TOOLS
|
|
4
|
+
const SPECIFIER_ENFORCED_TOOLS = new Set(COARSE_SHELL_TOOLS);
|
|
6
5
|
export function parseSkillToolEntry(entry) {
|
|
7
6
|
const parsed = parsePermissionRule(entry);
|
|
8
|
-
const name =
|
|
7
|
+
const name = parsed.toolName;
|
|
9
8
|
return parsed.ruleContent === undefined ? { raw: entry, name } : { raw: entry, name, specifier: parsed.ruleContent };
|
|
10
9
|
}
|
|
11
10
|
export function isSkillSpecifierEnforced(canonicalName) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import { isAbsolute, join, relative } from "node:path";
|
|
3
|
-
import { canonicalToolName } from "./tool-name-aliases.js";
|
|
4
3
|
import { isSkillSpecifierEnforced, parseSkillToolEntry } from "./skill-tool-specifier.js";
|
|
5
4
|
const SKILL_FILE = "SKILL.md";
|
|
6
5
|
const RESOURCE_DIRS = ["assets", "references", "scripts"];
|
|
@@ -340,7 +339,7 @@ function manifestFromAllowedTools(declared, disallowed, skillName, deployedTools
|
|
|
340
339
|
return undefined;
|
|
341
340
|
let allowTools = names;
|
|
342
341
|
if (deployedTools !== undefined) {
|
|
343
|
-
const mounted = new Set(deployedTools
|
|
342
|
+
const mounted = new Set(deployedTools);
|
|
344
343
|
allowTools = names.filter((n) => mounted.has(parseSkillToolEntry(n).name));
|
|
345
344
|
for (const n of names) {
|
|
346
345
|
if (!mounted.has(parseSkillToolEntry(n).name)) {
|
|
@@ -353,7 +352,7 @@ function manifestFromAllowedTools(declared, disallowed, skillName, deployedTools
|
|
|
353
352
|
}
|
|
354
353
|
}
|
|
355
354
|
if (disallowed.length > 0) {
|
|
356
|
-
const denied = new Set(disallowed
|
|
355
|
+
const denied = new Set(disallowed);
|
|
357
356
|
allowTools = allowTools.filter((n) => !denied.has(parseSkillToolEntry(n).name));
|
|
358
357
|
}
|
|
359
358
|
return { allowTools, lineageId: `skill:${skillName}` };
|
|
@@ -104,7 +104,7 @@ export interface BackgroundAgentTaskHandle extends SemaTaskHandle {
|
|
|
104
104
|
durable?: DurableAgentLane;
|
|
105
105
|
notify?: (notification: import("./task-notification.js").TaskNotificationPayload, opts?: {
|
|
106
106
|
priority?: import("./task-notification.js").SystemInjectionPriority;
|
|
107
|
-
}) =>
|
|
107
|
+
}) => "queued" | "parked" | "dropped_duplicate" | Promise<"queued" | "parked" | "dropped_duplicate">;
|
|
108
108
|
retainedContinuation?: boolean;
|
|
109
109
|
reviveCycle?: number;
|
|
110
110
|
cycleSeq?: number;
|
|
@@ -63,13 +63,17 @@ export interface AccessibleTaskRow {
|
|
|
63
63
|
parentTaskId?: string;
|
|
64
64
|
parentSessionId?: string;
|
|
65
65
|
rootSessionId?: string;
|
|
66
|
+
createdAt?: number;
|
|
66
67
|
}
|
|
67
68
|
export declare class TaskRegistry {
|
|
68
69
|
private handles;
|
|
69
70
|
private sessionReapHooks;
|
|
70
71
|
private bgQuiescenceWatchers;
|
|
71
|
-
private legacyToTaskId;
|
|
72
72
|
private readonly notifier;
|
|
73
|
+
notifierFailureCounts(): ReadonlyArray<{
|
|
74
|
+
site: string;
|
|
75
|
+
count: number;
|
|
76
|
+
}>;
|
|
73
77
|
private readonly core;
|
|
74
78
|
durableAgentArmed(id: string): boolean;
|
|
75
79
|
durableAgentRowProbe(id: string): (() => Promise<boolean>) | undefined;
|
|
@@ -9,7 +9,7 @@ import { registerMonitorLane, pollMonitorLane, stopMonitorLane } from "./task-re
|
|
|
9
9
|
import { registerWorkflowLane, pollWorkflowLane, stopWorkflowLane } from "./task-registry-workflow.js";
|
|
10
10
|
import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, renderSpoolBody, spoolDropNote, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
|
|
11
11
|
export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR } from "./task-registry-shared.js";
|
|
12
|
-
import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME,
|
|
12
|
+
import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME, TASK_OUTPUT_CONTRACT, TASK_STOP_CONTRACT, TASK_OUTPUT_MISSING_ID_MESSAGE, TASK_STOP_MISSING_ID_MESSAGE, TASK_STOP_PARAMS, resolveTaskIdArg, REGISTRY_TASK_TOOL_CAPS, composeTaskOutputDescription, composeTaskOutputParams, composeTaskStopDescription, } from "./task-tool-shape.js";
|
|
13
13
|
import { durableAgentArmedLane, durableAgentRowProbeLane, beginDurableClaimLane, endDurableClaimLane, reapDurableAgentsLane, releaseDurableTranscriptAnchorLane, bindBackgroundAgentSessionLane, registerBackgroundAgentLane, parkBackgroundAgentLane, reconcileParkedAgentsLane, claimParkedAgentLane, rollbackParkedClaimLane, consumeParkedFlipLane, finalizeParkedResumeLane, settleBackgroundAgentLane, abortBackgroundAgentsForOwnerLane, serveDurableAgentRowLane, resolveBackgroundAgentByNameLane, markRetainedContinuationLane, reviveBackgroundAgentLane, settleRevivedAgentLane, unmarkRetainedContinuationLane, attachAgentNotifyLane, deliverToRunningAgentLane, runningBackgroundAgentLabelsLane, runningAgentFooterLane, notFoundRunningAgentsTail, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
|
|
14
14
|
export { canAccessWorkflowRun, clipTaskOutput, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE };
|
|
15
15
|
const BLOCK_DEFAULT_TIMEOUT_MS = 30_000;
|
|
@@ -102,8 +102,10 @@ export class TaskRegistry {
|
|
|
102
102
|
handles = new Map();
|
|
103
103
|
sessionReapHooks = new Set();
|
|
104
104
|
bgQuiescenceWatchers = new Map();
|
|
105
|
-
legacyToTaskId = new Map();
|
|
106
105
|
notifier = createSafeNotifier();
|
|
106
|
+
notifierFailureCounts() {
|
|
107
|
+
return this.notifier.failedSites().map((site) => ({ site, count: this.notifier.failuresAt(site) }));
|
|
108
|
+
}
|
|
107
109
|
core = (() => {
|
|
108
110
|
const self = this;
|
|
109
111
|
return {
|
|
@@ -226,7 +228,7 @@ export class TaskRegistry {
|
|
|
226
228
|
const prefix = TASK_PREFIX[type];
|
|
227
229
|
for (let i = 0; i < 32; i++) {
|
|
228
230
|
const id = `${prefix}${randomBytes(8).toString("hex")}`;
|
|
229
|
-
if (!this.handles.has(id)
|
|
231
|
+
if (!this.handles.has(id))
|
|
230
232
|
return id;
|
|
231
233
|
}
|
|
232
234
|
throw new Error(`TaskRegistry: exhausted task id mint retries for ${type}`);
|
|
@@ -253,7 +255,6 @@ export class TaskRegistry {
|
|
|
253
255
|
...(input.onTerminal !== undefined ? { onTerminal: input.onTerminal } : {}),
|
|
254
256
|
...(input.outputFile !== undefined ? { outputFile: input.outputFile } : {}),
|
|
255
257
|
});
|
|
256
|
-
this.legacyToTaskId.set(String(input.shellId), id);
|
|
257
258
|
if (input.onTerminal)
|
|
258
259
|
this.startBashWatcher(id, input.onTerminal);
|
|
259
260
|
return id;
|
|
@@ -290,6 +291,7 @@ export class TaskRegistry {
|
|
|
290
291
|
...(hb.parentTaskId !== undefined ? { parentTaskId: hb.parentTaskId } : {}),
|
|
291
292
|
...(hb.parentSessionId !== undefined ? { parentSessionId: hb.parentSessionId } : {}),
|
|
292
293
|
...(hb.rootSessionId !== undefined ? { rootSessionId: hb.rootSessionId } : {}),
|
|
294
|
+
createdAt: h.createdAt,
|
|
293
295
|
};
|
|
294
296
|
}
|
|
295
297
|
markStopSource(id, source) {
|
|
@@ -318,17 +320,9 @@ export class TaskRegistry {
|
|
|
318
320
|
return marked;
|
|
319
321
|
}
|
|
320
322
|
markStopSourceByShellId(shellId, source, env) {
|
|
321
|
-
const sid = String(shellId);
|
|
322
|
-
const taskId = this.legacyToTaskId.get(sid);
|
|
323
|
-
if (taskId !== undefined) {
|
|
324
|
-
const handle = this.handles.get(taskId);
|
|
325
|
-
if (handle && handle.type === "background_bash" && (env === undefined || handle.env === env)) {
|
|
326
|
-
this.markStopSource(taskId, source);
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
323
|
if (env === undefined)
|
|
331
324
|
return;
|
|
325
|
+
const sid = String(shellId);
|
|
332
326
|
for (const [id, handle] of this.handles) {
|
|
333
327
|
if (handle.type === "background_bash" && String(handle.shellId) === sid && handle.env === env) {
|
|
334
328
|
this.markStopSource(id, source);
|
|
@@ -349,9 +343,6 @@ export class TaskRegistry {
|
|
|
349
343
|
}
|
|
350
344
|
return true;
|
|
351
345
|
};
|
|
352
|
-
const taskId = this.legacyToTaskId.get(sid);
|
|
353
|
-
if (taskId !== undefined && clear(taskId))
|
|
354
|
-
return;
|
|
355
346
|
if (env === undefined)
|
|
356
347
|
return;
|
|
357
348
|
for (const [id, handle] of this.handles) {
|
|
@@ -958,11 +949,8 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
958
949
|
if (now - handle.updatedAt < terminalTtlMs)
|
|
959
950
|
continue;
|
|
960
951
|
this.handles.delete(id);
|
|
961
|
-
if (handle.type === "background_bash")
|
|
962
|
-
|
|
963
|
-
if (handle.watcher !== undefined)
|
|
964
|
-
clearInterval(handle.watcher);
|
|
965
|
-
}
|
|
952
|
+
if (handle.type === "background_bash" && handle.watcher !== undefined)
|
|
953
|
+
clearInterval(handle.watcher);
|
|
966
954
|
if (handle.type === "monitor" && handle.watcher !== undefined) {
|
|
967
955
|
handle.timers.clearInterval(handle.watcher);
|
|
968
956
|
handle.watcher = undefined;
|
|
@@ -999,7 +987,6 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
999
987
|
this.handles.delete(id);
|
|
1000
988
|
if (handle.status === "running" && handle.owner !== undefined)
|
|
1001
989
|
evictedRunningOwners.add(handle.owner);
|
|
1002
|
-
this.legacyToTaskId.delete(String(handle.shellId));
|
|
1003
990
|
if (handle.watcher !== undefined)
|
|
1004
991
|
clearInterval(handle.watcher);
|
|
1005
992
|
deleted++;
|
|
@@ -1009,10 +996,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1009
996
|
return deleted;
|
|
1010
997
|
}
|
|
1011
998
|
resolveInternal(id, access) {
|
|
1012
|
-
const
|
|
1013
|
-
if (!taskId)
|
|
1014
|
-
return undefined;
|
|
1015
|
-
const handle = this.handles.get(taskId);
|
|
999
|
+
const handle = this.handles.get(id);
|
|
1016
1000
|
if (!handle || !canAccess(handle, access))
|
|
1017
1001
|
return undefined;
|
|
1018
1002
|
return handle;
|
|
@@ -1216,14 +1200,13 @@ export function createTaskOutputTool(opts) {
|
|
|
1216
1200
|
const caps = { ...REGISTRY_TASK_TOOL_CAPS, notification: opts.notificationWired !== false };
|
|
1217
1201
|
return defineTool({
|
|
1218
1202
|
name: TASK_OUTPUT_TOOL_NAME,
|
|
1219
|
-
aliases: [...TASK_OUTPUT_ALIASES],
|
|
1220
1203
|
contract: TASK_OUTPUT_CONTRACT,
|
|
1221
1204
|
description: composeTaskOutputDescription(caps),
|
|
1222
1205
|
parameters: composeTaskOutputParams(caps),
|
|
1223
1206
|
effect: "read",
|
|
1224
1207
|
execute: async (rawArgs, ctx) => {
|
|
1225
1208
|
const args = rawArgs;
|
|
1226
|
-
const id = resolveTaskIdArg(args.task_id
|
|
1209
|
+
const id = resolveTaskIdArg(args.task_id);
|
|
1227
1210
|
if (!id)
|
|
1228
1211
|
return errorResult(TASK_OUTPUT_MISSING_ID_MESSAGE);
|
|
1229
1212
|
const requestedMs = typeof args.timeout === "number" ? args.timeout : undefined;
|
|
@@ -1266,14 +1249,13 @@ export function createTaskOutputTool(opts) {
|
|
|
1266
1249
|
export function createTaskStopTool(opts) {
|
|
1267
1250
|
return defineTool({
|
|
1268
1251
|
name: TASK_STOP_TOOL_NAME,
|
|
1269
|
-
aliases: [...TASK_STOP_ALIASES],
|
|
1270
1252
|
contract: TASK_STOP_CONTRACT,
|
|
1271
1253
|
description: composeTaskStopDescription(REGISTRY_TASK_TOOL_CAPS),
|
|
1272
1254
|
parameters: TASK_STOP_PARAMS,
|
|
1273
1255
|
effect: "write",
|
|
1274
1256
|
execute: async (rawArgs, ctx) => {
|
|
1275
1257
|
const args = rawArgs;
|
|
1276
|
-
const id = resolveTaskIdArg(args.task_id, args.shell_id
|
|
1258
|
+
const id = resolveTaskIdArg(args.task_id, args.shell_id);
|
|
1277
1259
|
if (!id)
|
|
1278
1260
|
return errorResult(TASK_STOP_MISSING_ID_MESSAGE);
|
|
1279
1261
|
const r = await opts.registry.stopTask(id, { owner: ctx.taskId ?? opts.owner, scope: ctx.principal ?? opts.scope, ...((ctx.sessionId ?? opts.sessionId) !== undefined ? { sessionId: ctx.sessionId ?? opts.sessionId } : {}) }, { workflowStore: opts.workflowStore, agentStore: opts.agentStore });
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
export declare const TASK_OUTPUT_TOOL_NAME = "TaskOutput";
|
|
3
3
|
export declare const TASK_STOP_TOOL_NAME = "TaskStop";
|
|
4
|
-
export declare const TASK_OUTPUT_ALIASES: readonly ["BashOutput", "AgentOutputTool", "BashOutputTool", "AgentOutput", "WorkflowStatus"];
|
|
5
|
-
export declare const TASK_STOP_ALIASES: readonly ["KillShell", "KillBash"];
|
|
6
4
|
export declare const TASK_OUTPUT_CONTRACT: {
|
|
7
5
|
readonly contractId: "core.task_output@1";
|
|
8
6
|
readonly implementationRevision: "1";
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
export const TASK_OUTPUT_TOOL_NAME = "TaskOutput";
|
|
3
3
|
export const TASK_STOP_TOOL_NAME = "TaskStop";
|
|
4
|
-
export const TASK_OUTPUT_ALIASES = ["BashOutput", "AgentOutputTool", "BashOutputTool", "AgentOutput", "WorkflowStatus"];
|
|
5
|
-
export const TASK_STOP_ALIASES = ["KillShell", "KillBash"];
|
|
6
4
|
export const TASK_OUTPUT_CONTRACT = { contractId: "core.task_output@1", implementationRevision: "1" };
|
|
7
5
|
export const TASK_STOP_CONTRACT = { contractId: "core.task_stop@1", implementationRevision: "1" };
|
|
8
6
|
export const TASK_OUTPUT_MISSING_ID_MESSAGE = "Error (TaskOutput): Missing required parameter: task_id";
|
|
@@ -50,9 +48,8 @@ export function composeTaskOutputDescription(caps) {
|
|
|
50
48
|
"poll captures any final output. ");
|
|
51
49
|
}
|
|
52
50
|
if (!caps.lanes)
|
|
53
|
-
parts.push("Optional `filter` is a regex keeping only matching lines.
|
|
54
|
-
parts.
|
|
55
|
-
return parts.join("");
|
|
51
|
+
parts.push("Optional `filter` is a regex keeping only matching lines.");
|
|
52
|
+
return parts.join("").trimEnd();
|
|
56
53
|
}
|
|
57
54
|
export function composeTaskStopDescription(caps) {
|
|
58
55
|
return [
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export declare const
|
|
2
|
-
export declare function canonicalToolName(name: string): string;
|
|
1
|
+
export declare const RETIRED_TOOL_NAMES: ReadonlyMap<string, string>;
|