@sema-agent/core 7.6.0 → 7.6.2

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 (78) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +2 -2
  3. package/dist/agents/cascade.d.ts +2 -3
  4. package/dist/agents/repair-loop.d.ts +2 -2
  5. package/dist/agents/retain-ledger.d.ts +2 -3
  6. package/dist/agents/send-message-tool.d.ts +2 -2
  7. package/dist/agents/session-util.d.ts +2 -2
  8. package/dist/agents/subagent.d.ts +3 -4
  9. package/dist/agents/teacher.d.ts +2 -2
  10. package/dist/agents/team.d.ts +2 -2
  11. package/dist/agents/verify.d.ts +5 -6
  12. package/dist/core/agent-definition.d.ts +172 -0
  13. package/dist/core/agent-definition.js +1 -0
  14. package/dist/core/checkpoint-store.d.ts +8 -4
  15. package/dist/core/delegation-frames.d.ts +298 -0
  16. package/dist/core/delegation-frames.js +21 -0
  17. package/dist/core/engine-notice.d.ts +555 -0
  18. package/dist/core/engine-notice.js +55 -0
  19. package/dist/core/gate-fold.d.ts +12 -0
  20. package/dist/core/gate-fold.js +158 -0
  21. package/dist/core/gate-lanes.d.ts +93 -0
  22. package/dist/core/gate-lanes.js +626 -0
  23. package/dist/core/hands-band.d.ts +134 -0
  24. package/dist/core/hands-band.js +1 -0
  25. package/dist/core/hooks.d.ts +20 -101
  26. package/dist/core/hooks.js +53 -854
  27. package/dist/core/mcp-failure.d.ts +43 -5
  28. package/dist/core/mcp-failure.js +31 -14
  29. package/dist/core/mcp-server-spec.d.ts +217 -0
  30. package/dist/core/mcp-server-spec.js +1 -0
  31. package/dist/core/model-seat.d.ts +99 -0
  32. package/dist/core/model-seat.js +1 -0
  33. package/dist/core/reminder-mint.d.ts +10 -0
  34. package/dist/core/reminder-mint.js +3 -0
  35. package/dist/core/runner/contracts.d.ts +382 -6
  36. package/dist/core/runner/gate-exit.d.ts +177 -9
  37. package/dist/core/runner/gate-exit.js +70 -1
  38. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
  39. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
  40. package/dist/core/runner/prepare-run-refs.d.ts +12 -0
  41. package/dist/core/runner/prepare-run-refs.js +5 -0
  42. package/dist/core/runner/prepare-task.d.ts +2 -2
  43. package/dist/core/runner/runtask.d.ts +4 -71
  44. package/dist/core/runner/runtask.js +18 -6
  45. package/dist/core/runner-deps.d.ts +1416 -0
  46. package/dist/core/runner-deps.js +1 -0
  47. package/dist/core/runtime-caps.d.ts +164 -0
  48. package/dist/core/runtime-caps.js +1 -0
  49. package/dist/core/task-event.d.ts +910 -0
  50. package/dist/core/task-event.js +1 -0
  51. package/dist/core/task-limits.d.ts +110 -0
  52. package/dist/core/task-limits.js +1 -0
  53. package/dist/core/task-result.d.ts +809 -0
  54. package/dist/core/task-result.js +1 -0
  55. package/dist/core/task-spec.d.ts +1370 -0
  56. package/dist/core/task-spec.js +1 -0
  57. package/dist/core/task-stream.d.ts +382 -0
  58. package/dist/core/task-stream.js +1 -0
  59. package/dist/core/tool-spec.d.ts +1174 -0
  60. package/dist/core/tool-spec.js +1 -0
  61. package/dist/core/types.d.ts +26 -7691
  62. package/dist/core/types.js +2 -76
  63. package/dist/core/warm-resume.d.ts +2 -2
  64. package/dist/index.d.ts +2 -1
  65. package/dist/index.js +1 -1
  66. package/dist/orchestration/goal.d.ts +2 -2
  67. package/dist/orchestration/run-spec.d.ts +2 -2
  68. package/dist/orchestration/run-workflow-tool.d.ts +3 -3
  69. package/dist/orchestration/workflow.d.ts +4 -4
  70. package/dist/scenarios/scenario-registry.d.ts +3 -3
  71. package/dist/scenarios/teacher-quickstart.d.ts +2 -2
  72. package/dist/server/http.d.ts +2 -2
  73. package/dist/stores/file/fs-atomic.d.ts +88 -12
  74. package/dist/stores/file/fs-atomic.js +184 -55
  75. package/dist/stores/file/index.d.ts +1 -0
  76. package/dist/stores/file/index.js +1 -0
  77. package/package.json +1 -1
  78. package/test/export-surface.snapshot.json +9 -1
