@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.
- package/CHANGELOG.md +63 -0
- package/dist/agents/roster-store.d.ts +1 -0
- package/dist/agents/send-message-tool.js +6 -0
- package/dist/agents/subagent.d.ts +31 -1
- package/dist/agents/subagent.js +67 -21
- package/dist/agents/teacher.js +15 -3
- package/dist/agents/team.js +10 -0
- package/dist/agents/verify.js +7 -0
- package/dist/brain/anthropic.js +27 -10
- package/dist/brain/open-responses.js +19 -4
- package/dist/brain/openai.js +32 -5
- package/dist/core/a2a.js +1 -1
- package/dist/core/background-agent-store.d.ts +2 -1
- package/dist/core/background-agent-store.js +1 -0
- package/dist/core/checkpoint-store.d.ts +2 -0
- package/dist/core/checkpoint-store.js +1 -0
- package/dist/core/memory-recall.js +8 -3
- package/dist/core/memory.d.ts +5 -0
- package/dist/core/memory.js +6 -4
- package/dist/core/runner/assemble-result.js +9 -0
- package/dist/core/runner/prepare-task.d.ts +12 -1
- package/dist/core/runner/prepare-task.js +110 -38
- package/dist/core/runner/runtask.d.ts +12 -0
- package/dist/core/runner/runtask.js +169 -37
- package/dist/core/runner/session-file-state-replay.d.ts +7 -0
- package/dist/core/runner/session-file-state-replay.js +56 -0
- package/dist/core/runner/synthetic-tools.js +1 -1
- package/dist/core/runner/tool-disclosure.d.ts +1 -0
- package/dist/core/runner/tool-disclosure.js +24 -9
- package/dist/core/runner/tool-output-projection.js +5 -4
- package/dist/core/runner/turn-attachments.d.ts +2 -0
- package/dist/core/runner/turn-attachments.js +14 -5
- package/dist/core/session-reconcile.d.ts +7 -3
- package/dist/core/session-reconcile.js +3 -2
- package/dist/core/strategy-store.d.ts +1 -1
- package/dist/core/strategy-store.js +27 -4
- package/dist/core/task-registry-agent.d.ts +3 -0
- package/dist/core/task-registry-agent.js +9 -2
- package/dist/core/task-registry-shared.d.ts +1 -0
- package/dist/core/task-registry.d.ts +2 -0
- package/dist/core/tools.js +9 -1
- package/dist/core/trace.d.ts +1 -0
- package/dist/core/types.d.ts +8 -1
- package/dist/engine/loop/agent-loop.js +168 -22
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/orchestration/workflow-governance.js +19 -0
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-primitives.js +4 -1
- package/dist/orchestration/workflow.js +1 -1
- package/dist/prompts/coordinator.d.ts +1 -1
- package/dist/prompts/coordinator.js +1 -1
- package/dist/prompts/default.d.ts +1 -0
- package/dist/prompts/default.js +3 -0
- package/dist/stores/file/memory-store.js +3 -7
- package/dist/tools/fs/fs-bash.js +7 -4
- package/dist/tools/fs/fs-shared.d.ts +1 -0
- package/dist/tools/fs/fs-shared.js +4 -0
- package/dist/tools/fs/index.d.ts +1 -0
- package/dist/tools/fs/index.js +7 -4
- package/dist/tools/web.d.ts +21 -1
- package/dist/tools/web.js +126 -11
- package/package.json +2 -2
package/dist/brain/openai.js
CHANGED
|
@@ -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
|
|
512
|
-
|
|
513
|
-
|
|
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 (
|
|
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
|
|
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;
|
|
@@ -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
|
|
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
|
};
|
package/dist/core/memory.d.ts
CHANGED
|
@@ -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;
|
package/dist/core/memory.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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[];
|