@sema-agent/core 5.9.0 → 5.11.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/dist/agents/roster-store.d.ts +1 -0
  3. package/dist/agents/send-message-tool.js +6 -0
  4. package/dist/agents/subagent.d.ts +31 -1
  5. package/dist/agents/subagent.js +67 -21
  6. package/dist/agents/teacher.js +15 -3
  7. package/dist/agents/team.js +10 -0
  8. package/dist/agents/verify.js +7 -0
  9. package/dist/brain/anthropic.js +27 -10
  10. package/dist/brain/open-responses.js +19 -4
  11. package/dist/brain/openai.js +32 -5
  12. package/dist/core/a2a.js +1 -1
  13. package/dist/core/background-agent-store.d.ts +2 -1
  14. package/dist/core/background-agent-store.js +1 -0
  15. package/dist/core/checkpoint-store.d.ts +2 -0
  16. package/dist/core/checkpoint-store.js +1 -0
  17. package/dist/core/memory-recall.js +8 -3
  18. package/dist/core/memory.d.ts +5 -0
  19. package/dist/core/memory.js +6 -4
  20. package/dist/core/runner/assemble-result.js +9 -0
  21. package/dist/core/runner/prepare-task.d.ts +12 -1
  22. package/dist/core/runner/prepare-task.js +110 -38
  23. package/dist/core/runner/runtask.d.ts +12 -0
  24. package/dist/core/runner/runtask.js +169 -37
  25. package/dist/core/runner/session-file-state-replay.d.ts +7 -0
  26. package/dist/core/runner/session-file-state-replay.js +56 -0
  27. package/dist/core/runner/synthetic-tools.js +1 -1
  28. package/dist/core/runner/tool-disclosure.d.ts +1 -0
  29. package/dist/core/runner/tool-disclosure.js +24 -9
  30. package/dist/core/runner/tool-output-projection.js +5 -4
  31. package/dist/core/runner/turn-attachments.d.ts +2 -0
  32. package/dist/core/runner/turn-attachments.js +14 -5
  33. package/dist/core/session-reconcile.d.ts +7 -3
  34. package/dist/core/session-reconcile.js +3 -2
  35. package/dist/core/strategy-store.d.ts +1 -1
  36. package/dist/core/strategy-store.js +27 -4
  37. package/dist/core/task-registry-agent.d.ts +3 -0
  38. package/dist/core/task-registry-agent.js +9 -2
  39. package/dist/core/task-registry-shared.d.ts +1 -0
  40. package/dist/core/task-registry.d.ts +2 -0
  41. package/dist/core/tools.js +9 -1
  42. package/dist/core/trace.d.ts +1 -0
  43. package/dist/core/types.d.ts +8 -1
  44. package/dist/engine/loop/agent-loop.js +168 -22
  45. package/dist/orchestration/run-workflow-tool.js +1 -1
  46. package/dist/orchestration/workflow-governance.js +19 -0
  47. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  48. package/dist/orchestration/workflow-primitives.js +4 -1
  49. package/dist/orchestration/workflow.js +1 -1
  50. package/dist/prompts/coordinator.d.ts +1 -1
  51. package/dist/prompts/coordinator.js +1 -1
  52. package/dist/prompts/default.d.ts +1 -0
  53. package/dist/prompts/default.js +3 -0
  54. package/dist/stores/file/memory-store.js +3 -7
  55. package/dist/tools/fs/fs-bash.js +7 -4
  56. package/dist/tools/fs/fs-shared.d.ts +1 -0
  57. package/dist/tools/fs/fs-shared.js +4 -0
  58. package/dist/tools/fs/index.d.ts +1 -0
  59. package/dist/tools/fs/index.js +7 -4
  60. package/dist/tools/web.d.ts +21 -1
  61. package/dist/tools/web.js +126 -11
  62. package/package.json +2 -2
