@sema-agent/core 4.0.0 → 5.0.1

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.
Files changed (60) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/agents/cascade.d.ts +1 -0
  3. package/dist/agents/cascade.js +1 -1
  4. package/dist/agents/repair-loop.d.ts +2 -0
  5. package/dist/agents/repair-loop.js +30 -6
  6. package/dist/agents/roster-store.d.ts +1 -0
  7. package/dist/agents/roster-store.js +1 -1
  8. package/dist/agents/send-message-tool.d.ts +1 -0
  9. package/dist/agents/send-message-tool.js +3 -1
  10. package/dist/agents/subagent.d.ts +3 -1
  11. package/dist/agents/subagent.js +22 -34
  12. package/dist/agents/tool-filter.js +6 -7
  13. package/dist/agents/verify.d.ts +1 -0
  14. package/dist/agents/verify.js +1 -1
  15. package/dist/core/arg-summary.d.ts +21 -1
  16. package/dist/core/arg-summary.js +61 -14
  17. package/dist/core/auto-compaction.d.ts +1 -0
  18. package/dist/core/auto-compaction.js +1 -1
  19. package/dist/core/fs-write-gate-policy.js +2 -3
  20. package/dist/core/hooks.d.ts +1 -0
  21. package/dist/core/hooks.js +1 -1
  22. package/dist/core/mcp.js +0 -6
  23. package/dist/core/permission-rules.d.ts +2 -2
  24. package/dist/core/permission-rules.js +12 -3
  25. package/dist/core/runner/active-skill-scope.js +1 -2
  26. package/dist/core/runner/prepare-task.js +38 -18
  27. package/dist/core/runner/runtask.js +19 -16
  28. package/dist/core/runner/session-rule-policy.d.ts +2 -2
  29. package/dist/core/runner/session-rule-policy.js +4 -4
  30. package/dist/core/sensitive-path-policy.js +2 -3
  31. package/dist/core/session-reconcile.js +1 -2
  32. package/dist/core/skill-tool-specifier.js +2 -3
  33. package/dist/core/skills-directory.js +2 -3
  34. package/dist/core/task-registry-shared.d.ts +1 -1
  35. package/dist/core/task-registry.d.ts +5 -1
  36. package/dist/core/task-registry.js +12 -30
  37. package/dist/core/task-tool-shape.d.ts +0 -2
  38. package/dist/core/task-tool-shape.js +2 -5
  39. package/dist/core/tool-name-aliases.d.ts +1 -2
  40. package/dist/core/tool-name-aliases.js +42 -60
  41. package/dist/core/tool-policy.js +11 -12
  42. package/dist/core/trace.d.ts +7 -0
  43. package/dist/core/untrusted-egress.d.ts +4 -2
  44. package/dist/core/untrusted-egress.js +21 -9
  45. package/dist/engine/execution-env/node-execution-env.js +1 -1
  46. package/dist/engine/loop/agent-loop.js +3 -12
  47. package/dist/index.d.ts +3 -2
  48. package/dist/index.js +2 -2
  49. package/dist/orchestration/run-spec.js +2 -3
  50. package/dist/orchestration/run-workflow-tool.js +0 -1
  51. package/dist/orchestration/workflow-governance.js +2 -1
  52. package/dist/orchestration/workflow.d.ts +1 -0
  53. package/dist/orchestration/workflow.js +1 -1
  54. package/dist/prompt-assembly/packs/sema-default.js +1 -3
  55. package/dist/prompts/default.js +1 -3
  56. package/dist/prompts/simple-sections.d.ts +0 -1
  57. package/dist/prompts/simple-sections.js +0 -1
  58. package/dist/tools/fs/fs-bash.js +6 -8
  59. package/dist/tools/fs/fs-write.js +0 -1
  60. package/package.json +1 -1
@@ -104,6 +104,7 @@ export interface ToolGateResult {
104
104
  preToolContext: string[];
105
105
  }
