@sema-agent/core 7.42.1 → 7.44.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 +173 -0
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +3 -1
- package/dist/agents/subagent.js +5 -3
- package/dist/agents/teacher.js +1 -1
- package/dist/agents/verify.js +2 -0
- package/dist/brain/errors.d.ts +23 -2
- package/dist/brain/errors.js +26 -2
- package/dist/brain/stream-engine.d.ts +2 -1
- package/dist/brain/stream-engine.js +10 -9
- package/dist/core/allow-entry.d.ts +50 -31
- package/dist/core/allow-entry.js +100 -37
- package/dist/core/ask-origin.d.ts +16 -5
- package/dist/core/ask-origin.js +11 -3
- package/dist/core/ask-unresolvable-notice.d.ts +3 -2
- package/dist/core/ask-unresolvable-notice.js +1 -1
- package/dist/core/auto-mode.d.ts +5 -4
- package/dist/core/auto-mode.js +3 -1
- package/dist/core/checkpoint-store.d.ts +44 -20
- package/dist/core/checkpoint-store.js +10 -16
- package/dist/core/child-clamp-seats.d.ts +15 -1
- package/dist/core/child-clamp-seats.js +6 -2
- package/dist/core/classifier-unavailable-arm.d.ts +42 -23
- package/dist/core/classifier-unavailable-arm.js +14 -3
- package/dist/core/decision-receipt.js +2 -0
- package/dist/core/engine-notice.d.ts +16 -14
- package/dist/core/engine-notice.js +4 -3
- package/dist/core/gate-lanes.js +37 -29
- package/dist/core/governance-codes.d.ts +4 -3
- package/dist/core/governance-codes.js +1 -0
- package/dist/core/hooks.d.ts +6 -1
- package/dist/core/hooks.js +3 -0
- package/dist/core/memory-engine/engine.d.ts +2 -2
- package/dist/core/memory-engine/engine.js +7 -14
- package/dist/core/memory-engine/file-backend.d.ts +2 -2
- package/dist/core/memory-engine/file-backend.js +5 -7
- package/dist/core/memory-engine/layout.d.ts +37 -4
- package/dist/core/memory-engine/layout.js +34 -5
- package/dist/core/mode-policy.d.ts +5 -4
- package/dist/core/mode-policy.js +4 -0
- package/dist/core/permission-rule-consent.d.ts +3 -2
- package/dist/core/permission-rule-consent.js +25 -20
- package/dist/core/permission-rule-model.d.ts +47 -7
- package/dist/core/permission-rule-model.js +80 -6
- package/dist/core/permission-rules.d.ts +8 -2
- package/dist/core/permission-rules.js +26 -9
- package/dist/core/runner/classifier-ladder.d.ts +10 -8
- package/dist/core/runner/classifier-ladder.js +3 -1
- package/dist/core/runner/contracts.d.ts +1 -16
- package/dist/core/runner/denial-limit-arms.d.ts +25 -8
- package/dist/core/runner/denial-limit-arms.js +24 -10
- package/dist/core/runner/gate-exit.js +2 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +2 -2
- package/dist/core/runner/permission-rule-lanes.js +8 -2
- package/dist/core/runner/prepare-ask-lane.d.ts +3 -0
- package/dist/core/runner/prepare-ask-lane.js +2 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -3
- package/dist/core/runner/prepare-caps-and-workflow.js +3 -2
- package/dist/core/runner/prepare-config-doors.d.ts +3 -0
- package/dist/core/runner/prepare-config-doors.js +6 -5
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-inherited-gate.d.ts +7 -5
- package/dist/core/runner/prepare-inherited-gate.js +5 -3
- package/dist/core/runner/prepare-park-ask.d.ts +4 -4
- package/dist/core/runner/prepare-park-ask.js +2 -4
- package/dist/core/runner/prepare-policy-chain.d.ts +7 -1
- package/dist/core/runner/prepare-policy-chain.js +59 -38
- package/dist/core/runner/prepare-project-context.js +6 -2
- package/dist/core/runner/prepare-task.js +6 -6
- package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -5
- package/dist/core/runner/prepare-wiring-manifest.js +8 -9
- package/dist/core/runner/resume-checkpoint-screen.js +1 -1
- package/dist/core/runner/runtask.js +1 -5
- package/dist/core/runner/steer-admission.d.ts +10 -11
- package/dist/core/runner/steer-admission.js +6 -8
- package/dist/core/runner/stream-steer-verb.d.ts +5 -4
- package/dist/core/runner/stream-steer-verb.js +6 -8
- package/dist/core/runner/synthetic-tools.d.ts +3 -2
- package/dist/core/runner/synthetic-tools.js +1 -1
- package/dist/core/session-policy-store.d.ts +3 -3
- package/dist/core/session-policy-store.js +18 -4
- package/dist/core/skill-grant-binding.d.ts +44 -0
- package/dist/core/skill-grant-binding.js +22 -0
- package/dist/core/skill-grant.d.ts +59 -28
- package/dist/core/skill-grant.js +96 -23
- package/dist/core/steer-replay.d.ts +46 -0
- package/dist/core/steer-replay.js +22 -0
- package/dist/core/task-event.d.ts +3 -2
- package/dist/core/task-spec.d.ts +14 -1
- package/dist/core/task-stream.d.ts +12 -6
- package/dist/core/tighten-task-spec.d.ts +2 -0
- package/dist/core/tighten-task-spec.js +3 -0
- package/dist/core/tool-errors.d.ts +2 -2
- package/dist/core/tool-name-aliases.d.ts +35 -5
- package/dist/core/tool-name-aliases.js +29 -4
- package/dist/core/tool-policy.d.ts +10 -0
- package/dist/core/tool-policy.js +9 -2
- package/dist/core/tool-spec.d.ts +7 -0
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.d.ts +56 -14
- package/dist/core/wiring-manifest.js +29 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +5 -4
- package/dist/orchestration/run-workflow-tool.d.ts +4 -0
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/orchestration/workflow-primitives.d.ts +3 -1
- package/dist/orchestration/workflow-primitives.js +6 -6
- package/dist/orchestration/workflow-types.d.ts +5 -0
- package/dist/orchestration/workflow.d.ts +3 -2
- package/dist/orchestration/workflow.js +14 -6
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +21 -5
- package/wire-changes/core@7.43.0.json +985 -0
- package/wire-changes/core@7.44.0.json +469 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { GateOutcome } from "./gate-outcome.js";
|
|
2
2
|
import { type ExecutionOutcomeRecordWord } from "./checkpoint-execution-record.js";
|
|
3
3
|
import type { CheckpointPause } from "./pause-registry.js";
|
|
4
|
-
import {
|
|
4
|
+
import type { SystemInjectionPriority } from "./task-notification.js";
|
|
5
|
+
import { type SteerReplayFacet } from "./steer-replay.js";
|
|
5
6
|
import { type QuestionAnswer } from "./ask-question.js";
|
|
6
7
|
import type { ReadEntry } from "../tools/fs/safety.js";
|
|
7
8
|
import type { RepairBundle } from "../agents/repair-loop.js";
|
|
@@ -816,6 +817,8 @@ export type PendingAction = {
|
|
|
816
817
|
* its settlement never reads as the fallback's auto-deny (see `tool_end.autoDenied`). A durable
|
|
817
818
|
* card renders the counts and the limit; it has no countdown to render. Echo-only. */
|
|
818
819
|
denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
820
|
+
/** (additive) The PARK twin of `AskRequest.classifierNoRuling`, written only as a member of its closed set. Display metadata. */
|
|
821
|
+
classifierNoRuling?: import("./auto-mode.js").AutoModeFallbackCause;
|
|
819
822
|
/** (additive, same precedent) — the PARK twin of `AskRequest.origin`: WHICH AUTHORITY raised the
|
|
820
823
|
* parked ask, one word from the closed {@link import("./ask-origin.js").AskOrigin} set, the same
|
|
821
824
|
* word the synchronous card is rendered by (engine-stamped at the gate, never a policy's claim).
|
|
@@ -843,6 +846,8 @@ export type PendingAction = {
|
|
|
843
846
|
boundInputHash: string;
|
|
844
847
|
/** A parked `Skill` call's binding to its card — the skill's name and its two lists ({@link import("./skill-grant.js").skillGrantDigest}, judged by {@link import("./skill-grant.js").parkedSkillApprovalRefusal}); only that row carries it. */
|
|
845
848
|
boundGrantDigest?: string;
|
|
849
|
+
/** The value {@link boundGrantDigest} hashes — the card's twin of `AskRequest.boundGrants`, minted by the same factory; written exactly when the digest is. Display metadata (additive, no version bump): the resume judges the digest alone. */
|
|
850
|
+
boundGrants?: import("./skill-grant-binding.js").BoundSkillGrants;
|
|
846
851
|
/**
|
|
847
852
|
* design/388 §2.5 boundary ② (B18; additive — no checkpoint-version bump, the `previewWithheld`
|
|
848
853
|
* precedent: an optional group an older reader ignores): the IDENTITY of the row the park minted
|
|
@@ -1955,6 +1960,12 @@ export interface CheckpointSummary {
|
|
|
1955
1960
|
* the row to learn them (`autoDenyAfterMs` is `0` on every row — see the row member). Pure echo;
|
|
1956
1961
|
* OMITTED when the row carries none. */
|
|
1957
1962
|
denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
1963
|
+
/** The row's `PendingAction.tool_approval.classifierNoRuling`, echoed ONLY as a member of its closed set (else omitted). */
|
|
1964
|
+
classifierNoRuling?: import("./auto-mode.js").AutoModeFallbackCause;
|
|
1965
|
+
/** The row's `boundGrantDigest`, echoed (a string; else omitted): the hash of {@link boundGrants}. ECHO-ONLY. */
|
|
1966
|
+
boundGrantDigest?: string;
|
|
1967
|
+
/** The row's `boundGrants` — a fresh copy of the skill and its two lists, published only when it hashes to {@link boundGrantDigest} (never a list beside a digest that binds another). Deployment text: render with contextual escaping. ECHO-ONLY. */
|
|
1968
|
+
boundGrants?: import("./skill-grant-binding.js").BoundSkillGrants;
|
|
1958
1969
|
/** The row's `PendingAction.tool_approval.origin`, projected for the same one-call reason: a card
|
|
1959
1970
|
* rendered by the ask's origin word must not re-open the row to learn it. Echoed ONLY when the stored
|
|
1960
1971
|
* value is a member of the closed set ({@link import("./ask-origin.js").isAskOrigin}) — a bad value
|
|
@@ -2034,14 +2045,15 @@ export type CheckpointErrorReason = "version_newer" | "env_factory_missing" | "g
|
|
|
2034
2045
|
* fabricated "open" still needs the LIVE resolution to say open too. */
|
|
2035
2046
|
| "write_face_damaged" | "skill_grants_damaged" | "constraint_chain_missing";
|
|
2036
2047
|
/**
|
|
2037
|
-
* The codes a RESUME's pre-claim configuration screen carries as `detail.code` of a `checkpoint.config_refused` — the
|
|
2038
|
-
* words prepare's mode, output-style and recorded-mode doors throw, held unchanged so one refusal
|
|
2039
|
-
* run (`TaskResult.errorCode`) and on a resume
|
|
2040
|
-
* `config.output_style_invalid`:
|
|
2041
|
-
*
|
|
2042
|
-
*
|
|
2048
|
+
* The codes a RESUME's pre-claim configuration screen carries as `detail.code` of a `checkpoint.config_refused` — the seven
|
|
2049
|
+
* words prepare's mode, output-style, approval-reach, invoked-skill and recorded-mode doors throw, held unchanged so one refusal
|
|
2050
|
+
* reads the same on a fresh run (`TaskResult.errorCode`) and on a resume. `config.spec_retired_key`: a retired spec key;
|
|
2051
|
+
* `config.output_style_invalid`: a malformed output-style seat; `config.permission_mode_invalid`: a mode word outside the closed
|
|
2052
|
+
* set (the config's `permissionMode`, or one the row recorded); `config.shell_gate_value_retired`: a retired shell-floor word;
|
|
2053
|
+
* `config.approval_reach_invalid`: an `approvalReach` outside its set; `config.user_invoked_skill_unknown` / `skill_deny_unreadable`:
|
|
2054
|
+
* `userInvokedSkills` names a skill the config does not carry / one whose `disallowed-tools` entry the grammar reads as nothing.
|
|
2043
2055
|
*/
|
|
2044
|
-
export declare const CHECKPOINT_CONFIG_REFUSAL_CODES: readonly ["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired"];
|
|
2056
|
+
export declare const CHECKPOINT_CONFIG_REFUSAL_CODES: readonly ["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired", "config.approval_reach_invalid", "config.user_invoked_skill_unknown", "config.skill_deny_unreadable"];
|
|
2045
2057
|
export type CheckpointConfigRefusalCode = (typeof CHECKPOINT_CONFIG_REFUSAL_CODES)[number];
|
|
2046
2058
|
export declare const isCheckpointConfigRefusalCode: (word: unknown) => word is CheckpointConfigRefusalCode;
|
|
2047
2059
|
/** A typed checkpoint-store error so callers branch on `code` (mirrors `SessionError`). */
|
|
@@ -2119,10 +2131,12 @@ export declare class CheckpointError extends Error {
|
|
|
2119
2131
|
* the caller delivers or removes a parked steer first. Nothing is written on this path. */
|
|
2120
2132
|
| "steering.queue_full"
|
|
2121
2133
|
/** design/171 §6.3 (falsification review adoption): the append reused an `inputId` already parked on
|
|
2122
|
-
* this checkpoint, but with DIFFERENT content. Re-appending an IDENTICAL
|
|
2123
|
-
*
|
|
2124
|
-
*
|
|
2125
|
-
*
|
|
2134
|
+
* this checkpoint, but with DIFFERENT content. Re-appending an IDENTICAL steer stays the idempotent
|
|
2135
|
+
* no-op a retry needs; a differing one is refused, because swallowing it would lose the second
|
|
2136
|
+
* operator's instruction — the exact silent loss the queue replaced. `detail.differs` names the facets
|
|
2137
|
+
* that differ: with `text` among them it is another instruction (retry it under a fresh id); without,
|
|
2138
|
+
* it is the parked one re-sent at another tier / trust / attribution (a fresh id would park it twice —
|
|
2139
|
+
* replay it with its original values). Nothing is written on this path. */
|
|
2126
2140
|
| "steering.duplicate_input_id"
|
|
2127
2141
|
/** design/373 §4.3 (D2): the deployment's `userPromptSubmit` hook refused a wake resume's
|
|
2128
2142
|
* `message` at the entrance screen (block verdict, or a fail-closed timeout/crash — the
|
|
@@ -2206,8 +2220,8 @@ export declare class CheckpointError extends Error {
|
|
|
2206
2220
|
* computable from a self-reported closure, so the edited resume is refused typed pre-CAS (the
|
|
2207
2221
|
* checkpoint stays `pending`, still decidable); an UNEDITED approval is untouched. */
|
|
2208
2222
|
| "resume.constraint_unprojectable";
|
|
2209
|
-
/** Structured discrimination a caller can branch on where the code alone is ambiguous.
|
|
2210
|
-
*
|
|
2223
|
+
/** Structured discrimination a caller can branch on where the code alone is ambiguous. `field`
|
|
2224
|
+
* names WHICH part of the decision payload was rejected on a
|
|
2211
2225
|
* `checkpoint.invalid_outcome` — `"boundCallId"` (the action you decided on has been replaced:
|
|
2212
2226
|
* re-fetch the pending list), `"boundInputHash"` (the input you reviewed has changed under the same
|
|
2213
2227
|
* action: re-review), `"boundGrantDigest"` (a parked `Skill` approval does not cover the skill the decision selects — another skill, or the card's with other lists: deny, re-activate), or `"answer"` (the content-ask answer is missing, or was attached to a
|
|
@@ -2237,6 +2251,10 @@ export declare class CheckpointError extends Error {
|
|
|
2237
2251
|
* (`code` remains the only REQUIRED discriminant), which is exactly why the obligation to SPELL it
|
|
2238
2252
|
* is a gate rather than a type: see the closed set's own doc. */
|
|
2239
2253
|
reason?: CheckpointErrorReason;
|
|
2254
|
+
/** On `steering.duplicate_input_id` — the facets the repeat differs from the parked steer in (closed
|
|
2255
|
+
* {@link SteerReplayFacet}, in that set's order; always present on that code, never empty). Absent on
|
|
2256
|
+
* every other code. The live verb's refusal carries the same value. */
|
|
2257
|
+
differs?: readonly SteerReplayFacet[];
|
|
2240
2258
|
} | undefined;
|
|
2241
2259
|
constructor(code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found"
|
|
2242
2260
|
/** `runner.resume` was handed an {@link ResumeOutcome} whose `gate` arm does not match the
|
|
@@ -2311,10 +2329,12 @@ export declare class CheckpointError extends Error {
|
|
|
2311
2329
|
* the caller delivers or removes a parked steer first. Nothing is written on this path. */
|
|
2312
2330
|
| "steering.queue_full"
|
|
2313
2331
|
/** design/171 §6.3 (falsification review adoption): the append reused an `inputId` already parked on
|
|
2314
|
-
* this checkpoint, but with DIFFERENT content. Re-appending an IDENTICAL
|
|
2315
|
-
*
|
|
2316
|
-
*
|
|
2317
|
-
*
|
|
2332
|
+
* this checkpoint, but with DIFFERENT content. Re-appending an IDENTICAL steer stays the idempotent
|
|
2333
|
+
* no-op a retry needs; a differing one is refused, because swallowing it would lose the second
|
|
2334
|
+
* operator's instruction — the exact silent loss the queue replaced. `detail.differs` names the facets
|
|
2335
|
+
* that differ: with `text` among them it is another instruction (retry it under a fresh id); without,
|
|
2336
|
+
* it is the parked one re-sent at another tier / trust / attribution (a fresh id would park it twice —
|
|
2337
|
+
* replay it with its original values). Nothing is written on this path. */
|
|
2318
2338
|
| "steering.duplicate_input_id"
|
|
2319
2339
|
/** design/373 §4.3 (D2): the deployment's `userPromptSubmit` hook refused a wake resume's
|
|
2320
2340
|
* `message` at the entrance screen (block verdict, or a fail-closed timeout/crash — the
|
|
@@ -2398,8 +2418,8 @@ export declare class CheckpointError extends Error {
|
|
|
2398
2418
|
* computable from a self-reported closure, so the edited resume is refused typed pre-CAS (the
|
|
2399
2419
|
* checkpoint stays `pending`, still decidable); an UNEDITED approval is untouched. */
|
|
2400
2420
|
| "resume.constraint_unprojectable", message: string,
|
|
2401
|
-
/** Structured discrimination a caller can branch on where the code alone is ambiguous.
|
|
2402
|
-
*
|
|
2421
|
+
/** Structured discrimination a caller can branch on where the code alone is ambiguous. `field`
|
|
2422
|
+
* names WHICH part of the decision payload was rejected on a
|
|
2403
2423
|
* `checkpoint.invalid_outcome` — `"boundCallId"` (the action you decided on has been replaced:
|
|
2404
2424
|
* re-fetch the pending list), `"boundInputHash"` (the input you reviewed has changed under the same
|
|
2405
2425
|
* action: re-review), `"boundGrantDigest"` (a parked `Skill` approval does not cover the skill the decision selects — another skill, or the card's with other lists: deny, re-activate), or `"answer"` (the content-ask answer is missing, or was attached to a
|
|
@@ -2429,6 +2449,10 @@ export declare class CheckpointError extends Error {
|
|
|
2429
2449
|
* (`code` remains the only REQUIRED discriminant), which is exactly why the obligation to SPELL it
|
|
2430
2450
|
* is a gate rather than a type: see the closed set's own doc. */
|
|
2431
2451
|
reason?: CheckpointErrorReason;
|
|
2452
|
+
/** On `steering.duplicate_input_id` — the facets the repeat differs from the parked steer in (closed
|
|
2453
|
+
* {@link SteerReplayFacet}, in that set's order; always present on that code, never empty). Absent on
|
|
2454
|
+
* every other code. The live verb's refusal carries the same value. */
|
|
2455
|
+
differs?: readonly SteerReplayFacet[];
|
|
2432
2456
|
} | undefined);
|
|
2433
2457
|
}
|
|
2434
2458
|
/**
|
|
@@ -2,12 +2,13 @@ import { randomBytes, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { uuidv7 } from "../internal/harness.js";
|
|
3
3
|
import { PROBE_CAUSE_PATH_MAX, PROBE_REASON_MAX, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
|
|
4
4
|
import { carriesBidiControls } from "./tool-policy.js";
|
|
5
|
-
import { readDenialLimitFallback } from "./auto-mode.js";
|
|
5
|
+
import { isAutoModeFallbackCause, readDenialLimitFallback } from "./auto-mode.js";
|
|
6
6
|
import { isAskOrigin, isPersistedRuleMandate } from "./ask-origin.js";
|
|
7
|
+
import { readBoundBinding } from "./skill-grant-binding.js";
|
|
7
8
|
import { checkpointExecutionRecorded, executionRecordDisposition, ExecutionOutcomeConflictError, } from "./checkpoint-execution-record.js";
|
|
8
9
|
import { renderUntrustedCommandText, stripFormatCharacters } from "./permission-rule-model.js";
|
|
9
10
|
import { redactSecrets } from "./untrusted-egress.js";
|
|
10
|
-
import {
|
|
11
|
+
import { steerReplayRefusal } from "./steer-replay.js";
|
|
11
12
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
12
13
|
import { isPermissionMode } from "./ask-origin.js";
|
|
13
14
|
import { wireOf } from "./tool-name-readings.js";
|
|
@@ -309,12 +310,14 @@ export function summarizeCheckpoint(cp) {
|
|
|
309
310
|
...(() => {
|
|
310
311
|
const origin = tool?.origin;
|
|
311
312
|
const mandate = tool?.mandate;
|
|
312
|
-
|
|
313
|
+
const why = tool?.classifierNoRuling;
|
|
314
|
+
return { ...(isAskOrigin(origin) ? { origin } : {}), ...(isPersistedRuleMandate(mandate) ? { mandate } : {}), ...(isAutoModeFallbackCause(why) ? { classifierNoRuling: why } : {}) };
|
|
313
315
|
})(),
|
|
316
|
+
...readBoundBinding(tool),
|
|
314
317
|
...(hasBidi ? { hasBidiControls: true } : {}),
|
|
315
318
|
};
|
|
316
319
|
}
|
|
317
|
-
export const CHECKPOINT_CONFIG_REFUSAL_CODES = Object.freeze(["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired"]);
|
|
320
|
+
export const CHECKPOINT_CONFIG_REFUSAL_CODES = Object.freeze(["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired", "config.approval_reach_invalid", "config.user_invoked_skill_unknown", "config.skill_deny_unreadable"]);
|
|
318
321
|
export const isCheckpointConfigRefusalCode = (word) => CHECKPOINT_CONFIG_REFUSAL_CODES.some((w) => w === word);
|
|
319
322
|
export class CheckpointError extends Error {
|
|
320
323
|
code;
|
|
@@ -545,22 +548,13 @@ export function readPendingSteerQueue(state) {
|
|
|
545
548
|
}
|
|
546
549
|
return [...(state.pendingSteerQueue ?? [])].sort((a, b) => a.seq - b.seq);
|
|
547
550
|
}
|
|
548
|
-
function samePendingSteerPayload(a, b) {
|
|
549
|
-
return (a.text === b.text &&
|
|
550
|
-
a.trusted === b.trusted &&
|
|
551
|
-
steerPriorityOf(a) === steerPriorityOf(b) &&
|
|
552
|
-
a.actor?.id === b.actor?.id &&
|
|
553
|
-
a.actor?.hostAsserted === b.actor?.hostAsserted &&
|
|
554
|
-
a.actor?.issuer === b.actor?.issuer);
|
|
555
|
-
}
|
|
556
551
|
export function appendPendingSteer(state, entry) {
|
|
557
552
|
const current = readPendingSteerQueue(state);
|
|
558
553
|
const collision = current.find((e) => e.inputId === entry.inputId);
|
|
559
554
|
if (collision !== undefined) {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
555
|
+
const refusal = steerReplayRefusal(collision, entry, { seat: "parked", inputId: entry.inputId });
|
|
556
|
+
if (refusal !== undefined)
|
|
557
|
+
throw new CheckpointError("steering.duplicate_input_id", refusal.message, refusal.detail);
|
|
564
558
|
return current;
|
|
565
559
|
}
|
|
566
560
|
const nextSeq = current.reduce((max, e) => Math.max(max, e.seq), 0) + 1;
|
|
@@ -28,7 +28,7 @@ import type { TaskSpec } from "./types.js";
|
|
|
28
28
|
* The clamp seats, as string literals (the closed set the scan and the wire-manifest reader both parse —
|
|
29
29
|
* a spread or an identifier here reads as an empty set).
|
|
30
30
|
*/
|
|
31
|
-
export declare const CHILD_CLAMP_SEATS: readonly ["handsReadOnly", "interactiveTools", "readFace", "writeFace", "readDenyPatterns"];
|
|
31
|
+
export declare const CHILD_CLAMP_SEATS: readonly ["handsReadOnly", "interactiveTools", "approvalReach", "readFace", "writeFace", "readDenyPatterns"];
|
|
32
32
|
/** One clamp seat's name. */
|
|
33
33
|
export type ChildClampSeat = (typeof CHILD_CLAMP_SEATS)[number];
|
|
34
34
|
/**
|
|
@@ -58,6 +58,8 @@ export interface ChildClampSource {
|
|
|
58
58
|
handsReadOnly: boolean | undefined;
|
|
59
59
|
/** The hard-headless clamp: only `false` travels. */
|
|
60
60
|
interactiveTools: boolean | undefined;
|
|
61
|
+
/** The declaration that nobody on the run can be asked to approve: only `"none"` travels. */
|
|
62
|
+
approvalReach: TaskSpec["approvalReach"] | undefined;
|
|
61
63
|
/** The read containment face: only `"roots"` travels. */
|
|
62
64
|
readFace: TaskSpec["readFace"] | undefined;
|
|
63
65
|
/** The write containment face: only `"roots"` travels. */
|
|
@@ -77,6 +79,18 @@ export interface ChildClampSource {
|
|
|
77
79
|
* — so a clamp can never end one level deep, and a new seat cannot reach some constructors and not others.
|
|
78
80
|
*/
|
|
79
81
|
export declare function childClampSeats(source: ChildClampSource): Partial<TaskSpec>;
|
|
82
|
+
/**
|
|
83
|
+
* The interaction posture a leg keeps under the clamps it inherited: `"interactive"` promises a person on the leg, and a
|
|
84
|
+
* leg that inherited the hard-headless clamp (`interactiveTools: false`) or the declaration that nobody can be asked
|
|
85
|
+
* (`approvalReach: "none"`) cannot keep that promise — the clamp is the tightening value, so the posture becomes
|
|
86
|
+
* `"headless"` (stated, so the leg's own resolution does not fall through to a deployment default of `"interactive"`);
|
|
87
|
+
* every other posture is returned as it is. Read where an inherited posture meets inherited clamps: the workflow lane
|
|
88
|
+
* (a baseline's posture beside the host's clamps) and the revival fold (the spawn's posture beside the waker's clamps).
|
|
89
|
+
*/
|
|
90
|
+
export declare function postureUnderClamps(posture: "interactive" | "headless" | undefined, clamps: {
|
|
91
|
+
readonly interactiveTools: boolean | undefined;
|
|
92
|
+
readonly approvalReach: TaskSpec["approvalReach"] | undefined;
|
|
93
|
+
}): "interactive" | "headless" | undefined;
|
|
80
94
|
/**
|
|
81
95
|
* The OWN posture of an engine-built leg that never asks the person (the observer sidecar, the verifier): no
|
|
82
96
|
* human-facing tool is mounted AND the leg declares itself headless. Both, always together — the spec's posture
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export const CHILD_CLAMP_SEATS = Object.freeze(["handsReadOnly", "interactiveTools", "readFace", "writeFace", "readDenyPatterns"]);
|
|
1
|
+
export const CHILD_CLAMP_SEATS = Object.freeze(["handsReadOnly", "interactiveTools", "approvalReach", "readFace", "writeFace", "readDenyPatterns"]);
|
|
2
2
|
export const CHILD_CLAMP_SEATS_NOT_CARRIED = Object.freeze({
|
|
3
3
|
toolPolicy: "not a clamp a child spec copies: a parent's denies reach its children through the ancestor-constraint chain the child's own prepare folds, not by putting the parent's policy object on the child seat",
|
|
4
4
|
shellGate: "travels on the inherited-gate lane (`InheritedGate.shellGate`, a trusted internals seat), not on the child spec — a spec-level copy would be a second source for the same clamp",
|
|
5
5
|
permissionMode: "travels on the inherited-gate lane (`InheritedGate.permissionMode` / `permissionModeCeiling` — the parent's declared word and its LIVE seat): a child with no word of its own inherits that ceiling on every read, and a word it does declare is clamped under it; a spec-level copy would freeze the parent's word at spawn and be a second source for the same clamp",
|
|
6
|
-
onAsk: "per-lane by construction: the delegation lane wraps the parent's approver with spawn provenance before it becomes the child's seat, and a lane with no approver must hand down none",
|
|
6
|
+
onAsk: "per-lane by construction: the delegation lane wraps the parent's approver with spawn provenance before it becomes the child's seat, and a lane with no approver must hand down none (a parent that declared nobody can be asked hands down no approver — its frozen seat is empty — and the declaration rides `approvalReach`)",
|
|
7
7
|
hooks: "a deployment seat, not an inherited clamp — `tightenTaskSpec` refuses to merge two hook sets rather than composing them, so there is no tightening value to carry",
|
|
8
8
|
excludeTools: "roster control rather than containment: the lanes that copy a parent's roster do so beside these seats, and the workflow lane authors its child roster from the governance baseline instead of the host's",
|
|
9
9
|
deferTools: "roster control, same reading as `excludeTools` above",
|
|
@@ -21,6 +21,7 @@ export function childClampSeats(source) {
|
|
|
21
21
|
return {
|
|
22
22
|
...(source.handsReadOnly === true ? { handsReadOnly: true } : {}),
|
|
23
23
|
...(source.interactiveTools === false ? { interactiveTools: false } : {}),
|
|
24
|
+
...(source.approvalReach === "none" ? { approvalReach: "none" } : {}),
|
|
24
25
|
...(source.readFace === "roots" ? { readFace: "roots" } : {}),
|
|
25
26
|
...(source.writeFace === "roots" ? { writeFace: "roots" } : {}),
|
|
26
27
|
...(source.readDenyPatterns !== undefined && source.readDenyPatterns.length > 0
|
|
@@ -28,6 +29,9 @@ export function childClampSeats(source) {
|
|
|
28
29
|
: {}),
|
|
29
30
|
};
|
|
30
31
|
}
|
|
32
|
+
export function postureUnderClamps(posture, clamps) {
|
|
33
|
+
return posture === "interactive" && (clamps.interactiveTools === false || clamps.approvalReach === "none") ? "headless" : posture;
|
|
34
|
+
}
|
|
31
35
|
export function engineHeadlessLegSeats() {
|
|
32
36
|
return { interactiveTools: false, interactionPosture: "headless" };
|
|
33
37
|
}
|
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The ONE exit for an auto-mode classification that
|
|
3
|
-
*
|
|
4
|
-
* in the policy fold, so the rule is written once.
|
|
2
|
+
* The ONE exit for an auto-mode classification that did not RULE ({@link rungDidNotRule}: the classifier leg failed or its
|
|
3
|
+
* budget ran out — `error` / `timeout` — or it replied with no verdict at all — `parse_error`) — shared by both classifier
|
|
4
|
+
* stations, the leg's gate and an ancestor's frozen classifier in the policy fold, so the rule is written once.
|
|
5
5
|
*
|
|
6
|
-
* ONE fork, on the APPROVAL SEAT (does an ask this station hands on reach a person — a live approver, or a durable
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* handed-on ask is a person's, not the classifier's.
|
|
6
|
+
* ONE fork, on the APPROVAL SEAT (does an ask this station hands on reach a person — a live approver, or a durable park):
|
|
7
|
+
* with one, the ask goes on carrying why (`classifierNoRuling`, its text saying it too), and a person decides — never
|
|
8
|
+
* counted (a person's refusal of it is a person's); without one, an unavailable classification is refused with CC's `x1t`
|
|
9
|
+
* sentence (its FORM, `unavailable`, attested on the deny) and is never counted, while a reply with no verdict goes back to
|
|
10
|
+
* the station's ruling path (`counted`): refused with its own sentence and counted on the denial limit, so the bound falls
|
|
11
|
+
* back to a person (CC: `Cr` excludes unavailable; failureMode is counted). The handed-on ask carries `requiresRealApproval`:
|
|
12
|
+
* no automatic lane may answer it in the classifier's stead — not a blanket `onAsk:"allow"`, not the sandbox admission, not a
|
|
13
|
+
* budget resolver over its park. Asking where a person can be asked is a declared departure (CC refuses).
|
|
15
14
|
*
|
|
16
15
|
* The observer hears every such classification with where the call went, except while the call is being aborted — a halt,
|
|
17
|
-
* or the run's end
|
|
16
|
+
* or the run's end. It is observe-only: a throw never changes the arm.
|
|
18
17
|
*/
|
|
19
|
-
import { type AutoModeClassifierRound, type
|
|
20
|
-
import { type PermissionResult, type ToolCallRequest } from "./tool-policy.js";
|
|
21
|
-
/** Where
|
|
18
|
+
import { type AutoModeClassifierRound, type AutoModeDenialTracker, type AutoModeFallbackCause, type UnarmedDenialLimitFallback } from "./auto-mode.js";
|
|
19
|
+
import { type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "./tool-policy.js";
|
|
20
|
+
/** Where a classification that did not rule sent the call: `asked` = on to the approval seat, `denied` = refused. */
|
|
22
21
|
export type ClassifierUnavailableDisposition = "asked" | "denied";
|
|
23
|
-
/** What the stations' observer receives — the facts the `classifier.unavailable` notice is composed from.
|
|
22
|
+
/** What the stations' observer receives — the facts the `classifier.unavailable` notice is composed from. `cause` is why
|
|
23
|
+
* the classification did not rule ({@link AutoModeFallbackCause}: `error` / `timeout`, or `parse_error` for a reply with no
|
|
24
|
+
* verdict). */
|
|
24
25
|
export interface ClassifierUnavailableInfo {
|
|
25
26
|
toolName: string;
|
|
26
27
|
toolCallId: string;
|
|
27
|
-
cause:
|
|
28
|
+
cause: AutoModeFallbackCause;
|
|
28
29
|
disposition: ClassifierUnavailableDisposition;
|
|
29
30
|
}
|
|
30
31
|
export type ClassifierUnavailableObserver = (info: ClassifierUnavailableInfo) => void;
|
|
@@ -34,24 +35,42 @@ export interface ClassifierUnavailableSeat {
|
|
|
34
35
|
approvalSeat?: boolean;
|
|
35
36
|
onUnavailable?: ClassifierUnavailableObserver;
|
|
36
37
|
}
|
|
37
|
-
/** The arm the fork chose: the ask to hand on, or the
|
|
38
|
-
|
|
38
|
+
/** The arm the fork chose: the ask to hand on, the refusal, or — a reply with no verdict and no seat — the station's own
|
|
39
|
+
* counted ruling path. */
|
|
40
|
+
export type ClassifierNoRulingArm<A> = {
|
|
39
41
|
kind: "asked";
|
|
40
42
|
ask: A;
|
|
41
43
|
} | {
|
|
42
44
|
kind: "denied";
|
|
43
45
|
deny: PermissionResult;
|
|
46
|
+
} | {
|
|
47
|
+
kind: "counted";
|
|
44
48
|
};
|
|
45
|
-
export declare function
|
|
49
|
+
export declare function classifierNoRulingArm<A extends Extract<PermissionResult, {
|
|
46
50
|
action: "ask";
|
|
47
51
|
}>>(args: {
|
|
48
52
|
ask: A;
|
|
49
53
|
req: ToolCallRequest;
|
|
50
|
-
cause:
|
|
54
|
+
cause: AutoModeFallbackCause;
|
|
51
55
|
/** The ladder's attribution, for the refusal's sentence (it names both rungs when the ladder moved). */
|
|
52
56
|
attribution?: AutoModeClassifierRound;
|
|
53
57
|
seat: ClassifierUnavailableSeat | undefined;
|
|
54
58
|
/** The call is being aborted (its per-call signal — a halt, or the run's end): the observer is not told (the arm is chosen
|
|
55
59
|
* as ever). */
|
|
56
60
|
aborted: boolean;
|
|
57
|
-
}):
|
|
61
|
+
}): ClassifierNoRulingArm<A>;
|
|
62
|
+
/**
|
|
63
|
+
* The no-ruling card's settlement (both stations): a card THIS station's no-ruling arm handed on for this call (`mintedHere` —
|
|
64
|
+
* a policy's own `classifierNoRuling`, or a card another station minted, is not this station's classification and counts
|
|
65
|
+
* nothing here) that the seat's deployment POLICY refused (settlement `policy_refused` — a machine settled it, not a person)
|
|
66
|
+
* counts on the denial limit like a ruling refusal, because "handed to a person" means a person settled it. Returns the fallback the count minted when it reached the
|
|
67
|
+
* bound — the station then puts the same call to the same seat as the denial-limit fallback, and a deny nobody judged there
|
|
68
|
+
* stops the run through the existing headless arm. A person's answer (allow or refusal), a fallback card, and every other
|
|
69
|
+
* settlement count nothing.
|
|
70
|
+
*/
|
|
71
|
+
export declare function settleNoRulingAsk(opts: {
|
|
72
|
+
ask: PermissionResult;
|
|
73
|
+
mintedHere: boolean;
|
|
74
|
+
resolved: Pick<ResolvedAsk, "action" | "settlement">;
|
|
75
|
+
tracker: AutoModeDenialTracker | undefined;
|
|
76
|
+
}): UnarmedDenialLimitFallback | undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { classifierNoRulingAskMessage, classifierUnavailableDenyMessage } from "./auto-mode.js";
|
|
2
2
|
import { attestClassifierDenyCause, carryEngineAttestations } from "./tool-policy.js";
|
|
3
|
-
export function
|
|
3
|
+
export function classifierNoRulingArm(args) {
|
|
4
4
|
const { ask, req, cause } = args;
|
|
5
5
|
const seated = args.seat?.approvalSeat === true;
|
|
6
6
|
if (!args.aborted) {
|
|
@@ -11,7 +11,18 @@ export function classifierUnavailableArm(args) {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
if (seated)
|
|
14
|
-
return { kind: "asked", ask: carryEngineAttestations(ask, { ...ask, message:
|
|
14
|
+
return { kind: "asked", ask: carryEngineAttestations(ask, { ...ask, message: classifierNoRulingAskMessage(ask.message ?? `approval required for "${req.toolName}"`, cause), requiresRealApproval: true, classifierNoRuling: cause }) };
|
|
15
|
+
if (cause === "parse_error")
|
|
16
|
+
return { kind: "counted" };
|
|
15
17
|
const deny = { action: "deny", message: classifierUnavailableDenyMessage(req.toolName, cause, args.attribution), decisionReason: "classifier" };
|
|
16
18
|
return { kind: "denied", deny: attestClassifierDenyCause(deny, "unavailable", req) };
|
|
17
19
|
}
|
|
20
|
+
export function settleNoRulingAsk(opts) {
|
|
21
|
+
const { ask, resolved } = opts;
|
|
22
|
+
if (!opts.mintedHere || ask.action !== "ask" || ask.classifierNoRuling === undefined || ask.denialLimitFallback !== undefined)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (resolved.action !== "deny" || resolved.settlement?.kind !== "policy_refused")
|
|
25
|
+
return undefined;
|
|
26
|
+
const tracked = opts.tracker?.recordBlock();
|
|
27
|
+
return tracked?.limitReached === true ? tracked.fallback : undefined;
|
|
28
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { types } from "node:util";
|
|
2
2
|
import { ASK_CLASSES, ASK_ORIGINS, isAskClass, isAskOrigin, isPersistedRuleMandate, PERSISTED_RULE_MANDATES } from "./ask-origin.js";
|
|
3
|
+
import { AUTO_MODE_FALLBACK_CAUSES, isAutoModeFallbackCause } from "./auto-mode.js";
|
|
3
4
|
import { thrownText } from "./thrown-value.js";
|
|
4
5
|
const ENGINE_REFUSALS = new WeakSet();
|
|
5
6
|
export const refusedOutOfContract = (message) => {
|
|
@@ -28,6 +29,7 @@ const CLOSED_MEMBERS = [
|
|
|
28
29
|
{ member: "guard", words: GUARDS, has: memberOf(GUARDS) },
|
|
29
30
|
{ member: "ruleUnreadable", words: RULE_UNREADABLE_WORDS, has: memberOf(RULE_UNREADABLE_WORDS) },
|
|
30
31
|
{ member: "authoredBy", words: AUTHORED_BY_WORDS, has: memberOf(AUTHORED_BY_WORDS) },
|
|
32
|
+
{ member: "classifierNoRuling", words: AUTO_MODE_FALLBACK_CAUSES, has: isAutoModeFallbackCause },
|
|
31
33
|
{ member: "requiresRealApproval", words: [true, false], has: (v) => v === true || v === false },
|
|
32
34
|
{ member: "probeMandated", words: [true], has: (v) => v === true },
|
|
33
35
|
{ member: "mandated", words: [true], has: (v) => v === true },
|
|
@@ -394,16 +394,17 @@ export interface EngineNotice {
|
|
|
394
394
|
* frame's two count keys). They are NOT redelivered (a steer aimed at a finished run must not
|
|
395
395
|
* fire at the next one — unlike ENGINE notes, which pend per session); the loud half of the
|
|
396
396
|
* #257 contract's "accepted = enqueued, not consumed" sentence;
|
|
397
|
-
* `detail: { steer, cause, taskId?, sessionId?, runId? }` / `{ followUp, taskId?, sessionId?, runId? }`
|
|
398
|
-
* (`cause` on
|
|
397
|
+
* `detail: { steer, cause, taskId?, sessionId?, runId? }` / `{ followUp, cause, taskId?, sessionId?, runId? }`
|
|
398
|
+
* (`cause` on both rows, closed `USER_INPUT_UNDRAINED_CAUSES`: `"leg_end"` — this engine's arm, the
|
|
399
399
|
* leg ended with it still owed; `"crashed"` — minted by the SERVING LAYER, not this engine, before the
|
|
400
|
-
* session's next run, for
|
|
401
|
-
* serving layer's, so only it can count them
|
|
400
|
+
* session's next run, for inputs it had accepted whose consumption was not confirmed before the process died: their queue was the
|
|
401
|
+
* serving layer's, so only it can count them, and it picks the family by the LANE the input was queued on (a `later`
|
|
402
|
+
* steer rides the follow-up lane). One code per family for both arms, so a consumer de-duplicating on `code` keeps
|
|
402
403
|
* one row; `sessionId` = #433's routing half for these two `"user"`-audience rows; `runId` = #499's
|
|
403
404
|
* per-run identity, the key this account is actually about — see
|
|
404
405
|
* {@link undrainedUserInputNotices}; all omitted when the caller has none, never fabricated). A delegated child
|
|
405
406
|
* leg is announced on its PARENT's session as well — the direct parent only — as a second row per family,
|
|
406
|
-
* `detail: { steer | followUp, cause
|
|
407
|
+
* `detail: { steer | followUp, cause, taskId?, sessionId: <the parent's>, childSessionId }` (no `runId`: nothing
|
|
407
408
|
* carries the parent's), message tail `(a child leg, <childSessionId>)`; `childSessionId` present = a child reported it.
|
|
408
409
|
* Per-run, at most once per family (the terminal sweep is a single site) — which is why
|
|
409
410
|
* `taskId`/`sessionId` cannot key it: both answer the same value for two runs of one session.
|
|
@@ -959,16 +960,17 @@ export declare function settleOwedUserInput(account: OwedUserInputAccount, rowRe
|
|
|
959
960
|
parent?: ParentLegIds | undefined;
|
|
960
961
|
}, onNotice: ((notice: EngineNotice) => void) | undefined): void;
|
|
961
962
|
/**
|
|
962
|
-
* Why a person's
|
|
963
|
-
*
|
|
964
|
-
* · `
|
|
965
|
-
*
|
|
966
|
-
*
|
|
967
|
-
*
|
|
968
|
-
*
|
|
963
|
+
* Why a person's input is reported as not consumed — the `detail.cause` of BOTH undrained families
|
|
964
|
+
* (`task.user_steer_undrained` / `task.user_followup_undrained`; the family is the lane the input was queued on). Closed:
|
|
965
|
+
* · `leg_end` — the leg ended with the input still owed (this engine mints it, at the leg's settlement);
|
|
966
|
+
* · `crashed` — the engine process ended before the leg settled, and an input the serving layer had accepted for it was
|
|
967
|
+
* not confirmed consumed before the process died (a live leg gives the serving layer no evidence that an input reached
|
|
968
|
+
* the model, so such an input MAY have been consumed). Only the serving layer can count these (the queue was its own), so the serving layer
|
|
969
|
+
* mints this arm, choosing the family by the lane the input was queued on, before the next run of the session starts —
|
|
970
|
+
* the one member-level exception to {@link import("./governance-codes.js").NOTICE_MINTER}'s rows for the two codes, which stay `"engine"`.
|
|
969
971
|
*/
|
|
970
|
-
export declare const
|
|
971
|
-
export type
|
|
972
|
+
export declare const USER_INPUT_UNDRAINED_CAUSES: readonly ["leg_end", "crashed"];
|
|
973
|
+
export type UserInputUndrainedCause = (typeof USER_INPUT_UNDRAINED_CAUSES)[number];
|
|
972
974
|
/** Where a child leg's account is ALSO announced: its spawner's session, and that run's task id when the spawner carried one
|
|
973
975
|
* (it never carries the run id, and the row never invents one). */
|
|
974
976
|
interface ParentLegIds {
|
|
@@ -51,7 +51,7 @@ export function settleOwedUserInput(account, rowReturned, ids, onNotice) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
const SETTLED_OWED_ACCOUNTS = new WeakSet();
|
|
54
|
-
export const
|
|
54
|
+
export const USER_INPUT_UNDRAINED_CAUSES = Object.freeze(["leg_end", "crashed"]);
|
|
55
55
|
export function parentLegIds(internals) {
|
|
56
56
|
const parentSessionId = internals?.parentSessionId;
|
|
57
57
|
if (typeof parentSessionId !== "string" || parentSessionId === "")
|
|
@@ -70,16 +70,17 @@ export function undrainedUserInputNotices(counts, taskId, sessionId, runId, pare
|
|
|
70
70
|
const copyTail = ` (a child leg, ${sessionId})`;
|
|
71
71
|
const own = [];
|
|
72
72
|
const copies = [];
|
|
73
|
+
const cause = "leg_end";
|
|
73
74
|
if (counts.steer > 0) {
|
|
74
75
|
const message = `${counts.steer} user steer(s) ${tail}`;
|
|
75
|
-
const count = { steer: counts.steer, cause
|
|
76
|
+
const count = { steer: counts.steer, cause };
|
|
76
77
|
own.push({ code: "task.user_steer_undrained", message, detail: { ...count, ...tid, ...sid, ...rid } });
|
|
77
78
|
if (toParent)
|
|
78
79
|
copies.push({ code: "task.user_steer_undrained", message: `${message}${copyTail}`, detail: { ...count, ...ptid, sessionId: parentSession, childSessionId: sessionId } });
|
|
79
80
|
}
|
|
80
81
|
if (counts.followUp > 0) {
|
|
81
82
|
const message = `${counts.followUp} user follow-up(s) ${tail}`;
|
|
82
|
-
const count = { followUp: counts.followUp };
|
|
83
|
+
const count = { followUp: counts.followUp, cause };
|
|
83
84
|
own.push({ code: "task.user_followup_undrained", message, detail: { ...count, ...tid, ...sid, ...rid } });
|
|
84
85
|
if (toParent)
|
|
85
86
|
copies.push({ code: "task.user_followup_undrained", message: `${message}${copyTail}`, detail: { ...count, ...ptid, sessionId: parentSession, childSessionId: sessionId } });
|