@sema-agent/core 7.49.5 → 7.50.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 +138 -0
- package/dist/agents/observer.js +3 -10
- package/dist/agents/subagent-steps.d.ts +6 -14
- package/dist/agents/subagent-steps.js +3 -17
- package/dist/brain/stream-shared.d.ts +1 -1
- package/dist/core/allow-entry.d.ts +1 -1
- package/dist/core/allow-entry.js +1 -1
- package/dist/core/ask-origin.d.ts +15 -11
- package/dist/core/ask-origin.js +6 -14
- package/dist/core/auto-mode-prompt.js +6 -2
- package/dist/core/checkpoint-store.d.ts +6 -4
- package/dist/core/checkpoint-store.js +1 -1
- package/dist/core/context-edit.d.ts +2 -2
- package/dist/core/decision-receipt.d.ts +2 -2
- package/dist/core/decision-receipt.js +30 -11
- package/dist/core/delegation-frames.d.ts +66 -18
- package/dist/core/delegation-frames.js +22 -0
- package/dist/core/engine-notice.d.ts +7 -5
- package/dist/core/frame-settlement.d.ts +6 -1
- package/dist/core/gate-fold.js +19 -13
- package/dist/core/gate-lanes.js +12 -11
- package/dist/core/gate-outcome.d.ts +2 -2
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +28 -20
- package/dist/core/human-input-projection.js +2 -0
- package/dist/core/lsp-diagnostics.d.ts +3 -0
- package/dist/core/lsp-diagnostics.js +6 -0
- package/dist/core/lsp-session.d.ts +39 -17
- package/dist/core/lsp-session.js +118 -58
- package/dist/core/lsp.d.ts +83 -3
- package/dist/core/lsp.js +146 -16
- package/dist/core/mcp.js +2 -1
- package/dist/core/media-byte-cap.d.ts +14 -0
- package/dist/core/media-byte-cap.js +15 -3
- package/dist/core/memory-engine/consolidation-driver.d.ts +23 -1
- package/dist/core/memory-engine/consolidation-driver.js +54 -6
- package/dist/core/memory-engine/consolidation-write-gate.d.ts +46 -0
- package/dist/core/memory-engine/consolidation-write-gate.js +82 -0
- package/dist/core/memory-engine/consolidation.d.ts +17 -0
- package/dist/core/memory-engine/consolidation.js +1 -0
- package/dist/core/memory-engine/distiller.d.ts +8 -2
- package/dist/core/memory-engine/distiller.js +8 -6
- package/dist/core/memory-engine/engine.d.ts +37 -4
- package/dist/core/memory-engine/engine.js +127 -38
- package/dist/core/memory-engine/file-backend.d.ts +8 -3
- package/dist/core/memory-engine/file-backend.js +44 -16
- package/dist/core/memory-engine/index.d.ts +2 -2
- package/dist/core/memory-engine/index.js +1 -1
- package/dist/core/memory-engine/landing-admission.d.ts +65 -0
- package/dist/core/memory-engine/landing-admission.js +68 -0
- package/dist/core/memory-engine/types.d.ts +23 -0
- package/dist/core/mode-policy.d.ts +3 -2
- package/dist/core/mode-policy.js +2 -0
- package/dist/core/permission-rule-model.d.ts +19 -13
- package/dist/core/permission-rule-model.js +7 -4
- package/dist/core/permission-rule-provider.d.ts +11 -0
- package/dist/core/permission-rules.d.ts +1 -1
- package/dist/core/permission-rules.js +5 -2
- package/dist/core/persisted-rule-arms.d.ts +15 -7
- package/dist/core/persisted-rule-arms.js +13 -6
- package/dist/core/reminder-mint.d.ts +5 -0
- package/dist/core/reminder-mint.js +1 -0
- package/dist/core/retired-keys.d.ts +8 -0
- package/dist/core/retired-keys.js +4 -0
- package/dist/core/rule-lane-reads.d.ts +4 -4
- package/dist/core/rule-lane-reads.js +10 -1
- package/dist/core/runner/construction-doors.d.ts +10 -4
- package/dist/core/runner/construction-doors.js +25 -2
- package/dist/core/runner/contracts.d.ts +20 -3
- package/dist/core/runner/env-capability-census.js +1 -1
- package/dist/core/runner/gate-exit.js +2 -1
- package/dist/core/runner/memory-consolidation-driver.d.ts +6 -4
- package/dist/core/runner/memory-consolidation-driver.js +5 -2
- package/dist/core/runner/memory-consolidation-write-judge.d.ts +17 -0
- package/dist/core/runner/memory-consolidation-write-judge.js +14 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +16 -22
- package/dist/core/runner/permission-rule-lanes.js +13 -43
- package/dist/core/runner/person-path-rows.d.ts +244 -0
- package/dist/core/runner/person-path-rows.js +387 -0
- package/dist/core/runner/prepare-acquire-reconcile.js +8 -1
- package/dist/core/runner/prepare-artifact.d.ts +5 -3
- package/dist/core/runner/prepare-artifact.js +2 -8
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
- package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
- package/dist/core/runner/prepare-context-lane.js +2 -2
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -3
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +19 -10
- package/dist/core/runner/prepare-hands-readface.js +57 -43
- package/dist/core/runner/prepare-lsp.d.ts +15 -13
- package/dist/core/runner/prepare-lsp.js +11 -11
- package/dist/core/runner/prepare-policy-chain.d.ts +8 -4
- package/dist/core/runner/prepare-policy-chain.js +6 -7
- package/dist/core/runner/prepare-run-refs.js +1 -1
- package/dist/core/runner/prepare-task.js +24 -16
- package/dist/core/runner/prepare-turn-wiring.d.ts +13 -6
- package/dist/core/runner/prepare-turn-wiring.js +19 -8
- package/dist/core/runner/read-root-grant.d.ts +1 -25
- package/dist/core/runner/read-root-grant.js +1 -17
- package/dist/core/runner/retired-runner-deps.d.ts +2 -2
- package/dist/core/runner/run-brain-sinks.d.ts +1 -5
- package/dist/core/runner/run-brain-sinks.js +2 -4
- package/dist/core/runner/run-harness-handlers.js +5 -4
- package/dist/core/runner/run-identity-wiring.d.ts +4 -4
- package/dist/core/runner/run-identity-wiring.js +10 -1
- package/dist/core/runner/run-leg.d.ts +1 -1
- package/dist/core/runner/run-leg.js +22 -9
- package/dist/core/runner/run-settle-and-teardown.d.ts +0 -4
- package/dist/core/runner/run-settle-and-teardown.js +7 -4
- package/dist/core/runner/run-terminal-adoption.d.ts +1 -1
- package/dist/core/runner/run-terminal-adoption.js +1 -1
- package/dist/core/runner/run-turn-boundary.js +2 -2
- package/dist/core/runner/runtask.js +8 -8
- package/dist/core/runner/session-file-state-replay.d.ts +14 -4
- package/dist/core/runner/session-file-state-replay.js +8 -0
- package/dist/core/runner/stream-steer-verb.js +3 -0
- package/dist/core/runner/tool-end-body.d.ts +5 -4
- package/dist/core/runner/tool-end-body.js +4 -4
- package/dist/core/runner/tool-output-projection.d.ts +6 -4
- package/dist/core/runner/tool-output-projection.js +66 -47
- package/dist/core/runner-deps.d.ts +6 -3
- package/dist/core/shared-memory/types.d.ts +2 -2
- package/dist/core/shell-read-silence.d.ts +23 -0
- package/dist/core/shell-read-silence.js +18 -0
- package/dist/core/synthetic-read-verdict.d.ts +80 -21
- package/dist/core/synthetic-read-verdict.js +89 -18
- package/dist/core/task-event.d.ts +113 -25
- package/dist/core/task-spec.d.ts +44 -0
- package/dist/core/task-tool-shape.d.ts +1 -1
- package/dist/core/tool-catalog-entries.js +16 -2
- package/dist/core/tool-policy.d.ts +25 -15
- package/dist/core/tool-policy.js +9 -13
- package/dist/core/trace.d.ts +3 -2
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +1 -1
- package/dist/core/untrusted-text.d.ts +2 -2
- package/dist/core/user-attachment.d.ts +164 -0
- package/dist/core/user-attachment.js +437 -0
- package/dist/engine/compaction/compaction.js +10 -3
- package/dist/engine/compaction/utils.js +6 -0
- package/dist/engine/execution-env/node-execution-env.d.ts +19 -1
- package/dist/engine/execution-env/node-execution-env.js +125 -0
- package/dist/engine/harness/agent-harness.d.ts +12 -2
- package/dist/engine/harness/agent-harness.js +15 -3
- package/dist/engine/harness/types.d.ts +21 -0
- package/dist/engine/llm/content-block-render.d.ts +15 -0
- package/dist/engine/llm/content-block-render.js +10 -0
- package/dist/engine/llm/index.d.ts +1 -0
- package/dist/engine/llm/index.js +1 -0
- package/dist/engine/llm/types.d.ts +80 -4
- package/dist/engine/llm/types.js +43 -1
- package/dist/engine/session/import-validate.js +2 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +2 -1
- package/dist/internal/harness-types.d.ts +1 -1
- package/dist/internal/llm.d.ts +2 -2
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/workflow.d.ts +4 -5
- package/dist/orchestration/workflow.js +2 -2
- package/dist/prompts/default.d.ts +1 -1
- package/dist/tools/artifact/artifact-tool.js +1 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
- package/dist/tools/fs/dir-entries.d.ts +4 -10
- package/dist/tools/fs/dir-entries.js +4 -11
- package/dist/tools/fs/file-read-check.js +3 -3
- package/dist/tools/fs/fs-pdf.js +2 -4
- package/dist/tools/fs/fs-read.d.ts +47 -4
- package/dist/tools/fs/fs-read.js +63 -19
- package/dist/tools/fs/fs-search-tools.d.ts +3 -3
- package/dist/tools/fs/fs-search-tools.js +7 -2
- package/dist/tools/fs/fs-write.d.ts +1 -1
- package/dist/tools/fs/fs-write.js +7 -6
- package/dist/tools/fs/index.d.ts +7 -1
- package/dist/tools/fs/index.js +56 -34
- package/dist/tools/fs/read-deny-home.d.ts +19 -1
- package/dist/tools/fs/read-deny-home.js +32 -22
- package/dist/tools/fs/read-deny.d.ts +14 -0
- package/dist/tools/fs/read-deny.js +8 -0
- package/dist/tools/fs/repo-map.d.ts +3 -2
- package/dist/tools/fs/repo-map.js +11 -3
- package/dist/tools/fs/safety.d.ts +41 -2
- package/dist/tools/fs/safety.js +12 -3
- package/dist/tools/fs/shell-read-check.d.ts +12 -3
- package/dist/tools/fs/shell-read-check.js +28 -15
- package/dist/tools/fs/shell-read-device.d.ts +1 -1
- package/dist/tools/fs/shell-read-judge.d.ts +9 -4
- package/dist/tools/fs/shell-read-judge.js +1 -1
- package/dist/tools/loop-tick.d.ts +2 -2
- package/package.json +3 -1
- package/test/export-surface.snapshot.json +39 -1
- package/wire-changes/core@7.50.0.json +1699 -0
- package/dist/core/runner/persisted-path-rows.d.ts +0 -88
- package/dist/core/runner/persisted-path-rows.js +0 -108
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ShellReadVerdict } from "../tools/fs/shell-read-check.js";
|
|
2
|
+
import type { GatePass } from "./runner/gate-exit.js";
|
|
3
|
+
import type { AskGuard } from "./tool-policy.js";
|
|
4
|
+
/** Whose silence a station's answer carries, each its own class on the question it keeps or mints: unread rows are a rule that could
|
|
5
|
+
* not be read (the rule lane's class); the finding `unanswered` is, unless rows are unread, the environment's silence — shell text the
|
|
6
|
+
* engine cannot read (a guard) — and the marks only pick whose. Either is a question only a person answers; `probe` follows the finding. */
|
|
7
|
+
export declare function silencedBy(verdict: ShellReadVerdict, d: {
|
|
8
|
+
readonly guards?: readonly AskGuard[];
|
|
9
|
+
readonly ruleUnreadable?: unknown;
|
|
10
|
+
}): {
|
|
11
|
+
ruleUnreadable?: "persisted_rows";
|
|
12
|
+
guards?: readonly AskGuard[];
|
|
13
|
+
requiresRealApproval?: true;
|
|
14
|
+
};
|
|
15
|
+
/** The finding `unanswered` IS the silence, applied once, before any disposition arm: a station — a host-mounted one included — may
|
|
16
|
+
* spell `vouch` or `mandated` beside it, and neither word vouches for what nobody judged. A deny stands; an ask keeps its words and
|
|
17
|
+
* gains the silence's class; an allow becomes the silence's question (`ask.message`), one only a person answers. Nothing is admitted,
|
|
18
|
+
* no `probe` is stamped and no grant is offered — the station made no finding. */
|
|
19
|
+
export declare function applyUnansweredSilence(pass: GatePass, verdict: ShellReadVerdict, ask: {
|
|
20
|
+
readonly message: string;
|
|
21
|
+
readonly account: string | undefined;
|
|
22
|
+
readonly tightenedBy: "shell_gate" | "safety";
|
|
23
|
+
}): void;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function silencedBy(verdict, d) {
|
|
2
|
+
const environment = verdict.environmentSilent === true || (verdict.finding === "unanswered" && verdict.rowsUnread !== true);
|
|
3
|
+
const rows = verdict.rowsUnread === true && d.ruleUnreadable === undefined ? { ruleUnreadable: "persisted_rows" } : {};
|
|
4
|
+
const guards = environment && d.guards?.includes("unverifiable_shell_text") !== true ? { guards: [...(d.guards ?? []), "unverifiable_shell_text"] } : {};
|
|
5
|
+
return environment || verdict.rowsUnread === true ? { ...rows, ...guards, requiresRealApproval: true } : {};
|
|
6
|
+
}
|
|
7
|
+
export function applyUnansweredSilence(pass, verdict, ask) {
|
|
8
|
+
if (verdict.details !== undefined)
|
|
9
|
+
pass.shellReadDetails = verdict.details;
|
|
10
|
+
if (pass.decision.action === "ask") {
|
|
11
|
+
pass.decision = { ...pass.decision, ...silencedBy(verdict, pass.decision) };
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (pass.decision.action !== "allow")
|
|
15
|
+
return;
|
|
16
|
+
pass.decision = { action: "ask", message: ask.message, decisionReason: "safety", ...silencedBy(verdict, {}), ...(ask.account !== undefined ? { probeReason: ask.account } : {}) };
|
|
17
|
+
pass.tightenedBy = ask.tightenedBy;
|
|
18
|
+
}
|
|
@@ -1,24 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The synthetic-
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* The synthetic-call judge — "would this call be refused outright?", asked without a model call, without an
|
|
3
|
+
* approver and without a hook. One judge, one set of layers, for every consumer that needs the answer for a file the
|
|
4
|
+
* model did not itself ask to touch. It has TWO FACES over one body (the layer walk below is written once):
|
|
5
|
+
*
|
|
6
|
+
* READ — a `Read` of a file the model did not ask for (the changed-file notice; a user's attachment). An ordinary
|
|
7
|
+
* `ask` — a person's own ask rule — is not a refusal: the person who would answer it is the one asking.
|
|
8
|
+
* WRITE — a `Write` of a path the engine itself writes in the background (the memory consolidation run). Every `ask`
|
|
9
|
+
* is an objection here: nobody is present to answer it, so a layer that would ask a person refuses the write.
|
|
10
|
+
*
|
|
11
|
+
* It never picks a direction. The read face answers in FOUR states:
|
|
6
12
|
*
|
|
7
13
|
* deny — some layer refuses it;
|
|
8
|
-
*
|
|
9
|
-
*
|
|
14
|
+
* mandated_ask — no layer refuses it, and some layer would put the read to a person no rule may answer for
|
|
15
|
+
* (the ask's own mandate reading, {@link ruleClearanceOf}: a real-approval demand, the deployment's
|
|
16
|
+
* own question, an ancestor's, a hook's; or an organization rule's ask) — the person who must
|
|
17
|
+
* answer is not necessarily the one asking;
|
|
18
|
+
* no_objection — every layer it consulted answered and none objected;
|
|
19
|
+
* unanswerable — no layer objected, and at least one could not answer (see the table below).
|
|
20
|
+
*
|
|
21
|
+
* The write face answers the same four, with `ask` in place of `mandated_ask`: any layer's ask, mandated or not.
|
|
10
22
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
23
|
+
* ONE PRECEDENCE on both faces: a deny anywhere wins over a held ask, and a held ask over an unanswerable layer; the walk
|
|
24
|
+
* goes on past an ask (a later layer's deny is the stronger answer). The faces differ only in which asks are held. The
|
|
25
|
+
* CONSUMER decides where `mandated_ask` and `unanswerable` go: a notice that merely tells the model a file changed reads
|
|
26
|
+
* both as no objection (the worst case is a notice about a file it then cannot re-read); a face that would put a file's
|
|
27
|
+
* contents in front of the model reads both as a refusal; a background write reads everything but `no_objection` as a
|
|
28
|
+
* refusal. Every answer but `unanswerable` names the location the layers judged (`judged`, the canonical view of the
|
|
29
|
+
* stamped target, when a stamp was taken), so a consumer that resolved the file itself can tell the judged file from the
|
|
30
|
+
* one it opens.
|
|
14
31
|
*
|
|
15
|
-
* THE LAYERS (every one that can refuse a model's own
|
|
32
|
+
* THE LAYERS (every one that can refuse a model's own call): the run's skill deny rows; the
|
|
16
33
|
* deny-narrowing fold (session rules, inherited ancestor rules, frozen constraint projections); the
|
|
17
34
|
* deployment's caller policy; each live inherited parent policy; the person's persisted-rule lane; the org
|
|
18
|
-
* lane. Not consulted, by design: PreToolUse hooks (the deployment's screening of a model's CALL), the
|
|
19
|
-
*
|
|
20
|
-
* and containment roots (the path's own resolution judges those
|
|
21
|
-
* anywhere wins over an unanswerable layer elsewhere.
|
|
35
|
+
* lane. Not consulted, by design: PreToolUse hooks (the deployment's screening of a model's CALL), the permission mode's write
|
|
36
|
+
* gate, the integrity guards and the write-protection table (a live `Write` meets them; the read face never needs them and the
|
|
37
|
+
* write face's background run is not asked), and the read-deny table and containment roots (the path's own resolution judges those first).
|
|
22
38
|
*
|
|
23
39
|
* FAULT → STATE (the live gate's own readings, through the same functions — never a second copy):
|
|
24
40
|
* | layer | fault | state |
|
|
@@ -28,11 +44,13 @@
|
|
|
28
44
|
* | caller / inherited policy | its deny carries an engine settlement (an approval | unanswerable `approval_seat` |
|
|
29
45
|
* | | seat that ended unanswered; in a combinePolicies | |
|
|
30
46
|
* | | composition a rule deny anywhere is returned instead)| |
|
|
31
|
-
* | persisted lane | store unreadable, throws, times out (readPersistedRuleLane ⇒ `{unreadable}`) |
|
|
47
|
+
* | persisted lane | a store partition (or all of it) is unreadable, the lane throws, times out (readPersistedRuleLane ⇒ `{unreadable}`) | unanswerable `store_unreadable` (both faces: a row nobody could read may forbid the call) |
|
|
32
48
|
* | persisted lane | the call cannot be read against the rows (`reason`) — applyPersistedTightening ⇒ `unreadable` | unanswerable `call_unreadable` |
|
|
33
49
|
* | org lane | unavailable: throws, garbage (readOrgRuleLane) | unanswerable `org_unavailable` |
|
|
50
|
+
* | any policy layer | answers with a rewrite of the arguments (`updatedInput`) | unanswerable `rewritten` |
|
|
51
|
+
* | inherited parent policy | answers `ask` (the ancestor's approver's question) | mandated_ask (write: ask) |
|
|
34
52
|
* | any layer | the judgment's budget (one dependency timeout, or a | unanswerable `timed_out` |
|
|
35
|
-
* | | batch's shared deadline) is spent |
|
|
53
|
+
* | | batch's shared deadline) is spent | |
|
|
36
54
|
*
|
|
37
55
|
* NEVER AN APPROVER. A deployment policy (the caller's, an inherited parent's) is consulted with an
|
|
38
56
|
* ALREADY-ABORTED signal and no approver seat: the `ToolPolicy.check` contract is that an aborted signal
|
|
@@ -45,32 +63,50 @@
|
|
|
45
63
|
* the notice face stays silent. The engine's own layers (narrowing, skill rows, the two lanes) get the
|
|
46
64
|
* run's real signal: they are pure matchers and store reads.
|
|
47
65
|
*
|
|
48
|
-
* THE PROBE'S IDENTITY. Every layer sees `
|
|
49
|
-
* call id `<face>-visibility-probe`
|
|
50
|
-
*
|
|
66
|
+
* THE PROBE'S IDENTITY. Every layer sees the probed tool's name (`Read` / `Write`), the probed arguments, and a stable
|
|
67
|
+
* synthetic call id (`<face>-visibility-probe` for a read, `<face>-write-probe` for a write; {@link syntheticReadProbeId},
|
|
68
|
+
* {@link syntheticWriteProbeId}), so a deployment's audit can tell a probe from a model's call. The write face stamps the
|
|
69
|
+
* facts the live gate stamps on a write (the roster's `Write` face, the tracked cwd and root, the advertised writable
|
|
70
|
+
* directories, the data root's exemption): a containment fence and a data-root exemption judge the probe as they judge the call.
|
|
51
71
|
*/
|
|
52
72
|
import type { ToolCallRequest, ToolCallFace, ToolPolicy } from "./tool-policy.js";
|
|
53
|
-
import type
|
|
73
|
+
import { type CallTargetStamp } from "./effective-path-target.js";
|
|
54
74
|
import { type SkillGrantLane } from "./skill-grant.js";
|
|
55
75
|
import type { OrgGateVerdict, PersistedRuleAnswer } from "./hooks.js";
|
|
56
76
|
/** The layers the judge consults, as named in its verdicts. */
|
|
57
77
|
export declare const SYNTHETIC_READ_LAYERS: readonly ["skill_rows", "narrowing", "caller_policy", "inherited_constraint", "persisted_rules", "org_rules"];
|
|
58
78
|
export type SyntheticReadLayer = (typeof SYNTHETIC_READ_LAYERS)[number];
|
|
59
79
|
/** Why a layer could not answer — the consumer's disclosure word. */
|
|
60
|
-
export declare const SYNTHETIC_READ_UNANSWERABLE_CAUSES: readonly ["threw", "timed_out", "approval_seat", "call_unreadable", "org_unavailable"];
|
|
80
|
+
export declare const SYNTHETIC_READ_UNANSWERABLE_CAUSES: readonly ["threw", "timed_out", "approval_seat", "call_unreadable", "org_unavailable", "rewritten", "store_unreadable"];
|
|
61
81
|
export type SyntheticReadUnanswerableCause = (typeof SYNTHETIC_READ_UNANSWERABLE_CAUSES)[number];
|
|
62
82
|
export type SyntheticReadVerdict = {
|
|
63
83
|
verdict: "deny";
|
|
64
84
|
layer: SyntheticReadLayer;
|
|
65
85
|
message?: string;
|
|
86
|
+
judged?: string;
|
|
87
|
+
} | {
|
|
88
|
+
verdict: "mandated_ask";
|
|
89
|
+
layer: SyntheticReadLayer;
|
|
90
|
+
message?: string;
|
|
91
|
+
judged?: string;
|
|
66
92
|
} | {
|
|
67
93
|
verdict: "no_objection";
|
|
94
|
+
judged?: string;
|
|
68
95
|
} | {
|
|
69
96
|
verdict: "unanswerable";
|
|
70
97
|
layer: SyntheticReadLayer;
|
|
71
98
|
cause: SyntheticReadUnanswerableCause;
|
|
72
99
|
reason: string;
|
|
73
100
|
};
|
|
101
|
+
/** The write face's answer: the read face's states with `ask` — any layer's ask, mandated or not — in place of `mandated_ask`. */
|
|
102
|
+
export type SyntheticWriteVerdict = Exclude<SyntheticReadVerdict, {
|
|
103
|
+
verdict: "mandated_ask";
|
|
104
|
+
}> | {
|
|
105
|
+
verdict: "ask";
|
|
106
|
+
layer: SyntheticReadLayer;
|
|
107
|
+
message?: string;
|
|
108
|
+
judged?: string;
|
|
109
|
+
};
|
|
74
110
|
export interface SyntheticReadLayers {
|
|
75
111
|
skillGrants?: Pick<SkillGrantLane, "denying">;
|
|
76
112
|
denyNarrowingPolicy?: ToolPolicy;
|
|
@@ -97,9 +133,27 @@ export interface SyntheticReadCall {
|
|
|
97
133
|
/** The live roster's face for `Read`, when the consumer has one. */
|
|
98
134
|
face?: ToolCallFace;
|
|
99
135
|
}
|
|
136
|
+
/** The path a synthetic `Write` probes. The request's other facts come from the judge's {@link SyntheticWriteFacts}. */
|
|
137
|
+
export interface SyntheticWriteCall {
|
|
138
|
+
file_path: string;
|
|
139
|
+
}
|
|
140
|
+
/** The facts the live gate stamps on a write, read at the moment of each judgment (the leg's cells move: the tracked cwd,
|
|
141
|
+
* the advertised-directory ledger). Every member is optional — a leg without hands has no roster face, cwd or root. */
|
|
142
|
+
export interface SyntheticWriteFacts {
|
|
143
|
+
/** The live roster's face for `Write` (a containment fence admits a write only by the tool's own declared path target). */
|
|
144
|
+
face?: ToolCallFace | undefined;
|
|
145
|
+
cwd?: string | undefined;
|
|
146
|
+
root?: string | undefined;
|
|
147
|
+
advertisedWritableDirs?: {
|
|
148
|
+
readonly roots: readonly string[];
|
|
149
|
+
} | undefined;
|
|
150
|
+
exemptRoots?: readonly string[] | undefined;
|
|
151
|
+
}
|
|
100
152
|
/** The stable synthetic call id a probe carries: `<face>-visibility-probe`, `face` a lowercase word
|
|
101
153
|
* (letters and hyphens) naming the consumer. */
|
|
102
154
|
export declare function syntheticReadProbeId(face: string): string;
|
|
155
|
+
/** The write face's stable synthetic call id: `<face>-write-probe`, `face` the same lowercase word. */
|
|
156
|
+
export declare function syntheticWriteProbeId(face: string): string;
|
|
103
157
|
/** One judgment's wait budget. `deadlineAt` (epoch ms) bounds the WHOLE judgment — every layer waits at most
|
|
104
158
|
* what is left of it, and a layer reached after it has passed is not consulted (`timed_out`). Absent ⇒ the
|
|
105
159
|
* dependency timeout from the start of this judgment. A consumer judging a batch passes one shared deadline,
|
|
@@ -108,4 +162,9 @@ export interface SyntheticReadBudget {
|
|
|
108
162
|
deadlineAt?: number;
|
|
109
163
|
}
|
|
110
164
|
export type SyntheticReadJudge = (call: SyntheticReadCall, face: string, budget?: SyntheticReadBudget) => Promise<SyntheticReadVerdict>;
|
|
165
|
+
export type SyntheticWriteJudge = (call: SyntheticWriteCall, face: string, budget?: SyntheticReadBudget) => Promise<SyntheticWriteVerdict>;
|
|
166
|
+
/** The READ face: only a mandated ask is held (a person's own ask is that person's question). */
|
|
111
167
|
export declare function createSyntheticReadJudge(layers: SyntheticReadLayers): SyntheticReadJudge;
|
|
168
|
+
/** The WRITE face: every ask is held as an objection (no approver is present), and a persisted store that cannot be read is
|
|
169
|
+
* unanswerable rather than silent. */
|
|
170
|
+
export declare function createSyntheticWriteJudge(layers: SyntheticReadLayers, facts: () => SyntheticWriteFacts): SyntheticWriteJudge;
|
|
@@ -1,20 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { callTargetReadingOf } from "./effective-path-target.js";
|
|
2
|
+
import { advertisedWritableRootsSeat, attestationOf, describeThrown, exemptRootsSeat, refuseOutOfContractDecision } from "./tool-policy.js";
|
|
3
|
+
import { ownedClearanceFacts, ruleClearanceOf } from "./ask-origin.js";
|
|
2
4
|
import { skillRowRefusal } from "./skill-grant.js";
|
|
3
5
|
import { applyPersistedTightening } from "./persisted-rule-arms.js";
|
|
4
6
|
import { readOrgRuleLane, readPersistedRuleLane } from "./rule-lane-reads.js";
|
|
5
7
|
import { ORG_ADJUDICATION_TIMEOUT_MS, settleOrgVerdictWithin } from "./permission-rule-org.js";
|
|
8
|
+
import { deepJsonEqual } from "./runner/decide-continuation.js";
|
|
6
9
|
export const SYNTHETIC_READ_LAYERS = Object.freeze(["skill_rows", "narrowing", "caller_policy", "inherited_constraint", "persisted_rules", "org_rules"]);
|
|
7
|
-
export const SYNTHETIC_READ_UNANSWERABLE_CAUSES = Object.freeze(["threw", "timed_out", "approval_seat", "call_unreadable", "org_unavailable"]);
|
|
10
|
+
export const SYNTHETIC_READ_UNANSWERABLE_CAUSES = Object.freeze(["threw", "timed_out", "approval_seat", "call_unreadable", "org_unavailable", "rewritten", "store_unreadable"]);
|
|
8
11
|
export function syntheticReadProbeId(face) {
|
|
12
|
+
return `${probeFaceWord(face)}-visibility-probe`;
|
|
13
|
+
}
|
|
14
|
+
export function syntheticWriteProbeId(face) {
|
|
15
|
+
return `${probeFaceWord(face)}-write-probe`;
|
|
16
|
+
}
|
|
17
|
+
function probeFaceWord(face) {
|
|
9
18
|
if (!/^[a-z][a-z-]*$/.test(face))
|
|
10
|
-
throw new Error(`
|
|
11
|
-
return
|
|
19
|
+
throw new Error(`synthetic probe id: the probe face must be a lowercase word (letters and hyphens), got ${JSON.stringify(face)}`);
|
|
20
|
+
return face;
|
|
12
21
|
}
|
|
13
22
|
const ABORTED = AbortSignal.abort();
|
|
23
|
+
function sameArguments(value, probed) {
|
|
24
|
+
try {
|
|
25
|
+
return deepJsonEqual(value, probed);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
14
31
|
export function createSyntheticReadJudge(layers) {
|
|
32
|
+
const judge = createProbeJudge(layers, "read");
|
|
15
33
|
return async (call, face, budget) => {
|
|
16
|
-
const deadlineAt = budget?.deadlineAt ?? Date.now() + ORG_ADJUDICATION_TIMEOUT_MS;
|
|
17
|
-
const left = () => Math.max(0, Math.min(ORG_ADJUDICATION_TIMEOUT_MS, deadlineAt - Date.now()));
|
|
18
34
|
const probe = {
|
|
19
35
|
toolName: "Read",
|
|
20
36
|
args: { file_path: call.file_path, ...(call.offset !== undefined ? { offset: call.offset } : {}), ...(call.limit !== undefined ? { limit: call.limit } : {}) },
|
|
@@ -22,32 +38,76 @@ export function createSyntheticReadJudge(layers) {
|
|
|
22
38
|
...(call.cwd !== undefined ? { cwd: call.cwd } : {}),
|
|
23
39
|
...(call.face !== undefined ? { face: call.face } : {}),
|
|
24
40
|
};
|
|
41
|
+
const v = await judge(probe, budget);
|
|
42
|
+
return v.verdict === "ask" ? { ...v, verdict: "mandated_ask" } : v;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function createSyntheticWriteJudge(layers, facts) {
|
|
46
|
+
const judge = createProbeJudge(layers, "write");
|
|
47
|
+
return async (call, face, budget) => {
|
|
48
|
+
const f = facts();
|
|
49
|
+
const probe = {
|
|
50
|
+
toolName: "Write",
|
|
51
|
+
args: { file_path: call.file_path, content: "" },
|
|
52
|
+
toolCallId: syntheticWriteProbeId(face),
|
|
53
|
+
...(f.cwd !== undefined ? { cwd: f.cwd } : {}),
|
|
54
|
+
...(f.root !== undefined ? { root: f.root } : {}),
|
|
55
|
+
...(f.face !== undefined ? { face: f.face } : {}),
|
|
56
|
+
...advertisedWritableRootsSeat(f.advertisedWritableDirs),
|
|
57
|
+
...(f.exemptRoots !== undefined ? exemptRootsSeat(f.exemptRoots) : {}),
|
|
58
|
+
};
|
|
59
|
+
return judge(probe, budget);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function createProbeJudge(layers, kind) {
|
|
63
|
+
const everyAskHeld = kind === "write";
|
|
64
|
+
return async (probe, budget) => {
|
|
65
|
+
const deadlineAt = budget?.deadlineAt ?? Date.now() + ORG_ADJUDICATION_TIMEOUT_MS;
|
|
66
|
+
const left = () => Math.max(0, Math.min(ORG_ADJUDICATION_TIMEOUT_MS, deadlineAt - Date.now()));
|
|
67
|
+
const probedArgs = structuredClone(probe.args);
|
|
25
68
|
const stamped = layers.stampCallTarget?.(probe, layers.signal);
|
|
26
69
|
const req = stamped !== undefined ? await stamped : probe;
|
|
70
|
+
const view = callTargetReadingOf(req)?.protective;
|
|
71
|
+
const judged = view?.kind === "path" && view.canonical !== undefined ? { judged: view.canonical } : {};
|
|
27
72
|
let unanswered;
|
|
73
|
+
let held;
|
|
28
74
|
const cannot = (layer, cause, reason) => {
|
|
29
75
|
unanswered ??= { verdict: "unanswerable", layer, cause, reason };
|
|
30
76
|
};
|
|
77
|
+
const ask = (layer, message, mandated) => {
|
|
78
|
+
if (everyAskHeld || mandated)
|
|
79
|
+
held ??= { verdict: "ask", layer, ...(message !== undefined ? { message } : {}), ...judged };
|
|
80
|
+
};
|
|
31
81
|
const spent = (layer) => {
|
|
32
82
|
if (left() > 0)
|
|
33
83
|
return false;
|
|
34
84
|
cannot(layer, "timed_out", "the judgment's wait budget was spent before this layer was asked");
|
|
35
85
|
return true;
|
|
36
86
|
};
|
|
37
|
-
const policyLayer = async (layer, policy, signal) => {
|
|
87
|
+
const policyLayer = async (layer, policy, signal, ancestorOwned = false) => {
|
|
38
88
|
if (spent(layer))
|
|
39
89
|
return undefined;
|
|
40
90
|
const waitMs = left();
|
|
41
91
|
let timedOut = false;
|
|
92
|
+
let thrown = undefined;
|
|
42
93
|
const TIMEOUT = Symbol("timeout");
|
|
43
94
|
let d;
|
|
44
95
|
try {
|
|
45
|
-
d = await settleOrgVerdictWithin(Promise.resolve()
|
|
96
|
+
d = await settleOrgVerdictWithin(Promise.resolve()
|
|
97
|
+
.then(() => policy.check(req, signal))
|
|
98
|
+
.catch((err) => {
|
|
99
|
+
thrown = { err };
|
|
100
|
+
return TIMEOUT;
|
|
101
|
+
}), TIMEOUT, { ...(layers.signal !== undefined ? { signal: layers.signal } : {}), timeoutMs: waitMs, onFallback: () => void (timedOut = true) });
|
|
46
102
|
}
|
|
47
103
|
catch (err) {
|
|
48
104
|
cannot(layer, "threw", describeThrown(err));
|
|
49
105
|
return undefined;
|
|
50
106
|
}
|
|
107
|
+
if (thrown !== undefined) {
|
|
108
|
+
cannot(layer, "threw", describeThrown(thrown.err));
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
51
111
|
if (d === TIMEOUT || timedOut) {
|
|
52
112
|
cannot(layer, "timed_out", `no answer within ${waitMs}ms (or the run ended first)`);
|
|
53
113
|
return undefined;
|
|
@@ -60,18 +120,25 @@ export function createSyntheticReadJudge(layers) {
|
|
|
60
120
|
cannot(layer, "threw", describeThrown(err));
|
|
61
121
|
return undefined;
|
|
62
122
|
}
|
|
123
|
+
if (read.action !== "deny" && ((read.updatedInput !== undefined && !sameArguments(read.updatedInput, probedArgs)) || !sameArguments(req.args, probedArgs))) {
|
|
124
|
+
cannot(layer, "rewritten", "the layer would rewrite the call's arguments, so its answer is not about this call");
|
|
125
|
+
}
|
|
126
|
+
if (read.action === "ask") {
|
|
127
|
+
ask(layer, read.message, ancestorOwned || ruleClearanceOf(ownedClearanceFacts(read), {}).mandated);
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
63
130
|
if (read.action !== "deny")
|
|
64
131
|
return undefined;
|
|
65
132
|
if (attestationOf(read, req)?.settlement !== undefined) {
|
|
66
|
-
cannot(layer, "approval_seat", "the policy would ask an approver, who is never consulted for a
|
|
133
|
+
cannot(layer, "approval_seat", "the policy would ask an approver, who is never consulted for a synthetic probe");
|
|
67
134
|
return undefined;
|
|
68
135
|
}
|
|
69
|
-
return { verdict: "deny", layer, ...(read.message !== undefined ? { message: read.message } : {}) };
|
|
136
|
+
return { verdict: "deny", layer, ...(read.message !== undefined ? { message: read.message } : {}), ...judged };
|
|
70
137
|
};
|
|
71
138
|
try {
|
|
72
139
|
const refusal = skillRowRefusal(layers.skillGrants, req);
|
|
73
140
|
if (refusal !== undefined)
|
|
74
|
-
return { verdict: "deny", layer: "skill_rows", ...(refusal.message !== undefined ? { message: refusal.message } : {}) };
|
|
141
|
+
return { verdict: "deny", layer: "skill_rows", ...(refusal.message !== undefined ? { message: refusal.message } : {}), ...judged };
|
|
75
142
|
}
|
|
76
143
|
catch (err) {
|
|
77
144
|
cannot("skill_rows", "threw", describeThrown(err));
|
|
@@ -82,14 +149,14 @@ export function createSyntheticReadJudge(layers) {
|
|
|
82
149
|
return v;
|
|
83
150
|
}
|
|
84
151
|
if (layers.callerPolicy !== undefined) {
|
|
85
|
-
const v = await policyLayer("caller_policy", layers.callerPolicy, ABORTED);
|
|
152
|
+
const v = await policyLayer("caller_policy", layers.callerPolicy, ABORTED, (layers.inheritedPolicies ?? []).includes(layers.callerPolicy));
|
|
86
153
|
if (v !== undefined)
|
|
87
154
|
return v;
|
|
88
155
|
}
|
|
89
156
|
for (const parent of layers.inheritedPolicies ?? []) {
|
|
90
157
|
if (parent === layers.callerPolicy)
|
|
91
158
|
continue;
|
|
92
|
-
const v = await policyLayer("inherited_constraint", parent, ABORTED);
|
|
159
|
+
const v = await policyLayer("inherited_constraint", parent, ABORTED, true);
|
|
93
160
|
if (v !== undefined)
|
|
94
161
|
return v;
|
|
95
162
|
}
|
|
@@ -97,17 +164,21 @@ export function createSyntheticReadJudge(layers) {
|
|
|
97
164
|
const read = await readPersistedRuleLane(layers.persistedRuleLane, req, layers.signal, left());
|
|
98
165
|
const tightened = applyPersistedTightening({ action: "allow" }, read).tightened;
|
|
99
166
|
if (tightened === "deny")
|
|
100
|
-
return { verdict: "deny", layer: "persisted_rules" };
|
|
101
|
-
if (tightened === "
|
|
102
|
-
|
|
167
|
+
return { verdict: "deny", layer: "persisted_rules", ...judged };
|
|
168
|
+
if (tightened === "ask")
|
|
169
|
+
ask("persisted_rules", undefined, false);
|
|
170
|
+
if (read.unreadable === true)
|
|
171
|
+
cannot("persisted_rules", read.reason !== undefined ? "call_unreadable" : "store_unreadable", read.reason ?? "a partition of the person's rule store could not be read, so whether a standing rule forbids this call is unknown");
|
|
103
172
|
}
|
|
104
173
|
if (layers.orgRuleLane !== undefined && !spent("org_rules")) {
|
|
105
174
|
const org = await readOrgRuleLane(layers.orgRuleLane, req, layers.signal, left());
|
|
106
175
|
if (org.status === "unavailable")
|
|
107
176
|
cannot("org_rules", left() > 0 ? "org_unavailable" : "timed_out", org.disclosures.join("; ") || "organization governance could not be read");
|
|
108
177
|
else if (org.verdict?.behavior === "deny")
|
|
109
|
-
return { verdict: "deny", layer: "org_rules", message: `an organization policy rule (${org.verdict.rule}) denies it
|
|
178
|
+
return { verdict: "deny", layer: "org_rules", message: `an organization policy rule (${org.verdict.rule}) denies it`, ...judged };
|
|
179
|
+
else if (org.verdict?.behavior === "ask")
|
|
180
|
+
ask("org_rules", `an organization policy rule (${org.verdict.rule}) requires approval for it`, true);
|
|
110
181
|
}
|
|
111
|
-
return unanswered ?? { verdict: "no_objection" };
|
|
182
|
+
return held ?? unanswered ?? { verdict: "no_objection", ...judged };
|
|
112
183
|
};
|
|
113
184
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* nothing else. Layer 0 vocabulary; `types.ts` re-exports every name below, so no consumer's import
|
|
7
7
|
* changes.
|
|
8
8
|
*/
|
|
9
|
-
import type { ActorAssertion } from "../internal/llm.js";
|
|
9
|
+
import type { ActorAssertion, UserAttachmentKind, UserAttachmentOutcome } from "../internal/llm.js";
|
|
10
10
|
import type { TaskResult } from "./task-result.js";
|
|
11
11
|
import type { TaskNotificationPayload } from "./task-notification.js";
|
|
12
12
|
import type { ProviderCondition, ProviderConditionSource } from "../brain/provider-dialect.js";
|
|
@@ -383,17 +383,28 @@ export type TaskEvent = ({
|
|
|
383
383
|
* (previously dropped here); the internal `details` payload is NOT exposed (e.g. a sub-agent tool's
|
|
384
384
|
* `details` is the entire child `TaskResult`).
|
|
385
385
|
*
|
|
386
|
-
* **Type is `unknown` and intentionally non-uniform** —
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
386
|
+
* **Type is `unknown` and intentionally non-uniform** — a `string` result stays a string (over the core text cap it is
|
|
387
|
+
* cut and ends with the `…[+N chars truncated]` marker); a `(TextContent|ImageContent|DocumentContent)[]` result stays
|
|
388
|
+
* an array whose blocks keep their slots. One rule for the array (see {@link truncated}): its text blocks (each at its
|
|
389
|
+
* text plus whatever else it carries) — with any block that is neither text nor an admitted image/document, at its
|
|
390
|
+
* serialization — are one character stream under the text cap, cut IN
|
|
391
|
+
* PLACE where the cap runs out (that block ends with the marker, the stream's later blocks leave); MEDIA blocks are not
|
|
392
|
+
* measured against the text cap and ride AS-IS while the leg's media budget lasts (`RunnerDeps.mediaByteCapBytes`,
|
|
393
|
+
* default `AGGREGATE_MEDIA_BUDGET_BYTES`: decoded bytes, spent as base64 on the wire — x 4/3 — after the text's
|
|
394
|
+
* serialized size), past which a block becomes `{ type, mimeType?, bytes, truncated: true }` in its slot (never a
|
|
395
|
+
* string; a media type over 255 characters is not echoed), and when not even a descriptor fits, it and every later
|
|
396
|
+
* media block fold into ONE `{ type: "omitted", blocks, bytes, truncated: true }` in its slot, so the media in an event
|
|
397
|
+
* is bounded at any block count. A consumer must handle both shapes.
|
|
390
398
|
*
|
|
391
399
|
* **UNTRUSTED RAW + for OBSERVABILITY ONLY**: it is external tool output (file reads, shell stderr, API
|
|
392
400
|
* bodies) and may carry secrets/paths/PII — core bounds only its SIZE; a consumer MUST redact and MAY
|
|
393
401
|
* further bound before it persists or displays it (same contract as `tool_start.args`). It is for a live
|
|
394
402
|
* UI / trace, NOT for re-feeding to a model: a truncated body would mislead an LLM, and the model already
|
|
395
403
|
* has the full, untruncated result in its own context — build model prompts from the session, never by
|
|
396
|
-
* replaying this field.
|
|
404
|
+
* replaying this field. The engine's two model-facing readers of it, the observer digest and the sub-agent step
|
|
405
|
+
* outcome, render a block through the one block rendering (text as text, any other block as its `[type: mimeType]`
|
|
406
|
+
* marker, never its payload).
|
|
407
|
+
* Absent when the tool produced no content body.
|
|
397
408
|
*/
|
|
398
409
|
output?: unknown;
|
|
399
410
|
/**
|
|
@@ -543,29 +554,31 @@ export type TaskEvent = ({
|
|
|
543
554
|
*/
|
|
544
555
|
notExecuted?: true;
|
|
545
556
|
/**
|
|
546
|
-
* design/99 §E1 — `true` when {@link output}
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
*
|
|
557
|
+
* design/99 §E1 — `true` exactly when {@link output} is not the content the tool returned: text was cut at the cap
|
|
558
|
+
* (the string, or the block at the cut, carries the marker; the stream's later blocks left), a media block was
|
|
559
|
+
* replaced by its descriptor, media folded into the summary, or content that could not be read was replaced by the
|
|
560
|
+
* one `[unserializable tool output]` string. Lets a consumer detect truncation programmatically
|
|
561
|
+
* instead of parsing the marker out of the text; a media block carries `truncated: true` on its own descriptor.
|
|
562
|
+
* Absent/false = `output` IS the returned content, every block in its slot. (The full untruncated result still
|
|
563
|
+
* lives in the model's context / `ToolResultStore`.)
|
|
550
564
|
*/
|
|
551
565
|
truncated?: boolean;
|
|
552
566
|
/**
|
|
553
|
-
* Present iff {@link truncated}: the TRUE original size (characters) of
|
|
554
|
-
* the
|
|
567
|
+
* Present iff {@link truncated} and the content could be read: the TRUE original size (characters) of
|
|
568
|
+
* the value this event's {@link output} was bounded from — for a string result, the string's own
|
|
555
569
|
* length; for a non-string result (a blocks array), the SUM of each block's own true size (a text
|
|
556
|
-
* block's `.text.length
|
|
557
|
-
*
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
570
|
+
* block's `.text.length` plus whatever else it carries at its serialized length; an image/document block's
|
|
571
|
+
* `.data.length`, i.e. its base64 payload, whether the
|
|
572
|
+
* block rode as-is or was replaced by its descriptor; any other block, outside the tool contract, its serialized
|
|
573
|
+
* length) — never `JSON.stringify(content).length`, which is
|
|
574
|
+
* polluted by two different kinds of noise: structural overhead (field-name/punctuation bytes that aren't
|
|
575
|
+
* content at all) AND, for text containing quotes/backslashes/control characters, escape inflation (a
|
|
576
|
+
* real result: 17298 reported vs 13696 true content characters).
|
|
562
577
|
*
|
|
563
|
-
* **Not
|
|
564
|
-
*
|
|
565
|
-
*
|
|
566
|
-
* `
|
|
567
|
-
* ahead of the cap plus the marker's `N` — and that gap is itself the honest signal that non-text bulk
|
|
568
|
-
* was dropped, not an inconsistency to paper over.
|
|
578
|
+
* **Not arithmetically tied to the marker's own `N`**: the marker counts how much MORE of the shown text
|
|
579
|
+
* exists beyond the cut point; `totalChars` counts the whole ORIGINAL value, media blocks included, so it
|
|
580
|
+
* can run far ahead of the cap plus `N`. Read what was cut from the marker (text) and from the
|
|
581
|
+
* descriptors' own `bytes` (media), not from the gap.
|
|
569
582
|
*/
|
|
570
583
|
totalChars?: number;
|
|
571
584
|
} & TaskEventIdentity) | ({
|
|
@@ -869,6 +882,10 @@ export type TaskEvent = ({
|
|
|
869
882
|
* serving layer replaying the ledger); the core's live emissions precede the commit and
|
|
870
883
|
* omit it — consumers there join via `message_committed` + stream order. */
|
|
871
884
|
entryId?: string;
|
|
885
|
+
/** Present only on the `"applied"` frame of an objective that declared attachments: per attachment, in order, what
|
|
886
|
+
* the engine read for it and will send (`outcome`) — an admission result, not a delivery. Each attachment is
|
|
887
|
+
* delivered as its own message after the objective; its `message_committed` is the delivery. */
|
|
888
|
+
attachments?: readonly HumanInputAttachment[];
|
|
872
889
|
} & TaskEventIdentity) | ({
|
|
873
890
|
/**
|
|
874
891
|
* design/99 §E3/§E10 — a live BRAIN-LAYER status the brain would otherwise absorb
|
|
@@ -897,11 +914,74 @@ export type TaskEvent = ({
|
|
|
897
914
|
* fact, not a default: a pre-373 producer event, or a parked record whose tier was not
|
|
898
915
|
* carried (the terminal sweep's re-pend) — never read absence as `"later"`. */
|
|
899
916
|
priority?: import("./task-notification.js").SystemInjectionPriority;
|
|
917
|
+
} & TaskEventIdentity) | ({
|
|
918
|
+
/**
|
|
919
|
+
* A DELEGATED CHILD STARTED: the one frame that says who the child is, at the beat it starts —
|
|
920
|
+
* before its first model request, so a consumer opens the child's row (a foreground child's panel
|
|
921
|
+
* line, a background child's fleet entry) without waiting a model turn for the first
|
|
922
|
+
* `task_progress`. Mirrors CC's `system/task_started`.
|
|
923
|
+
*
|
|
924
|
+
* ONE MINT PER CYCLE, at the child leg's spawn station, for every delegation lane alike
|
|
925
|
+
* (foreground, background, fork, workflow agent, named teammate): a first spawn mints it, and so
|
|
926
|
+
* does every revival of a completed child (a new cycle — `seq` moves on, the `taskId`/`sessionId`
|
|
927
|
+
* of the revived run say which). A durable RESUME of a parked leg is the same cycle continuing and
|
|
928
|
+
* mints NOTHING; a child whose prepare throws never reaches the station and mints nothing either
|
|
929
|
+
* (its terminal still arrives on the lane that spawned it). Ordered strictly after the leg's
|
|
930
|
+
* `wiring_manifest` and strictly before anything its first turn produces, so on the forward
|
|
931
|
+
* channel it is the child's first frame. Rides the same door as `task_progress` (the child's own
|
|
932
|
+
* stream, and the forward channel to the deployment); the process-level delegation-lifecycle
|
|
933
|
+
* `spawn` frame is a projection of this mint, delivered at the same instant under the same gate.
|
|
934
|
+
*
|
|
935
|
+
* Live and ephemeral like `task_progress`: never persisted, never replayed on resume, absent from
|
|
936
|
+
* checkpoints. Every member below is a value the run already speaks elsewhere — this frame
|
|
937
|
+
* introduces no new reading of any of them.
|
|
938
|
+
*
|
|
939
|
+
* CC mapping: `task_id` → {@link taskId} (run domain; a background child's registry handle rides
|
|
940
|
+
* {@link TaskEventIdentity.bgAgentId}), `tool_use_id` → `parentToolCallId`, `description` →
|
|
941
|
+
* {@link name}, `subagent_type` → {@link agentType}, `task_type`/`is_backgrounded` → {@link taskType}
|
|
942
|
+
* (absent = a foreground child), `run_id` → {@link seq} (the cycle), `parent_task_id` →
|
|
943
|
+
* {@link parentTaskId}, `session_id` → {@link sessionId}. Not carried: `prompt` (a delegated
|
|
944
|
+
* objective can hold a secret — the same reason {@link name} never falls back to it), `awaited`,
|
|
945
|
+
* `spawn_depth` (the nesting is the `parentTaskId` chain), and the shell/monitor-only members.
|
|
946
|
+
*/
|
|
947
|
+
type: "task_started";
|
|
948
|
+
/** The child RUN's id (`spec.taskId ?? sessionId`) — the key its `task_progress` frames carry. */
|
|
949
|
+
taskId: string;
|
|
950
|
+
/** The child run's session id — the handle its transcript is read by. */
|
|
951
|
+
sessionId: string;
|
|
952
|
+
/** The fleet-task kind, exactly as the child's `task_progress.taskType`: ABSENT for a foreground
|
|
953
|
+
* (synchronous) child, which has no fleet row. */
|
|
954
|
+
taskType?: DelegationTaskType;
|
|
955
|
+
/** The registry row's cycle generation, exactly as `task_progress.seq` (absent where the child has no
|
|
956
|
+
* `a*` row). A revived cycle's frame carries the bumped value. */
|
|
957
|
+
seq?: number;
|
|
958
|
+
/** Workflow-lane self-identification, as on `task_progress` (stamped where the workflow forwards). */
|
|
959
|
+
workflowRunId?: string;
|
|
960
|
+
/** The workflow agent's label, riding with {@link workflowRunId}. */
|
|
961
|
+
workflowAgentLabel?: string;
|
|
962
|
+
/** The spawning run's task id, exactly as `task_progress.parentTaskId`. */
|
|
963
|
+
parentTaskId?: string;
|
|
964
|
+
/** The spawning run's session id (the always-on parent linkage). */
|
|
965
|
+
parentSessionId?: string;
|
|
966
|
+
/** The delegation tree's root host session. */
|
|
967
|
+
rootSessionId?: string;
|
|
968
|
+
/** The child's display label, the same bounded single-line value its `task_progress.name` and its
|
|
969
|
+
* hook identity's `agentName` carry. UNTRUSTED display text. */
|
|
970
|
+
name?: string;
|
|
971
|
+
/** The explicit `Agent({name})` spawn identity (the teammate key), when the spawn carried one. */
|
|
972
|
+
spawnName?: string;
|
|
973
|
+
/** The resolved subagent type the child was spawned as (absent on a workflow-lane agent). */
|
|
974
|
+
agentType?: string;
|
|
975
|
+
/** The model the child leg was PREPARED with — the value its `task_progress.model` and
|
|
976
|
+
* `TaskResult.model` carry. */
|
|
977
|
+
model: string;
|
|
978
|
+
/** Epoch ms at the mint. */
|
|
979
|
+
startedAt: number;
|
|
900
980
|
} & TaskEventIdentity) | ({
|
|
901
981
|
/**
|
|
902
982
|
* design/99 MF-10 — a SUBAGENT PROGRESS TICK: the delegated sub-run's usage ACCRUING
|
|
903
983
|
* over time, emitted at each turn boundary WHILE it runs, so a parent UI animates the Task card's rollup
|
|
904
|
-
* (tokens/tool-uses climbing). Emitted ONLY for a
|
|
984
|
+
* (tokens/tool-uses climbing). Emitted ONLY for a delegated leg (the same eligibility as `task_started`); a top-level
|
|
905
985
|
* run's own usage is the consumer's own. `status:"running"` mid-run; the terminal usage is the `done` result.
|
|
906
986
|
* Live/ephemeral — NOT persisted (a resumable trace re-derives usage from the result), like `status`.
|
|
907
987
|
* Maps to CC `SDKTaskProgress` (camelCase here → snake_case `task_id`/`total_tokens`/`tool_uses`/`duration_ms`
|
|
@@ -1075,6 +1155,14 @@ export type TaskEvent = ({
|
|
|
1075
1155
|
export type HumanInputEvent = Extract<TaskEvent, {
|
|
1076
1156
|
type: "human_input";
|
|
1077
1157
|
}>;
|
|
1158
|
+
/** One attachment on a `human_input` frame: its position, the kind the engine found (absent when the read was refused
|
|
1159
|
+
* before the kind was known), what reached the model of its contents, and the path or name it was declared with. */
|
|
1160
|
+
export interface HumanInputAttachment {
|
|
1161
|
+
readonly ordinal: number;
|
|
1162
|
+
readonly kind?: UserAttachmentKind;
|
|
1163
|
+
readonly outcome: UserAttachmentOutcome;
|
|
1164
|
+
readonly label: string;
|
|
1165
|
+
}
|
|
1078
1166
|
/**
|
|
1079
1167
|
* Outcome of a manual {@link TaskStream.compact} request (the enum names align with
|
|
1080
1168
|
* the diagnosis decision tree's drain exits, so a consumer can tell "202 processed" states apart):
|