@sema-agent/server 7.48.0 → 7.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/USAGE.md +39 -0
  2. package/dist/approval-card.d.ts +1 -1
  3. package/dist/auth-bridge.d.ts +2 -2
  4. package/dist/auth-bridge.js +1 -1
  5. package/dist/boot/config-center.js +2 -2
  6. package/dist/boot/engine-lease.d.ts +95 -0
  7. package/dist/boot/engine-lease.js +56 -0
  8. package/dist/boot/runner-deps.d.ts +4 -2
  9. package/dist/boot/shutdown.d.ts +2 -0
  10. package/dist/boot/shutdown.js +16 -0
  11. package/dist/config-center/apply-effective.js +1 -1
  12. package/dist/config-center/http-client.d.ts +1 -1
  13. package/dist/config-center/http-client.js +1 -1
  14. package/dist/config-center/restart-signal.js +1 -1
  15. package/dist/config-center/skills-mcp.js +1 -1
  16. package/dist/config-provider.d.ts +2 -2
  17. package/dist/config-provider.js +2 -2
  18. package/dist/config-types.d.ts +21 -2
  19. package/dist/config.js +24 -2
  20. package/dist/fleet/subagent-tail-bus.d.ts +1 -1
  21. package/dist/fleet/subagent-tail-bus.js +10 -0
  22. package/dist/fleet-client.d.ts +1 -1
  23. package/dist/fleet-client.js +1 -1
  24. package/dist/fleet-lease.d.ts +1 -1
  25. package/dist/fleet-lease.js +1 -1
  26. package/dist/hooks/hook-runner.d.ts +1 -1
  27. package/dist/hooks/hook-runner.js +1 -1
  28. package/dist/http/routes/approvals-assistant.js +65 -0
  29. package/dist/http/routes/notify-wake.js +1 -1
  30. package/dist/http/routes/runs.d.ts +1 -1
  31. package/dist/http/routes/runs.js +1 -1
  32. package/dist/http/routes/sessions.js +1 -1
  33. package/dist/http/routes/tasks.js +15 -9
  34. package/dist/http/send.d.ts +14 -0
  35. package/dist/http/send.js +5 -0
  36. package/dist/http/server.d.ts +5 -0
  37. package/dist/http/server.js +24 -8
  38. package/dist/http/wire-types.d.ts +1 -1
  39. package/dist/orchestration/workflow-completion-inbox.d.ts +12 -1
  40. package/dist/orchestration/workflow-completion-inbox.js +6 -1
  41. package/dist/org-memory-admission.js +1 -1
  42. package/dist/plugins/checkpoint-store-sql.d.ts +108 -1
  43. package/dist/plugins/checkpoint-store-sql.js +65 -0
  44. package/dist/plugins/local-checkpoint-store.d.ts +14 -1
  45. package/dist/plugins/local-checkpoint-store.js +22 -1
  46. package/dist/plugins/scheduler-support.d.ts +1 -1
  47. package/dist/plugins/scheduler-support.js +1 -1
  48. package/dist/plugins/usage-window-store-sql.d.ts +2 -2
  49. package/dist/plugins/usage-window-store-sql.js +17 -6
  50. package/dist/run-local.js +1 -1
  51. package/dist/runs.js +3 -3
  52. package/dist/runtime-caps-resolver.d.ts +1 -1
  53. package/dist/runtime-governance.d.ts +1 -1
  54. package/dist/task-settings.d.ts +1 -1
  55. package/dist/trace/core-keyset-guard.d.ts +3 -3
  56. package/dist/trace/engine-notice-wire.d.ts +13 -3
  57. package/dist/trace/engine-notice-wire.js +4 -0
  58. package/dist/trace/injection-tier.d.ts +33 -0
  59. package/dist/trace/injection-tier.js +7 -0
  60. package/dist/trace/project.d.ts +5 -1
  61. package/dist/trace/project.js +5 -0
  62. package/package.json +3 -3
@@ -1,5 +1,5 @@
1
1
  import { type Checkpoint, type CheckpointSummary, type CheckpointToken, type PendingSteerInput, type ReopenReason, type ResolveExpectation, type ResumeOutcome, type StoreDurability, type StoreFidelity } from "@sema-agent/core";
