@sema-agent/core 7.0.1 → 7.1.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/agents/repair-loop.d.ts +8 -7
  3. package/dist/agents/roster-store.d.ts +7 -2
  4. package/dist/agents/subagent.js +29 -5
  5. package/dist/brain/errors.d.ts +18 -0
  6. package/dist/brain/errors.js +3 -0
  7. package/dist/brain/stream-engine.js +6 -4
  8. package/dist/core/context-edit.d.ts +3 -0
  9. package/dist/core/governance-codes.d.ts +1 -1
  10. package/dist/core/governance-codes.js +4 -0
  11. package/dist/core/hooks.d.ts +26 -6
  12. package/dist/core/hooks.js +8 -5
  13. package/dist/core/image-downsample.d.ts +4 -3
  14. package/dist/core/memory-engine/engine.d.ts +62 -5
  15. package/dist/core/memory-engine/engine.js +90 -19
  16. package/dist/core/memory-engine/index.d.ts +1 -1
  17. package/dist/core/memory-engine/layout.d.ts +11 -3
  18. package/dist/core/roles.d.ts +36 -9
  19. package/dist/core/roles.js +19 -6
  20. package/dist/core/runner/prepare-memory.js +29 -22
  21. package/dist/core/runner/prepare-task.d.ts +5 -3
  22. package/dist/core/runner/prepare-task.js +88 -47
  23. package/dist/core/runner/runtask.d.ts +12 -3
  24. package/dist/core/runner/runtask.js +32 -4
  25. package/dist/core/safety-axis-vocab.d.ts +1 -1
  26. package/dist/core/strategy-store.d.ts +4 -1
  27. package/dist/core/task-registry-shared.d.ts +7 -3
  28. package/dist/core/tool-errors.d.ts +1 -1
  29. package/dist/core/tool-policy.d.ts +40 -7
  30. package/dist/core/tool-policy.js +63 -9
  31. package/dist/core/types.d.ts +97 -11
  32. package/dist/engine/compaction/compaction.js +6 -2
  33. package/dist/engine/harness/agent-harness.d.ts +28 -6
  34. package/dist/engine/harness/agent-harness.js +34 -2
  35. package/dist/engine/harness/messages.js +4 -0
  36. package/dist/engine/harness/types.d.ts +37 -0
  37. package/dist/engine/harness/types.js +5 -0
  38. package/dist/engine/session/session.js +3 -2
  39. package/dist/index.d.ts +1 -1
  40. package/dist/internal/harness.d.ts +1 -0
  41. package/dist/internal/harness.js +1 -0
  42. package/dist/orchestration/builtin-workflows.d.ts +17 -9
  43. package/dist/orchestration/run-workflow-tool.d.ts +9 -1
  44. package/dist/orchestration/run-workflow-tool.js +18 -8
  45. package/dist/orchestration/workflow-governance.js +1 -1
  46. package/dist/orchestration/workflow-types.d.ts +1 -0
  47. package/dist/orchestration/workflow.d.ts +9 -1
  48. package/dist/orchestration/workflow.js +5 -5
  49. package/dist/stores/file/mailbox-store.d.ts +2 -1
  50. package/package.json +1 -1
  51. package/test/export-surface.snapshot.json +3 -1
@@ -457,37 +457,47 @@ export class MemoryEngine {
457
457
  return "unpersisted";
458
458
  }
459
459
  }
460
- let outcome;
460
+ let raw;
461
461
  try {
462
- outcome = this.captureRecords.mark(sessionId, record);
462
+ raw = this.captureRecords.mark(sessionId, record);
463
463
  }
464
464
  catch {
465
- outcome = "unpersisted";
465
+ raw = "unpersisted";
466
466
  }
467
- if (outcome !== "unpersisted" && !this.captureOptOutSessions.has(sessionId))
468
- this.captureOptOutSessions.set(sessionId, record);
469
- return outcome;
467
+ const settle = (outcome) => {
468
+ if (outcome !== "unpersisted" && !this.captureOptOutSessions.has(sessionId))
469
+ this.captureOptOutSessions.set(sessionId, record);
470
+ return outcome;
471
+ };
472
+ return raw instanceof Promise ? raw.then(settle, () => "unpersisted") : settle(raw);
470
473
  }
471
474
  sessionCaptureOptOut(sessionId) {
472
- return this.sessionCaptureOptOutOrFault(sessionId).record;
475
+ const r = this.sessionCaptureOptOutOrFault(sessionId);
476
+ return r instanceof Promise ? r.then((s) => s.record) : r.record;
473
477
  }
474
478
  sessionCaptureOptOutOrFault(sessionId) {
475
479
  const inProcess = this.captureOptOutSessions.get(sessionId);
476
480
  if (inProcess !== undefined)
477
481
  return { record: inProcess, fault: false };
482
+ let raw;
478
483
  try {
479
- const record = this.captureRecords.read(sessionId);
480
- return record !== undefined ? { record, fault: false } : { fault: false };
484
+ raw = this.captureRecords.read(sessionId);
481
485
  }
482
486
  catch {
483
487
  return { fault: true };
484
488
  }
489
+ const fold = (record) => record !== undefined ? { record, fault: false } : { fault: false };
490
+ return raw instanceof Promise ? raw.then(fold, () => ({ fault: true })) : fold(raw);
485
491
  }