106
106
  export interface ToolGateInput {
107
+ onNotifyError?: (failure: import("./safe-notify.js").SafeNotifyFailure) => void;
107
108
  event: {
108
109
  toolCallId: string;
109
110
  toolName: string;
@@ -59,7 +59,7 @@ export async function runToolGate(input) {
59
59
  let currentInput = event.input;
60
60
  const preToolContext = [];
61
61
  let hookAsk;
62
- const notifier = createSafeNotifier();
62
+ const notifier = createSafeNotifier(input.onNotifyError !== undefined ? { onError: input.onNotifyError } : undefined);
63
63
  if (preToolUse) {
64
64
  let r;
65
65
  try {
package/dist/core/mcp.js CHANGED
@@ -756,9 +756,6 @@ function applyCallerAxisOverride(name, hint, override) {
756
756
  const LIST_MCP_RESOURCES = "ListMcpResourcesTool";
757
757
  const READ_MCP_RESOURCE = "ReadMcpResourceTool";
758
758
  const READ_MCP_RESOURCE_DIR = "ReadMcpResourceDirTool";
759
- const LEGACY_LIST_MCP_RESOURCES = "ListMcpResources";
760
- const LEGACY_READ_MCP_RESOURCE = "ReadMcpResource";
761
- const LEGACY_READ_MCP_RESOURCE_DIR = "ReadMcpResourceDir";
762
759
  const MCP_SKILLS_EXTENSION = "io.modelcontextprotocol/skills";
763
760
  const MAX_DIR_READ_PAGES = 20;
764
761
  const DIR_READ_NOT_A_DIRECTORY_RE = /not a directory|isn'?t a directory|not a folder/i;
@@ -843,7 +840,6 @@ function buildResourceTools(resourceServers) {
843
840
  if (listable.length > 0) {
844
841
  tools.push({
845
842
  name: LIST_MCP_RESOURCES,
846
- aliases: [LEGACY_LIST_MCP_RESOURCES],
847
843
  description: "List the resources available from connected MCP servers (uri / name / description). Pass `server` to limit " +
848
844
  "to one server, or omit it to list across all. Resource metadata is external/untrusted data.",
849
845
  label: LIST_MCP_RESOURCES,
@@ -903,7 +899,6 @@ function buildResourceTools(resourceServers) {
903
899
  if (readable.length > 0) {
904
900
  tools.push({
905
901
  name: READ_MCP_RESOURCE,
906
- aliases: [LEGACY_READ_MCP_RESOURCE],
907
902
  description: "Read a specific MCP resource by `server` + `uri` (from ListMcpResourcesTool). Its content is external/untrusted data.",
908
903
  label: READ_MCP_RESOURCE,
909
904
  executionMode: "parallel",
@@ -953,7 +948,6 @@ function buildResourceTools(resourceServers) {
953
948
  if (listable.length > 0) {
954
949
  tools.push({
955
950
  name: READ_MCP_RESOURCE_DIR,
956
- aliases: [LEGACY_READ_MCP_RESOURCE_DIR],
957
951
  description: "List the MCP resources UNDER a directory `uri` on `server` — its child resources (those whose uri is " +
958
952
  "nested below it; subdirectories carry mimeType \"inode/directory\"). Use it to browse a hierarchical " +
959
953
  "resource namespace after ListMcpResourcesTool. Resource metadata is external/untrusted data.",
@@ -1,4 +1,4 @@
1
- import type { ToolPolicy } from "./tool-policy.js";
1
+ import type { NamedToolPolicy } from "./tool-policy.js";
2
2
  export declare const BASH_GENERIC_PARAMS: ReadonlySet<string>;
3
3
  export interface PermissionRule {
4
4
  rule: string;
@@ -32,4 +32,4 @@ export declare function parsePermissionRule(rule: string): ParsedPermissionRule;
32
32
  export declare function wildcardMatch(pattern: string, value: string): boolean;
33
33
  export declare const fileToolParamVocabulary: (canonicalTool: string) => ReadonlySet<string> | undefined;
34
34
  export declare function validatePermissionRules(rules: PermissionRule[], opts?: Pick<PermissionRulePolicyOptions, "primaryFieldGeneric" | "caps">): PermissionRuleIssue[];
35
- export declare function createPermissionRulePolicy(rules: PermissionRule[], opts?: PermissionRulePolicyOptions): ToolPolicy;
35
+ export declare function createPermissionRulePolicy(rules: PermissionRule[], opts?: PermissionRulePolicyOptions): NamedToolPolicy;
@@ -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 = canonicalToolName(parsed.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;
@@ -260,9 +259,19 @@ export function createPermissionRulePolicy(rules, opts) {
260
259
  }
261
260
  }
262
261
  const defaultAction = opts?.defaultAction ?? "allow";
262
+ const nameSets = { deny: [], ask: [], allow: [] };
263
+ for (const [toolName, entry] of byTool) {
264
+ if (entry.bare.deny !== undefined || entry.param.deny.length > 0)
265
+ nameSets.deny.push(toolName);
266
+ if (entry.bare.ask !== undefined || entry.param.ask.length > 0)
267
+ nameSets.ask.push(toolName);
268
+ if (entry.bare.allow !== undefined)
269
+ nameSets.allow.push(toolName);
270
+ }
263
271
  return {
272
+ nameSets: [nameSets],
264
273
  check(req) {
265
- const entry = byTool.get(canonicalToolName(req.toolName));
274
+ const entry = byTool.get(req.toolName);
266
275
  if (entry) {
267
276
  if (entry.bare.deny) {
268
277
  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 = canonicalToolName(req.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 { canonicalToolName } from "../tool-name-aliases.js";
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";
@@ -1378,6 +1378,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1378
1378
  ...(deps.backgroundAgentStore !== undefined ? { agentStore: deps.backgroundAgentStore } : {}),
1379
1379
  ...(deps.mailboxStore !== undefined ? { mailbox: deps.mailboxStore } : {}),
1380
1380
  ...(reviveSpawn !== undefined ? { reviveSpawn } : {}),
1381
+ onNotifyError: (f) => emitTrace(deps.tracer, () => ({ kind: "observer.notify_failed", version: 1, taskId: hostTaskId, site: f.site, message: f.error.message, ts: Date.now() })),
1381
1382
  })));
1382
1383
  if (!(spec.tools ?? []).some((t) => t.name === AGENT_TRANSCRIPT_TOOL_NAME)) {
1383
1384
  tools.push(firstPartyOffload(createAgentTranscriptTool({
@@ -2018,9 +2019,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2018
2019
  };
2019
2020
  }
2020
2021
  if (spec.agents !== undefined && spec.agents.length > 0) {
2021
- const known = new Set(tools.flatMap((t) => [canonicalToolName(t.name), ...(t.aliases ?? []).map((a) => canonicalToolName(a))]));
2022
+ const known = new Set(tools.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
2022
2023
  for (const def of spec.agents) {
2023
- const unknownAllow = (def.allowTools ?? []).filter((n) => n !== "*" && !known.has(canonicalToolName(n)));
2024
+ const unknownAllow = (def.allowTools ?? []).filter((n) => n !== "*" && !known.has(n));
2024
2025
  if (unknownAllow.length > 0) {
2025
2026
  try {
2026
2027
  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 +2032,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2031
2032
  for (const n of def.denyTools ?? []) {
2032
2033
  if (n === "*")
2033
2034
  continue;
2034
- if (!known.has(canonicalToolName(n))) {
2035
+ if (!known.has(n)) {
2035
2036
  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
2037
  e.code = "config.agent.unknown_tool";
2037
2038
  throw e;
@@ -2190,18 +2191,30 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2190
2191
  }
2191
2192
  }
2192
2193
  const policy = spec.toolPolicy ?? deps.toolPolicy;
2193
- {
2194
- const nameGroups = toolPolicyNameSets(policy);
2194
+ const auditPolicyNames = (auditee) => {
2195
+ const nameGroups = toolPolicyNameSets(auditee);
2195
2196
  if (nameGroups.length > 0) {
2196
- const known = new Set(tools.flatMap((t) => [canonicalToolName(t.name), ...(t.aliases ?? []).map((a) => canonicalToolName(a))]));
2197
+ const known = new Set(tools.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
2197
2198
  for (const t of harnessTools)
2198
- known.add(canonicalToolName(t.name));
2199
+ known.add(t.name);
2199
2200
  const unmatched = new Set();
2200
2201
  for (const g of nameGroups) {
2201
2202
  for (const [kind, list] of [["deny", g.deny], ["ask", g.ask], ["allow", g.allow]]) {
2202
2203
  for (const n of list ?? []) {
2203
- if (!known.has(canonicalToolName(n)))
2204
- unmatched.add(`"${n}" (${kind})`);
2204
+ if (known.has(n))
2205
+ continue;
2206
+ const retired = RETIRED_TOOL_NAMES.get(n);
2207
+ if (retired !== undefined) {
2208
+ 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.`);
2209
+ err.code = "config.legacy_tool_name";
2210
+ throw err;
2211
+ }
2212
+ if (n.includes("__") && !n.startsWith("mcp__")) {
2213
+ const err = new Error(`tool policy ${kind}-list entry "${n}" is a pre-prefix MCP tool name and matches nothing in this run's roster — MCP tools are named "mcp__<server>__<tool>" and legacy-name normalization was removed (RB-476-A), so this entry would silently guard nothing. Prefix the entry with "mcp__".`);
2214
+ err.code = "config.legacy_tool_name";
2215
+ throw err;
2216
+ }
2217
+ unmatched.add(`"${n}" (${kind})`);
2205
2218
  }
2206
2219
  }
2207
2220
  }
@@ -2224,7 +2237,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2224
2237
  }
2225
2238
  }
2226
2239
  }
2227
- }
2240
+ };
2241
+ auditPolicyNames(policy);
2228
2242
  let sessionRulePolicy;
2229
2243
  if (deps.sessionPolicyStore && spec.sessionId) {
2230
2244
  const rules = await deps.sessionPolicyStore.getRules(spec.sessionId, spec.principal);
@@ -2261,6 +2275,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2261
2275
  })
2262
2276
  : undefined;
2263
2277
  const narrowingLayers = [sessionRulePolicy, ...ancestorRulePolicies, skillScopePolicy].filter((p) => p !== undefined);
2278
+ for (const layer of narrowingLayers)
2279
+ auditPolicyNames(layer);
2264
2280
  const denyNarrowingPolicy = narrowingLayers.length === 0 ? undefined : narrowingLayers.length === 1 ? narrowingLayers[0] : combinePolicies(...narrowingLayers);
2265
2281
  const basePolicyForResumeEdit = spec.toolPolicy ?? deps.toolPolicy;
2266
2282
  const sameInstanceAncestorCount = policy === undefined ? 0 : (inheritedParentConstraints ?? []).reduce((n, pc) => (pc.policy === policy ? n + 1 : n), 0);
@@ -2871,6 +2887,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2871
2887
  const token = mintCheckpointToken();
2872
2888
  const gate = { kind: "resource_limit", reason };
2873
2889
  const resourceLedgerOut = debitLedger(priorLedger, sliceSpend, resourceTotal);
2890
+ const mintedAt = Date.now();
2874
2891
  const cp = {
2875
2892
  token,
2876
2893
  version: resourceLedgerOut.totalWalltimeSec !== undefined ? WALLTIME_CHECKPOINT_VERSION : RESOURCE_CHECKPOINT_VERSION,
@@ -2881,8 +2898,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2881
2898
  pendingAction: { kind: "resource_limit", reason },
2882
2899
  state: serializeCheckpointState(remoteHandle),
2883
2900
  status: "pending",
2884
- createdAt: Date.now(),
2885
- deadline: Date.now() + (sanitizedTtlMs(rs.ttlMs) ?? DEFAULT_RESOURCE_TTL_MS),
2901
+ createdAt: mintedAt,
2902
+ deadline: mintedAt + (sanitizedTtlMs(rs.ttlMs) ?? DEFAULT_RESOURCE_TTL_MS),
2886
2903
  suspendCount: priorSuspendCount,
2887
2904
  humanReview: humanReviewRef.count > 0
2888
2905
  ? { count: humanReviewRef.count, totalWaitMs: humanReviewRef.totalWaitMs, gates: [...humanReviewRef.gates] }
@@ -2951,6 +2968,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2951
2968
  const scope = checkpointScopeOf(spec);
2952
2969
  const gate = { kind: "plan_review", reason: reason ?? `plan review requested` };
2953
2970
  const reviewLedger = debitLedger(priorLedger, liveSpendRef.get?.() ?? { costMicroUsd: 0, tokens: 0, turns: 0, walltimeMs: 0 }, resourceTotal, { countSlice: false });
2971
+ const mintedAt = Date.now();
2954
2972
  const cp = {
2955
2973
  token,
2956
2974
  version: reviewLedger.totalWalltimeSec !== undefined ? WALLTIME_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
@@ -2961,9 +2979,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2961
2979
  pendingAction: { kind: "plan_review" },
2962
2980
  state: serializeCheckpointState(remoteHandle),
2963
2981
  status: "pending",
2964
- createdAt: Date.now(),
2982
+ createdAt: mintedAt,
2965
2983
  suspendedAt: now(),
2966
- deadline: Date.now() + (sanitizedTtlMs(spec.durableApproval?.ttlMs) ?? DEFAULT_RESOURCE_TTL_MS),
2984
+ deadline: mintedAt + (sanitizedTtlMs(spec.durableApproval?.ttlMs) ?? DEFAULT_RESOURCE_TTL_MS),
2967
2985
  suspendCount: priorSuspendCount + 1,
2968
2986
  humanReview: humanReviewRef.count > 0
2969
2987
  ? { count: humanReviewRef.count, totalWaitMs: humanReviewRef.totalWaitMs, gates: [...humanReviewRef.gates] }
@@ -3075,6 +3093,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3075
3093
  const ttlMs = sanitizedTtlMs(durableApproval?.ttlMs);
3076
3094
  const checkpointState = serializeCheckpointState(remoteHandle);
3077
3095
  const approvalLedger = debitLedger(priorLedger, liveSpendRef.get?.() ?? { costMicroUsd: 0, tokens: 0, turns: 0, walltimeMs: 0 }, resourceTotal, { countSlice: false });
3096
+ const mintedAt = Date.now();
3078
3097
  cp = {
3079
3098
  token,
3080
3099
  version: approvalLedger.totalWalltimeSec !== undefined ? WALLTIME_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
@@ -3097,12 +3116,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3097
3116
  },
3098
3117
  state: checkpointState,
3099
3118
  deadline: ttlMs !== undefined
3100
- ? Date.now() + ttlMs
3119
+ ? mintedAt + ttlMs
3101
3120
  : durableApproval !== undefined
3102
3121
  ? undefined
3103
- : Date.now() + DEFAULT_UNATTENDED_APPROVAL_TTL_MS,
3122
+ : mintedAt + DEFAULT_UNATTENDED_APPROVAL_TTL_MS,
3104
3123
  status: "pending",
3105
- createdAt: Date.now(),
3124
+ createdAt: mintedAt,
3106
3125
  suspendedAt: now(),
3107
3126
  humanReview: humanReviewRef.count > 0
3108
3127
  ? { count: humanReviewRef.count, totalWaitMs: humanReviewRef.totalWaitMs, gates: [...humanReviewRef.gates] }
@@ -3160,6 +3179,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3160
3179
  let result;
3161
3180
  try {
3162
3181
  result = await runToolGate({
3182
+ 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
3183
  event: e,
3164
3184
  preToolUse: hooks?.preToolUse,
3165
3185
  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 = canonicalToolName(event.toolName);
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
- const cwdNow = prepared.cwdRef?.current;
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) => canonicalToolName(t.name) === "TodoWrite");
1918
- const taskToolsMounted = rs.attach.attachState !== undefined && prepared.tools.some((t) => canonicalToolName(t.name) === "TaskCreate");
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(canonicalToolName(t.name));
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(canonicalToolName(name));
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 === canonicalToolName(pendingAction.toolName))) {
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 === canonicalToolName(pendingAction.toolName));
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?.(canonicalToolName(pendingAction.toolName), executedIsError ? undefined : res.details);
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,6 +1,6 @@
1
1
  import type { ExecutionEnv } from "../../internal/harness-types.js";
2
2
  import type { ToolEffect } from "../types.js";
3
- import { type ToolPolicy } from "../tool-policy.js";
3
+ import { type NamedToolPolicy } from "../tool-policy.js";
4
4
  import type { SessionPermissionRules } from "../session-policy-store.js";
5
5
  export declare const PATH_WRITE_TOOLS: ReadonlySet<string>;
6
6
  export declare function isWithin(root: string, p: string): boolean;
@@ -8,4 +8,4 @@ export declare function createSessionRulePolicy(rules: SessionPermissionRules, o
8
8
  env: ExecutionEnv;
9
9
  rootPath?: string;
10
10
  toolEffects?: ReadonlyMap<string, ToolEffect>;
11
- }): ToolPolicy;
11
+ }): NamedToolPolicy;
@@ -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((rules.toolDeny ?? []).map(canonicalToolName));
26
- const toolAllow = rules.toolAllow ? new Set(rules.toolAllow.map(canonicalToolName)) : undefined;
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 } : {}),
@@ -33,8 +32,9 @@ export function createSessionRulePolicy(rules, opts) {
33
32
  : undefined;
34
33
  const allowDirs = rules.allowDirs && rules.allowDirs.length > 0 ? rules.allowDirs : undefined;
35
34
  return {
35
+ nameSets: [{ ...(rules.toolDeny?.length ? { deny: [...rules.toolDeny] } : {}), ...(rules.toolAllow?.length ? { allow: [...rules.toolAllow] } : {}) }],
36
36
  async check(req, signal) {
37
- const toolName = canonicalToolName(req.toolName);
37
+ const toolName = req.toolName;
38
38
  if (toolDeny.has(toolName))
39
39
  return deny(`tool "${req.toolName}" is denied by a session rule`);
40
40
  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((opts.tools ?? DEFAULT_GUARDED_TOOLS).map(canonicalToolName));
58
+ const guarded = new Set(opts.tools ?? DEFAULT_GUARDED_TOOLS);
60
59
  return {
61
60
  async check(req, signal) {
62
- const canonical = canonicalToolName(req.toolName);
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(canonicalToolName(orphan.toolName)) ?? "write";
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.map(canonicalToolName));
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 = canonicalToolName(parsed.toolName);
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.map(canonicalToolName));
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.map(canonicalToolName));
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
- }) => void | "queued" | "parked" | "dropped_duplicate" | Promise<"queued" | "parked" | "dropped_duplicate">;
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;