@@ -0,0 +1,12 @@
1
+ import { type GatePass } from "./runner/gate-exit.js";
2
+ import type { ToolGateResult } from "./hooks.js";
3
+ /**
4
+ * Run the fold over the pass: adjudicate, fold the hook-ask, tighten. Writes `req` (re-minted over the FINAL
5
+ * post-screen args), `decision`, `deniedBy` (a direct policy deny's attribution), `policyRewrite` and
6
+ * `tightenedBy`; reads `currentInput` and `hookAsk`. Async because the adjudication and the reversibility
7
+ * probe await; the station ORDER is this function's statement order. Ends by handing the pass to the group after
8
+ * it (`pass.afterFold`, the lanes) in the LAST station's own continuation — no suspension point between the
9
+ * fourth tighten and the org face's read of its rule state (see the seat's note) — and returns what that group
10
+ * returns.
11
+ */
12
+ export declare function runGateFold(pass: GatePass): Promise<ToolGateResult>;
@@ -0,0 +1,158 @@
1
+ import { inlineUntrusted } from "./untrusted-text.js";
2
+ import { PROBE_REASON_MAX, normalizeProbeCause } from "./checkpoint-store.js";
3
+ import { traceHookCrash } from "./runner/gate-exit.js";
4
+ function withProbeTimeout(p, ms, signal) {
5
+ if (ms === undefined && signal === undefined)
6
+ return p;
7
+ return new Promise((resolve, reject) => {
8
+ let settled = false;
9
+ const done = (fn, v) => {
10
+ if (settled)
11
+ return;
12
+ settled = true;
13
+ if (timer !== undefined)
14
+ clearTimeout(timer);
15
+ signal?.removeEventListener("abort", onAbort);
16
+ fn(v);
17
+ };
18
+ const onAbort = () => done(reject, new Error("reversibilityProbe aborted (task ended)"));
19
+ const timer = ms !== undefined
20
+ ? setTimeout(() => done(reject, new Error(`reversibilityProbe timed out after ${ms}ms`)), ms)
21
+ : undefined;
22
+ if (signal?.aborted) {
23
+ onAbort();
24
+ return;
25
+ }
26
+ signal?.addEventListener("abort", onAbort);
27
+ p.then((v) => done(resolve, v), (err) => done(reject, err));
28
+ });
29
+ }
30
+ const DEFAULT_PROBE_TIMEOUT_MS = 30_000;
31
+ export async function runGateFold(pass) {
32
+ const { input, toolName, toolCallId, callSignal, ledger, notifier, adjudicate } = pass;
33
+ pass.req = { toolName, args: pass.currentInput, toolCallId };
34
+ pass.decision = adjudicate ? await (callSignal !== undefined ? adjudicate(pass.req, callSignal) : adjudicate(pass.req)) : { action: "allow" };
35
+ {
36
+ const refusedBy = ledger.fromDecision(pass.decision);
37
+ if (refusedBy !== undefined)
38
+ pass.deniedBy = refusedBy;
39
+ }
40
+ if ((pass.decision.action === "allow" || pass.decision.action === "ask") && pass.decision.updatedInput !== undefined) {
41
+ pass.policyRewrite = pass.decision.updatedInput;
42
+ }
43
+ else if (pass.hookAsk?.updatedInput !== undefined) {
44
+ pass.policyRewrite = pass.hookAsk.updatedInput;
45
+ }
46
+ if (pass.decision.action === "allow" && pass.hookAsk) {
47
+ pass.decision = {
48
+ action: "ask",
49
+ ...(pass.hookAsk.message !== undefined ? { message: pass.hookAsk.message } : {}),
50
+ decisionReason: "hook",
51
+ ...(pass.hookAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
52
+ ...(pass.hookAsk.updatedInput !== undefined ? { updatedInput: pass.hookAsk.updatedInput } : {}),
53
+ };
54
+ }
55
+ else if (pass.decision.action === "ask" && pass.hookAsk) {
56
+ const hookMessage = pass.hookAsk.message;
57
+ const foldedMessage = hookMessage !== undefined && hookMessage !== pass.decision.message
58
+ ? `${pass.decision.message !== undefined ? `${pass.decision.message} ` : ""}(a PreToolUse hook also asks: ${hookMessage})`
59
+ : pass.decision.message;
60
+ pass.decision = {
61
+ ...pass.decision,
62
+ decisionReason: "hook",
63
+ ...(foldedMessage !== undefined ? { message: foldedMessage } : {}),
64
+ ...(pass.hookAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
65
+ };
66
+ }
67
+ if (input.egress && pass.decision.action === "allow") {
68
+ pass.decision = {
69
+ action: "ask",
70
+ message: `tool "${toolName}" performs an external write (egress) — explicit approval required`,
71
+ decisionReason: "safety",
72
+ };
73
+ pass.tightenedBy = "safety";
74
+ }
75
+ const alreadyAsking = pass.decision.action === "ask";
76
+ if ((pass.decision.action === "allow" || alreadyAsking) && (input.irreversibility === "always" || input.irreversibility === "maybe")) {
77
+ let tighten = !alreadyAsking;
78
+ let probeReason;
79
+ let probeCause;
80
+ let probeMandated;
81
+ if (input.irreversibility === "maybe" && input.reversibilityProbe) {
82
+ let reversible = false;
83
+ const probeArgs = pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput;
84
+ const suppliedProbeMs = input.approvalTimeoutMs;
85
+ let probeTimeoutMs;
86
+ if (suppliedProbeMs === undefined) {
87
+ probeTimeoutMs = DEFAULT_PROBE_TIMEOUT_MS;
88
+ }
89
+ else if (Number.isFinite(suppliedProbeMs) && suppliedProbeMs >= 0 && suppliedProbeMs <= 2_147_483_647) {
90
+ probeTimeoutMs = suppliedProbeMs;
91
+ }
92
+ else {
93
+ probeTimeoutMs = DEFAULT_PROBE_TIMEOUT_MS;
94
+ traceHookCrash(input, new Error(`approvalTimeoutMs must be a non-negative finite number no greater than 2147483647 (got ${String(suppliedProbeMs)}) — the reversibilityProbe deadline falls back to the ${DEFAULT_PROBE_TIMEOUT_MS}ms default`), notifier);
95
+ }
96
+ try {
97
+ const verdict = await withProbeTimeout(Promise.resolve(input.reversibilityProbe(probeArgs)), probeTimeoutMs, input.abortSignal);
98
+ reversible = verdict?.reversible === true;
99
+ if (!reversible) {
100
+ const raw = verdict?.reason;
101
+ if (typeof raw === "string" && raw.trim().length > 0)
102
+ probeReason = inlineUntrusted(raw, PROBE_REASON_MAX);
103
+ probeCause = normalizeProbeCause(verdict?.cause);
104
+ if (verdict?.mandated === true)
105
+ probeMandated = true;
106
+ }
107
+ }
108
+ catch (err) {
109
+ reversible = false;
110
+ if (input.abortSignal?.aborted !== true)
111
+ traceHookCrash(input, err, notifier);
112
+ }
113
+ tighten = !alreadyAsking && !reversible;
114
+ }
115
+ if (tighten) {
116
+ pass.decision = {
117
+ action: "ask",
118
+ message: `tool "${toolName}" performs an irreversible action — explicit approval required`,
119
+ decisionReason: "safety",
120
+ ...(probeReason !== undefined ? { probeReason } : {}),
121
+ ...(probeCause !== undefined ? { probeCause } : {}),
122
+ ...(probeMandated === true ? { probeMandated: true } : {}),
123
+ };
124
+ pass.tightenedBy = input.shellGated === true ? "shell_gate" : "safety";
125
+ }
126
+ else if (alreadyAsking && probeMandated === true && pass.decision.action === "ask") {
127
+ pass.decision = { ...pass.decision, probeMandated: true };
128
+ }
129
+ }
130
+ const breakerKnownOpen = (d) => {
131
+ try {
132
+ return d.breakerOpen();
133
+ }
134
+ catch {
135
+ return false;
136
+ }
137
+ };
138
+ if (input.peerMessage === true && pass.decision.action === "allow" && input.autoMode !== undefined && !breakerKnownOpen(input.autoMode.decider)) {
139
+ pass.decision = {
140
+ action: "ask",
141
+ message: `tool "${toolName}" sends a message to another agent — routed for classifier review in auto mode`,
142
+ decisionReason: "safety",
143
+ };
144
+ pass.tightenedBy = "safety";
145
+ }
146
+ if (pass.decision.action === "allow" && input.writeProtectionCheck !== undefined) {
147
+ const writeProtectedHit = input.writeProtectionCheck(toolName, pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput);
148
+ if (writeProtectedHit !== null) {
149
+ pass.decision = {
150
+ action: "ask",
151
+ message: `tool "${toolName}" writes to a write-protected path (table entry "${writeProtectedHit.name}") — explicit approval required`,
152
+ decisionReason: "safety",
153
+ };
154
+ pass.tightenedBy = "safety";
155
+ }
156
+ }
157
+ return await pass.afterFold(pass);
158
+ }
@@ -0,0 +1,93 @@
1
+ import { type GatePass } from "./runner/gate-exit.js";
2
+ import type { OrgGateVerdict, PersistedRuleAnswer, PersistedRuleHit, ToolGateResult } from "./hooks.js";
3
+ /**
4
+ * design/252 review r3 — read a foreign {@link OrgGateVerdict} the way the personal-rule answer is read:
5
+ * OWN DATA properties only, never the prototype chain, never an accessor.
6
+ *
7
+ * Unlike the personal-rule normalizer below, this one accepts ANY non-array object as the carrier —
8
+ * not only a plain record. The two seams tighten in opposite directions when a shape is refused: the
9
+ * personal lane is a LOOSENING seam, so refusing a class instance degrades toward asking; this answer
10
+ * carries the org's DENY, and folding a structurally valid `{status:"available", verdict:{behavior:
11
+ * "deny"}}` class instance into `unavailable` would LOOSEN it — from a deny nobody can approve into a
12
+ * real-approval ask a person can clear. A class instance's fields are its own data properties, so the
13
+ * own-data read below already gives the full pollution guarantee (inherited members never authorize);
14
+ * the prototype test added nothing here but the downgrade.
15
+ *
16
+ * The two seams are the same class of trust boundary and were not being read the same way. What that
17
+ * cost here is worse than on the personal lane, because this answer is GOVERNANCE:
18
+ * · an inherited `revision` (a polluted `Object.prototype`) stamped a fabricated snapshot version onto
19
+ * a human's approval request — a WRONG record, which is worse than an absent one;
20
+ * · an inherited `verdict` invented an org rule, and with it an org ask or deny the lane never gave;
21
+ * · an answer that is not a record at all read as "available, nothing to say", i.e. the governance
22
+ * fail-open the availability contract exists to prevent.
23
+ * Anything this function cannot read as a well-formed answer becomes `unavailable` — the fail-CLOSED
24
+ * word, never the empty one.
25
+ */
26
+ export declare function normalizeOrgGateVerdict(answer: unknown, unreadable: string): OrgGateVerdict;
27
+ /**
28
+ * Normalize the accepted {@link ToolGateInput.persistedRules} answers into one reading.
29
+ *
30
+ * `{}` = a clean negative (no rule admits this call). `{ unreadable: true }` = the lane could not read
31
+ * its source. `{ hit }` = a match, whose `rules` is the non-empty coverage set (design/375). An answer
32
+ * outside every accepted shape — a number, `null`, an object with neither `rules` nor `unreadable`, an
33
+ * EMPTY `rules` array, the retired pre-375 `{ rule, dots? }` single-rule object — is read as a clean
34
+ * negative rather than a match: this is a LOOSENING seam, so an answer nobody can name degrades toward
35
+ * asking, never toward an allow built on it. All-or-nothing across the set's TEXTS for the same
36
+ * reason: a set with one unreadable member is a different (smaller) claim than the lane made, and a
37
+ * decision must not stand on a claim nobody made — one bad member drops the whole answer to the clean
38
+ * negative. Per-member DOTS stay individually optional (identity lost ⇒ `"not_reported"`, exactly the
39
+ * single-rule contract).
40
+ *
41
+ * The hit's dots are COPIED, not aliased. The array travels onto an ask that may sit in front of a
42
+ * person for a long time; a lane that retains and mutates its own array would otherwise change what the
43
+ * approver is looking at, and what an audit later reads, after the evidence was stamped.
44
+ */
45
+ export declare function normalizePersistedRuleHit(hit: PersistedRuleAnswer): {
46
+ hit?: PersistedRuleHit;
47
+ unreadable?: true;
48
+ coverage?: readonly import("./permission-rule-model.js").SegmentCoverage[];
49
+ };
50
+ /**
51
+ * The mandate provenance of one call, judged from the SAME mark inputs the gate is driven with —
52
+ * the single source for "could a persisted allow rule clear this ask?". Allow rules silence the
53
+ * classifier's questions, never a mandated one, and this predicate is the mandated-family half of
54
+ * that boundary (the real-approval/governance half rides the decision's own `requiresRealApproval`
55
+ * bit, which the org layer stamps):
56
+ * · `probeMandated` — this CALL's own reversibility probe declared its demotion STRUCTURAL
57
+ * (`"probe_mandate"`), judged FIRST because it is the only per-call member here: the three below
58
+ * are properties of the TOOL and are true of every call on the seat, so a mandate that is true of
59
+ * this one call must not be shadowed by the tier that happens to carry it (#502: the built-in
60
+ * shell probe raises it for a listed reader naming a path outside the session's roots — a boundary
61
+ * the deployment declared, which is exactly what the classify tier alone cannot say);
62
+ * · `egress` — the tool's own external-write mark, judged next: it is the tool's declaration even
63
+ * when the coarse doctrine also installed a shell tier on the same seat;
64
+ * · `shellGated` + tier `"always"` — the operator's per-call confirmation doctrine
65
+ * (`"operator_always"`); the classify doctrine installs `"maybe"`, and THOSE asks stay the rule
66
+ * lane's home turf (`undefined`) — that is the don't-ask-again main case, and the per-call member
67
+ * above is deliberately the ONLY thing that carves a mandate out of it;
68
+ * · a tool's OWN `"always"`/`"maybe"` irreversibility tier without the doctrine (`"tool_marks"`).
69
+ *
70
+ * Two consumers, one derivation: the gate's silencing arm (a matching rule is disclosed as shadowed
71
+ * instead of clearing the ask) and the runner's suggestion factory (a mandated ask offers no
72
+ * "stop asking me this" option — a rule minted from it would never clear it). A drift between the
73
+ * two would let a card offer a rule the lane then refuses to honor. The per-call member reaches both
74
+ * the same way every other per-call fact does: the gate stamps it on the surviving ask, the
75
+ * synchronous sites read it off the decision they spread, and the park leg threads its own parameter.
76
+ */
77
+ export declare function persistedRuleMandateOf(marks: {
78
+ egress?: boolean;
79
+ shellGated?: boolean;
80
+ irreversibility?: "never" | "maybe" | "always";
81
+ /** #502: the surviving ask's engine-stamped `probeMandated` — see
82
+ * {@link import("./types.js").ReversibilityVerdict.mandated}. */
83
+ probeMandated?: boolean;
84
+ }): "operator_always" | "tool_marks" | "probe_mandate" | undefined;
85
+ /**
86
+ * Run the lanes over the pass: the org layer over the fold's verdict, then — for a surviving ask — the rewrite
87
+ * merge, the rule lane, the stamp, the classifier, the sandbox leg, the parks, the content lane, the resolution
88
+ * and the edit chain. Returns the gate result when a park SUSPENDED the call (the three park sites' early
89
+ * exits), otherwise undefined with the pass holding a standing deny or allow for the exit. Reads every fold
90
+ * cell; writes `decision`, `deniedBy`, `currentInput`, `req.args`, `parkFailed`, `preToolContext`, and the
91
+ * ledger through its own methods. The station ORDER is this function's statement order.
92
+ */
93
+ export declare function runGateLanes(pass: GatePass): Promise<ToolGateResult>;