@sema-agent/core 5.57.0 → 5.58.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 +48 -0
- package/dist/agents/cascade.d.ts +1 -1
- package/dist/agents/cumulative-stats.d.ts +1 -1
- package/dist/agents/observer.d.ts +2 -2
- package/dist/agents/peer-admission.d.ts +1 -1
- package/dist/agents/retain-ledger.d.ts +2 -2
- package/dist/agents/roster-store.d.ts +8 -8
- package/dist/agents/send-message-tool.d.ts +2 -2
- package/dist/agents/subagent-steps.d.ts +1 -1
- package/dist/agents/subagent.d.ts +13 -13
- package/dist/agents/team.d.ts +5 -5
- package/dist/agents/tool-filter.d.ts +2 -2
- package/dist/agents/verify.d.ts +1 -1
- package/dist/bench/metrics.d.ts +35 -35
- package/dist/brain/degrading.d.ts +1 -1
- package/dist/brain/errors.d.ts +3 -3
- package/dist/brain/reasoning.d.ts +2 -2
- package/dist/brain/repetition.d.ts +1 -1
- package/dist/brain/status-sink.d.ts +2 -2
- package/dist/brain/stream-shared.d.ts +1 -1
- package/dist/config/catalog.d.ts +5 -5
- package/dist/core/arg-summary.d.ts +4 -4
- package/dist/core/ask-class.d.ts +2 -2
- package/dist/core/ask-question.d.ts +1 -1
- package/dist/core/auto-compaction.d.ts +15 -15
- package/dist/core/auto-mode.d.ts +5 -5
- package/dist/core/background-agent-store.d.ts +20 -20
- package/dist/core/background-shell.d.ts +4 -4
- package/dist/core/checkpoint-store.d.ts +35 -27
- package/dist/core/context-edit.d.ts +1 -1
- package/dist/core/context-guard.d.ts +1 -1
- package/dist/core/exec-output-tail.d.ts +6 -6
- package/dist/core/file-snapshot-store.d.ts +8 -8
- package/dist/core/git-worktree-env.d.ts +3 -3
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +73 -33
- package/dist/core/hooks.js +87 -25
- package/dist/core/image-downsample.d.ts +1 -1
- package/dist/core/locked-config.d.ts +1 -1
- package/dist/core/lsp.d.ts +1 -1
- package/dist/core/mailbox-store.d.ts +1 -1
- package/dist/core/mcp.d.ts +3 -3
- package/dist/core/memory-engine/consolidation-driver.d.ts +207 -0
- package/dist/core/memory-engine/consolidation-driver.js +378 -0
- package/dist/core/memory-engine/consolidation.d.ts +46 -2
- package/dist/core/memory-engine/consolidation.js +1 -0
- package/dist/core/memory-engine/data-plane.d.ts +1 -1
- package/dist/core/memory-engine/distiller.d.ts +550 -0
- package/dist/core/memory-engine/distiller.js +598 -0
- package/dist/core/memory-engine/dual-root.d.ts +1 -1
- package/dist/core/memory-engine/engine.d.ts +47 -3
- package/dist/core/memory-engine/engine.js +37 -3
- package/dist/core/memory-engine/file-backend.d.ts +1 -1
- package/dist/core/memory-engine/index.d.ts +4 -2
- package/dist/core/memory-engine/index.js +4 -2
- package/dist/core/memory-engine/origin-clearance.d.ts +1 -1
- package/dist/core/memory-engine/scope-contract.d.ts +4 -4
- package/dist/core/memory-engine/sync-client.d.ts +16 -16
- package/dist/core/memory-engine/sync.d.ts +4 -4
- package/dist/core/memory-recall.d.ts +1 -1
- package/dist/core/memory.d.ts +2 -2
- package/dist/core/permission-rule-consent.d.ts +185 -36
- package/dist/core/permission-rule-consent.js +219 -44
- package/dist/core/permission-rule-model.d.ts +194 -31
- package/dist/core/permission-rule-model.js +93 -35
- package/dist/core/permission-rules.d.ts +9 -9
- package/dist/core/remote-env.d.ts +8 -8
- package/dist/core/roles.d.ts +3 -3
- package/dist/core/roles.js +1 -0
- package/dist/core/runner/assemble-result.d.ts +2 -2
- package/dist/core/runner/compaction-call-options.d.ts +3 -3
- package/dist/core/runner/memory-consolidation-driver.d.ts +49 -0
- package/dist/core/runner/memory-consolidation-driver.js +60 -0
- package/dist/core/runner/memory-consolidation.d.ts +1 -1
- package/dist/core/runner/prepare-config-doors.d.ts +3 -3
- package/dist/core/runner/prepare-task.d.ts +21 -21
- package/dist/core/runner/prepare-task.js +21 -14
- package/dist/core/runner/prepare-workspace-restore.d.ts +2 -2
- package/dist/core/runner/runtask.d.ts +11 -11
- package/dist/core/runner/session-rule-policy.d.ts +1 -1
- package/dist/core/runner/teardown-bounded.d.ts +1 -1
- package/dist/core/runner/tool-disclosure.d.ts +2 -2
- package/dist/core/runner/turn-attachments.d.ts +11 -11
- package/dist/core/scheduler.d.ts +5 -5
- package/dist/core/secret-env.d.ts +1 -1
- package/dist/core/sensitive-path-policy.d.ts +1 -1
- package/dist/core/session-policy-store.d.ts +2 -2
- package/dist/core/session-reconcile.d.ts +2 -2
- package/dist/core/session-store.d.ts +3 -3
- package/dist/core/session.d.ts +1 -1
- package/dist/core/shutdown-debug.d.ts +2 -2
- package/dist/core/side-query.d.ts +2 -2
- package/dist/core/spec-contract.d.ts +1 -1
- package/dist/core/store-contracts/contract-harness.d.ts +2 -2
- package/dist/core/store-contracts/contract-kit-version.d.ts +2 -2
- package/dist/core/store-contracts/mailbox-store-contract.d.ts +1 -1
- package/dist/core/store-contracts/mailbox-store-contract.js +1 -1
- package/dist/core/task-notification.d.ts +5 -5
- package/dist/core/task-registry-agent.d.ts +12 -12
- package/dist/core/task-registry-monitor.d.ts +1 -1
- package/dist/core/task-registry-shared.d.ts +41 -41
- package/dist/core/task-registry.d.ts +12 -12
- package/dist/core/tool-detach.d.ts +2 -2
- package/dist/core/tool-errors.d.ts +3 -3
- package/dist/core/tool-policy.d.ts +55 -28
- package/dist/core/tool-result-budget.d.ts +1 -1
- package/dist/core/tool-result-store.d.ts +2 -2
- package/dist/core/tools.d.ts +1 -1
- package/dist/core/trace.d.ts +26 -23
- package/dist/core/types.d.ts +123 -70
- package/dist/core/untrusted-egress.d.ts +1 -1
- package/dist/core/untrusted-text.d.ts +7 -7
- package/dist/core/wiring-manifest.d.ts +5 -5
- package/dist/core/workflow-journal-store.d.ts +14 -14
- package/dist/core/workflow-run-store-contract.d.ts +1 -1
- package/dist/core/workflow-run-store-contract.js +1 -1
- package/dist/core/workflow-run-store.d.ts +4 -4
- package/dist/engine/compaction/compaction.d.ts +3 -3
- package/dist/engine/compaction/utils.d.ts +2 -2
- package/dist/engine/execution-env/kill-tree.d.ts +1 -1
- package/dist/engine/execution-env/node-execution-env.d.ts +8 -8
- package/dist/engine/harness/agent-harness.d.ts +6 -6
- package/dist/engine/harness/messages.d.ts +1 -1
- package/dist/engine/harness/types.d.ts +10 -10
- package/dist/engine/llm/types.d.ts +14 -14
- package/dist/engine/loop/agent-loop.d.ts +3 -3
- package/dist/engine/loop/types.d.ts +4 -4
- package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
- package/dist/engine/session/import-validate.d.ts +1 -1
- package/dist/engine/session/log-digest.d.ts +1 -1
- package/dist/engine/session/memory-repo.d.ts +2 -2
- package/dist/engine/session/session.d.ts +4 -4
- package/dist/fixtures/index.d.ts +4 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +3 -2
- package/dist/orchestration/goal.d.ts +1 -1
- package/dist/orchestration/run-spec.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +12 -12
- package/dist/orchestration/workflow-governance.d.ts +4 -4
- package/dist/orchestration/workflow-observe.d.ts +1 -1
- package/dist/orchestration/workflow-script-runner.d.ts +1 -1
- package/dist/orchestration/workflow-script-store.d.ts +9 -9
- package/dist/orchestration/workflow-size-guideline.d.ts +1 -1
- package/dist/orchestration/workflow-types.d.ts +5 -5
- package/dist/orchestration/workflow.d.ts +10 -10
- package/dist/prompt-assembly/artifact-store.d.ts +1 -1
- package/dist/prompt-assembly/artifact.d.ts +1 -1
- package/dist/prompt-assembly/assemble.d.ts +1 -1
- package/dist/prompt-assembly/composer.d.ts +2 -2
- package/dist/prompt-assembly/epoch.d.ts +2 -2
- package/dist/prompt-assembly/event-registry.d.ts +1 -1
- package/dist/prompt-assembly/explain.d.ts +3 -3
- package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
- package/dist/prompt-assembly/turn-snapshot.d.ts +4 -4
- package/dist/prompt-assembly/types.d.ts +12 -12
- package/dist/prompts/coordinator.d.ts +1 -1
- package/dist/prompts/default.d.ts +8 -8
- package/dist/prompts/simple-sections.d.ts +3 -3
- package/dist/prompts/supervisor.d.ts +2 -2
- package/dist/scenarios/full-body.d.ts +3 -3
- package/dist/scenarios/scenario-registry.d.ts +1 -1
- package/dist/stores/cc/sidecar-transcript.d.ts +3 -3
- package/dist/stores/file/fs-atomic.d.ts +2 -2
- package/dist/stores/file/index.d.ts +1 -1
- package/dist/stores/file/session-store.d.ts +2 -2
- package/dist/stores/file/workflow-journal-store.d.ts +4 -4
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
- package/dist/tools/fs/encoding.d.ts +4 -4
- package/dist/tools/fs/fs-bash.d.ts +3 -3
- package/dist/tools/fs/fs-pdf.d.ts +1 -1
- package/dist/tools/fs/fs-shared.d.ts +6 -6
- package/dist/tools/fs/index.d.ts +2 -2
- package/dist/tools/fs/notebook.d.ts +1 -1
- package/dist/tools/fs/pdf.d.ts +1 -1
- package/dist/tools/fs/read-deny.d.ts +1 -1
- package/dist/tools/fs/safety.d.ts +9 -9
- package/dist/tools/fs/search.d.ts +2 -2
- package/dist/tools/monitor.d.ts +3 -3
- package/dist/tools/task-list.d.ts +2 -2
- package/dist/tools/web.d.ts +4 -4
- package/dist/tools/worktree.d.ts +5 -5
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +56 -3
|
@@ -32,6 +32,12 @@
|
|
|
32
32
|
* deny/ask layer (`permission-rule-org.ts`) judges every segment and runs ahead of the allow lane at
|
|
33
33
|
* the gate — a compound-prefix allow can therefore never outrun a deny on any segment it admits.
|
|
34
34
|
*
|
|
35
|
+
* design/375 adds upstream's OTHER half of that per-segment evaluation on the allow side: a compound
|
|
36
|
+
* whose EVERY segment is admitted by some eligible rule is covered by the CONJUNCTION of those rules
|
|
37
|
+
* (`findAdmittingRule` answers with the coverage SET). No single rule widens: the matcher predicates
|
|
38
|
+
* above are untouched, and the set form is the decision layer's answer, fenced by the same
|
|
39
|
+
* ahead-running per-segment deny/ask layer.
|
|
40
|
+
*
|
|
35
41
|
* The floor is `parseLeadingCommandName` + `splitShellCompoundSegments` — the one simple-command parser
|
|
36
42
|
* and the one segmentation, both already shared with the read-only classifier, the reversibility probe,
|
|
37
43
|
* the coarse command-name policy and the skill tool specifier. A second tokenizer would drift, and drift
|
|
@@ -367,35 +373,181 @@ export declare function scopeCoversCwd(scope: RuleScope, cwd: string | undefined
|
|
|
367
373
|
* when tombstones are applied, so a rule with an empty `adds` is a rule that no longer exists. */
|
|
368
374
|
export declare function isRuleLive(rule: PersistedAllowRule): boolean;
|
|
369
375
|
/**
|
|
370
|
-
*
|
|
376
|
+
* design/375 §5.1 — MAY this rule speak about this call's CONTEXT? Tool identity plus scope coverage,
|
|
377
|
+
* and nothing about the rule's liveness: the CONTEXT question is the half a not-yet-persisted rule (a
|
|
378
|
+
* card's proposed candidate, which has no add dots yet) can already answer.
|
|
379
|
+
*
|
|
380
|
+
* The tool conjunct is not decoration: more than one tool takes a shell command string, and matching on
|
|
381
|
+
* the command alone would let an approval given on one surface resolve a call on another. The scope
|
|
382
|
+
* conjunct is the project fence — project A's approval is not project B's, and a project rule with no
|
|
383
|
+
* cwd to compare against covers nothing (fail-closed, {@link scopeCoversCwd}).
|
|
371
384
|
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
|
|
385
|
+
* `scope` rides beside the rule rather than inside it for the PROPOSED half's sake: a
|
|
386
|
+
* {@link ParsedAllowRule} carries no scope, so a proposal is judged against the scope it would LAND in.
|
|
387
|
+
*/
|
|
388
|
+
export declare function eligibleContext(rule: {
|
|
389
|
+
tool: string;
|
|
390
|
+
scope: RuleScope;
|
|
391
|
+
}, call: {
|
|
392
|
+
tool: string;
|
|
393
|
+
cwd: string | undefined;
|
|
394
|
+
}): boolean;
|
|
395
|
+
/**
|
|
396
|
+
* design/375 §5.1 — MAY this PERSISTED rule participate in admitting this call at all? The liveness
|
|
397
|
+
* layer on top of {@link eligibleContext}: a tombstoned rule (zero surviving adds) is a rule that no
|
|
398
|
+
* longer exists and must not count as coverage anywhere.
|
|
399
|
+
*
|
|
400
|
+
* THE single eligibility predicate for every consumer that reads the persisted store — the gate's
|
|
401
|
+
* conjunction arm and its coverage-set selection here, the suggestion face's coverage judgement and the
|
|
402
|
+
* consent prepare's ({@link segmentCoverageOf} is the shared instrument). One predicate, so a rule that
|
|
403
|
+
* one consumer refuses cannot be a rule another counts: the historically inlined trio of checks
|
|
404
|
+
* (live / tool / scope) is this function, verbatim.
|
|
405
|
+
*/
|
|
406
|
+
export declare function eligiblePersisted(rule: PersistedAllowRule, call: {
|
|
407
|
+
tool: string;
|
|
408
|
+
cwd: string | undefined;
|
|
409
|
+
}): boolean;
|
|
410
|
+
/**
|
|
411
|
+
* Find the rules that admit this command for a task in `cwd`, or `undefined`.
|
|
412
|
+
*
|
|
413
|
+
* THE ANSWER IS A COVERAGE SET (design/375 §5.1, the upstream per-segment form): a single rule that
|
|
414
|
+
* admits the whole command is a one-element set — the historical arm, table order preserved verbatim —
|
|
415
|
+
* and a COMPOUND whose every segment is admitted by some eligible rule is covered by the CONJUNCTION of
|
|
416
|
+
* those rules, returned as the set that covered it. The judging ladder, in order:
|
|
417
|
+
* 1. the fail-closed floor (a shape the lane cannot read matches nothing), evaluated once;
|
|
418
|
+
* 2. the single-rule arm — whole-string exact, single/compound-prefix — exactly the historical loop:
|
|
419
|
+
* first table-order match wins and is what the decision names. A single rule naming the command is
|
|
420
|
+
* found BEFORE any conjunction: reporting one rule is narrower than reporting a set;
|
|
421
|
+
* 3. the conjunction arm, compounds only: every segment must be admitted by some eligible rule under
|
|
422
|
+
* the MATCH reading (the allow side's own under-approximating instrument — never the deny side's
|
|
423
|
+
* wider PROGRAM_RUNS reading). Per segment the FIRST table-order admitting rule is selected, and
|
|
424
|
+
* the coverage set is those selections in segment order, de-duplicated — the same command against
|
|
425
|
+
* the same rule table always names the same set (reporting-order determinism, the historical
|
|
426
|
+
* "order is defined only for reporting" contract extended to sets).
|
|
427
|
+
*
|
|
428
|
+
* What the conjunction arm deliberately does NOT change: {@link ruleAdmitsCommand} itself. A
|
|
429
|
+
* single-command prefix body still never admits a chain — `Bash(npm:*)` does not admit
|
|
430
|
+
* `npm test && curl evil.example` — but the chain is now admitted when EVERY segment has a standing
|
|
431
|
+
* yes, which is the upstream decision form (per-segment allow, any-segment deny decided strictly
|
|
432
|
+
* earlier at the gate's org layer). A trailing-terminator spelling (`a && b ;`) keeps the conservative
|
|
433
|
+
* answer by construction: under the MATCH reading its tail is an empty segment the floor refuses, so
|
|
434
|
+
* the whole command is outside the lane and asks.
|
|
435
|
+
*
|
|
436
|
+
* Order is defined only for reporting (what the decision names); no rule is stronger than another —
|
|
437
|
+
* they are all the same one answer, "the person already said yes to this".
|
|
375
438
|
*/
|
|
376
439
|
export declare function findAdmittingRule(rules: readonly PersistedAllowRule[], call: {
|
|
377
440
|
tool: string;
|
|
378
441
|
command: string;
|
|
379
442
|
cwd: string | undefined;
|
|
380
|
-
}): PersistedAllowRule | undefined;
|
|
381
|
-
/**
|
|
382
|
-
|
|
443
|
+
}): readonly PersistedAllowRule[] | undefined;
|
|
444
|
+
/**
|
|
445
|
+
* design/375 §5.2 — the per-segment coverage table for `command`: which segments an eligible rule
|
|
446
|
+
* already admits, judged with the SAME split, the same MATCH reading and the same eligibility
|
|
447
|
+
* predicates as the gate's conjunction arm — one instrument, so the suggestion face and the consent
|
|
448
|
+
* prepare can never call a segment covered that the gate would ask about (or vice versa).
|
|
449
|
+
*
|
|
450
|
+
* TWO SOURCES, two predicates, and the seat labels are the contract:
|
|
451
|
+
* · `persisted` — rules already in the store, judged {@link eligiblePersisted} ∧ admit: a tombstoned
|
|
452
|
+
* rule, another project's rule and another tool's rule are uniformly NOT coverage;
|
|
453
|
+
* · `proposed` — rules of the CURRENT batch that are not yet persisted (no add dots yet), judged
|
|
454
|
+
* {@link eligibleContext} ∧ admit against the scope each would land in. Judging these with the
|
|
455
|
+
* persisted predicate would make "this batch, once redeemed, covers the command" permanently false
|
|
456
|
+
* — the liveness layer is the one thing a proposal legitimately lacks.
|
|
457
|
+
*
|
|
458
|
+
* Returns `undefined` for a command the lane cannot read at all (the fail-closed floor — the caller's
|
|
459
|
+
* honest answer is then "no coverage table", which every consumer must treat as all-uncovered). The
|
|
460
|
+
* reported `segment` texts are the FOLDED spellings, trimmed — display/correlation seats, never
|
|
461
|
+
* adjudication inputs.
|
|
462
|
+
*/
|
|
463
|
+
export declare function segmentCoverageOf(command: string, rules: {
|
|
464
|
+
persisted: readonly PersistedAllowRule[];
|
|
465
|
+
proposed?: readonly {
|
|
466
|
+
rule: ParsedAllowRule;
|
|
467
|
+
scope: RuleScope;
|
|
468
|
+
}[];
|
|
469
|
+
}, call: {
|
|
470
|
+
tool: string;
|
|
471
|
+
cwd: string | undefined;
|
|
472
|
+
}): readonly SegmentCoverage[] | undefined;
|
|
473
|
+
/** One row of a per-segment coverage table (see {@link segmentCoverageOf}): the FOLDED, trimmed
|
|
474
|
+
* segment text (a display/correlation seat, never adjudication input) and whether an eligible rule
|
|
475
|
+
* already admits it. */
|
|
476
|
+
export interface SegmentCoverage {
|
|
477
|
+
readonly segment: string;
|
|
478
|
+
readonly covered: boolean;
|
|
479
|
+
}
|
|
480
|
+
/** One member of a {@link RuleOffer} batch: a rule the engine offers to mint for ONE segment of a
|
|
481
|
+
* compound command. `segment` is the FOLDED segment text the member was minted from — a rendering
|
|
482
|
+
* seat, never adjudication input. */
|
|
483
|
+
export interface SegmentRuleSuggestion {
|
|
383
484
|
/** The canonical rule text the host would redeem. */
|
|
384
|
-
rule: string;
|
|
385
|
-
match: PersistedRuleMatch;
|
|
485
|
+
readonly rule: string;
|
|
486
|
+
readonly match: PersistedRuleMatch;
|
|
386
487
|
/** The command pattern in the rule, for a host that wants to render it without re-parsing. */
|
|
387
|
-
command: string;
|
|
488
|
+
readonly command: string;
|
|
489
|
+
/** The (folded) segment this suggestion came from. Raw post-rewrite command bytes — a substring
|
|
490
|
+
* of the same call's `args`/`command` values riding beside it on every surface that carries
|
|
491
|
+
* this seat — so render it with contextual escaping, exactly like those. */
|
|
492
|
+
readonly segment: string;
|
|
388
493
|
}
|
|
389
494
|
/**
|
|
390
|
-
*
|
|
495
|
+
* design/375 §3.1 — ONE "don't ask again" option on an approval card. A CLOSED discriminated union
|
|
496
|
+
* (`kind`); consumers parse it per element, and an element whose `kind` they do not know is dropped
|
|
497
|
+
* (single-row degrade) while keeping the ORIGINAL wire index for every element they keep — selection
|
|
498
|
+
* and redemption are keyed on original indices, so a consumer that cannot preserve the mapping must
|
|
499
|
+
* suppress its persistence actions entirely (fail toward asking).
|
|
500
|
+
*
|
|
501
|
+
* · `kind: "single"` — one rule covering the WHOLE command (the exact spelling, or a broader
|
|
502
|
+
* reviewed prefix form for a simple command).
|
|
503
|
+
* · `kind: "batch"` — a CONJUNCTION batch of per-segment rules for a compound command. Choosing it
|
|
504
|
+
* is one yes to ALL of `rules` at once — there is no per-member selection inside a batch; a
|
|
505
|
+
* consumer that wants a narrower answer picks the single offer or approves once without a rule.
|
|
506
|
+
* `rules` has 1..5 members (the card-lane minting cap), in segment order, deduplicated by rule
|
|
507
|
+
* text. `uncoveredSegments` is the honest surplus disclosure of the cap: judged at MINT time
|
|
508
|
+
* against the coverage snapshot the card was drawn from ∪ this batch's FINAL (deduplicated,
|
|
509
|
+
* capped) rules, it counts the violating segments still admitted by neither — 0 means "once this
|
|
510
|
+
* batch is redeemed, this compound is fully covered by that snapshot's lights". It is a statement
|
|
511
|
+
* about the minting snapshot, not a standing guarantee: a concurrent rule removal can date it.
|
|
512
|
+
*
|
|
513
|
+
* Array contract (`ruleOffers`, the durable park row, and the consent record's projection while
|
|
514
|
+
* the card is PENDING all share it): at most 2 offers; the whole-string exact single is index 0
|
|
515
|
+
* whenever present; the batch offer — at most one — is always last. It is a draw-time contract,
|
|
516
|
+
* not a settled-record invariant: confirming an EDITED candidate appends a third single offer
|
|
517
|
+
* AFTER the batch in the same write that settles the record, so a SETTLED record's projection may
|
|
518
|
+
* carry it. ADVISORY display metadata, never adjudication input, and never a
|
|
519
|
+
* rule by itself: minting is a separate act that goes through the approval-record protocol, so a
|
|
520
|
+
* surface that ignores offers entirely changes nothing about what is allowed. The authoritative
|
|
521
|
+
* render source for a card is the PREPARED record's own offers (record-is-what-you-see); the ask-time
|
|
522
|
+
* projection here may drift from it across a coverage change, and the drift direction is harmless
|
|
523
|
+
* (the record is what gets confirmed).
|
|
524
|
+
*/
|
|
525
|
+
export type RuleOffer = {
|
|
526
|
+
readonly kind: "single";
|
|
527
|
+
/** The canonical rule text the host would redeem. */
|
|
528
|
+
readonly rule: string;
|
|
529
|
+
readonly match: PersistedRuleMatch;
|
|
530
|
+
/** The command pattern in the rule, for a host that wants to render it without re-parsing. */
|
|
531
|
+
readonly command: string;
|
|
532
|
+
} | {
|
|
533
|
+
readonly kind: "batch";
|
|
534
|
+
/** 1..5 per-segment rules, segment order, deduplicated by rule text. */
|
|
535
|
+
readonly rules: readonly SegmentRuleSuggestion[];
|
|
536
|
+
/** Mint-time honest surplus: violating segments admitted by neither the coverage snapshot nor
|
|
537
|
+
* this batch's final rules (see the union doc above for the exact arithmetic). */
|
|
538
|
+
readonly uncoveredSegments: number;
|
|
539
|
+
};
|
|
540
|
+
/**
|
|
541
|
+
* The offers presented on an approval card for `command` (design/375 §5.2).
|
|
391
542
|
*
|
|
392
|
-
* **Array order is a documented CONTRACT, not an implementation accident
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
* Selection indices
|
|
396
|
-
*
|
|
543
|
+
* **Array order is a documented CONTRACT, not an implementation accident** (see {@link RuleOffer}):
|
|
544
|
+
* the whole-string EXACT single is index 0 whenever present; a SIMPLE command may carry a broader
|
|
545
|
+
* reviewed PREFIX single after it; a COMPOUND carries at most one BATCH offer, always last.
|
|
546
|
+
* Selection indices are keyed against this order, so consumers may rely on it; redemption TICKETS
|
|
547
|
+
* are NOT — they key on the consent record's flat CANDIDATE list, and a chosen offer is redeemed
|
|
548
|
+
* via `redeemRuleBatch` (see the consent record's own doc for the two index spaces).
|
|
397
549
|
*
|
|
398
|
-
* The prefix
|
|
550
|
+
* The prefix single is the LONGEST body two sources agree to offer: {@link SUGGESTION_LEXICON}'s
|
|
399
551
|
* deeper-than-two-words rows first, and otherwise {@link genericPrefixBody}'s general two-word form
|
|
400
552
|
* (`argv[0]` + a token shaped like a subcommand). Read those two for the grammar, its adopted origin,
|
|
401
553
|
* and the operand false positive it accepts; the short version is that a bare verb (`git`), an
|
|
@@ -407,9 +559,10 @@ export interface RuleSuggestion {
|
|
|
407
559
|
* "no prefix".
|
|
408
560
|
*
|
|
409
561
|
* Every produced candidate must survive the round trip — parse as a rule, come back as the match form
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
* redemption would refuse is worse
|
|
562
|
+
* its seat is offering, AND admit the very text it was minted from (the whole command for a single,
|
|
563
|
+
* its own segment for a batch member). Enforced on EVERY seat, fail-closed: a candidate that would
|
|
564
|
+
* not round-trip is silently not offered, since offering an option redemption would refuse is worse
|
|
565
|
+
* than offering one fewer.
|
|
413
566
|
*
|
|
414
567
|
* The FORM half of that check is not decoration. A command may end in the rule grammar's own prefix
|
|
415
568
|
* marker — `rm :*` is a legal thing to type — and wrapping it as an exact rule produces the text
|
|
@@ -419,19 +572,29 @@ export interface RuleSuggestion {
|
|
|
419
572
|
* for a lexicon command like `git status :*`, emitted twice — once mislabelled, once as the real prefix
|
|
420
573
|
* candidate). The seat therefore believes the PARSER about what it got back, never its own request.
|
|
421
574
|
*
|
|
422
|
-
* A COMPOUND (`./gradlew build && ./gradlew test`) fills the exact
|
|
423
|
-
* whole chain and admits exactly it
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
575
|
+
* A COMPOUND (`./gradlew build && ./gradlew test`) fills the exact single — the offered rule spells
|
|
576
|
+
* the whole chain and admits exactly it — and a BATCH offer besides: every segment NOT already
|
|
577
|
+
* covered (per `ctx.coverage`) yields its own single-segment best form — a prefix when the
|
|
578
|
+
* single-command grammar grants one for that segment's tokens (the interception screens apply
|
|
579
|
+
* unchanged), otherwise the segment's exact — deduplicated by rule text in segment order and capped
|
|
580
|
+
* at FIVE (upstream's own batch cap; the overflow is disclosed via `uncoveredSegments` rather than
|
|
581
|
+
* silently truncated away). A segment whose every form the floor refuses simply contributes no
|
|
582
|
+
* member (and stays counted as uncovered — the honest gap). The COMPOUND-PREFIX candidate this seat
|
|
583
|
+
* used to mint here is deliberately NO LONGER OFFERED (design/375 §5.3): the batch takes its card
|
|
584
|
+
* position, while the compound-prefix GRAMMAR remains a fully supported rule family — the matcher,
|
|
585
|
+
* the hand-written/edited/import channels and every persisted such rule are untouched.
|
|
586
|
+
*
|
|
587
|
+
* `ctx.coverage` is the per-segment coverage table minted by the SAME store read that adjudicated
|
|
588
|
+
* the call (see {@link segmentCoverageOf} and the gate's engine-stamped seat) — positional over this
|
|
589
|
+
* function's own folded segments, belt-checked by text: a table whose length or segment texts do not
|
|
590
|
+
* line up is discarded whole, and an absent or discarded table reads as ALL-UNCOVERED (fail toward
|
|
591
|
+
* over-offering; every offered rule still passes the validator at redemption, so an over-offer widens
|
|
592
|
+
* nothing).
|
|
432
593
|
*
|
|
433
594
|
* Returns an empty array for anything the rule lane cannot speak for (redirections, substitutions,
|
|
434
595
|
* subshells, backgrounding) — the card then simply carries no "don't ask again" option, which is the
|
|
435
596
|
* honest answer.
|
|
436
597
|
*/
|
|
437
|
-
export declare function suggestRulesForCommand(command: string
|
|
598
|
+
export declare function suggestRulesForCommand(command: string, ctx?: {
|
|
599
|
+
readonly coverage?: readonly SegmentCoverage[];
|
|
600
|
+
}): RuleOffer[];
|
|
@@ -247,60 +247,118 @@ export function scopeCoversCwd(scope, cwd) {
|
|
|
247
247
|
export function isRuleLive(rule) {
|
|
248
248
|
return rule.adds.length > 0;
|
|
249
249
|
}
|
|
250
|
-
export function
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
250
|
+
export function eligibleContext(rule, call) {
|
|
251
|
+
return rule.tool === call.tool && scopeCoversCwd(rule.scope, call.cwd);
|
|
252
|
+
}
|
|
253
|
+
export function eligiblePersisted(rule, call) {
|
|
254
|
+
return isRuleLive(rule) && eligibleContext(rule, call);
|
|
255
|
+
}
|
|
256
|
+
function firstEligibleAdmittingRule(rules, commandText, call) {
|
|
254
257
|
for (const rule of rules) {
|
|
255
|
-
if (!
|
|
256
|
-
continue;
|
|
257
|
-
if (rule.tool !== call.tool)
|
|
258
|
-
continue;
|
|
259
|
-
if (!scopeCoversCwd(rule.scope, call.cwd))
|
|
258
|
+
if (!eligiblePersisted(rule, call))
|
|
260
259
|
continue;
|
|
261
|
-
if (ruleAdmitsCommand(rule,
|
|
260
|
+
if (ruleAdmitsCommand(rule, commandText))
|
|
262
261
|
return rule;
|
|
263
262
|
}
|
|
264
263
|
return undefined;
|
|
265
264
|
}
|
|
266
|
-
export function
|
|
265
|
+
export function findAdmittingRule(rules, call) {
|
|
266
|
+
const shape = ruleLaneShapeOf(call.command, MATCH_READING);
|
|
267
|
+
if ("reject" in shape)
|
|
268
|
+
return undefined;
|
|
269
|
+
const whole = firstEligibleAdmittingRule(rules, call.command, call);
|
|
270
|
+
if (whole !== undefined)
|
|
271
|
+
return [whole];
|
|
272
|
+
if (shape.segments.length < 2)
|
|
273
|
+
return undefined;
|
|
274
|
+
const covering = [];
|
|
275
|
+
for (const segment of shape.segments) {
|
|
276
|
+
const first = firstEligibleAdmittingRule(rules, segment, call);
|
|
277
|
+
if (first === undefined)
|
|
278
|
+
return undefined;
|
|
279
|
+
if (!covering.includes(first))
|
|
280
|
+
covering.push(first);
|
|
281
|
+
}
|
|
282
|
+
return covering;
|
|
283
|
+
}
|
|
284
|
+
export function segmentCoverageOf(command, rules, call) {
|
|
285
|
+
const folded = foldSpacing(command);
|
|
286
|
+
if (folded === undefined)
|
|
287
|
+
return undefined;
|
|
288
|
+
const shape = ruleLaneShapeOf(folded, MATCH_READING);
|
|
289
|
+
if ("reject" in shape)
|
|
290
|
+
return undefined;
|
|
291
|
+
const proposed = rules.proposed ?? [];
|
|
292
|
+
return shape.segments.map((segment) => ({
|
|
293
|
+
segment: segment.trim(),
|
|
294
|
+
covered: firstEligibleAdmittingRule(rules.persisted, segment, call) !== undefined ||
|
|
295
|
+
proposed.some((p) => eligibleContext({ tool: p.rule.tool, scope: p.scope }, call) && ruleAdmitsCommand(p.rule, segment)),
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
export function suggestRulesForCommand(command, ctx) {
|
|
267
299
|
const shape = ruleLaneShapeOf(command, MATCH_READING);
|
|
268
300
|
if ("reject" in shape)
|
|
269
301
|
return [];
|
|
270
302
|
const folded = foldSpacing(command);
|
|
271
303
|
if (folded === undefined)
|
|
272
304
|
return [];
|
|
273
|
-
const
|
|
305
|
+
const offers = [];
|
|
274
306
|
const exact = parseAllowRuleText(formatAllowRuleText(folded, "exact"));
|
|
275
307
|
if ("rule" in exact && exact.rule.match === "exact" && ruleAdmitsCommand(exact.rule, command)) {
|
|
276
|
-
|
|
308
|
+
offers.push({ kind: "single", rule: exact.rule.rule, match: "exact", command: exact.rule.command });
|
|
277
309
|
}
|
|
278
|
-
const offerPrefix = (body) => {
|
|
279
|
-
if (body === undefined)
|
|
280
|
-
return;
|
|
281
|
-
const text = formatAllowRuleText(body, "prefix");
|
|
282
|
-
const parsed = parseAllowRuleText(text);
|
|
283
|
-
if ("rule" in parsed && parsed.rule.match === "prefix" && ruleAdmitsCommand(parsed.rule, command)) {
|
|
284
|
-
out.push({ rule: parsed.rule.rule, match: "prefix", command: parsed.rule.command });
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
310
|
const tokensOf = (s) => s.split(/\s+/).filter((t) => t !== "");
|
|
288
311
|
const bodyOf = (tokens) => longestReviewedBody(tokens) ?? genericPrefixBody(tokens);
|
|
289
|
-
if (
|
|
290
|
-
if (
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const rawLast = foldedShape.segments[foldedShape.segments.length - 1] ?? "";
|
|
297
|
-
const lastBody = bodyOf(tokensOf(rawLast));
|
|
298
|
-
if (lastBody !== undefined) {
|
|
299
|
-
const cut = folded.length - rawLast.length + (rawLast.length - rawLast.trimStart().length);
|
|
300
|
-
offerPrefix(folded.slice(0, cut) + lastBody);
|
|
312
|
+
if (shape.segments.length === 1) {
|
|
313
|
+
if (offers.length === 1) {
|
|
314
|
+
const body = bodyOf(tokensOf(folded));
|
|
315
|
+
if (body !== undefined) {
|
|
316
|
+
const parsed = parseAllowRuleText(formatAllowRuleText(body, "prefix"));
|
|
317
|
+
if ("rule" in parsed && parsed.rule.match === "prefix" && ruleAdmitsCommand(parsed.rule, command)) {
|
|
318
|
+
offers.push({ kind: "single", rule: parsed.rule.rule, match: "prefix", command: parsed.rule.command });
|
|
301
319
|
}
|
|
302
320
|
}
|
|
303
321
|
}
|
|
322
|
+
return offers;
|
|
323
|
+
}
|
|
324
|
+
const foldedShape = ruleLaneShapeOf(folded, MATCH_READING);
|
|
325
|
+
if ("reject" in foldedShape || foldedShape.segments.length !== shape.segments.length)
|
|
326
|
+
return offers;
|
|
327
|
+
const segments = foldedShape.segments.map((s) => s.trim());
|
|
328
|
+
const table = ctx?.coverage;
|
|
329
|
+
const alignedTable = table !== undefined && table.length === segments.length && table.every((row, i) => row.segment === segments[i]) ? table : undefined;
|
|
330
|
+
const coveredAt = (i) => alignedTable?.[i]?.covered === true;
|
|
331
|
+
const mintSegmentRule = (segment) => {
|
|
332
|
+
const body = bodyOf(tokensOf(segment));
|
|
333
|
+
if (body !== undefined) {
|
|
334
|
+
const parsed = parseAllowRuleText(formatAllowRuleText(body, "prefix"));
|
|
335
|
+
if ("rule" in parsed && parsed.rule.match === "prefix" && ruleAdmitsCommand(parsed.rule, segment)) {
|
|
336
|
+
return { rule: parsed.rule.rule, match: "prefix", command: parsed.rule.command };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const segExact = parseAllowRuleText(formatAllowRuleText(segment, "exact"));
|
|
340
|
+
if ("rule" in segExact && segExact.rule.match === "exact" && ruleAdmitsCommand(segExact.rule, segment)) {
|
|
341
|
+
return { rule: segExact.rule.rule, match: "exact", command: segExact.rule.command };
|
|
342
|
+
}
|
|
343
|
+
return undefined;
|
|
344
|
+
};
|
|
345
|
+
const minted = [];
|
|
346
|
+
for (let i = 0; i < segments.length; i++) {
|
|
347
|
+
if (coveredAt(i))
|
|
348
|
+
continue;
|
|
349
|
+
const segment = segments[i];
|
|
350
|
+
const rule = mintSegmentRule(segment);
|
|
351
|
+
if (rule !== undefined && !minted.some((m) => m.rule === rule.rule))
|
|
352
|
+
minted.push({ ...rule, segment });
|
|
353
|
+
}
|
|
354
|
+
const batchRules = minted.slice(0, 5);
|
|
355
|
+
if (batchRules.length > 0) {
|
|
356
|
+
const parsedBatch = batchRules.flatMap((r) => {
|
|
357
|
+
const p = parseAllowRuleText(r.rule);
|
|
358
|
+
return "rule" in p ? [p.rule] : [];
|
|
359
|
+
});
|
|
360
|
+
const uncoveredSegments = segments.filter((segment, i) => !coveredAt(i) && !parsedBatch.some((p) => ruleAdmitsCommand(p, segment))).length;
|
|
361
|
+
offers.push({ kind: "batch", rules: batchRules, uncoveredSegments });
|
|
304
362
|
}
|
|
305
|
-
return
|
|
363
|
+
return offers;
|
|
306
364
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* run-loop 介入的「声明式数据→ToolPolicy」工厂(`createCoarseCommandNamePolicy` 同形态先例)。
|
|
8
8
|
* core 不读设置文件、不管 source 层级合并 —— Settings-loader 把各层合并成**单一规则表**后编译
|
|
9
9
|
* **一个** policy(不要按层各编译再组合:combinePolicies 的 fold 里 ask 胜 allow,跨 policy 的
|
|
10
|
-
* 显式 allow 规则会被另一 policy 的 ask 吞掉
|
|
10
|
+
* 显式 allow 规则会被另一 policy 的 ask 吞掉)。
|
|
11
11
|
*
|
|
12
12
|
* 🔴 与 CC 的三处有意差(design/127 r2):
|
|
13
13
|
* - CC 参数级 generic 道无 allow lane(gnn 只有 deny/ask)—— 我们同样:`allow` + 参数级 content =
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* **工厂 validator strict**:降级即 invalid(fail-fast 世界观,写错的规则不能静默变宽)。
|
|
17
17
|
* - 已知主字段参数规则(如 `Bash(command:…)`)默认 **reject**:通配是 trim 后**整串 lexical**
|
|
18
18
|
* 匹配,不是 shell AST / 前缀 lane —— `Bash(command:git push*)` 匹配不了 `cd /x && git push`,
|
|
19
|
-
*
|
|
19
|
+
* 「以为挡住了」比没挡更危险。`primaryFieldGeneric:"allow-lexical"` 显式解锁。
|
|
20
20
|
*/
|
|
21
21
|
import type { NamedToolPolicy } from "./tool-policy.js";
|
|
22
22
|
/** [K-PLATFORM-SWEEP] MINOR#11: Bash's REAL non-primary arg names, accepted as generic param rules
|
|
@@ -28,7 +28,7 @@ export interface PermissionRule {
|
|
|
28
28
|
/** CC 语法:`"Tool"` 或 `"Tool(param:pattern)"`。`Tool()`/`Tool(*)` 归一为裸规则;括号可 `\(`/`\)` 转义。 */
|
|
29
29
|
rule: string;
|
|
30
30
|
behavior: "deny" | "ask" | "allow";
|
|
31
|
-
/** Provenance
|
|
31
|
+
/** Provenance:进 deny/ask 的 message 供审计(e.g. `"policySettings"`)。层级
|
|
32
32
|
* 优先级/tighten-only 语义在 Settings-loader,不在这里。 */
|
|
33
33
|
source?: string;
|
|
34
34
|
}
|
|
@@ -40,7 +40,7 @@ export interface ParsedPermissionRule {
|
|
|
40
40
|
}
|
|
41
41
|
export interface PermissionRuleIssue {
|
|
42
42
|
rule: string;
|
|
43
|
-
/** `invalid.*` = 语法/语义错;`unsupported.*` = CC 合法但 v1 不支持的 lane(
|
|
43
|
+
/** `invalid.*` = 语法/语义错;`unsupported.*` = CC 合法但 v1 不支持的 lane(独立
|
|
44
44
|
* 分类,绝不静默 no-op —— `Bash(npm run:*)` 被当 generic 参数规则接受后永不匹配是最危险的失败形态)。 */
|
|
45
45
|
code: "invalid.empty_tool" | "invalid.paren" | "invalid.param_split" | "invalid.allow_param_rule" | "invalid.primary_field" | "invalid.cap_exceeded" | "unsupported.bash_prefix"
|
|
46
46
|
/** design/179 D7: a Bash prefix rule under `bashPrefixLane:"rules-store"` — reported so a migration
|
|
@@ -65,20 +65,20 @@ export interface PermissionRuleCaps {
|
|
|
65
65
|
maxStars: number;
|
|
66
66
|
/** Scalar arg values longer than this are NOT matched textually — the rule **fail-closes** to its
|
|
67
67
|
* lane (a deny rule targeting an oversized value denies; an ask rule asks). 租户规则 × 模型巨串
|
|
68
|
-
*
|
|
68
|
+
* 的热路径成本上界。 */
|
|
69
69
|
maxScalarValueChars: number;
|
|
70
70
|
}
|
|
71
71
|
export interface PermissionRulePolicyOptions {
|
|
72
72
|
/** Invalid/unsupported rules: `"throw"`(default — 部署作者写错要立刻知道)aggregates ALL issues
|
|
73
73
|
* into one error; `"skip"` drops them (pair with {@link validatePermissionRules} for a report, and/or
|
|
74
|
-
* supply {@link onIssue}). Exception(
|
|
74
|
+
* supply {@link onIssue}). Exception(实现审 M3):`invalid.cap_exceeded` on the WHOLE rule set
|
|
75
75
|
* (maxRules) throws EVEN under `"skip"` — skipping it would compile an EMPTY table and fail-open. */
|
|
76
76
|
onInvalidRule?: "throw" | "skip";
|
|
77
77
|
/** Called once per dropped rule under `"skip"` (audit surface — silent drops are how a deployment
|
|
78
78
|
* discovers, months later, that its deny never existed). Default: one aggregated `console.warn`. */
|
|
79
79
|
onIssue?: (issue: PermissionRuleIssue) => void;
|
|
80
80
|
/** What the policy returns when NO rule matches. Default `"allow"` = 本 policy 无意见(组合语义)。
|
|
81
|
-
* default-closed 部署用 `"ask"`/`"deny"` —— 必须在这 INSIDE the policy(
|
|
81
|
+
* default-closed 部署用 `"ask"`/`"deny"` —— 必须在这 INSIDE the policy(链尾
|
|
82
82
|
* ask policy 会在 fold 里吞掉本 policy 的显式 allow)。 */
|
|
83
83
|
defaultAction?: "allow" | "ask" | "deny";
|
|
84
84
|
/** 已知主字段参数规则(`Bash(command:…)` 等)的处置。默认 `"reject"`(整串 lexical ≠ 前缀/AST,
|
|
@@ -136,7 +136,7 @@ export declare function namespacedRuleNameCovers(ruleName: string, toolName: str
|
|
|
136
136
|
export declare function isNamespacedCoveringRuleName(name: string): boolean;
|
|
137
137
|
/** Exported for the lockstep guard only (see {@link FILE_TOOL_PARAMS}) — not part of the rule DSL. */
|
|
138
138
|
export declare const fileToolParamVocabulary: (canonicalTool: string) => ReadonlySet<string> | undefined;
|
|
139
|
-
/** Dry-run
|
|
139
|
+
/** Dry-run 校验/迁移报告:不 throw,返回全部 issues(含 `unsupported.*` 分类,
|
|
140
140
|
* 供从 CC settings 迁移的部署逐条对照)。 */
|
|
141
141
|
export declare function validatePermissionRules(rules: PermissionRule[], opts?: Pick<PermissionRulePolicyOptions, "primaryFieldGeneric" | "caps" | "bashPrefixLane">): PermissionRuleIssue[];
|
|
142
142
|
/**
|
|
@@ -145,7 +145,7 @@ export declare function validatePermissionRules(rules: PermissionRule[], opts?:
|
|
|
145
145
|
* bare allow → `defaultAction`. `check()` is idempotent and side-effect-free (combinePolicies 契约);
|
|
146
146
|
* all parsing/validation happens HERE, the hot path never re-parses.
|
|
147
147
|
*
|
|
148
|
-
* ⚠️ Composition note
|
|
148
|
+
* ⚠️ Composition note: an `allow` from this policy is "no objection", NOT a
|
|
149
149
|
* cross-policy override — another policy's ask/deny still outranks it in the combinePolicies fold.
|
|
150
150
|
* Express default-closed via `defaultAction`, never via a trailing always-ask policy.
|
|
151
151
|
*/
|
|
@@ -52,7 +52,7 @@ export interface WorkspaceHandle {
|
|
|
52
52
|
snapshotId?: SnapshotId;
|
|
53
53
|
/** Token for {@link RemoteExecutionEnv.reconnect} when the VM is still running (vs suspended to a snapshot). */
|
|
54
54
|
sessionToken?: SessionToken;
|
|
55
|
-
/** How a checkpointed workspace is restored on resume (1.257.2 hardening
|
|
55
|
+
/** How a checkpointed workspace is restored on resume (1.257.2 hardening):
|
|
56
56
|
* `"park_only"` = durable-park-only suspend degrade (non-suspendable env; workspace persists on the
|
|
57
57
|
* target, resume skips `resumeVM`). Absent = legacy/snapshot handle — resume treats a MISSING
|
|
58
58
|
* `snapshotId` as corruption (fail-closed) unless the resumed env is itself non-suspendable
|
|
@@ -98,7 +98,7 @@ export interface VmLifecycleOptions {
|
|
|
98
98
|
/** Abort a hung pause/restore. */
|
|
99
99
|
abortSignal?: AbortSignal;
|
|
100
100
|
/**
|
|
101
|
-
*
|
|
101
|
+
* (resume workspace-root fidelity) — the CHECKPOINTED {@link WorkspaceHandle} of the workspace
|
|
102
102
|
* being restored, passed by the engine on the `resumeVM` leg. An adapter SHOULD bind the restored
|
|
103
103
|
* workspace root (cwd / mount) to `priorHandle.mountPath` rather than its CURRENT deployment
|
|
104
104
|
* configuration: across a config-knob change window (e.g. a mount-path rotation) the resumed task's
|
|
@@ -289,7 +289,7 @@ export interface RemoteExecutionEnv extends ExecutionEnv {
|
|
|
289
289
|
*
|
|
290
290
|
* ⚠️ LOAD-BEARING INVARIANT (opus review 1.257.2): `suspendable:false` on a remote env also asserts
|
|
291
291
|
* the workspace is EXTERNALLY DURABLE — it persists on the target across the env object's lifetime
|
|
292
|
-
* (true for SSH hosts / ADB devices). The durable-park-only suspend degrade
|
|
292
|
+
* (true for SSH hosts / ADB devices). The durable-park-only suspend degrade rests
|
|
293
293
|
* on this: it skips `suspendVM` and trusts the factory to reconnect to the SAME workspace on resume.
|
|
294
294
|
* An adapter for a non-suspendable EPHEMERAL backend (e.g. a snapshot-less container torn down with
|
|
295
295
|
* the transport) must NOT be modeled as `suspendable:false` remote — it would silently take the
|
|
@@ -359,7 +359,7 @@ export interface RemoteExecutionEnv extends ExecutionEnv {
|
|
|
359
359
|
* expired while suspended). Any failed step → `Err("post_resume_failed")` and the caller MUST destroy the env.
|
|
360
360
|
*
|
|
361
361
|
* Note: a resumed guest's network/long-lived connections (git remote / API / registry) are NOT guaranteed to
|
|
362
|
-
* survive the snapshot (
|
|
362
|
+
* survive the snapshot (decision 2), so this is also where the VM-internal agent rebuilds those.
|
|
363
363
|
*
|
|
364
364
|
* 🔴 Ordering red line (design/48 §5/#6): at-rest encryption of the memory snapshot must be ensured BEFORE
|
|
365
365
|
* secrets are injected — never let plaintext credentials land in an unencrypted snapshot. That encryption is a
|
|
@@ -391,10 +391,10 @@ export interface RemoteExecutionEnv extends ExecutionEnv {
|
|
|
391
391
|
* once on task end, and `prepareTask` calls it on a prepare-time throw; a remote impl may also be reaped,
|
|
392
392
|
* so a defensive double-call must not error or double-bill.
|
|
393
393
|
*
|
|
394
|
-
*
|
|
394
|
+
* contract note: on a NON-isolated env (no `capabilities.isolation` — host lane, SSH host),
|
|
395
395
|
* destroy() is workspace/object-level cleanup and must NOT reap still-running background processes —
|
|
396
396
|
* that is `disposeBackgroundShells`' job, which honours the timeout/session keep-alive except-list
|
|
397
|
-
* (
|
|
397
|
+
* (monitor timeout anchor). An isolated env (container/VM) naturally takes everything down;
|
|
398
398
|
* the Runner's envDying settle accounts for that with an accurate killed receipt beforehand.
|
|
399
399
|
*/
|
|
400
400
|
destroy(): Promise<void>;
|
|
@@ -423,7 +423,7 @@ export interface ExecutionEnvFactoryContext {
|
|
|
423
423
|
*/
|
|
424
424
|
isolation?: "worktree";
|
|
425
425
|
/**
|
|
426
|
-
*
|
|
426
|
+
* Field-reported (sub-agents landing in an EMPTY sandbox): the PARENT task's
|
|
427
427
|
* effective working root, threaded (like `isolation`) via the TRUSTED `RunInternals` channel when this
|
|
428
428
|
* task is a sub-agent (workflow `ctx.agent` / Task delegation). CC parity: a Task sub-agent inherits the
|
|
429
429
|
* main session's cwd. A single-user/TOC factory SHOULD root the child env here (unless `isolation`
|
|
@@ -436,7 +436,7 @@ export interface ExecutionEnvFactoryContext {
|
|
|
436
436
|
/**
|
|
437
437
|
* A **trusted control-plane** factory that mints a per-task {@link ExecutionEnv} (design/48 §5 answers core
|
|
438
438
|
* gap-a / §7 Q7). Lives on `RunnerDeps` (deployment-level) — NOT on `TaskSpec` — so an untrusted caller can
|
|
439
|
-
* never inject its own env and escape the sandbox (design/44 §7 Q4 red line;
|
|
439
|
+
* never inject its own env and escape the sandbox (design/44 §7 Q4 red line; decision 3: the Docker
|
|
440
440
|
* fast-lane must be control-plane-assigned, never task-selectable).
|
|
441
441
|
*
|
|
442
442
|
* The remote model is "one container per task", so the factory is invoked once per task. The Runner owns the
|
package/dist/core/roles.d.ts
CHANGED
|
@@ -24,10 +24,10 @@ export declare const CC_MODEL_TIER_ALIASES: Readonly<Record<string, string>>;
|
|
|
24
24
|
* - a binding may itself be a catalog name or a Model object (resolved through `resolveModel`);
|
|
25
25
|
* an unknown binding name throws at construction (config error surfaces at boot, not first use).
|
|
26
26
|
*/
|
|
27
|
-
/**
|
|
27
|
+
/** the DISPLAY face of a string model ref: a CC tier ALIAS (haiku/sonnet/…) resolves to its
|
|
28
28
|
* sema tier name (lite/flash/…) for labels — the alias verbatim reads as a strong claim about a
|
|
29
29
|
* specific vendor's model while the actual routing may land anywhere ("haiku" showing over a
|
|
30
|
-
* DeepSeek run,
|
|
30
|
+
* DeepSeek run, an operator field report). A non-alias string is the deployment's own model key — its
|
|
31
31
|
* honest name — and passes through unchanged. Display only; routing (`expandTiers`) is untouched. */
|
|
32
32
|
export declare function resolveModelDisplayLabel(ref: string): string;
|
|
33
33
|
export declare function expandTiers(models: Record<string, Model> | undefined, tiers: Record<string, ModelRef> | undefined): Record<string, Model> | undefined;
|
|
@@ -49,7 +49,7 @@ export interface ModelMention {
|
|
|
49
49
|
*/
|
|
50
50
|
export declare function parseModelMention(text: string, allowedNames: Iterable<string>): ModelMention;
|
|
51
51
|
/**
|
|
52
|
-
* Role → 档位默认表(
|
|
52
|
+
* Role → 档位默认表(裁定 2026-07-11:「role 体系用户不感知」)。配了 `RunnerDeps.tiers` 的部署,
|
|
53
53
|
* 各 role 未显式配置时自动落到语义档位——用户/壳只感知档位,roles 降级为内部路由层:
|
|
54
54
|
* - `default` → `pro`(主力档兜底;用户/部署 roles.default / TaskSpec.model 恒可覆盖);
|
|
55
55
|
* - `summarize` → `flash`(压缩本该便宜;flash 未绑沿降档链到 lite);
|
package/dist/core/roles.js
CHANGED
|
@@ -42,7 +42,7 @@ export interface Stats {
|
|
|
42
42
|
toolArg?: string;
|
|
43
43
|
}>;
|
|
44
44
|
};
|
|
45
|
-
/** TB telemetry B2
|
|
45
|
+
/** TB telemetry B2: engine-mechanism engagement counters. Set by the run loop
|
|
46
46
|
* only when ≥1 engaged; flows straight through to `TaskResult.stats.mechanisms` via the
|
|
47
47
|
* public-stats spread. */
|
|
48
48
|
mechanisms?: {
|
|
@@ -102,7 +102,7 @@ export interface ResultFlags {
|
|
|
102
102
|
threw: unknown;
|
|
103
103
|
/** design/99 MF-25: the EFFECTIVE (resolved) model id that served the task — echoed on `TaskResult.model`. */
|
|
104
104
|
model?: string;
|
|
105
|
-
/** RB-368
|
|
105
|
+
/** RB-368: some spend was accounted while the serving model had NO configured price table
|
|
106
106
|
* (no `RunnerDeps.pricing` entry, no `Model.cost`). The numeric total is then a fabricated
|
|
107
107
|
* undercount — `stats.costMicroUsd`/`costBreakdown` are OMITTED from the public result instead of
|
|
108
108
|
* reporting a 0 indistinguishable from "declared free". The internal accumulator (budget gate's
|