@@ -481,9 +481,17 @@ export function createOpenAIBrain(config = {}) {
481
481
  finalContent.push({ type: "text", text: textFace });
482
482
  const toolCalls = [];
483
483
  const malformed = [];
484
+ const unnamed = [];
485
+ let emptyPlaceholders = 0;
484
486
  for (const acc of [...toolAccum.entries()].sort((a, b) => a[0] - b[0]).map((e) => e[1])) {
485
- if (!acc.name)
487
+ if (!acc.name) {
488
+ if (!acc.id && acc.args === "") {
489
+ emptyPlaceholders++;
490
+ continue;
491
+ }
492
+ unnamed.push(`id="${acc.id || "?"}"(${acc.args.slice(0, 200)})`);
486
493
  continue;
494
+ }
487
495
  const tc = closeToolCallAccum(acc);
488
496
  if (tc) {
489
497
  toolCalls.push(tc);
@@ -508,16 +516,35 @@ export function createOpenAIBrain(config = {}) {
508
516
  }
509
517
  }
510
518
  }
511
- const toolError = malformed.length > 0
512
- ? `tool call argument(s) not valid JSON (likely truncated, finish_reason="${finishReason ?? "?"}"): ${malformed.join("; ")}`
513
- : undefined;
519
+ const emptySlots = `<empty slot>${emptyPlaceholders > 1 ? ` ×${emptyPlaceholders}` : ""}`;
520
+ const sawRealAction = toolCalls.length > 0 || malformed.length > 0 || unnamed.length > 0;
521
+ if (emptyPlaceholders > 0 && sawRealAction) {
522
+ unnamed.push(emptySlots);
523
+ }
524
+ else if (!sawRealAction && finishReason === "tool_calls") {
525
+ unnamed.push(emptyPlaceholders > 0 ? emptySlots : "<no tool_call delta arrived on the stream>");
526
+ }
527
+ const toolErrorParts = [];
528
+ if (malformed.length > 0) {
529
+ toolErrorParts.push(`tool call argument(s) not valid JSON (likely truncated, finish_reason="${finishReason ?? "?"}"${sentMaxTokens !== undefined ? `; request output cap in effect: ${sentMaxTokens} tokens — a cut mid-arguments commonly means the cap was hit` : ""}): ${malformed.join("; ")}`);
530
+ }
531
+ if (unnamed.length > 0) {
532
+ toolErrorParts.push(`tool call(s) arrived with no tool name and cannot be executed (finish_reason="${finishReason ?? "?"}"): ${unnamed.join("; ")}`);
533
+ }
534
+ const toolError = toolErrorParts.length > 0 ? toolErrorParts.join(" | ") : undefined;
514
535
  const noUsableContent = toolCalls.length === 0 && !accumText.trim();
515
- if (toolError !== undefined) {
536
+ if (malformed.length > 0) {
516
537
  finalContent.push({
517
538
  type: "text",
518
539
  text: `\n[note: ${malformed.length} tool call(s) were truncated (finish_reason="${finishReason ?? "?"}") and dropped — re-issue them next turn: ${malformed.join("; ")}]`,
519
540
  });
520
541
  }
542
+ if (unnamed.length > 0) {
543
+ finalContent.push({
544
+ type: "text",
545
+ text: `\n[note: ${unnamed.length} tool call(s) arrived with no tool name and were dropped — re-issue them next turn with an explicit tool name: ${unnamed.join("; ")}]`,
546
+ });
547
+ }
521
548
  if (malformedFrames > 0) {
522
549
  finalContent.push({
523
550
  type: "text",
package/dist/core/a2a.js CHANGED
@@ -23,7 +23,7 @@ const A2A_CARD_DESCRIPTION_MAX_CHARS = 240;
23
23
  const A2A_ID_MAX_CHARS = 160;
24
24
  const A2A_RESULT_BODY_MAX_CHARS = 100_000;
25
25
  const A2A_ERROR_TEXT_MAX_CHARS = 240;
26
- const A2A_RESULT_FENCE_REASON = "the peer is an autonomous agent, so its output is worker text, not tool data";
26
+ const A2A_RESULT_FENCE_REASON = "the peer is an agent acting on its own, so its output is worker text, not tool data";
27
27
  const A2A_CARD_PATHS = ["/.well-known/agent-card.json", "/.well-known/agent.json"];
28
28
  const A2A_PROTOCOL_VERSION = "1.0";
29
29
  const A2A_JSONRPC_TRANSPORT = "JSONRPC";
@@ -40,13 +40,14 @@ export interface BackgroundAgentRecord {
40
40
  errorCode?: string;
41
41
  errorRetryable?: boolean;
42
42
  errorKind?: string;
43
+ errorRetryAfterMs?: number;
43
44
  resultIsPartial?: boolean;
44
45
  recentSteps?: SubagentStep[];
45
46
  editedFiles?: SubagentEditedFile[];
46
47
  usage?: BackgroundAgentUsage;
47
48
  rev: number;
48
49
  }
49
- export declare const REVIVED_ROW_CLEARED_FIELDS: readonly ["settledAt", "stoppedBy", "completionId", "finalOutput", "finalOutputFull", "error", "errorCode", "errorRetryable", "errorKind", "resultIsPartial", "summary", "recentSteps", "editedFiles", "usage"];
50
+ export declare const REVIVED_ROW_CLEARED_FIELDS: readonly ["settledAt", "stoppedBy", "completionId", "finalOutput", "finalOutputFull", "error", "errorCode", "errorRetryable", "errorKind", "errorRetryAfterMs", "resultIsPartial", "summary", "recentSteps", "editedFiles", "usage"];
50
51
  export declare function clearRevivedRowTerminalPayload(record: BackgroundAgentRecord): void;
51
52
  export interface BackgroundAgentRowSummary {
52
53
  handle: string;
@@ -9,6 +9,7 @@ export const REVIVED_ROW_CLEARED_FIELDS = [
9
9
  "errorCode",
10
10
  "errorRetryable",
11
11
  "errorKind",
12
+ "errorRetryAfterMs",
12
13
  "resultIsPartial",
13
14
  "summary",
14
15
  "recentSteps",
@@ -24,6 +24,7 @@ export interface RiskDescriptor {
24
24
  shell?: boolean;
25
25
  };
26
26
  toolName: string;
27
+ shellGateDoctrine?: "classify" | "always";
27
28
  summary?: string;
28
29
  touchedPaths?: string[];
29
30
  }
@@ -39,6 +40,7 @@ export declare function buildRiskDescriptor(input: {
39
40
  args: unknown;
40
41
  safety?: SafetyAxis;
41
42
  shellGated?: boolean;
43
+ shellGateDoctrine?: "classify" | "always";
42
44
  }): RiskDescriptor;
43
45
  export type CheckpointGate = {
44
46
  kind: "human";
@@ -115,6 +115,7 @@ export function buildRiskDescriptor(input) {
115
115
  severity: riskSeverity(axes),
116
116
  axes,
117
117
  toolName,
118
+ ...(input.shellGated && input.shellGateDoctrine !== undefined ? { shellGateDoctrine: input.shellGateDoctrine } : {}),
118
119
  ...(summary !== undefined ? { summary } : {}),
119
120
  ...(touchedPaths !== undefined ? { touchedPaths } : {}),
120
121
  };
@@ -30,6 +30,11 @@ export const RECALL_CAVEAT = "These notes were recalled as relevant, but they ar
30
30
  "- If the user is about to act on your recommendation (not just asking about history), verify first.\n" +
31
31
  "- If a note names a file, check the file exists; if it names a function or symbol, grep for it; if it names a flag or value, read the current source.\n" +
32
32
  "- Memory is a snapshot from when it was written, not a live view — prefer `git log` or reading the code over recalling the snapshot.";
33
+ function isNewerNote(candidate, incumbent) {
34
+ if (candidate.timestampMissing || incumbent.timestampMissing)
35
+ return false;
36
+ return candidate.mtimeMs > incumbent.mtimeMs;
37
+ }
33
38
  export function resolveLinkedIds(headers, selected, max) {
34
39
  if (max <= 0 || selected.length === 0)
35
40
  return [];
@@ -38,7 +43,7 @@ export function resolveLinkedIds(headers, selected, max) {
38
43
  if (!h.name)
39
44
  continue;
40
45
  const prev = byName.get(h.name);
41
- if (!prev || h.mtimeMs > prev.mtimeMs)
46
+ if (!prev || isNewerNote(h, prev))
42
47
  byName.set(h.name, h);
43
48
  }
44
49
  const selectedIds = new Set(selected.map((r) => r.id));
@@ -100,8 +105,8 @@ export function validateSelectedIds(headers, ids, max) {
100
105
  export function composeSelectiveBody(manifestText, selected, nowMs, linked = [], recallable = true) {
101
106
  const renderNote = (r, label) => {
102
107
  const ageMs = nowMs - r.mtimeMs;
103
- const verify = ageMs > ONE_DAY_MS ? " — verify it's still current" : "";
104
- const stale = ` (written ${formatMemoryAge(ageMs)}${verify})`;
108
+ const verify = r.timestampMissing || ageMs > ONE_DAY_MS ? " — verify it's still current" : "";
109
+ const stale = r.timestampMissing ? ` (write time unknown${verify})` : ` (written ${formatMemoryAge(ageMs)}${verify})`;
105
110
  const prefix = label ? `${sanitizeUntrustedText(label, ["user_memory"])} ` : "";
106
111
  return `- ${prefix}${sanitizeUntrustedText(r.text, ["user_memory"])}${stale}`;
107
112
  };
@@ -40,6 +40,7 @@ export interface MemoryNoteHeader {
40
40
  id: string;
41
41
  description: string;
42
42
  mtimeMs: number;
43
+ timestampMissing?: true;
43
44
  name?: string;
44
45
  type?: string;
45
46
  consolidationGenerated?: boolean;
@@ -84,6 +85,10 @@ export declare class InMemoryMemoryStore implements MemoryStore {
84
85
  export declare function expandLexicalTerms(term: string): string[];
85
86
  export declare function lexicalSearchMatch(query: string, text: string): boolean;
86
87
  export declare function firstSentence(text: string): string;
88
+ export declare function parseNoteTimestamp(ts: unknown): {
89
+ mtimeMs: number;
90
+ timestampMissing?: true;
91
+ };
87
92
  export interface NormalizedMemorySpec {
88
93
  scopes: string[];
89
94
  writeScope: string | null;
@@ -268,7 +268,7 @@ export class InMemoryMemoryStore {
268
268
  return entries.map((e) => ({
269
269
  id: e.id,
270
270
  description: e.description ?? firstSentence(e.text),
271
- mtimeMs: mtimeMsOf(e.ts),
271
+ ...parseNoteTimestamp(e.ts),
272
272
  ...(e.name ? { name: e.name } : {}),
273
273
  ...(e.type ? { type: e.type } : {}),
274
274
  ...(e.consolidationGenerated ? { consolidationGenerated: true } : {}),
@@ -291,7 +291,7 @@ export class InMemoryMemoryStore {
291
291
  id: e.id,
292
292
  text: e.text,
293
293
  description: e.description ?? firstSentence(e.text),
294
- mtimeMs: mtimeMsOf(e.ts),
294
+ ...parseNoteTimestamp(e.ts),
295
295
  ...(e.name ? { name: e.name } : {}),
296
296
  ...(e.type ? { type: e.type } : {}),
297
297
  ...(e.consolidationGenerated ? { consolidationGenerated: true } : {}),
@@ -347,9 +347,11 @@ export function firstSentence(text) {
347
347
  const cut = dot >= 0 && dot < 160 ? dot + 1 : Math.min(t.length, 120);
348
348
  return t.slice(0, cut).trim();
349
349
  }
350
- function mtimeMsOf(ts) {
350
+ export function parseNoteTimestamp(ts) {
351
+ if (typeof ts !== "string" || ts.trim() === "")
352
+ return { mtimeMs: 0, timestampMissing: true };
351
353
  const ms = Date.parse(`${ts.replace(" ", "T")}:00Z`);
352
- return Number.isFinite(ms) ? ms : 0;
354
+ return Number.isFinite(ms) ? { mtimeMs: ms } : { mtimeMs: 0, timestampMissing: true };
353
355
  }
354
356
  export function normalizeMemorySpec(input) {
355
357
  if (!input)
@@ -86,6 +86,15 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
86
86
  status = "failed";
87
87
  errorCode = "suspend.loop";
88
88
  errorMessage = "task suspended too many times (resume/restart loop) — exceeded the suspend limit";
89
+ const gates = stats.humanReview?.gates ?? [];
90
+ if (gates.length > 0 && gates.every((g) => g.decision === "allow")) {
91
+ errorMessage +=
92
+ `. Diagnosis: all ${gates.length} recorded gate decision(s) on this run were ALLOW — a gate that keeps ` +
93
+ `asking combined with an approver that keeps approving consumes the suspend allowance on legitimate ` +
94
+ `work. Remedies: answer asks at a LIVE onAsk (a synchronous allow parks nothing and consumes no ` +
95
+ `suspend), keep provably-benign commands inside the read boundary so the classifier auto-allows them, ` +
96
+ `or raise maxSuspends for genuinely approval-heavy tasks.`;
97
+ }
89
98
  }
90
99
  else if (flags.threw) {
91
100
  status = "failed";
@@ -9,6 +9,7 @@ import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
9
9
  import type { OnAsk, ToolPolicy } from "../tool-policy.js";
10
10
  import { type ActiveSkillFrame } from "./active-skill-scope.js";
11
11
  import type { SessionPermissionRules } from "../session-policy-store.js";
12
+ import { type RecoveredOrphan } from "../session-reconcile.js";
12
13
  import { CacheBreakDetector, type ToolFingerprintInput } from "../cache-break-detector.js";
13
14
  import { type BrainCallGuardrailRef } from "../../brain/timeout.js";
14
15
  import { type OutputRef, type BlockedRef, type SkillListingEntry } from "./synthetic-tools.js";
@@ -18,7 +19,7 @@ import type { TaskNotificationPayload } from "../task-notification.js";
18
19
  import { type CwdRef } from "../../tools/fs/index.js";
19
20
  import { type WorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
20
21
  import type { Runner } from "./runtask.js";
21
- import { type CheckpointGate, type CheckpointState, type CheckpointToken, type ResourceLedger, type PlatformLimitReason, type ResourceLimitReason } from "../checkpoint-store.js";
22
+ import { type CheckpointGate, type CheckpointState, type CheckpointStore, type CheckpointToken, type ResourceLedger, type PlatformLimitReason, type ResourceLimitReason } from "../checkpoint-store.js";
22
23
  import type { ActiveWorktreeSession, AgentMessage, AgentTool, ExecutionEnv } from "../../internal/harness.js";
23
24
  import type { NestedUsageAccum, RunnerDeps, TaskEvent, TaskLimits, TaskResult, TaskSpec, ToolActivity, ToolEffect } from "../types.js";
24
25
  import type { RepairBundle } from "../../agents/repair-loop.js";
@@ -42,6 +43,11 @@ export declare function checkpointScopeOf(spec: {
42
43
  };
43
44
  principal?: string;
44
45
  }): string;
46
+ export declare function resolveCheckpointStore(spec: {
47
+ checkpointStore?: CheckpointStore | null;
48
+ }, deps: {
49
+ checkpointStore?: CheckpointStore;
50
+ }): CheckpointStore | undefined;
45
51
  export interface Prepared {
46
52
  harness: AgentHarness;
47
53
  session: StoredSession;
@@ -115,6 +121,7 @@ export interface Prepared {
115
121
  } | undefined;
116
122
  activeTools: Set<string>;
117
123
  deferredToolNames?: ReadonlySet<string>;
124
+ toolMaterializeStatic: boolean;
118
125
  memoryEngineSession?: {
119
126
  engine: MemoryEngine;
120
127
  handle: MemorySessionHandle;
@@ -128,6 +135,9 @@ export interface Prepared {
128
135
  scope?: string;
129
136
  restoreMode?: "snapshot" | "park_only";
130
137
  };
138
+ suspendProgressRef: {
139
+ executedApproved: boolean;
140
+ };
131
141
  reviewRef: {
132
142
  token?: CheckpointToken;
133
143
  gate?: CheckpointGate;
@@ -187,6 +197,7 @@ export interface Prepared {
187
197
  now: () => number;
188
198
  tools: AgentTool[];
189
199
  toolEffects: Map<string, ToolEffect>;
200
+ wakeRecovered: RecoveredOrphan[];
190
201
  promptOverheadTokens: number;
191
202
  readTaskFile?: (path: string) => Promise<string | null>;
192
203
  recentlyReadFiles?: () => string[];