2
- import { type PendingCheckpoint } from "./checkpoint-store-sql.js";
2
+ import { type DecidedApprovalRecord, type PendingCheckpoint } from "./checkpoint-store-sql.js";
3
3
  export interface LocalCheckpointStoreOptions {
4
4
  /** taskId join for the pending card (the local FileRunStore's `getActiveTaskId`). Absent ⇒ `taskId: null`. */
5
5
  getActiveTaskId?: (sessionId: string) => Promise<string | undefined>;
@@ -91,6 +91,19 @@ export declare class LocalCheckpointStore {
91
91
  * 索引里,其 expired 对本探针不可见(core File store 的 listByScope 是 pending-only,无法枚举历史行回填)。
92
92
  * 旧存量锁死的恢复把手 = cancel verb([868]①,不依赖本索引);增量行全覆盖。 */
93
93
  hasExpiredBySession(sessionId: string): Promise<boolean>;
94
+ /**
95
+ * #368 件1 —— SQL 孪生 `findDecidedApprovalsForBinding` 的本地形(判据、词表、失败方向逐条同源;
96
+ * 那处顶注是唯一真源,这里只记两处**本地形自己的**差别)。
97
+ *
98
+ * · 枚举面走 [868] 的 `sessionTokens` 索引 —— 与 `hasExpiredBySession` 同一条路(core 的 File store
99
+ * 只能 pending-only 枚举,跨态读只有 `get(token)`),所以它那条**存量限制**在这里逐字同样成立:
100
+ * 索引建立之前 park 的老行对本口不可见 ⇒ 回放判别不出 ⇒ 调用方逐字回落 404(保守方向)。
101
+ * · **没有排序**:本口交出全部命中行,由调用方按「判词一致才回放」判(理由见 SQL 孪生顶注的
102
+ * codex R1-[medium] 四段——正是为了消掉「local 只有 createdAt、SQL 有 decided_at_ms」这条排序键
103
+ * 分岔)。同理回放的 wire 形**刻意不投** `decidedAtMs`:core 的 `Checkpoint` 上没有那个字段,
104
+ * 投了就是「SQL 车道有、local 车道没有」的键集分岔。
105
+ */
106
+ findDecidedApprovalsForBinding(sessionId: string, boundCallId: string): Promise<DecidedApprovalRecord[]>;
94
107
  /** `undefined` = no pending row; else the owner scope (local scopes are always strings — never null). */
95
108
  peekPendingScope(sessionId: string): Promise<string | null | undefined>;
96
109
  /**
@@ -3,7 +3,7 @@ import { existsSync, readFileSync, readdirSync, renameSync, unlinkSync, mkdirSyn
3
3
  import { join } from "node:path";
4
4
  import { isApprovalGateKind } from "../tool-approval.js";
5
5
  import { FileCheckpointStore, atomicWriteFile, sanitizePathComponent, } from "@sema-agent/core";
6
- import { boundedRuleOffers, boundedToolInput, TERMINAL_BACKSTOP_MS, TERMINAL_GRACE_MS } from "./checkpoint-store-sql.js";
6
+ import { approvalDecisionOfWinner, approvalPayloadFingerprint, boundedRuleOffers, boundedToolInput, TERMINAL_BACKSTOP_MS, TERMINAL_GRACE_MS } from "./checkpoint-store-sql.js";
7
7
  import { UNMANAGED_RETENTION } from "./retention-store-sql.js";
8
8
  function terminalAtOf(cp) {
9
9
  return Math.max(cp.createdAt + TERMINAL_BACKSTOP_MS, (cp.deadline ?? 0) + TERMINAL_GRACE_MS);
@@ -195,6 +195,27 @@ export class LocalCheckpointStore {
195
195
  }
196
196
  return false;
197
197
  }
198
+ async findDecidedApprovalsForBinding(sessionId, boundCallId) {
199
+ const tokens = this.sessionTokens.get(sessionId);
200
+ if (!tokens)
201
+ return [];
202
+ const out = [];
203
+ for (const t of [...tokens]) {
204
+ const cp = await this.inner.get(t);
205
+ if (!cp || cp.status !== "resolved")
206
+ continue;
207
+ const pa = cp.pendingAction;
208
+ if (pa?.toolCallId !== boundCallId)
209
+ continue;
210
+ out.push({
211
+ scope: cp.scope,
212
+ boundInputHash: pa?.boundInputHash ?? null,
213
+ decision: approvalDecisionOfWinner(cp.resolvedOutcome, boundCallId),
214
+ payloadFp: cp.resolvedOutcome === undefined ? null : approvalPayloadFingerprint(cp.resolvedOutcome),
215
+ });
216
+ }
217
+ return out;
218
+ }
198
219
  async peekPendingScope(sessionId) {
199
220
  for (const { cp } of await this.pendings())
200
221
  if (cp.sessionId === sessionId)
@@ -1,6 +1,6 @@
1
1
  import { SchedulerError } from "@sema-agent/core";
2
2
  import type { SchedulerCapability, ScheduledIntent, SchedulerContext, ScheduledTaskId, ScheduledTaskSummary } from "@sema-agent/core";
3
- import type { SchedulerRecord } from "@sema-agent/registry-core/scheduler-store";
3
+ import type { SchedulerRecord } from "@sema-agent/settings-schema/scheduler-store";
4
4
  export type { SchedulerRecord };
5
5
  /** core `Result<T,E>` shape (harness/types) — `{ok:true,value} | {ok:false,error}`. */
6
6
  type Result<T, E> = {
@@ -2,7 +2,7 @@ import { join } from "node:path";
2
2
  import { homedir } from "node:os";
3
3
  import { randomBytes, createHash } from "node:crypto";
4
4
  import { SchedulerError, isValidCronExpr } from "@sema-agent/core";
5
- import { loadSchedulerStore, saveSchedulerStore } from "@sema-agent/registry-core/node";
5
+ import { loadSchedulerStore, saveSchedulerStore } from "@sema-agent/settings-schema/node";
6
6
  const ok = (value) => ({ ok: true, value });
7
7
  const err = (error) => ({ ok: false, error });
8
8
  export const DEFAULT_SCHEDULER_CAPS = {
@@ -30,13 +30,13 @@ export declare function ensurePgUsageWindowSchema(q: (text: string, params?: unk
30
30
  export declare class TiDBUsageWindowStore implements UsageWindowStore {
31
31
  private readonly pool;
32
32
  constructor(pool: MySqlPool);
33
- charge(key: string, tokens: number, at: number, windows: readonly UsageWindow[]): Promise<void>;
33
+ charge(key: string, tokens: number, at: number, windows: readonly UsageWindow[], costMicroUsd?: number | null): Promise<void>;
34
34
  read(key: string, windows: readonly UsageWindow[], now: number): Promise<readonly UsageWindowReading[]>;
35
35
  }
36
36
  export declare class PgUsageWindowStore implements UsageWindowStore {
37
37
  private readonly pool;
38
38
  constructor(pool: PgPool);
39
- charge(key: string, tokens: number, at: number, windows: readonly UsageWindow[]): Promise<void>;
39
+ charge(key: string, tokens: number, at: number, windows: readonly UsageWindow[], costMicroUsd?: number | null): Promise<void>;
40
40
  read(key: string, windows: readonly UsageWindow[], now: number): Promise<readonly UsageWindowReading[]>;
41
41
  }
42
42
  //# sourceMappingURL=usage-window-store-sql.d.ts.map
@@ -14,13 +14,24 @@ function parseRecord(raw, key) {
14
14
  }
15
15
  const badShape = (detail) => new Error(`usage_window record for key "${key}" has an invalid shape (${detail}) — refusing to mis-count a governance window`);
16
16
  const finite = (x) => typeof x === "number" && Number.isFinite(x);
17
+ const costKeys = (e) => {
18
+ if (e.costMicroUsd !== undefined && (typeof e.costMicroUsd !== "number" || !Number.isSafeInteger(e.costMicroUsd) || e.costMicroUsd < 0)) {
19
+ throw badShape("`costMicroUsd` present but not a non-negative safe integer");
20
+ }
21
+ if (e.costUnknown !== undefined && e.costUnknown !== true)
22
+ throw badShape("`costUnknown` present but not literal true");
23
+ return {
24
+ ...(e.costMicroUsd !== undefined ? { costMicroUsd: e.costMicroUsd } : {}),
25
+ ...(e.costUnknown === true ? { costUnknown: true } : {}),
26
+ };
27
+ };
17
28
  const slots = r.slots.map((s) => {
18
29
  if (s === null || typeof s !== "object")
19
30
  throw badShape("`slots` entry is not an object");
20
31
  const { at, tokens } = s;
21
32
  if (!finite(at) || !finite(tokens))
22
33
  throw badShape("`slots` entry has a non-finite-numeric `at`/`tokens`");
23
- return { at, tokens };
34
+ return { at, tokens, ...costKeys(s) };
24
35
  });
25
36
  const buckets = r.buckets.map((b) => {
26
37
  if (b === null || typeof b !== "object")
@@ -29,7 +40,7 @@ function parseRecord(raw, key) {
29
40
  if (!finite(windowMs) || windowMs <= 0 || !finite(openedAt) || !finite(tokens)) {
30
41
  throw badShape("`buckets` entry has a non-finite-numeric `windowMs`/`openedAt`/`tokens` (windowMs must be > 0)");
31
42
  }
32
- return { windowMs, openedAt, tokens };
43
+ return { windowMs, openedAt, tokens, ...costKeys(b) };
33
44
  });
34
45
  return { slots, buckets };
35
46
  }
@@ -54,7 +65,7 @@ export class TiDBUsageWindowStore {
54
65
  constructor(pool) {
55
66
  this.pool = pool;
56
67
  }
57
- async charge(key, tokens, at, windows) {
68
+ async charge(key, tokens, at, windows, costMicroUsd) {
58
69
  const c = await this.pool.getConnection();
59
70
  try {
60
71
  await c.beginTransaction();
@@ -68,7 +79,7 @@ export class TiDBUsageWindowStore {
68
79
  throw new Error(`usage_window row for key "${key}" vanished between ensure and lock — refusing to drop a governance charge`);
69
80
  }
70
81
  const record = parseRecord(rows[0].record, key);
71
- const next = chargeUsageRecord(record, tokens, at, windows);
82
+ const next = chargeUsageRecord(record, tokens, at, windows, costMicroUsd);
72
83
  await c.query(`UPDATE ${USAGE_WINDOW_TABLE} SET record = ?, updated_at_ms = ? WHERE usage_key = ?`, [JSON.stringify(next), at, key]);
73
84
  await c.commit();
74
85
  }
@@ -91,7 +102,7 @@ export class PgUsageWindowStore {
91
102
  constructor(pool) {
92
103
  this.pool = pool;
93
104
  }
94
- async charge(key, tokens, at, windows) {
105
+ async charge(key, tokens, at, windows, costMicroUsd) {
95
106
  const c = await this.pool.connect();
96
107
  try {
97
108
  await c.query("BEGIN");
@@ -105,7 +116,7 @@ export class PgUsageWindowStore {
105
116
  throw new Error(`usage_window row for key "${key}" vanished between ensure and lock — refusing to drop a governance charge`);
106
117
  }
107
118
  const record = parseRecord(rows[0].record, key);
108
- const next = chargeUsageRecord(record, tokens, at, windows);
119
+ const next = chargeUsageRecord(record, tokens, at, windows, costMicroUsd);
109
120
  await c.query(`UPDATE ${USAGE_WINDOW_TABLE} SET record = $1, updated_at_ms = $2 WHERE usage_key = $3`, [JSON.stringify(next), at, key]);
110
121
  await c.query("COMMIT");
111
122
  }
package/dist/run-local.js CHANGED
@@ -4,7 +4,7 @@ import { join } from "node:path";
4
4
  import { realpathSync } from "node:fs";
5
5
  import { createInterface } from "node:readline/promises";
6
6
  import { fileURLToPath } from "node:url";
7
- import { loadRemoteExec } from "@sema-agent/registry-core/node";
7
+ import { loadRemoteExec } from "@sema-agent/settings-schema/node";
8
8
  import { Runner, uuidv7, parseModelMention, AdoptionError, FileStorageBackend, NodeExecutionEnv, NodeLspManager, TtlSessionStore, CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore } from "@sema-agent/core";
9
9
  import { hasOperatorGateIntent } from "./approval.js";
10
10
  import { createBrain } from "./brain.js";
package/dist/runs.js CHANGED
@@ -252,7 +252,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
252
252
  }
253
253
  }
254
254
  },
255
- onTaskNotification: (n) => {
255
+ onTaskNotification: (n, opts) => {
256
256
  if (n.task_type === "workflow")
257
257
  return;
258
258
  defaultSubagentTailBus.forgetHandleContentMode(n.task_id);
@@ -263,12 +263,12 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
263
263
  const parked = !legLive && Boolean(workflowCompletionInbox && spec.sessionId);
264
264
  completionDiagLog?.("task_notification_observed", { route: "bg-run-start", taskId: n.task_id, taskType: n.task_type, status: n.status, hadFleetRow: hadRow, legLive, parkedDurable: parked });
265
265
  if (parked) {
266
- void workflowCompletionInbox.enqueue(taskNotificationInboxEntry(spec.sessionId, elicitOwner ?? owner, n, Date.now(), taskId)).catch((err) => completionDiagLog?.("park_enqueue_failed", { route: "bg-run-start", taskId: n.task_id, err: err instanceof Error ? err.message : String(err) }));
266
+ void workflowCompletionInbox.enqueue(taskNotificationInboxEntry(spec.sessionId, elicitOwner ?? owner, n, Date.now(), taskId, opts?.priority)).catch((err) => completionDiagLog?.("park_enqueue_failed", { route: "bg-run-start", taskId: n.task_id, err: err instanceof Error ? err.message : String(err) }));
267
267
  }
268
268
  else if (legLive) {
269
269
  const key = taskNotificationStreamKey(n);
270
270
  if (notifiedKeys.get(key) === undefined) {
271
- notifiedKeys.set(key, append("task_notification", taskNotificationEventData({ notification: n })).then(() => true, () => false));
271
+ notifiedKeys.set(key, append("task_notification", taskNotificationEventData({ notification: n, ...(opts?.priority !== undefined ? { priority: opts.priority } : {}) })).then(() => true, () => false));
272
272
  }
273
273
  }
274
274
  },
@@ -27,7 +27,7 @@
27
27
  * this seam feeds the ENGINE, and `allowUltracode` is enforced at the service settings-resolution layer, not here.
28
28
  */
29
29
  import type { RuntimeCaps } from "@sema-agent/core";
30
- import type { EntitlementRuntimeCaps } from "@sema-agent/registry-core";
30
+ import type { EntitlementRuntimeCaps } from "@sema-agent/settings-schema";
31
31
  import { fetchPrincipalCaps, type ExecutionRuling } from "./config-center/facade.js";
32
32
  import type { ScenarioRuling } from "./capabilities/scenarios.js";
33
33
  import type { ServiceConfig } from "./config-types.js";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Runtime governance compiler — the "second baton" (center §10, core 1.114.0): turn the
3
- * operator's declarative `runtime.autonomy` + `runtime.commandPolicy` (`@sema-agent/registry-core`,
3
+ * operator's declarative `runtime.autonomy` + `runtime.commandPolicy` (`@sema-agent/settings-schema`,
4
4
  * carried to the service via sema-registry `EffectiveConfig.runtime`) into TIGHTEN-ONLY overrides on the
5
5
  * per-task `TaskSpec`. Pure so it is unit-testable in isolation (the spec is otherwise assembled inside
6
6
  * main.ts's `resolveSpec`).
@@ -36,7 +36,7 @@
36
36
  * main.ts 单用户闸),仅 malformed 才报 deferred;旧「需要 remote hook-runner」是接线前拍照。)
37
37
  */
38
38
  import { type ExecutionEnv, type TaskSpec, type ThinkingLevel, type ToolPolicy, type PromptProvider } from "@sema-agent/core";
39
- import type { HooksConfig } from "@sema-agent/registry-core/hooks";
39
+ import type { HooksConfig } from "@sema-agent/settings-schema/hooks";
40
40
  /** The permission MODE a client may request (SDK `SettingsPermissions.defaultMode`), the [820]/[822] five-word
41
41
  * table. ⚠️ This is a DELIBERATE deployment set, NOT a copy of any CC set (A-065 P2-1 corpus check): CC's own
42
42
  * full enum is SIX words (= ours + `dontAsk`), CC's managed/remote form accepts FOUR (= ours − `bypassPermissions`,
@@ -27,9 +27,9 @@ type _GuardBgNotif = AssertAllKeysHandled<Exclude<keyof BackgroundChildEvent, Bg
27
27
  type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
28
28
  type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
29
29
  type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval";
30
- type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason" | "hasBidiControls";
30
+ type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason" | "hasBidiControls" | "previewWithheld";
31
31
  type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
32
- type TaskEventHandled = "text_delta" | "reasoning_delta" | "tool_start" | "tool_end" | "turn_end" | "compacted" | "diagnostics" | "message_committed" | "status" | "task_notification" | "task_progress" | "steering_injected" | "workspace_changed" | "done" | "context_usage" | "compaction_outcome" | "human_input" | "wiring_manifest";
32
+ type TaskEventHandled = "text_delta" | "reasoning_delta" | "tool_start" | "tool_end" | "turn_end" | "compacted" | "diagnostics" | "message_committed" | "status" | "task_notification" | "task_progress" | "steering_injected" | "workspace_changed" | "done" | "context_usage" | "compaction_outcome" | "human_input" | "wiring_manifest" | "text_end";
33
33
  type _GuardTaskEvent = AssertAllKeysHandled<Exclude<TaskEvent["type"], TaskEventHandled>>;
34
34
  type MailboxProjected = "seq" | "from" | "content" | "sentAt" | "hopChain";
35
35
  type _GuardMailbox = AssertAllKeysHandled<Exclude<keyof MailboxMessage, MailboxProjected>>;
@@ -45,7 +45,7 @@ type _GuardPermissionTraceReverse = AssertAllKeysHandled<Exclude<PermissionTrace
45
45
  type AssertNonEmpty<T> = [T] extends [never] ? "EMPTY" : never;
46
46
  type _GuardPermissionTraceNonEmpty = AssertAllKeysHandled<AssertNonEmpty<PermissionTraceKind>>;
47
47
  type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls";
48
- type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "preview" | "principal" | "sourceTaskId" | "contentKind" | "restoreMode" | "checkpointId";
48
+ type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "preview" | "principal" | "sourceTaskId" | "contentKind" | "restoreMode" | "checkpointId" | "previewWithheld";
49
49
  type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
50
50
  export type CoreKeysetGuards = [
51
51
  _GuardNotification,
@@ -1,10 +1,20 @@
1
1
  /**
2
- * 上 wire 的通告码 —— **显式闭集常量表**(设计稿 ①,起步三码)。
2
+ * 上 wire 的通告码 —— **显式闭集常量表**(设计稿 ①,起步三码;今天九码)。
3
3
  *
4
- * 三码同族:都是「这条 session 的记忆姿态」的披露,resume 之后仍然相关 ⇒ 也进 durable 账本
4
+ * 起步的那六码同族:都是「这条 session 的记忆姿态」的披露,resume 之后仍然相关 ⇒ 也进 durable 账本
5
5
  * (cli 断连补看走既有 events 重放腿,[4634](ii))。
6
+ *
7
+ * ⚠️ **自 #366(core 5.61.0)起本表不再是单一族**:`task.turn_interrupted` 与 `task.user_*_undrained`
8
+ * 两族是前三条**非记忆面**的成员(前者=一次插话真的切断了在飞的 turn;后两者=用户的输入被彻底丢弃)。
9
+ * 判据没有变(受众 × 投递可行性 × 去重单位三条合取,见顶注),变的只是「碰巧六条都是记忆码」这个
10
+ * 事实——别再把「是不是记忆姿态码」当成入册的隐性第四条判据。这条隐性判据正是 `task.user_*_undrained`
11
+ * 两码在明拒表里陈腐多时的帮凶之一(见它们的键注)。
12
+ *
13
+ * 🔴 **表长了就得同步三处**(#366 实测,少一处即门红):`test/engine-notice-wire.test.ts` 的闭集 pin、
14
+ * 同文件 G1 e2e 的全码扇出夹具、以及契约文档附录 D.3 那张表(含**小节标题里的码数** —— 那道门本批
15
+ * 刚立,立完就在下一次加码时自己咬住了)。
6
16
  */
7
- export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed"];
17
+ export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained"];
8
18
  export type EngineNoticeWireCode = (typeof ENGINE_NOTICE_WIRE_CODES)[number];
9
19
  /** 白名单谓词(单点):路由与门都读这一个,不许第二处手抄码串。 */
10
20
  export declare function isEngineNoticeWireCode(code: string): code is EngineNoticeWireCode;
@@ -7,6 +7,10 @@ export const ENGINE_NOTICE_WIRE_CODES = [
7
7
  "memory.hold_opened",
8
8
  "memory.hold_released",
9
9
  "memory.hold_disposed",
10
+ "task.turn_interrupted",
11
+ "steering.parked_input_blocked",
12
+ "task.user_steer_undrained",
13
+ "task.user_followup_undrained",
10
14
  ];
11
15
  const WIRE_CODES = new Set(ENGINE_NOTICE_WIRE_CODES);
12
16
  export function isEngineNoticeWireCode(code) {
@@ -0,0 +1,33 @@
1
+ /**
2
+ * design/373 注入档位(core 5.61.0)的**闭集单点**(#366)。
3
+ *
4
+ * ## 为什么本仓要自己留一份词表
5
+ * 理想写法是复用 core 的谓词 `isSystemInjectionPriority` / 数据表 `SYSTEM_INJECTION_PRIORITIES`。
6
+ * 但 core 5.61.0 **没有把它们放进根 barrel**(`dist/index.d.ts` 只导出了 `SystemInjectionPriority`
7
+ * 这个**类型**),而 `@sema-agent/core` 的 `package.json#exports` 只开了 `.` / `./bench` / `./fixtures`
8
+ * 三口 ⇒ 深导入被 exports map 关死,结构上够不着运行期的那份。
9
+ *
10
+ * 于是用本仓既有的**双向锚**姿势(`http/routes/notify-wake.ts` 的 `NOTIFY_STATUSES` 先例):
11
+ * · `satisfies` 管「表内不许有词表外的词」—— core 删/改档位 ⇒ 编译红;
12
+ * · 穷举断言管「词表不许有表外的词」—— core 加档位 ⇒ 编译红。
13
+ * 两条合起来 = 上游任何一侧的增删都在编译期被逼着处置,而不是运行时静默漂。
14
+ *
15
+ * ## 为什么单独一个叶子模块
16
+ * 消费点有两个且分属两条依赖链:`trace/project.ts`(wire 投影)与
17
+ * `orchestration/workflow-completion-inbox.ts`(durable park 载荷)。手抄两份就是值拷贝漂移源
18
+ * (本仓 [1532] 起的老病族);把它挂在任一方又会在两条链之间拉一条没必要的边。零依赖叶子最干净。
19
+ *
20
+ * 🔴 上游哪天补了根导出:删掉本文件,两处改成一行谓词调用。
21
+ */
22
+ import type { SystemInjectionPriority } from "@sema-agent/core";
23
+ /** 三档闭集,与 core `SystemInjectionPriority` 双向锚(见顶注)。 */
24
+ export declare const INJECTION_TIERS: readonly ["now", "next", "later"];
25
+ /**
26
+ * 边界守卫 —— 闭集内的值原样返回,**其余一律 `undefined`**(= 缺席)。
27
+ *
28
+ * 🔴 方向是**丢键**而不是补默认:core 的键注逐字写着「ABSENT is a fact, not a default …… never read
29
+ * absence as `"later"`」。一个闭集外的值意味着上游发了一个我们不认识的档位,这时候把它原样推上 wire
30
+ * 是在替上游对消费端做承诺,补一个默认则是把「不可知」洗成一条断言 —— 两者都比「说没有」更坏。
31
+ */
32
+ export declare function injectionTierOrAbsent(value: unknown): SystemInjectionPriority | undefined;
33
+ //# sourceMappingURL=injection-tier.d.ts.map
@@ -0,0 +1,7 @@
1
+ export const INJECTION_TIERS = ["now", "next", "later"];
2
+ const _injectionTierExhaustive = true;
3
+ void _injectionTierExhaustive;
4
+ export function injectionTierOrAbsent(value) {
5
+ return typeof value === "string" && INJECTION_TIERS.includes(value) ? value : undefined;
6
+ }
7
+ //# sourceMappingURL=injection-tier.js.map
@@ -12,7 +12,7 @@
12
12
  * old session-join `toolOutput` hook is gone, there's a real source now); `eventId`/`parentToolCallId` (§E2) ride
13
13
  * each content event, additive / tolerate-absent.
14
14
  */
15
- import type { ApprovalSettledBy, TaskResult } from "@sema-agent/core";
15
+ import type { ApprovalSettledBy, TaskResult, SystemInjectionPriority } from "@sema-agent/core";
16
16
  import { type AskDenyResolution } from "@sema-agent/core";
17
17
  import type { RunEvent, RunRecord } from "../plugins/store-contracts.js";
18
18
  import type { ModelUsageDelta, ModelUsageTracker, PromptManifestRecord } from "../budget.js";
@@ -212,6 +212,10 @@ export declare function taskNotificationEventData(ev: {
212
212
  };
213
213
  eventId?: string;
214
214
  parentToolCallId?: string;
215
+ /** design/373 §3.6(core 5.61.0,additive):本帧**投出去时的注入档位**。见下方投影处的键注 —— 它是
216
+ * **事件顶层**键,不是 {@link TaskNotificationPayload} 的键,所以只有把整只 core 事件交给本投影的腿
217
+ * 拿得到它;只拿到载荷的 park-drain 腿结构上没有这一位。 */
218
+ priority?: SystemInjectionPriority;
215
219
  }): Record<string, unknown>;
216
220
  /** The `compacted` payload — EXPLICIT WHITELIST shared by all three legs (bg runs.ts + resume append + the sync
217
221
  * live SSE frame; the 1.72 task_progress lesson: never `{...ev}`). trigger/tokensBefore (MF-18) verbatim;
@@ -1,5 +1,6 @@
1
1
  import { isApprovalSettledBy, isAskDenyResolution } from "@sema-agent/core";
2
2
  import { redactSecrets, redactDeep } from "./redact.js";
3
+ import { injectionTierOrAbsent } from "./injection-tier.js";
3
4
  export async function appendModelUsageDelta(append, modelUsage, taskId) {
4
5
  const delta = modelUsage?.drain(taskId);
5
6
  if (delta) {
@@ -141,6 +142,10 @@ export function taskNotificationEventData(ev) {
141
142
  ...(n.result !== undefined ? { result: redactSecrets(n.result) } : {}),
142
143
  ...(n.output_file !== undefined ? { output_file: redactSecrets(n.output_file) } : {}),
143
144
  ...(n.usage !== undefined ? { usage: n.usage } : {}),
145
+ ...(() => {
146
+ const tier = injectionTierOrAbsent(ev.priority);
147
+ return tier !== undefined ? { priority: tier } : {};
148
+ })(),
144
149
  ...identityFields(ev),
145
150
  };
146
151
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "7.48.0",
3
+ "version": "7.50.0",
4
4
  "description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -54,8 +54,8 @@
54
54
  "build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
55
55
  },
56
56
  "dependencies": {
57
- "@sema-agent/core": "^5.60.1",
58
- "@sema-agent/registry-core": "^0.19.0",
57
+ "@sema-agent/core": "5.63.0",
58
+ "@sema-agent/settings-schema": "1.0.0",
59
59
  "e2b": "^2.28.0",
60
60
  "libsodium-wrappers": "^0.8.4",
61
61
  "mysql2": "^3.22.4",