486
492
  listCaptureOptOutSessions() {
487
- const out = new Set(Object.keys(this.captureRecords.list()));
488
- for (const id of this.captureOptOutSessions.keys())
489
- out.add(id);
490
- return out;
493
+ const raw = this.captureRecords.list();
494
+ const fold = (rows) => {
495
+ const out = new Set(Object.keys(rows));
496
+ for (const id of this.captureOptOutSessions.keys())
497
+ out.add(id);
498
+ return out;
499
+ };
500
+ return raw instanceof Promise ? raw.then(fold) : fold(raw);
491
501
  }
492
502
  async sweepSessionCaptureResidue(handle) {
493
503
  const out = { swept: [], restored: [], failures: [] };
@@ -1546,7 +1556,7 @@ export class MemoryEngine {
1546
1556
  warnings: [],
1547
1557
  };
1548
1558
  const writeScope = handle.writeScope;
1549
- const captureRead = opts?.sessionId !== undefined ? this.sessionCaptureOptOutOrFault(opts.sessionId) : { fault: false };
1559
+ const captureRead = opts?.sessionId !== undefined ? await this.sessionCaptureOptOutOrFault(opts.sessionId) : { fault: false };
1550
1560
  const captureOptOut = captureRead.record;
1551
1561
  if (writeScope === null || opts?.admitNothing !== undefined || captureOptOut !== undefined || captureRead.fault) {
1552
1562
  try {
@@ -2194,7 +2204,7 @@ export class MemoryEngine {
2194
2204
  return report;
2195
2205
  }
2196
2206
  }
2197
- const preCommitRead = lineageSessionId !== undefined ? this.sessionCaptureOptOutOrFault(lineageSessionId) : { fault: false, record: undefined };
2207
+ const preCommitRead = lineageSessionId !== undefined ? await this.sessionCaptureOptOutOrFault(lineageSessionId) : { fault: false, record: undefined };
2198
2208
  if (preCommitRead.record !== undefined || preCommitRead.fault) {
2199
2209
  if (lineageArmed) {
2200
2210
  try {
@@ -2796,7 +2806,7 @@ export class MemoryEngine {
2796
2806
  return false;
2797
2807
  return frozenToken === undefined || held.token === frozenToken;
2798
2808
  }
2799
- consolidationEligibility(headers) {
2809
+ async consolidationEligibility(headers) {
2800
2810
  let exclusions;
2801
2811
  try {
2802
2812
  exclusions = this.readChallengeExclusions();
@@ -2823,7 +2833,7 @@ export class MemoryEngine {
2823
2833
  }
2824
2834
  let optOutSessions;
2825
2835
  try {
2826
- optOutSessions = this.listCaptureOptOutSessions();
2836
+ optOutSessions = await this.listCaptureOptOutSessions();
2827
2837
  }
2828
2838
  catch (err) {
2829
2839
  throw new ConsolidationRefusedError("memory.consolidation_governance_unreadable", `memory consolidation refused: the capture opt-out roster cannot be trusted (fail-closed): ${err instanceof Error ? err.message : String(err)}`);
@@ -2874,7 +2884,7 @@ export class MemoryEngine {
2874
2884
  }
2875
2885
  const face = this.committedAuditFace();
2876
2886
  const headers = await face.listHeaders([scope]);
2877
- const { eligible } = this.consolidationEligibility(headers);
2887
+ const { eligible } = await this.consolidationEligibility(headers);
2878
2888
  const fingerprint = opts.full === true ? undefined : row?.fingerprint;
2879
2889
  const candidateIds = [];
2880
2890
  for (const [id, h] of eligible) {
@@ -3040,7 +3050,7 @@ export class MemoryEngine {
3040
3050
  const at = this.now();
3041
3051
  const face = this.committedAuditFace();
3042
3052
  const headers = await face.listHeaders([scope]);
3043
- const { exclusions, superseded } = this.consolidationEligibility(headers);
3053
+ const { exclusions, superseded } = await this.consolidationEligibility(headers);
3044
3054
  const headerById = new Map(headers.map((h) => [h.id, h]));
3045
3055
  const wholeReasons = [];
3046
3056
  for (let i = 0; i < proposal.products.length; i++) {
@@ -3921,6 +3931,67 @@ export class MemoryEngine {
3921
3931
  }
3922
3932
  return await b.listScopes();
3923
3933
  }
3934
+ async sessionMemoryStatus(sessionId) {
3935
+ const out = {};
3936
+ const capture = await this.sessionCaptureOptOutOrFault(sessionId);
3937
+ if (capture.fault) {
3938
+ out.optOutSource = "fault";
3939
+ }
3940
+ else if (capture.record !== undefined) {
3941
+ out.captureOptedOut = true;
3942
+ out.optOutSource = "record";
3943
+ }
3944
+ else {
3945
+ out.captureOptedOut = false;
3946
+ }
3947
+ let mine;
3948
+ try {
3949
+ const committed = readLineageRecord(this.controlDir).committed;
3950
+ const ids = new Set();
3951
+ let lastAt;
3952
+ for (const [entryId, sessions] of Object.entries(committed)) {
3953
+ const c = sessions[sessionId];
3954
+ if (c === undefined)
3955
+ continue;
3956
+ ids.add(entryId);
3957
+ if (lastAt === undefined || c.lastAt > lastAt)
3958
+ lastAt = c.lastAt;
3959
+ }
3960
+ mine = ids;
3961
+ out.committedCount = ids.size;
3962
+ if (lastAt !== undefined)
3963
+ out.lastCaptureAt = lastAt;
3964
+ }
3965
+ catch {
3966
+ }
3967
+ if (mine !== undefined) {
3968
+ if (mine.size === 0) {
3969
+ out.foldedCount = 0;
3970
+ }
3971
+ else {
3972
+ try {
3973
+ const enumeration = await this.listMemoryScopes();
3974
+ if (enumeration.supported) {
3975
+ const face = this.committedAuditFace();
3976
+ const headers = await face.listHeaders(enumeration.scopes);
3977
+ const productIds = headers.filter((h) => h.distilled !== undefined).map((h) => h.id);
3978
+ const folded = new Set();
3979
+ if (productIds.length > 0) {
3980
+ for (const p of await face.getByIds(productIds)) {
3981
+ for (const row of p.frontmatter.distilled?.inputs ?? [])
3982
+ if (mine.has(row.id))
3983
+ folded.add(row.id);
3984
+ }
3985
+ }
3986
+ out.foldedCount = folded.size;
3987
+ }
3988
+ }
3989
+ catch {
3990
+ }
3991
+ }
3992
+ }
3993
+ return out;
3994
+ }
3924
3995
  committedAuditFace() {
3925
3996
  const b = this.backend;
3926
3997
  return b.restrictedAdoptionView?.({ audit: false }) ?? b.retrievalView?.() ?? this.backend;
@@ -1,4 +1,4 @@
1
- export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, memoryRecallDisciplineSegment, entryFileHeadCarriesOrigin, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, renderAnnouncements, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationIncompleteNotice, memoryConsolidationRefusedNotice, memoryConsolidationWithheldNotice, MEMORY_CAPTURE_OPTOUT_NOTICE, memoryCaptureOptedOutNotice, memoryCaptureOptOutUnpersistedNotice, type ConsolidationCommitReceipt, type ConsolidationReconcileReport, type ConsolidationResolveReceipt, type ConsolidationPlanSummary, type ConsolidationPlanFoldEvidence, } from "./engine.js";
1
+ export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, memoryRecallDisciplineSegment, entryFileHeadCarriesOrigin, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, renderAnnouncements, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationIncompleteNotice, memoryConsolidationRefusedNotice, memoryConsolidationWithheldNotice, MEMORY_CAPTURE_OPTOUT_NOTICE, memoryCaptureOptedOutNotice, memoryCaptureOptOutUnpersistedNotice, type ConsolidationCommitReceipt, type ConsolidationReconcileReport, type ConsolidationResolveReceipt, type ConsolidationPlanSummary, type ConsolidationPlanFoldEvidence, type SessionMemoryStatus, } from "./engine.js";
2
2
  export { MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_INDEX_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type CleanMemorySearchHit, type ExposedMemorySearchHit, type MemoryGetDetails, type MemoryIndexDetails, type MemoryIndexRow, type CleanMemoryIndexRow, type ExposedMemoryIndexRow, } from "./tools.js";
3
3
  export { MEMORY_EXPOSURE_BANNER, MEMORY_EXPOSURE_HANDLE_TAG, MEMORY_PROVENANCE_RECALL_SENTENCE, MEMORY_PROVENANCE_SEARCH_SENTENCE, memoryExposureIndexRow, parseMemoryExposureIndexRow, } from "./provenance-wording.js";
4
4
  export { scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE } from "./scan.js";
@@ -407,11 +407,19 @@ export declare function listSessionCaptureOptOut(controlDir: string): Record<str
407
407
  * (`controlDir` + `sessionId` coordinates that outlive any live object — see the settlement handle
408
408
  * doc in prepare-task.ts), and a live store object cannot ride that lane; its migration needs its
409
409
  * own seam design and is registered here rather than half-built.
410
+ *
411
+ * DUAL FORM (#511 件2, additive): each leg may answer `T` OR `Promise<T>` — a SQL/remote carrier is
412
+ * async by nature and could not implement the sync-only trio at all. Every core consumption point
413
+ * awaits (or `.then`-chains where a sync public seat must stay sync-transparent), so a sync store's
414
+ * behavior is unchanged and an async store's Promise legs keep the exact same contract per leg:
415
+ * a rejected `mark` promise is held to the `"unpersisted"` refusal arm, a rejected `read` promise
416
+ * is the INDETERMINATE fault axis, a rejected `list` promise propagates as the enumeration failure.
417
+ * The File trio below keeps its synchronous signatures byte-identical.
410
418
  */
411
419
  export interface SessionCaptureRecordStore {
412
- mark(sessionId: string, record: SessionCaptureOptOutRecord): SessionCaptureOptOutMarkOutcome;
413
- read(sessionId: string): SessionCaptureOptOutRecord | undefined;
414
- list(): Record<string, SessionCaptureOptOutRecord>;
420
+ mark(sessionId: string, record: SessionCaptureOptOutRecord): SessionCaptureOptOutMarkOutcome | Promise<SessionCaptureOptOutMarkOutcome>;
421
+ read(sessionId: string): SessionCaptureOptOutRecord | undefined | Promise<SessionCaptureOptOutRecord | undefined>;
422
+ list(): Record<string, SessionCaptureOptOutRecord> | Promise<Record<string, SessionCaptureOptOutRecord>>;
415
423
  }
416
424
  /** The core-default {@link SessionCaptureRecordStore}: the file trio over `controlDir` (single-
417
425
  * process / single-host deployments keep today's carrier byte-identical). */
@@ -9,6 +9,12 @@ export declare function resolveModel(ref: ModelRef, models?: Record<string, Mode
9
9
  export declare const DEFAULT_TIER_ORDER: readonly string[];
10
10
  /** CC 档位词 → sema 档位(内置固定,兼容层):fable/mythos→max · opus→pro · sonnet→flash · haiku→lite。 */
11
11
  export declare const CC_MODEL_TIER_ALIASES: Readonly<Record<string, string>>;
12
+ /** the DISPLAY face of a string model ref: a CC tier ALIAS (haiku/sonnet/…) resolves to its
13
+ * sema tier name (lite/flash/…) for labels — the alias verbatim reads as a strong claim about a
14
+ * specific vendor's model while the actual routing may land anywhere ("haiku" showing over a
15
+ * DeepSeek run, an operator field report). A non-alias string is the deployment's own model key — its
16
+ * honest name — and passes through unchanged. Display only; routing (`expandTiers`) is untouched. */
17
+ export declare function resolveModelDisplayLabel(ref: string): string;
12
18
  /**
13
19
  * Expand a deployment's tier bindings into the model catalog: every tier name (and every CC alias whose
14
20
  * tier resolves) becomes a catalog key pointing at the bound Model, so the ENTIRE resolve surface
@@ -22,14 +28,13 @@ export declare const CC_MODEL_TIER_ALIASES: Readonly<Record<string, string>>;
22
28
  * - an existing catalog key of the same name WINS (deployment SHADOW semantics, mirroring agents):
23
29
  * a deployment that already ships a model literally named "pro" keeps it untouched;
24
30
  * - a binding may itself be a catalog name or a Model object (resolved through `resolveModel`);
25
- * an unknown binding name throws at construction (config error surfaces at boot, not first use).
31
+ * an unknown binding name throws at construction (config error surfaces at boot, not first use);
32
+ * - `"best"` (dynamic head-of-chain alias, CC `$L` parity) resolves to the FIRST tier in
33
+ * {@link DEFAULT_TIER_ORDER} that carries a DIRECT binding — "the strongest model this deployment
34
+ * actually bound", not a fixed tier — under the same SHADOW rule (a deployment catalog key named
35
+ * `best` wins); with no bound known-order tier (or no tiers at all) the key stays absent and
36
+ * `resolveModel("best")` refuses like any unknown ref.
26
37
  */
27
- /** the DISPLAY face of a string model ref: a CC tier ALIAS (haiku/sonnet/…) resolves to its
28
- * sema tier name (lite/flash/…) for labels — the alias verbatim reads as a strong claim about a
29
- * specific vendor's model while the actual routing may land anywhere ("haiku" showing over a
30
- * DeepSeek run, an operator field report). A non-alias string is the deployment's own model key — its
31
- * honest name — and passes through unchanged. Display only; routing (`expandTiers`) is untouched. */
32
- export declare function resolveModelDisplayLabel(ref: string): string;
33
38
  export declare function expandTiers(models: Record<string, Model> | undefined, tiers: Record<string, ModelRef> | undefined): Record<string, Model> | undefined;
34
39
  export interface ModelMention {
35
40
  /** The matched model name — always one of `allowedNames` — or undefined if none was found. */
@@ -44,8 +49,27 @@ export interface ModelMention {
44
49
  * pick *which configured model* to use, but can never inject an arbitrary `baseUrl`/`apiKey`/Model
45
50
  * (see design/11). Matches the first allow-listed mention; the rest of the text is returned cleaned.
46
51
  *
47
- * Typical use: `const { model, cleanedText } = parseModelMention(userMsg, Object.keys(deps.models));`
52
+ * **Case-INSENSITIVE, deliberately** and deliberately UNLIKE the catalog, whose lookups
53
+ * ({@link resolveModel}) are exact. The two answer different questions and the asymmetry is the
54
+ * design, not an oversight: a chat mention is a HUMAN-typed UX surface where `@Opus` and `@opus` are
55
+ * the same intent, so intake is wide; a catalog key is a machine identifier a deployment declared, so
56
+ * lookup is exact. The join is safe because the wide half never widens the narrow one: this returns
57
+ * the allowlist's OWN canonical spelling (never the user's casing), so the value handed to
58
+ * `resolveModel` is always a key the deployment wrote. The only visible consequence is that a catalog
59
+ * carrying two keys differing ONLY in case is addressed by whichever the allowlist iterates last —
60
+ * `@x` cannot name both, and there is no spelling that disambiguates them.
61
+ *
62
+ * Typical use: `const { model, cleanedText } = parseModelMention(userMsg, Object.keys(runner.agentCatalog.models ?? {}));`
48
63
  * then `runTask({ objective: cleanedText, model })` (omit `model` to fall back to the default role).
64
+ *
65
+ * **Feed it the EXPANDED table.** The allowlist this takes is the host's own, and on a deployment that
66
+ * configured `RunnerDeps.tiers` the host's `deps.models` is NOT what the Runner resolves against: the
67
+ * constructor runs {@link expandTiers} once and keeps the expansion PRIVATE, so tier words and CC
68
+ * aliases (`pro`/`flash`/`opus`/`sonnet`/`best`/…) are catalog keys inside the Runner and absent from
69
+ * the host's table. Passing `Object.keys(deps.models)` therefore makes a user's `@opus` silently not
70
+ * match — the mention falls through as ordinary text and the task quietly runs on the default role.
71
+ * `runner.agentCatalog.models` is the expanded table (a per-read copy); a caller that has no Runner in
72
+ * hand can pre-expand its own with the exported {@link expandTiers}.
49
73
  */
50
74
  export declare function parseModelMention(text: string, allowedNames: Iterable<string>): ModelMention;
51
75
  /**
@@ -55,7 +79,10 @@ export declare function parseModelMention(text: string, allowedNames: Iterable<s
55
79
  * - `summarize` → `flash`(压缩本该便宜;flash 未绑沿降档链到 lite);
56
80
  * - `advisor` → 第一档(`ultra` 起,未绑降档到 max/pro——teacher 用最强的);
57
81
  * - `verifier` → `pro`;
58
- * - `subagent`/`team`/`synthesize` 无档位默认 = FALLBACK 继承主模型(LLM 可经 model 参数自选档)
82
+ * - `classifier` `flash`(design/143 批2b:auto-mode 权限分类腿是便宜档;flash 未绑沿降档链到 lite);
83
+ * - `subagent`/`team`/`synthesize` 无档位默认 = 走 FALLBACK 继承主模型(LLM 可经 model 参数自选档);
84
+ * - `consolidate` 同样无档位默认,但它的 FALLBACK 刻意只有 `summarize` 一站(design/376,见
85
+ * {@link FALLBACK} 的注):未配即响亮拒,不静默骑主模型。
59
86
  * 未配 tiers 的部署:catalog 无 TIERS_ACTIVE 标记 → 表整体 INERT,行为与从前逐字节一致
60
87
  * (即使 catalog 恰有裸模型名 `pro`/`flash` 也不入档位路由——INERT 靠配置成立,不靠键名运气)。
61
88
  */
@@ -18,7 +18,7 @@ export const CC_MODEL_TIER_ALIASES = {
18
18
  haiku: "lite",
19
19
  };
20
20
  export function resolveModelDisplayLabel(ref) {
21
- return CC_MODEL_TIER_ALIASES[ref] ?? ref;
21
+ return Object.hasOwn(CC_MODEL_TIER_ALIASES, ref) ? CC_MODEL_TIER_ALIASES[ref] : ref;
22
22
  }
23
23
  const TIERS_ACTIVE = Symbol.for("sema.tiersActive");
24
24
  export function expandTiers(models, tiers) {
@@ -27,7 +27,7 @@ export function expandTiers(models, tiers) {
27
27
  const out = { ...models };
28
28
  out[TIERS_ACTIVE] = true;
29
29
  const bound = (name) => {
30
- const b = tiers[name];
30
+ const b = Object.hasOwn(tiers, name) ? tiers[name] : undefined;
31
31
  return b === undefined ? undefined : resolveModel(b, models);
32
32
  };
33
33
  const resolveTier = (name) => {
@@ -44,20 +44,33 @@ export function expandTiers(models, tiers) {
44
44
  }
45
45
  return undefined;
46
46
  };
47
+ const shadowed = (key) => Object.hasOwn(out, key) && Boolean(out[key]);
48
+ const put = (key, m) => {
49
+ Object.defineProperty(out, key, { value: m, writable: true, enumerable: true, configurable: true });
50
+ };
47
51
  const tierNames = new Set([...DEFAULT_TIER_ORDER, ...Object.keys(tiers)]);
48
52
  for (const name of tierNames) {
49
- if (out[name])
53
+ if (shadowed(name))
50
54
  continue;
51
55
  const m = resolveTier(name);
52
56
  if (m)
53
- out[name] = m;
57
+ put(name, m);
54
58
  }
55
59
  for (const [alias, tier] of Object.entries(CC_MODEL_TIER_ALIASES)) {
56
- if (out[alias])
60
+ if (shadowed(alias))
57
61
  continue;
58
62
  const m = resolveTier(tier);
59
63
  if (m)
60
- out[alias] = m;
64
+ put(alias, m);
65
+ }
66
+ if (!shadowed("best")) {
67
+ for (const name of DEFAULT_TIER_ORDER) {
68
+ const m = bound(name);
69
+ if (m) {
70
+ put("best", m);
71
+ break;
72
+ }
73
+ }
61
74
  }
62
75
  return out;
63
76
  }
@@ -174,8 +174,8 @@ export async function prepareMemory(input) {
174
174
  };
175
175
  const onEngineIncident = (err) => deps.onError?.(err, { phase: "memory", sessionId });
176
176
  const adoptionRestricted = input.memoryPersistenceDeclared === false;
177
- const armCaptureOptOut = (eng) => {
178
- const standingRead = eng.sessionCaptureOptOutOrFault(sessionId);
177
+ const armCaptureOptOut = async (eng) => {
178
+ const standingRead = await eng.sessionCaptureOptOutOrFault(sessionId);
179
179
  if (standingRead.fault) {
180
180
  captureIndeterminate = true;
181
181
  deps.onError?.(new Error(`the capture opt-out record store is unreadable at prepare — this session's capture state is indeterminate, and every memory commit boundary (read-side adoption included) suppresses fail-closed until the store answers.`), { phase: "memory", sessionId });
@@ -199,7 +199,7 @@ export async function prepareMemory(input) {
199
199
  let anyFault = standingRead.fault;
200
200
  let found = false;
201
201
  for (const [ancestorId, ancestorDir] of rows) {
202
- const samePlane = eng.sessionCaptureOptOutOrFault(ancestorId);
202
+ const samePlane = await eng.sessionCaptureOptOutOrFault(ancestorId);
203
203
  if (samePlane.record !== undefined) {
204
204
  found = true;
205
205
  break;
@@ -208,7 +208,7 @@ export async function prepareMemory(input) {
208
208
  anyFault = true;
209
209
  if (ancestorDir !== undefined && ancestorDir !== eng.controlPlaneDir) {
210
210
  try {
211
- const crossPlane = deps.memoryCaptureRecordStore !== undefined ? deps.memoryCaptureRecordStore({ controlDir: ancestorDir }).read(ancestorId) : readSessionCaptureOptOut(ancestorDir, ancestorId);
211
+ const crossPlane = deps.memoryCaptureRecordStore !== undefined ? await deps.memoryCaptureRecordStore({ controlDir: ancestorDir }).read(ancestorId) : readSessionCaptureOptOut(ancestorDir, ancestorId);
212
212
  if (crossPlane !== undefined) {
213
213
  found = true;
214
214
  break;
@@ -230,7 +230,7 @@ export async function prepareMemory(input) {
230
230
  }
231
231
  let forkInherited = false;
232
232
  if (!floored && !captureDeclared && standing === undefined && input.captureForkOrigin !== undefined) {
233
- const src = eng.sessionCaptureOptOutOrFault(input.captureForkOrigin);
233
+ const src = await eng.sessionCaptureOptOutOrFault(input.captureForkOrigin);
234
234
  if (src.record !== undefined)
235
235
  forkInherited = true;
236
236
  else if (src.fault && !captureIndeterminate) {
@@ -246,7 +246,7 @@ export async function prepareMemory(input) {
246
246
  throw captureOptOutDeniedError(verdict.detail);
247
247
  }
248
248
  if (standing === undefined) {
249
- const outcome = eng.markSessionCaptureOptOut(sessionId, captureDeclared ? `declared on TaskSpec.memory.capture` : forkInherited ? `inherited capture opt-out from the forked source session` : `inherited capture opt-out floor from the spawning session`);
249
+ const outcome = await eng.markSessionCaptureOptOut(sessionId, captureDeclared ? `declared on TaskSpec.memory.capture` : forkInherited ? `inherited capture opt-out from the forked source session` : `inherited capture opt-out floor from the spawning session`);
250
250
  if (outcome === "unpersisted") {
251
251
  deliverEngineNotice(deps.onNotice, memoryCaptureOptOutUnpersistedNotice({ sessionId, ingress: "declaration" }));
252
252
  throw captureOptOutUnpersistedError(sessionId, "declaration");
@@ -287,14 +287,14 @@ export async function prepareMemory(input) {
287
287
  let injectFn;
288
288
  let harvestBoth;
289
289
  let toolPlanes;
290
- const admitNothingOptsNow = () => {
290
+ const admitNothingOptsNow = async () => {
291
291
  if (input.memoryPersistenceDeclared === false) {
292
292
  return { admitNothing: { reason: "harvest admitted nothing: memory persistence is declared unavailable for this session (memoryPersistenceCapable: false)" } };
293
293
  }
294
294
  if (captureLineageIndeterminate) {
295
295
  return { admitNothing: { reason: `harvest admitted nothing: this session's fork-lineage metadata is unreadable — whether it continues an opted-out source cannot be established, so commits are suppressed fail-closed` } };
296
296
  }
297
- const own = captureOptedOut ? { record: { at: 0, reason: "in-run state" }, fault: false } : writeEngine.sessionCaptureOptOutOrFault(sessionId);
297
+ const own = captureOptedOut ? { record: { at: 0, reason: "in-run state" }, fault: false } : await writeEngine.sessionCaptureOptOutOrFault(sessionId);
298
298
  if (own.record !== undefined) {
299
299
  return { admitNothing: { reason: `harvest admitted nothing: this session declared memory capture opt-out (memory.capture: "off")` } };
300
300
  }
@@ -309,11 +309,11 @@ export async function prepareMemory(input) {
309
309
  if (input.captureForkOrigin !== undefined && !ancestors.has(input.captureForkOrigin))
310
310
  ancestors.set(input.captureForkOrigin, undefined);
311
311
  for (const [ancestorId, ancestorDir] of ancestors) {
312
- const samePlane = writeEngine.sessionCaptureOptOutOrFault(ancestorId);
312
+ const samePlane = await writeEngine.sessionCaptureOptOutOrFault(ancestorId);
313
313
  let crossPlane;
314
314
  if (ancestorDir !== undefined && ancestorDir !== writeEngine.controlPlaneDir) {
315
315
  try {
316
- crossPlane = deps.memoryCaptureRecordStore !== undefined ? deps.memoryCaptureRecordStore({ controlDir: ancestorDir }).read(ancestorId) : readSessionCaptureOptOut(ancestorDir, ancestorId);
316
+ crossPlane = deps.memoryCaptureRecordStore !== undefined ? await deps.memoryCaptureRecordStore({ controlDir: ancestorDir }).read(ancestorId) : readSessionCaptureOptOut(ancestorDir, ancestorId);
317
317
  }
318
318
  catch {
319
319
  return { admitNothing: { reason: `harvest admitted nothing: a spawning session's capture record store is unreadable — the floor state is indeterminate and commits are suppressed fail-closed` } };
@@ -362,7 +362,7 @@ export async function prepareMemory(input) {
362
362
  const personal = createPersonalEngine(personalBackendChosen);
363
363
  const personalEngine = personal.engine;
364
364
  const p = planes;
365
- armCaptureOptOut(p.writePlane === "personal" ? personalEngine : projectEngine);
365
+ await armCaptureOptOut(p.writePlane === "personal" ? personalEngine : projectEngine);
366
366
  const projectHandle = await projectEngine.materialize(p.project, p.writePlane === "project" ? memorySpec.writeScope : null, { adoptionRestricted: adoptionRestricted || captureOptedOut || captureIndeterminate, sessionId });
367
367
  materializedResidue.push(...planeScopes(p.project, p.writePlane === "project" ? memorySpec.writeScope : null));
368
368
  const personalHandle = await personalEngine.materialize(p.personal, p.writePlane === "personal" ? memorySpec.writeScope : null, { adoptionRestricted: adoptionRestricted || captureOptedOut || captureIndeterminate, sessionId });
@@ -393,7 +393,7 @@ export async function prepareMemory(input) {
393
393
  harvestBoth = async () => {
394
394
  const writeFirst = writeIsPersonal ? [personalEngine, personalHandle] : [projectEngine, projectHandle];
395
395
  const readOther = writeIsPersonal ? [projectEngine, projectHandle] : [personalEngine, personalHandle];
396
- const writeReport = await writeFirst[0].harvest(writeFirst[1], { ...pollutedOpts(writeFirst[0]), sessionId, ...admitNothingOptsNow() });
396
+ const writeReport = await writeFirst[0].harvest(writeFirst[1], { ...pollutedOpts(writeFirst[0]), sessionId, ...(await admitNothingOptsNow()) });
397
397
  let readReport;
398
398
  let readFailure;
399
399
  try {
@@ -411,14 +411,14 @@ export async function prepareMemory(input) {
411
411
  else if (personalOnly) {
412
412
  const personal = createPersonalEngine(choosePersonalBackend());
413
413
  const personalEngine = personal.engine;
414
- armCaptureOptOut(personalEngine);
414
+ await armCaptureOptOut(personalEngine);
415
415
  const handle = await personalEngine.materialize(memorySpec.scopes, memorySpec.writeScope, { adoptionRestricted: adoptionRestricted || captureOptedOut || captureIndeterminate, sessionId });
416
416
  materializedResidue.push(...planeScopes(memorySpec.scopes, memorySpec.writeScope));
417
417
  memoryMarkedEntriesServed = handle.markedEntriesPresent === true;
418
418
  writeEngine = personalEngine;
419
419
  writeHandle = handle;
420
420
  injectFn = () => personalEngine.inject(handle, { writeToolMounted: input.writeToolsMounted, reminderMark: input.reminderMark });
421
- harvestBoth = () => personalEngine.harvest(handle, { ...pollutedOpts(personalEngine), sessionId, ...admitNothingOptsNow() });
421
+ harvestBoth = async () => personalEngine.harvest(handle, { ...pollutedOpts(personalEngine), sessionId, ...(await admitNothingOptsNow()) });
422
422
  toolPlanes = [
423
423
  {
424
424
  backend: retrievalBackend(personal.backend, adoptionRestricted || captureOptedOut || captureIndeterminate || memorySpec.writeScope === null),
@@ -439,14 +439,14 @@ export async function prepareMemory(input) {
439
439
  ...captureStoreOpt(backend, identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) : deriveRepoControlPlaneDir(engineRoot, repoRoot)),
440
440
  ...(input.deps.memoryConsolidation !== undefined ? { consolidation: input.deps.memoryConsolidation } : {}),
441
441
  });
442
- armCaptureOptOut(engine);
442
+ await armCaptureOptOut(engine);
443
443
  const handle = await engine.materialize(memorySpec.scopes, memorySpec.writeScope, { adoptionRestricted: adoptionRestricted || captureOptedOut || captureIndeterminate, sessionId });
444
444
  materializedResidue.push(...planeScopes(memorySpec.scopes, memorySpec.writeScope));
445
445
  memoryMarkedEntriesServed = handle.markedEntriesPresent === true;
446
446
  writeEngine = engine;
447
447
  writeHandle = handle;
448
448
  injectFn = () => engine.inject(handle, { writeToolMounted: input.writeToolsMounted, reminderMark: input.reminderMark });
449
- harvestBoth = () => engine.harvest(handle, { ...pollutedOpts(engine), sessionId, ...admitNothingOptsNow() });
449
+ harvestBoth = async () => engine.harvest(handle, { ...pollutedOpts(engine), sessionId, ...(await admitNothingOptsNow()) });
450
450
  toolPlanes = [
451
451
  {
452
452
  backend: retrievalBackend(backend, adoptionRestricted || captureOptedOut || captureIndeterminate || memorySpec.writeScope === null),
@@ -457,7 +457,7 @@ export async function prepareMemory(input) {
457
457
  },
458
458
  ];
459
459
  }
460
- memoryWriteGateRef.current = (w) => {
460
+ memoryWriteGateRef.current = async (w) => {
461
461
  if (readOnlyEngine !== undefined && readOnlyHandle !== undefined) {
462
462
  const ro = readOnlyEngine.gateWrite(readOnlyHandle, w.key, w.content);
463
463
  if (!ro.ok)
@@ -477,7 +477,7 @@ export async function prepareMemory(input) {
477
477
  {
478
478
  const root = writeHandle.writableRoot;
479
479
  if (w.key === root || w.key.startsWith(`${root}${sep}`)) {
480
- const state = captureOptedOut ? { record: { at: 0, reason: "in-run state" }, fault: false } : writeEngine.sessionCaptureOptOutOrFault(sessionId);
480
+ const state = captureOptedOut ? { record: { at: 0, reason: "in-run state" }, fault: false } : await writeEngine.sessionCaptureOptOutOrFault(sessionId);
481
481
  if (state.record !== undefined || state.fault) {
482
482
  return {
483
483
  ok: false,
@@ -555,12 +555,19 @@ export async function prepareMemory(input) {
555
555
  inject: injectFn,
556
556
  harvest: harvestSafe,
557
557
  captureOptOut: {
558
- optedOut: () => captureOptedOut || writeEngine.sessionCaptureOptOutOrFault(sessionId).record !== undefined,
558
+ optedOut: () => {
559
+ if (captureOptedOut)
560
+ return true;
561
+ const r = writeEngine.sessionCaptureOptOutOrFault(sessionId);
562
+ return r instanceof Promise ? r.then((s) => s.record !== undefined) : r.record !== undefined;
563
+ },
559
564
  indeterminate: () => {
560
565
  if (captureOptedOut)
561
566
  return false;
562
- const s = writeEngine.sessionCaptureOptOutOrFault(sessionId);
563
- return s.record === undefined && (captureIndeterminate || s.fault);
567
+ const r = writeEngine.sessionCaptureOptOutOrFault(sessionId);
568
+ return r instanceof Promise
569
+ ? r.then((s) => s.record === undefined && (captureIndeterminate || s.fault))
570
+ : r.record === undefined && (captureIndeterminate || r.fault);
564
571
  },
565
572
  flip: async (reason) => {
566
573
  const entitlement = await resolveCaptureEntitlementFresh();
@@ -576,7 +583,7 @@ export async function prepareMemory(input) {
576
583
  throw e;
577
584
  }
578
585
  const trimmed = reason?.trim();
579
- const outcome = writeEngine.markSessionCaptureOptOut(sessionId, trimmed !== undefined && trimmed !== "" ? `host flip verb: ${trimmed.slice(0, 200)}` : "host flip verb");
586
+ const outcome = await writeEngine.markSessionCaptureOptOut(sessionId, trimmed !== undefined && trimmed !== "" ? `host flip verb: ${trimmed.slice(0, 200)}` : "host flip verb");
580
587
  if (outcome === "unpersisted") {
581
588
  deliverEngineNotice(deps.onNotice, memoryCaptureOptOutUnpersistedNotice({ sessionId, ingress: "flip-verb" }));
582
589
  throw captureOptOutUnpersistedError(sessionId, "flip-verb");
@@ -550,8 +550,9 @@ export interface Prepared {
550
550
  * the memory session mounted.
551
551
  */
552
552
  captureOptOut?: {
553
- optedOut: () => boolean;
554
- indeterminate: () => boolean;
553
+ /** Dual form: a sync capture store answers synchronously; a Promise-form store answers a Promise. `await` is correct on either arm. */
554
+ optedOut: () => boolean | Promise<boolean>;
555
+ indeterminate: () => boolean | Promise<boolean>;
555
556
  flip: (reason?: string) => Promise<{
556
557
  outcome: "created" | "existed";
557
558
  }>;
@@ -1120,7 +1121,8 @@ export interface PrepareResume {
1120
1121
  * to restore — prepare rebuilds the per-task env via `deps.executionEnvFactory` then `resumeVM(snapshotId)`
1121
1122
  * + `postResumeInit()` (instead of running on a fresh, empty env). Threaded HERE (not via `ResumeTaskConfig`)
1122
1123
  * so the factory stays a deployment-level `RunnerDeps` capability and never pollutes `TaskSpec` — preserving
1123
- * the "untrusted caller can't inject an env" red line (remote-env.ts:234, code-ready council round-2). */
1124
+ * the "untrusted caller can't inject an env" red line ({@link import("../remote-env.js").ExecutionEnvFactory}'s
1125
+ * own contract states it: "Lives on `RunnerDeps` (deployment-level) — NOT on `TaskSpec`"; code-ready council round-2). */
1124
1126
  workspaceHandle?: import("../remote-env.js").WorkspaceHandle;
1125
1127
  /**
1126
1128
  * design/174 — the call id of the CONTENT ask whose answer an operator's approval was spent on, when