@sema-agent/server 6.0.0 → 6.2.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/USAGE.md CHANGED
@@ -53,7 +53,7 @@ ANTHROPIC_API_KEY=sk-ant-… # 可选:ANTHROPIC_BASE_URL / ANTHROPIC_VERSION /
53
53
 
54
54
  **可选 — 韧性栈:分级超时 + 断路器(core 1.38,叠在 failover 之下)**
55
55
  ```bash
56
- # ③ 分级超时:任务级 timeoutSec 之下补两段,各记为可重试的 [network] → 触发重试/断路器/failover
56
+ # ③ 分级超时:任务级 limits.maxWalltimeMs(6.0.0 起,毫秒)之下补两段,各记为可重试的 [network] → 触发重试/断路器/failover
57
57
  MODEL_CONNECT_TIMEOUT_MS=8000 # fetch 迟迟不返响应头(网关连不上)→ 中止
58
58
  MODEL_FIRST_TOKEN_TIMEOUT_MS=30000 # SSE 已开但迟迟不吐第一个 delta(网关 hang);reasoning 的首个 thinking 也算首 token
59
59
  MODEL_IDLE_TIMEOUT_MS=20000 # (1.40.1)出过 token 后中途卡死:每个 delta 重置,静默超时→中止(补 first-token 只管首字)
@@ -415,14 +415,47 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
415
415
  | HTTP `200` + `status:"completed"` | 成功 | 取 `result` |
416
416
  | `status:"blocked"` | agent 主动报卡住 | 看 `blockedReason`,补信息再发 |
417
417
  | `status:"failed"` + `errorCode:"conflict"` | 跨实例乐观锁丢失 | 直接重试(幂等) |
418
- | `status:"timeout"` | 超时 | 拆小任务 / 提高超时(服务端 `limits`) |
418
+ | `status:"failed"` + `errorCode:"limits.max_walltime_exceeded"` | 墙钟到限(6.0.0 起 `status:"timeout"` 退役) | 拆小任务 / 提高 `limits.maxWalltimeMs`(毫秒) |
419
419
  | HTTP `401` | 缺 `Authorization` / 缺 `x-agent-principal`(要求时) | 补头 |
420
420
  | HTTP `403` / `404`(session/run) | 不是该 principal 的资源 | 用正确身份 |
421
421
  | HTTP `409`(`/v1/runs`) | 同 session 已有活跃 run | 等它完成 / 用返回的 `activeTaskId` |
422
422
  | HTTP `429` | 限流 | 看 `Retry-After` 退避 |
423
+ | HTTP `429` + `errorCode:"usage.window_exhausted"` | 部署级 token 治理窗耗尽(`USAGE_WINDOWS`) | 看响应体 `retryAfterSec` 退避;窗滑动/桶到期后放行 |
423
424
  | HTTP `501`(`/v1/runs`) | 内存模式不支持异步 | 配 MySQL 协议存储(`SESSION_BACKEND=mysql`) |
424
425
 
425
426
  **机器码**:每个 4xx/5xx 响应体都带一个 `errorCode`(与人类文案 `error` 并列),这是**唯一**该拿来做
426
427
  程序分支的字段——**别锚 `error` 文案**。前缀族固定:`auth.` / `request.` / `not_found.` / `conflict.` /
427
- `limit.` / `capability.`(本部署没接这个面)/ `feature.`(开关没开)/ `internal.` / `state.`;未知码按前缀
428
- 兜底永远安全。完整码表见 `docs/ASSISTANT-WIRE-CONTRACT.md` §附录 A。
428
+ `limit.` / `usage.`(部署级治理窗)/ `capability.`(本部署没接这个面)/ `feature.`(开关没开)/
429
+ `internal.` / `state.`;未知码按前缀兜底永远安全。完整码表见 `docs/ASSISTANT-WIRE-CONTRACT.md` §附录 A。
430
+
431
+ ### 9.1 limits.approachNotice(6.1.0 起)
432
+
433
+ 引擎默认在上下文用量到 80%/95% 时各注入一次接近提示(SSE 上是 `steering_injected` 帧、
434
+ `source:"limit_approach"`)。批处理场景嫌噪声:`limits:{approachNotice:false}` 关闭;调阈:
435
+ `limits:{approachNotice:{at:[0.5,0.9]}}`(两分数 (0,1] 且 r1<=r2,相等合法;坏形 400 点名)。
436
+
437
+ ### 9.2 部署级 token 治理窗(USAGE_WINDOWS,6.1.0 起)
438
+
439
+ 跨任务的滑动 token 预算(core `usageWindows`),缺省不设=关。单键 JSON 数组:
440
+
441
+ ```bash
442
+ USAGE_WINDOWS='[{"windowMs":18000000,"maxTokens":5000000,"anchor":"first-use"}]' # 5h 窗 500 万 token
443
+ # anchor:"first-use"=首次消费开桶、到期重开;"rolling"=滑动窗(最近 windowMs 内的消费之和)
444
+ ```
445
+
446
+ - **key 语义是 per-key 而不是一口总锅**:带 principal 的请求计到各自 principal 的窗,匿名请求计到
447
+ GLOBAL 锅——`REQUIRE_PRINCIPAL=false` 的混流部署下,总放行量=(principal 数+1)×maxTokens。要 fleet
448
+ 级总闸请用 quota/lease 面(那是按部署计费轴)。同一调用方有时带凭证有时不带,会在两口锅间劈叉计数。
449
+ - 耗尽:提交面立即 `429 usage.window_exhausted`(体 `retryAfterSec` + `Retry-After` 头);已受理的
450
+ 任务撞窗时,基建合格(checkpoint+durable approval)⇒ suspend(reason `usage_window`)等窗放行,
451
+ 不合格 ⇒ 响亮失败同码落终态记录。
452
+ - 计量覆盖:主任务、具名/委托 subagent、hook 内部任务全计(部署真花销都进窗)。
453
+ - 坏形 `USAGE_WINDOWS` 拒启(fail-loud 点名第几项);**改窗形≈重开账**(first-use 桶按 windowMs
454
+ 配对,改了窗长老桶下次记账即弃)。
455
+ - 存储:SQL 后端=跨副本一致(`usage_window` 表,行锁守恒);local=文件;纯内存 dev=进程内。
456
+
457
+ ### 9.3 沙箱寿命声明(6.1.0 起)
458
+
459
+ k8s 沙箱(pod `activeDeadlineSeconds` 真硬死线)向引擎声明 `lifetimeMs`——死线前 ~60s 引擎在 turn
460
+ 边界尝试 suspend(reason `env_lifetime`,基建合格时)而不是被平台杀在半途;单个 >60s 的工具调用仍可能
461
+ 冲过该窗口,声明是尽力座不是保证。e2b 沙箱有 keepalive 滚动续期、无固定死线,不声明(行为不变)。
@@ -34,6 +34,8 @@ export interface RunnerDepsCtx {
34
34
  rosterStore: RunnerDeps["rosterStore"];
35
35
  backgroundAgentStore: RunnerDeps["backgroundAgentStore"];
36
36
  mailboxStore: RunnerDeps["mailboxStore"];
37
+ /** design/166-T1:治理窗账本店(USAGE_WINDOWS 配置时才在场;与 config.usageWindows 成对进 deps)。 */
38
+ usageWindowStore: RunnerDeps["usageWindowStore"];
37
39
  brain: ReturnType<typeof createBrain>;
38
40
  pricing: ReturnType<typeof buildPricing>;
39
41
  tracer: ReturnType<typeof createTracer>;
@@ -66,7 +68,7 @@ export interface RunnerDepsCtx {
66
68
  }
67
69
  /** design/158 A10 留档发现②:main runner `RunnerDeps` 与 main.ts subRunner 字面量之间此前手工重复
68
70
  * 的 ~15 个键,类型标注见 {@link createSharedRunnerDeps} 头注。 */
69
- export type SharedRunnerDeps = Pick<RunnerDeps, "brain" | "models" | "roles" | "tiers" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "hooks" | "toolResultStore" | "sessionPolicyStore">;
71
+ export type SharedRunnerDeps = Pick<RunnerDeps, "brain" | "models" | "roles" | "tiers" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "hooks" | "toolResultStore" | "sessionPolicyStore" | "usageWindows" | "usageWindowStore">;
70
72
  /**
71
73
  * design/158 A10 留档发现②(review 2026-07-29,[1543]§三族A 同源修补的延续):main runner 的
72
74
  * `RunnerDeps` 字面量(下方 `createRunnerDeps`)与 `main.ts` 里 subRunner 的 `new Runner({...})`
@@ -95,7 +97,7 @@ export type SharedRunnerDeps = Pick<RunnerDeps, "brain" | "models" | "roles" | "
95
97
  * 属性,比再抽一层共享基座更强的同源保证),不重复收纳进这里。
96
98
  */
97
99
  /** 基座真实消费的窄面(Pick)——subRunner 调用点(main.ts)只需凑这 13 个字段,不必造全量 ctx。 */
98
- export type SharedRunnerDepsCtx = Pick<RunnerDepsCtx, "config" | "brain" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "deploymentHooks" | "toolResultStore" | "sessionPolicyStore">;
100
+ export type SharedRunnerDepsCtx = Pick<RunnerDepsCtx, "config" | "brain" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "deploymentHooks" | "toolResultStore" | "sessionPolicyStore" | "usageWindowStore">;
99
101
  export declare function createSharedRunnerDeps(ctx: SharedRunnerDepsCtx): SharedRunnerDeps;
100
102
  export declare function createRunnerDeps(ctx: RunnerDepsCtx): RunnerDeps;
101
103
  //# sourceMappingURL=runner-deps.d.ts.map
@@ -49,6 +49,12 @@ export function createSharedRunnerDeps(ctx) {
49
49
  // E6: operator-tightened session tool rules — core folds them into the ToolPolicy FIRST (subtract-only) for tasks
50
50
  // carrying a sessionId (a delegated subagent has none → inherits no rules). Opt-in: undefined ⇒ no rules read.
51
51
  sessionPolicyStore: ctx.sessionPolicyStore ? ctx.sessionPolicyStore : undefined,
52
+ // design/166-T1:治理窗两键**成对**且进共享基座——主/sub/hook 三 runner 全覆盖(漏挂 subRunner=
53
+ // 子代用量不进窗,治理可被 subagent 刷穿;双记不存在:core stats.nested 是旁路侧栏不折入
54
+ // stats.tokens,governance.commit 是 per-任务 delta 制)。store 只在 config.usageWindows 配置时
55
+ // 构造(boot/stores.ts),两键同真同假。
56
+ usageWindows: ctx.config.usageWindows ? ctx.config.usageWindows : undefined,
57
+ usageWindowStore: ctx.usageWindowStore ? ctx.usageWindowStore : undefined,
52
58
  };
53
59
  return shared;
54
60
  }
@@ -28,5 +28,6 @@ export declare function openStores(ctx: OpenStoresCtx): Promise<{
28
28
  memorySyncRunner: MemorySyncRunner | undefined;
29
29
  sessionStore: import("@sema-agent/core").SessionStore;
30
30
  breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
31
+ usageWindowStore: import("@sema-agent/core").UsageWindowStore | undefined;
31
32
  }>;
32
33
  //# sourceMappingURL=stores.d.ts.map
@@ -13,7 +13,7 @@
13
13
  * 3. `breakerState` 放在段尾:brain(下一段)要它,而它要 backend。
14
14
  */
15
15
  import { join } from "node:path";
16
- import { FileBackgroundAgentStore, FileMailboxStore, FileRosterStore } from "@sema-agent/core";
16
+ import { FileBackgroundAgentStore, FileMailboxStore, FileRosterStore, FileUsageWindowStore, InMemoryUsageWindowStore } from "@sema-agent/core";
17
17
  import { createMemorySyncRunner, createMemorySyncTransport } from "../memory-sync-client.js";
18
18
  import { PgMemoryEngineBackend, ensurePgMemoryEngineSchema } from "../plugins/memory-engine-pg.js";
19
19
  import { TiDBMemoryEngineBackend, ensureTiDBMemoryEngineSchema } from "../plugins/memory-engine-tidb.js";
@@ -22,6 +22,7 @@ import { TiDBMemoryHistoryStore, TiDBMemorySyncStore, ensureTiDBMemoryHistorySch
22
22
  import { MinioBlobBackend } from "../plugins/blob-backend.js";
23
23
  import { PgBackgroundAgentStore, TiDBBackgroundAgentStore, ensurePgBackgroundAgentSchema, ensureTiDBBackgroundAgentSchema } from "../plugins/background-agent-store-sql.js";
24
24
  import { PgMailboxStore, TiDBMailboxStore, ensurePgMailboxSchema, ensureTiDBMailboxSchema } from "../plugins/mailbox-store-sql.js";
25
+ import { PgUsageWindowStore, TiDBUsageWindowStore, ensurePgUsageWindowSchema, ensureTiDBUsageWindowSchema } from "../plugins/usage-window-store-sql.js";
25
26
  import { PgRosterStore, TiDBRosterStore, ensurePgRosterSchema, ensureTiDBRosterSchema } from "../plugins/roster-store-sql.js";
26
27
  import { LocalTaskAttachmentStore } from "../plugins/local-task-attachment-store.js";
27
28
  import { PgTaskAttachmentStore, TiDBTaskAttachmentStore, ensurePgTaskAttachmentSchema, ensureTiDBTaskAttachmentSchema } from "../plugins/task-attachment-store.js";
@@ -261,6 +262,31 @@ export async function openStores(ctx) {
261
262
  if (mailboxStore)
262
263
  logger.info("mailbox_store_enabled", { backend: pgPool ? "pg" : mysqlPool ? "tidb" : "file" });
263
264
  }
265
+ // design/166-T1:治理窗账本店——只在 USAGE_WINDOWS 配置时构造(两键成对进 RunnerDeps,core 的
266
+ // 「windows 有 store 无=INACTIVE 警告」在构造上被排除)。形态跟 mailbox:tidb/pg=SQL twins
267
+ // (ensure-first+行锁,真双库验证,usage-window-store-sql.ts 顶注);local=core FileUsageWindowStore
268
+ // (构造器内部自拼 usage-windows/,直传数据根——复审新6);纯内存 dev=core InMemoryUsageWindowStore
269
+ // (进程内计数,单进程 dev 的诚实形——治理窗设了就得生效,不因无持久层而静默失活)。
270
+ let usageWindowStore;
271
+ if (config.usageWindows) {
272
+ const mysqlPool = backend?.mysqlPool?.();
273
+ const pgPool = backend?.pgPool?.();
274
+ if (pgPool) {
275
+ await ensurePgUsageWindowSchema(async (text, params) => pgPool.query(text, params));
276
+ usageWindowStore = new PgUsageWindowStore(pgPool);
277
+ }
278
+ else if (mysqlPool) {
279
+ await ensureTiDBUsageWindowSchema(mysqlPool);
280
+ usageWindowStore = new TiDBUsageWindowStore(mysqlPool);
281
+ }
282
+ else if (backend?.kind === "local") {
283
+ usageWindowStore = new FileUsageWindowStore(config.localDataRoot ?? localRoot);
284
+ }
285
+ else {
286
+ usageWindowStore = new InMemoryUsageWindowStore();
287
+ }
288
+ logger.info("usage_window_store_enabled", { backend: pgPool ? "pg" : mysqlPool ? "tidb" : backend?.kind === "local" ? "file" : "memory", windows: config.usageWindows.length });
289
+ }
264
290
  // 142-S5 §1.4: the per-scope export READ face lights up only on the DB memory plane (the multi-tenant
265
291
  // truth lives in the scope-partitioned tables; the file posture exports by copying the memory dir, so
266
292
  // the route 501s honestly there). History-table WRITE wiring: inside the backend construction above
@@ -365,6 +391,7 @@ export async function openStores(ctx) {
365
391
  return {
366
392
  backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore,
367
393
  taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState,
394
+ usageWindowStore,
368
395
  };
369
396
  }
370
397
  //# sourceMappingURL=stores.js.map
@@ -129,7 +129,6 @@ export declare function semaIdentityProvider(): PromptProvider;
129
129
  * 无 systemPrompt 且无品牌时原样返回 core 常量(测试以引用比对锁 bench 同源,不做字符串快照)。
130
130
  */
131
131
  export declare function codePromptProvider(brand: boolean): PromptProvider;
132
- /** @deprecated [891] 改名 code——一版过渡期保留旧符号(与场景名 alias 同窗退役)。 */
133
132
  export declare function buildScenarios(deps: ScenarioDeps): Record<string, Scenario>;
134
133
  /** Resolve the scenario for a request (falls back to `default`). */
135
134
  export declare function selectScenario(scenarios: Record<string, Scenario>, name: string | undefined): Scenario;
@@ -85,8 +85,9 @@ export function codePromptProvider(brand) {
85
85
  ],
86
86
  };
87
87
  }
88
- /** @deprecated [891] 改名 code——一版过渡期保留旧符号(与场景名 alias 同窗退役)。 */
89
88
  // [2400] CAPS-OPS-11:autonomousPromptProvider 别名导出已随场景退役删除(真身=codePromptProvider)。
89
+ // 其 `@deprecated` JSDoc 曾残留于此,被 tsc 误挂到下方 buildScenarios(三处调用点全告 deprecated
90
+ // ——TODO-3 立案的「迁移」实为孤儿注释,随删除清账;buildScenarios 本身非退役面)。
90
91
  export function buildScenarios(deps) {
91
92
  // The default scenario filters skills by "default" — NOT by req.scenario (which may be an unknown name
92
93
  // that fell back here): the bundle must be a coherent "default" set, matching how main.ts resolves an
@@ -642,6 +642,16 @@ export interface ServiceConfigFlat {
642
642
  * Running rows are never reaped (that is the orphan-grace sweep's job). Default 30 days, floor 1m.
643
643
  * `WORKFLOW_RUN_RETENTION_MS`. */
644
644
  workflowRunRetentionMs: number;
645
+ /** design/166-T1 — core 5.8.0 部署级 token 治理窗(`RunnerDeps.usageWindows`):滑动窗内跨任务
646
+ * token 预算,key=principal(缺席=GLOBAL 一口锅,per-key 各一份额度——混流部署语义见 USAGE.md)。
647
+ * `USAGE_WINDOWS` 单键 JSON 数组(每项 {windowMs,maxTokens,anchor:"first-use"|"rolling"});
648
+ * 缺省未设=关(零行为变化)。fail-loud:坏 JSON/空数组/非正整数/未知 anchor → boot throw
649
+ * (parseUsageWindows)。窗形变更≈重开账(first-use 桶按 windowMs 配对,老桶下次 charge 即弃)。 */
650
+ usageWindows?: readonly {
651
+ windowMs: number;
652
+ maxTokens: number;
653
+ anchor: "first-use" | "rolling";
654
+ }[];
645
655
  /** core 1.364 durable background agents([1503] 提货单③):reaper 经 `TaskRegistry.reapDurableAgents`
646
656
  * (联合 reap——条件删赢了才 release 转录 session;裸 store.reap 会 strand 转录)按 scope 清 TERMINAL
647
657
  * `a*` 行,老于此值的删。SQL twins 走 DISTINCT-scope 枚举;local FileBackgroundAgentStore 单租户
@@ -888,7 +898,7 @@ export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled"
888
898
  * 函数不产出它,但它与 `autonomy` 是同一根治理棒的两半,归本组。 */
889
899
  export type ServiceAuthConfig = Pick<ServiceConfigFlat, "authToken" | "authTokens" | "allowUnauthedWrites" | "corsOrigins" | "principalHeader" | "requirePrincipal" | "autonomy" | "commandPolicy" | "operatorPrincipals" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "principalJwtMaxTtlSec" | "bindHost" | "bindHostSource">;
890
900
  /** 组:orchestration(编排 + 执行车道 + 沙箱面 + workflow/后台 agent 存留)。 */
891
- export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentParkClaimStaleMs" | "rosterRetentionMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sessionAutoTitle" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspEnabled" | "lspHostEnabled" | "imageBakes">;
901
+ export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "usageWindows" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentParkClaimStaleMs" | "rosterRetentionMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sessionAutoTitle" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspEnabled" | "lspHostEnabled" | "imageBakes">;
892
902
  /** 组:limitsHttp(HTTP 面 + 各类上限/配额/回收窗)。 */
893
903
  export type ServiceLimitsHttpConfig = Pick<ServiceConfigFlat, "port" | "attachmentOrphanGraceMs" | "workspaceFileMaxBytes" | "attachmentMaxBytes" | "attachmentMimeAllowlist" | "attachmentUnboundTtlMs" | "infraCostRates" | "drainGraceMs" | "sighupIdleGraceMs" | "rateLimitPerMin" | "maxTaskCostUsd" | "maxTaskTokens" | "maxPrincipalCostUsd" | "costQuotaWindowSec" | "reapIntervalSec" | "runStaleSec" | "toolResultTtlSec">;
894
904
  /** 组:observability(可观测)。 */
package/dist/config.d.ts CHANGED
@@ -11,6 +11,15 @@ export type { ServiceConfig, ScopedMcpServer, ImageBakeConfig, ServiceConfigFlat
11
11
  * config overlay (config-center/facade.ts `applyRuntimeHot`) can re-derive the ENV BASELINE to revert to when center
12
12
  * stops managing `autonomy` (a stale center override must not stick — see applyRuntimeHot). */
13
13
  export declare function parseAutonomy(raw: string | undefined): Autonomy | undefined;
14
+ /** design/166-T1 — parse `USAGE_WINDOWS`(单键 JSON 数组;逐窗散键在窗数可变时组合爆炸,JSON 是唯一
15
+ * 封闭形)。unset/空串 → undefined(关)。fail-loud 族(numEnv 同纪律):坏 JSON/非数组/空数组/
16
+ * 非正有限整数/未知 anchor 全部 boot throw 点名——「设了但无意义」静默关掉=部署方以为有治理实际没有。
17
+ * server 严于 core(core 允 maxTokens=0=恒拒锅;这里要求正整数,0 配置显然是笔误不是意图)。 */
18
+ export declare function parseUsageWindows(raw: string | undefined): readonly {
19
+ windowMs: number;
20
+ maxTokens: number;
21
+ anchor: "first-use" | "rolling";
22
+ }[] | undefined;
14
23
  /** Numeric env with validation: a non-numeric value FAILS at startup instead of silently becoming `NaN`
15
24
  * (which e.g. slips past the `runStaleSec` liveness guard and silently disables the reaper). */
16
25
  export declare function numEnv(name: string, fallback: string): number;
package/dist/config.js CHANGED
@@ -26,6 +26,34 @@ export function parseAutonomy(raw) {
26
26
  }
27
27
  return v;
28
28
  }
29
+ /** design/166-T1 — parse `USAGE_WINDOWS`(单键 JSON 数组;逐窗散键在窗数可变时组合爆炸,JSON 是唯一
30
+ * 封闭形)。unset/空串 → undefined(关)。fail-loud 族(numEnv 同纪律):坏 JSON/非数组/空数组/
31
+ * 非正有限整数/未知 anchor 全部 boot throw 点名——「设了但无意义」静默关掉=部署方以为有治理实际没有。
32
+ * server 严于 core(core 允 maxTokens=0=恒拒锅;这里要求正整数,0 配置显然是笔误不是意图)。 */
33
+ export function parseUsageWindows(raw) {
34
+ const v = raw?.trim();
35
+ if (!v)
36
+ return undefined;
37
+ let parsed;
38
+ try {
39
+ parsed = JSON.parse(v);
40
+ }
41
+ catch (e) {
42
+ throw new Error(`env USAGE_WINDOWS is not valid JSON: ${String(e)} — expected e.g. [{"windowMs":18000000,"maxTokens":5000000,"anchor":"first-use"}]`);
43
+ }
44
+ if (!Array.isArray(parsed) || parsed.length === 0) {
45
+ throw new Error(`env USAGE_WINDOWS must be a NON-EMPTY JSON array of {windowMs,maxTokens,anchor} (an empty array is a governance no-op nobody chose — unset the env to turn windows off)`);
46
+ }
47
+ const out = parsed.map((w, i) => {
48
+ const o = w;
49
+ const posInt = (n) => typeof n === "number" && Number.isInteger(n) && n > 0;
50
+ if (typeof w !== "object" || w === null || !posInt(o.windowMs) || !posInt(o.maxTokens) || (o.anchor !== "first-use" && o.anchor !== "rolling")) {
51
+ throw new Error(`env USAGE_WINDOWS[${i}] is invalid: expected {windowMs: positive int ms, maxTokens: positive int, anchor: "first-use"|"rolling"} (got ${JSON.stringify(w)})`);
52
+ }
53
+ return Object.freeze({ windowMs: o.windowMs, maxTokens: o.maxTokens, anchor: o.anchor });
54
+ });
55
+ return Object.freeze(out);
56
+ }
29
57
  /** Resolve a CSV of env-var NAMES into a {NAME: value} map from THIS process's env (out-of-band secret
30
58
  * forwarding into a sandbox). A name with no value set is skipped (not forwarded as undefined). The values
31
59
  * are secrets — they live only in the returned map (in-memory), never logged. */
@@ -1023,6 +1051,8 @@ function parseOrchestrationDomain(ctx) {
1023
1051
  throw new Error(`WORKFLOW_SIZE_GUIDELINE must be small|medium|large|unrestricted, got "${workflowSizeGuidelineRaw}"`);
1024
1052
  }
1025
1053
  const workflowSizeGuideline = workflowSizeGuidelineRaw;
1054
+ // design/166-T1:治理窗(fail-loud 解析;unset=undefined=关)。
1055
+ const usageWindows = parseUsageWindows(process.env.USAGE_WINDOWS);
1026
1056
  const bakeRunnerPrincipal = env("BAKE_RUNNER_PRINCIPAL", "system:bake-runner");
1027
1057
  // ── design/158 B4 ② — the four knobs whose canonical name went from negative to positive. Only the first
1028
1058
  // lands on `ServiceConfig`; the other three are read lazily at their use sites (main.ts's LKG gate,
@@ -1193,6 +1223,7 @@ function parseOrchestrationDomain(ctx) {
1193
1223
  workflowOrphanGraceMs: Math.max(60_000, numEnv("WORKFLOW_ORPHAN_GRACE_MS", String(24 * 60 * 60 * 1000))), // SVC-1: 24h default, floor 1m
1194
1224
  workflowJournalRetentionMs: Math.max(60_000, numEnv("WORKFLOW_JOURNAL_RETENTION_MS", String(7 * 24 * 60 * 60 * 1000))), // SVC-2: 7d default, floor 1m
1195
1225
  workflowRunRetentionMs: Math.max(60_000, numEnv("WORKFLOW_RUN_RETENTION_MS", String(30 * 24 * 60 * 60 * 1000))), // 30d default, floor 1m; SQL twins only
1226
+ ...(usageWindows ? { usageWindows } : {}), // design/166-T1:USAGE_WINDOWS 治理窗(fail-loud 已在 parseUsageWindows)
1196
1227
  backgroundAgentRetentionMs: Math.max(60_000, numEnv("BG_AGENT_RETENTION_MS", String(7 * 24 * 60 * 60 * 1000))), // core 1.364: 7d default, floor 1m
1197
1228
  backgroundAgentStaleRunningMs: Math.max(10 * 60_000, numEnv("BG_AGENT_STALE_RUNNING_MS", String(15 * 60_000))), // core 契约 ≥10min 硬钳(60s 写者心跳)
1198
1229
  backgroundAgentParkClaimStaleMs: Math.max(60_000, numEnv("BG_AGENT_PARK_CLAIM_STALE_MS", String(60 * 60_000))), // [1575] F1 修:default 1h,floor 1m(零/负值都不该让整块清算失活)
@@ -1334,7 +1365,7 @@ const ORCHESTRATION_GROUP_KEYS = [
1334
1365
  "remoteExec", "worktreeIsolation", "leaderEnabled", "leaderFanoutEnabled", "routerEnabled", "selfOrchestrationEnabled",
1335
1366
  "selfOrchestrationModels", "selfOrchestrationWorkerIsolation", "forkEnabled", "experimentalObserverAgents",
1336
1367
  "schedulerEnabled", "schedulerSessionWakeup", "schedulerSessionLifetime", "schedulerStorePath", "planModeEnabled", "workflowRunStoreBackend",
1337
- "workflowOrphanGraceMs", "workflowJournalRetentionMs", "workflowRunRetentionMs", "workflowAgentsReadOnly",
1368
+ "workflowOrphanGraceMs", "workflowJournalRetentionMs", "workflowRunRetentionMs", "usageWindows", "workflowAgentsReadOnly",
1338
1369
  "workflowSizeGuideline", "backgroundAgentRetentionMs", "backgroundAgentStaleRunningMs",
1339
1370
  "backgroundAgentParkClaimStaleMs", "rosterRetentionMs", "scratchpadSweepTtlMs", "sandboxPkgSource",
1340
1371
  "sessionAutoTitle", "selectEnvironmentTool", "envFactsEnabled", "toolDeferLongtail", "lspEnabled", "lspHostEnabled",
@@ -47,6 +47,8 @@ export interface RouteHelpers {
47
47
  rateLimited(req: IncomingMessage, res: ServerResponse): boolean;
48
48
  quotaExceeded(req: IncomingMessage, res: ServerResponse): boolean;
49
49
  leaseDenied(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
50
+ /** design/166-T1:治理窗 pre-admission(异步 202 车道的唯一 429 出路;key 公式与 core 单源)。 */
51
+ usageWindowDenied(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
50
52
  safeDecode(seg: string): string | null;
51
53
  isFleetWide(req: IncomingMessage): boolean;
52
54
  runOwnerOk(req: IncomingMessage, res: ServerResponse, owner: string | null): boolean;
@@ -111,7 +111,7 @@ export async function handleRunVerbs(req, res, url, ctx) {
111
111
  async function handleRunsBody(req, res, url, ctx, miss) {
112
112
  const { deps } = ctx;
113
113
  const { idemCache, inflightRuns, preemptableRuns, cancelledViaVerb, steerableRuns, wakeParkMints } = ctx.registry;
114
- const { readJson, rateLimited, quotaExceeded, leaseDenied, runOwnerOk, runSessionAcceptOk } = ctx.helpers;
114
+ const { readJson, rateLimited, quotaExceeded, leaseDenied, usageWindowDenied, runOwnerOk, runSessionAcceptOk } = ctx.helpers;
115
115
  const { prepareSpec } = ctx.legs;
116
116
  const source = ctx.req.source;
117
117
  // Async run: create + return ids immediately, execute in the background.
@@ -171,7 +171,9 @@ async function handleRunsBody(req, res, url, ctx, miss) {
171
171
  }
172
172
  // 🔴 复审 C1:lease gate for a genuinely-NEW submission (past the idempotency replay above). Keyed on the
173
173
  // VERIFIED submitter = the principal billed for this run's spend (correct principal for the sync door).
174
- if (await leaseDenied(req, res))
174
+ // design/166-T1:治理窗 pre-admission 同位(idempotency replay 之后——重放已建 run 必须拿回 202 收据,
175
+ // 不得因窗满改判 429;异步 202 车道的唯一 429 出路,core 进场拒是竞态窗兜底)。
176
+ if ((await leaseDenied(req, res)) || (await usageWindowDenied(req, res)))
175
177
  return;
176
178
  const resp = await idemCache.run(idemKey, async () => {
177
179
  const sessionId = prepared.spec.sessionId ?? uuidv7();
@@ -22,7 +22,7 @@ export async function handleTasks(req, res, url, ctx) {
22
22
  async function handleTasksBody(req, res, url, ctx, miss) {
23
23
  const { deps } = ctx;
24
24
  const { idemCache, inflightRuns, cancelledViaVerb, steerableRuns, counters } = ctx.registry;
25
- const { rateLimited, quotaExceeded, leaseDenied } = ctx.helpers;
25
+ const { rateLimited, quotaExceeded, leaseDenied, usageWindowDenied } = ctx.helpers;
26
26
  const { prepareSpec, finalizeTaskResult } = ctx.legs;
27
27
  const reqState = ctx.req;
28
28
  const source = ctx.req.source;
@@ -61,7 +61,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
61
61
  }
62
62
  return;
63
63
  }
64
- if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)))
64
+ if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)) || (await usageWindowDenied(req, res)))
65
65
  return;
66
66
  const prepared = await prepareSpec(req, res);
67
67
  if (!prepared)
@@ -344,6 +344,10 @@ export interface ServiceGovernanceDeps {
344
344
  costQuota?: QuotaTracker;
345
345
  /** D4 AP:worker 侧 quota-lease admission(协商式;center lane 未配 = 缺席 = 零行为)。 */
346
346
  fleetLease?: FleetLeaseManager;
347
+ /** design/166-T1:治理窗账本店(USAGE_WINDOWS 配置时在场,与 config.usageWindows 成对)——提交面
348
+ * pre-admission 读窗用(主力 /v1/runs 是 202 异步,core 进场拒发生在 202 之后永远变不成 429;
349
+ * 这里是异步车道唯一的 429 出路)。enforcement 真源在 core(RunnerDeps 同店),此门是快速座。 */
350
+ usageWindowStore?: import("@sema-agent/core").UsageWindowStore;
347
351
  }
348
352
  /** **部署自述面**:只被 /health、/v1/capabilities 与提交前置门读的部署事实(多为 live getter,
349
353
  * 不是快照——/health 必须回答「此刻」)。 */
@@ -1,7 +1,7 @@
1
1
  import http from "node:http";
2
2
  import { once } from "node:events";
3
3
  import { createHash } from "node:crypto";
4
- import { uuidv7, isThinkingLevel, expandTiers, resumeWithVerification, CheckpointError, HAND_TOOL_EFFECTS, defaultTaskRegistry, validatePendingSteer, subscribeWorkflow } from "@sema-agent/core";
4
+ import { uuidv7, isThinkingLevel, expandTiers, resumeWithVerification, CheckpointError, HAND_TOOL_EFFECTS, defaultTaskRegistry, validatePendingSteer, subscribeWorkflow, GLOBAL_USAGE_KEY, usageRetryAfterMs } from "@sema-agent/core";
5
5
  import { decideParkedAgent, findParkedAgentForCheckpoint } from "../parked-decide.js";
6
6
  import { matchCatalogModel } from "../model-select.js";
7
7
  import {} from "../config-center/facade.js";
@@ -10,7 +10,7 @@ import { verifyDirectDoorProof } from "../principal-jwt.js";
10
10
  import {} from "../memory-sync.js";
11
11
  import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_PERMISSION_RULES, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../task-settings.js";
12
12
  import { parseHooksConfig } from "../hooks/hook-runner.js";
13
- import { validateTaskAgents } from "../spec-fields.js";
13
+ import { normalizeApproachNotice, validateTaskAgents } from "../spec-fields.js";
14
14
  import { isValidCwd, MAX_ADDITIONAL_DIRS } from "../task-cwd.js";
15
15
  import { runInBackground, evictIfConflict, stripCheckpointToken, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort } from "../runs.js";
16
16
  import { looksLikeJwt } from "@sema-agent/registry-core/api/auth-bridge";
@@ -297,7 +297,7 @@ export function createHttpServer(rawDeps) {
297
297
  const routeCtxBase = {
298
298
  deps,
299
299
  registry: { idemCache, inflightRuns, preemptableRuns, cancelledViaVerb, steerableRuns, wakeParkMints, counters },
300
- helpers: { readJson, readRawBody, rateLimited, quotaExceeded, leaseDenied, safeDecode, isFleetWide, runOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite, runSessionAcceptOk },
300
+ helpers: { readJson, readRawBody, rateLimited, quotaExceeded, leaseDenied, usageWindowDenied, safeDecode, isFleetWide, runOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite, runSessionAcceptOk },
301
301
  local: { images: createImagesLocal(deps), sessions: createSessionsLocal(), sessionSync: createSessionSyncLocal() },
302
302
  // A9 尾批:提交/续跑「腿」只装引用(实现仍在本闭包里,逐字未动)——tasks/runs 共用 prepareSpec,
303
303
  // approvals/assistant/notify-wake 共用 resume 家族;走这一格才不会造出 routes/* 互相值 import 的环。
@@ -958,7 +958,12 @@ export function createHttpServer(rawDeps) {
958
958
  sendError(res, 400, "request.field_invalid", "limits.timeoutSec was retired in core 5.8.0 — use limits.maxWalltimeMs (milliseconds; opt-in wall clock)");
959
959
  return null;
960
960
  }
961
- for (const k of ["deadlineNudge", "callCapByDeadline", "gracefulFinalize"]) {
961
+ // 166-T3(靶4b):deadlineNudge 的替代=approachNotice(core RETIRED 表同源);另两键随机制退役无替代。
962
+ if (l.deadlineNudge !== undefined) {
963
+ sendError(res, 400, "request.field_invalid", "limits.deadlineNudge was retired in core 5.8.0 — use limits.approachNotice (default on at 80%/95%; false turns it off, {at: [r1, r2]} tunes thresholds)");
964
+ return null;
965
+ }
966
+ for (const k of ["callCapByDeadline", "gracefulFinalize"]) {
962
967
  if (l[k] !== undefined) {
963
968
  sendError(res, 400, "request.field_invalid", `limits.${k} was retired in core 5.8.0 (the deadline-pacing mechanism family is gone; there is no replacement key — remove it)`);
964
969
  return null;
@@ -971,6 +976,14 @@ export function createHttpServer(rawDeps) {
971
976
  return null;
972
977
  }
973
978
  }
979
+ // 166-T3:approachNotice fresh-submit fail-loud(判据单源 normalizeApproachNotice,阈值规则对齐 core a<=b)。
980
+ {
981
+ const v = l.approachNotice;
982
+ if (v !== undefined && normalizeApproachNotice(v) === undefined) {
983
+ sendError(res, 400, "request.field_invalid", "limits.approachNotice accepts false (turn off) or {at?: [r1, r2]} with fractions in (0, 1], r1 <= r2 (default on at 80%/95%)");
984
+ return null;
985
+ }
986
+ }
974
987
  }
975
988
  // [876] per-task subagents(core 1.295 TaskSpec.agents):fresh-submit 白名单门 fail-loud。与 resilience/
976
989
  // limits 的「容忍未知键」取舍相反 —— agents 是能力定义,静默丢字段=定义被悄悄削(丢一个 denyTools/
@@ -2271,6 +2284,35 @@ export function createHttpServer(rawDeps) {
2271
2284
  sendError(res, 429, "quota_exhausted", "budget exhausted (quota lease)", { retryAfterSec: adm.retryAfterSec, ...(adm.detail ?? {}) });
2272
2285
  return true;
2273
2286
  }
2287
+ /** design/166-T1 — 治理窗 pre-admission(quotaExceeded/leaseDenied 的兄弟门):key 公式与 core 单源
2288
+ * (`principal || GLOBAL_USAGE_KEY`,GLOBAL_USAGE_KEY 引 core 公共导出防漂移;principal=verified,
2289
+ * spoofable header 不进键)。窗耗尽 → 429 + retryAfterSec(族形一致,秒上取整)+ Retry-After 头。
2290
+ * 读窗失败(DB 瞬断/坏 record)→ warn + fail-open:enforcement 真源在 core 进场检查(同店同键,
2291
+ * 会响亮拒),此门只是异步车道的 429 快速座——门故障不该把提交面整个拖死。admission-pass 与
2292
+ * core entry-拒之间存在竞态窗:该形落终态 run 记录(errorCode usage.window_exhausted),契约文档在案。 */
2293
+ async function usageWindowDenied(req, res) {
2294
+ const store = deps.usageWindowStore;
2295
+ const windows = deps.config.usageWindows;
2296
+ if (!store || !windows || windows.length === 0)
2297
+ return false;
2298
+ const key = gatedPrincipal(req, deps.config) || GLOBAL_USAGE_KEY;
2299
+ let retryAfterMs;
2300
+ try {
2301
+ retryAfterMs = usageRetryAfterMs(await store.read(key, windows, Date.now()));
2302
+ }
2303
+ catch (e) {
2304
+ deps.logger?.warn?.("usage_window_admission_read_failed", { error: e instanceof Error ? e.message : String(e) });
2305
+ return false;
2306
+ }
2307
+ if (retryAfterMs === undefined)
2308
+ return false;
2309
+ deps.metrics?.inc("usage_window_rejected_total");
2310
+ deps.logger?.info?.("usage_window_exhausted", { hasPrincipal: key !== GLOBAL_USAGE_KEY, retryAfterMs });
2311
+ const retryAfterSec = Math.max(1, Math.ceil(retryAfterMs / 1000));
2312
+ res.setHeader("retry-after", String(retryAfterSec));
2313
+ sendError(res, 429, "usage.window_exhausted", "usage window exhausted (deployment token governance)", { retryAfterSec });
2314
+ return true;
2315
+ }
2274
2316
  /** Apply the per-principal rate limit; responds 429 and returns true when the caller is over. */
2275
2317
  function rateLimited(req, res) {
2276
2318
  if (!deps.rateLimiter)
@@ -157,10 +157,14 @@ export interface TaskRequestBody {
157
157
  * `maxOutputTokens`/`maxTurns`,均正整数;可选运营方上限旋钮各自封顶(TASK_TIMEOUT_MAX_SEC 保**秒**义,
158
158
  * 对毫秒键封顶 ×1000)。旧键 `timeoutSec` 与 deadline 族三 opt-out(机制退役)fresh-submit 400 响亮
159
159
  * 点名替代;`maxWalltimeMs` 缺席时保持既有 tenancy 墙钟姿势(resolveTaskLimits,spec-fields.ts)。 */
160
+ /** 166-T3:approachNotice——core 默认 ON(80%/95% 各提示一次);false 关,{at:[r1,r2]} 调阈((0,1] 且 r1<=r2)。 */
160
161
  limits?: {
161
162
  maxWalltimeMs?: number;
162
163
  maxOutputTokens?: number;
163
164
  maxTurns?: number;
165
+ approachNotice?: false | {
166
+ at?: [number, number];
167
+ };
164
168
  };
165
169
  /** design/133 (core 1.251) + G1 (core 1.253): turn-boundary attachment reminders (todo / changed-files /
166
170
  * plan-mode) + one-shot boundary notices (post-compaction background-task recap / deferred-tools delta).
package/dist/main.js CHANGED
@@ -158,7 +158,7 @@ async function main() {
158
158
  await configCenter.applyLocalRemoteExec();
159
159
  // design/158 A10:持久层装配搬到 src/boot/stores.ts(逐字)。⚠️ 该段就地归一 `config.sessionBackend`
160
160
  // 且承载三条 fail-loud 拒启断言 —— 位置即契约,理由见该文件头注。
161
- const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, } = await openStores({ config, logger, metrics, localRoot });
161
+ const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, usageWindowStore, } = await openStores({ config, logger, metrics, localRoot });
162
162
  // design/158 A10:计费/追踪/预算装配搬到 src/boot/budget-tracing.ts(逐字;tracer 与 side-query 同 sink 实例的
163
163
  // 「同段构造」契约见该文件头注)。
164
164
  const { brain, pricing, counterDegradeHook, costQuota, modelUsageTracker, promptManifestTracker, fleetUsage, fleetLease, tracer, sideQueryAccounting, toolResultStore, sessionPolicyStore, fileSnapshotStore, } = createBudgetAndTracing({ config, logger, metrics, backend, breakerState });
@@ -185,7 +185,7 @@ async function main() {
185
185
  const { principalCaps, centerRuntimeCapsResolver, runtimeCapsResolver } = createRuntimeCaps({ config, logger });
186
186
  // design/158 A10:RunnerDeps 装配段搬到 src/boot/runner-deps.ts(逐字;runStore 晚绑改取值,见该文件头注)。
187
187
  const runnerDeps = createRunnerDeps({
188
- config, logger, metrics, localRoot, promptSource: configCenter.promptSource, rosterStore, backgroundAgentStore, mailboxStore, brain,
188
+ config, logger, metrics, localRoot, promptSource: configCenter.promptSource, rosterStore, backgroundAgentStore, mailboxStore, usageWindowStore, brain,
189
189
  pricing, tracer, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine,
190
190
  memorySyncRunner, toolResultStore, sessionPolicyStore, runtimeCapsResolver, fileSnapshotStore,
191
191
  executionEnvFactory, lspManager, fleetBus, deploymentHooks, workflowRunStore, workflowJournalStore,
@@ -329,6 +329,7 @@ async function main() {
329
329
  deploymentHooks,
330
330
  toolResultStore,
331
331
  sessionPolicyStore,
332
+ usageWindowStore,
332
333
  }),
333
334
  // ── 以下为 subRunner 差异键(不在共享基座;逐个有因)──────────────────────────────────────
334
335
  sessionStore: subRunnerSessions, // 子代转录=私有短 TTL fork 路由店,生命周期异于宿主 durable 店
@@ -800,6 +801,7 @@ async function main() {
800
801
  rateLimiter,
801
802
  costQuota,
802
803
  fleetLease: fleetLease ? fleetLease : undefined, // lease admission 门(提交面,镜像 quotaExceeded)
804
+ usageWindowStore: usageWindowStore ? usageWindowStore : undefined, // design/166-T1:治理窗 pre-admission(与 RunnerDeps 同店同键)
803
805
  };
804
806
  // B1 活体 store 探测环(门与判据见 store-live-probe.ts 头注;探针原语=既有 S10 dbNowMs 真 DB 往返)
805
807
  const storeLiveProbe = backend && backend.kind !== "local" && backend.dbNowMs && config.storeProbeIntervalMs > 0
@@ -129,6 +129,11 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
129
129
  private readonly cfg;
130
130
  private readonly deps;
131
131
  constructor(config: K8sEnvConfig, deps?: K8sEnvDeps);
132
+ /** 166-T2 — ExecutionEnv 寿命声明:pod `activeDeadlineSeconds = ceil(timeoutMs/1000)` 是平台真硬死线
133
+ * (k8s 到点杀 pod,无 keepalive 续期机制)⇒ 恒声明,core 在死线−60s 处 suspend(基建合格)或响亮
134
+ * 终局(env.lifetime_expired)。`lifetimeStartedAt` 有意不声明:core 只在 prepare 期读一次,lazy-connect
135
+ * 下 pod 尚未创建 ⇒ core 回落铸造时刻为锚——比 pod 真启动早,提早 suspend 是保守方向。 */
136
+ get lifetimeMs(): number;
132
137
  private resolve;
133
138
  workspaceHandle(): WorkspaceHandle;
134
139
  /** Resolved lazily so an in-cluster pod picks up its service account without explicit config. */
@@ -159,6 +159,13 @@ export class RemoteK8sExecutionEnv {
159
159
  supportsDetach: true,
160
160
  };
161
161
  }
162
+ /** 166-T2 — ExecutionEnv 寿命声明:pod `activeDeadlineSeconds = ceil(timeoutMs/1000)` 是平台真硬死线
163
+ * (k8s 到点杀 pod,无 keepalive 续期机制)⇒ 恒声明,core 在死线−60s 处 suspend(基建合格)或响亮
164
+ * 终局(env.lifetime_expired)。`lifetimeStartedAt` 有意不声明:core 只在 prepare 期读一次,lazy-connect
165
+ * 下 pod 尚未创建 ⇒ core 回落铸造时刻为锚——比 pod 真启动早,提早 suspend 是保守方向。 */
166
+ get lifetimeMs() {
167
+ return this.cfg.timeoutMs;
168
+ }
162
169
  resolve(p) {
163
170
  return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
164
171
  }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * UsageWindowStore SQL twins(design/166-T1)——core 5.8.0 部署级 token 治理窗
3
+ * (`RunnerDeps.usageWindows` + `usageWindowStore`)的 TiDB/PG 双方言实现。
4
+ *
5
+ * 语义单源 = core 纯函数(`chargeUsageRecord`/`readUsageRecord`,dist/core/usage-window-store.js):
6
+ * 本层只负责 record 的持久化与多副本并发正确性;窗算法(rolling slots 按最宽 horizon 自剪、
7
+ * first-use bucket lapsed 即弃)完全由 core 纯函数执行——SQL 层零窗语义。
8
+ *
9
+ * 并发正确性(本 store 的靶心;两副本同 key 并发 charge 绝不能丢账——丢账方向=治理窗漏放):
10
+ * - **ensure-first + 行锁**(mailbox twin 正字先例,design/166 复审靶1):事务内先幂等
11
+ * INSERT IGNORE / ON CONFLICT DO NOTHING 建空行 ⇒ SELECT ... FOR UPDATE 时行恒在,
12
+ * 「无行 FOR UPDATE + INSERT」的并发死锁(InnoDB gap-lock)/dup 撞车形整个消掉。
13
+ * - charge 频率低(core 只在任务进场/turn 边界/收尾 delta 提交),悲观锁简单正确,不用乐观 CAS。
14
+ * - `read` 无锁单 SELECT(readUsageRecord 纯函数);行缺席 = EMPTY_USAGE_WINDOW_RECORD。
15
+ * - record JSON 坏形(手工污染)= **fail-loud throw**:治理窗算错=计费面撒谎,不 fail-open。
16
+ *
17
+ * 列型:record 用 MySQL 方言 LONGTEXT(rolling slots 高并发长窗可破 64K TEXT——复审新5,
18
+ * background-agent LONGTEXT 同案);PG TEXT 不限长。key 列 VARCHAR(190)(principal 或
19
+ * GLOBAL_USAGE_KEY,均为受控标识,无字节等价诉求——不是用户可写身份键)。
20
+ *
21
+ * residue(design/166 04 §复审处置在档):不再被 charge 的 key 行永存(单行成本可忽略);
22
+ * GLOBAL 单行=并发 charge 串一把行锁的吞吐热点(出现瓶颈再分片)。
23
+ */
24
+ import type { Pool as MySqlPool } from "mysql2/promise";
25
+ import type { Pool as PgPool } from "pg";
26
+ import type { UsageWindow, UsageWindowReading, UsageWindowStore } from "@sema-agent/core";
27
+ export declare const USAGE_WINDOW_TABLE = "usage_window";
28
+ export declare function ensureTiDBUsageWindowSchema(pool: MySqlPool): Promise<void>;
29
+ export declare function ensurePgUsageWindowSchema(q: (text: string, params?: unknown[]) => Promise<unknown>): Promise<void>;
30
+ export declare class TiDBUsageWindowStore implements UsageWindowStore {
31
+ private readonly pool;
32
+ constructor(pool: MySqlPool);
33
+ charge(key: string, tokens: number, at: number, windows: readonly UsageWindow[]): Promise<void>;
34
+ read(key: string, windows: readonly UsageWindow[], now: number): Promise<readonly UsageWindowReading[]>;
35
+ }
36
+ export declare class PgUsageWindowStore implements UsageWindowStore {
37
+ private readonly pool;
38
+ constructor(pool: PgPool);
39
+ charge(key: string, tokens: number, at: number, windows: readonly UsageWindow[]): Promise<void>;
40
+ read(key: string, windows: readonly UsageWindow[], now: number): Promise<readonly UsageWindowReading[]>;
41
+ }
42
+ //# sourceMappingURL=usage-window-store-sql.d.ts.map
@@ -0,0 +1,113 @@
1
+ import { EMPTY_USAGE_WINDOW_RECORD, chargeUsageRecord, readUsageRecord } from "@sema-agent/core";
2
+ export const USAGE_WINDOW_TABLE = "usage_window";
3
+ /** record 列 → UsageWindowRecord。坏形 fail-loud(治理窗算错=计费面撒谎;与快照/凭据类 store 的
4
+ * onCorruptRead 披露座不同族——这里没有「跳过一行继续」的安全语义,账本坏=停)。 */
5
+ function parseRecord(raw, key) {
6
+ let v;
7
+ try {
8
+ v = JSON.parse(String(raw));
9
+ }
10
+ catch (e) {
11
+ throw new Error(`usage_window record for key "${key}" is not valid JSON (${String(e)}) — refusing to mis-count a governance window`);
12
+ }
13
+ const r = v;
14
+ if (typeof v !== "object" || v === null || !Array.isArray(r.slots) || !Array.isArray(r.buckets)) {
15
+ throw new Error(`usage_window record for key "${key}" has an invalid shape (expected {slots[],buckets[]}) — refusing to mis-count a governance window`);
16
+ }
17
+ return v;
18
+ }
19
+ export async function ensureTiDBUsageWindowSchema(pool) {
20
+ await pool.query(`CREATE TABLE IF NOT EXISTS ${USAGE_WINDOW_TABLE} (
21
+ k VARCHAR(190) NOT NULL,
22
+ record LONGTEXT NOT NULL,
23
+ updated_at_ms BIGINT NOT NULL,
24
+ PRIMARY KEY (k)
25
+ ) COLLATE utf8mb4_bin`);
26
+ }
27
+ export async function ensurePgUsageWindowSchema(q) {
28
+ await q(`CREATE TABLE IF NOT EXISTS ${USAGE_WINDOW_TABLE} (
29
+ k VARCHAR(190) NOT NULL,
30
+ record TEXT NOT NULL,
31
+ updated_at_ms BIGINT NOT NULL,
32
+ PRIMARY KEY (k)
33
+ )`);
34
+ }
35
+ export class TiDBUsageWindowStore {
36
+ pool;
37
+ constructor(pool) {
38
+ this.pool = pool;
39
+ }
40
+ async charge(key, tokens, at, windows) {
41
+ const c = await this.pool.getConnection();
42
+ try {
43
+ await c.beginTransaction();
44
+ // ensure-first:幂等建空行(EMPTY record)⇒ 下一步 FOR UPDATE 行恒在(锁语义干净,无 gap-lock 死锁形)。
45
+ await c.query(`INSERT IGNORE INTO ${USAGE_WINDOW_TABLE} (k, record, updated_at_ms) VALUES (?, ?, ?)`, [
46
+ key,
47
+ JSON.stringify(EMPTY_USAGE_WINDOW_RECORD),
48
+ at,
49
+ ]);
50
+ const [rows] = (await c.query(`SELECT record FROM ${USAGE_WINDOW_TABLE} WHERE k = ? FOR UPDATE`, [key]));
51
+ if (rows.length === 0) {
52
+ // 本表无删除臂(不同于 mailbox 的 drop/reap),ensure 后空 = 真障碍 fail-loud。
53
+ throw new Error(`usage_window row for key "${key}" vanished between ensure and lock — refusing to drop a governance charge`);
54
+ }
55
+ const record = parseRecord(rows[0].record, key);
56
+ const next = chargeUsageRecord(record, tokens, at, windows);
57
+ await c.query(`UPDATE ${USAGE_WINDOW_TABLE} SET record = ?, updated_at_ms = ? WHERE k = ?`, [JSON.stringify(next), at, key]);
58
+ await c.commit();
59
+ }
60
+ catch (e) {
61
+ await c.rollback().catch(() => { });
62
+ throw e;
63
+ }
64
+ finally {
65
+ c.release();
66
+ }
67
+ }
68
+ async read(key, windows, now) {
69
+ const [rows] = (await this.pool.query(`SELECT record FROM ${USAGE_WINDOW_TABLE} WHERE k = ?`, [key]));
70
+ const record = rows.length === 0 ? EMPTY_USAGE_WINDOW_RECORD : parseRecord(rows[0].record, key);
71
+ return readUsageRecord(record, windows, now);
72
+ }
73
+ }
74
+ export class PgUsageWindowStore {
75
+ pool;
76
+ constructor(pool) {
77
+ this.pool = pool;
78
+ }
79
+ async charge(key, tokens, at, windows) {
80
+ const c = await this.pool.connect();
81
+ try {
82
+ await c.query("BEGIN");
83
+ // ensure-first(TiDB 同注):ON CONFLICT DO NOTHING 幂等建空行,再锁——插入不带锁,锁只从
84
+ // FOR UPDATE 来(复审靶1 的 PG 交错:并发首充双双 ensure,后到方 DO NOTHING 后必须真锁行)。
85
+ await c.query(`INSERT INTO ${USAGE_WINDOW_TABLE} (k, record, updated_at_ms) VALUES ($1, $2, $3) ON CONFLICT (k) DO NOTHING`, [
86
+ key,
87
+ JSON.stringify(EMPTY_USAGE_WINDOW_RECORD),
88
+ at,
89
+ ]);
90
+ const { rows } = await c.query(`SELECT record FROM ${USAGE_WINDOW_TABLE} WHERE k = $1 FOR UPDATE`, [key]);
91
+ if (rows.length === 0) {
92
+ throw new Error(`usage_window row for key "${key}" vanished between ensure and lock — refusing to drop a governance charge`);
93
+ }
94
+ const record = parseRecord(rows[0].record, key);
95
+ const next = chargeUsageRecord(record, tokens, at, windows);
96
+ await c.query(`UPDATE ${USAGE_WINDOW_TABLE} SET record = $1, updated_at_ms = $2 WHERE k = $3`, [JSON.stringify(next), at, key]);
97
+ await c.query("COMMIT");
98
+ }
99
+ catch (e) {
100
+ await c.query("ROLLBACK").catch(() => { });
101
+ throw e;
102
+ }
103
+ finally {
104
+ c.release();
105
+ }
106
+ }
107
+ async read(key, windows, now) {
108
+ const { rows } = await this.pool.query(`SELECT record FROM ${USAGE_WINDOW_TABLE} WHERE k = $1`, [key]);
109
+ const record = rows.length === 0 ? EMPTY_USAGE_WINDOW_RECORD : parseRecord(rows[0].record, key);
110
+ return readUsageRecord(record, windows, now);
111
+ }
112
+ }
113
+ //# sourceMappingURL=usage-window-store-sql.js.map
@@ -6,7 +6,7 @@
6
6
  * `POST /v1/assistant/tasks/:id/resume`) instead of FAILING; absent ⇒ a limit still FAILS.
7
7
  *
8
8
  * 🔴 EXCLUDE verify/cascade tasks. Core runs their implementation via `runWithVerification`/`runCascade`, which
9
- * map an inner durable suspend to a FAILED result (`errorCode: "unexpected.suspended"`). The service would then
9
+ * map an inner durable suspend to a FAILED result (`errorCode: "suspended.awaiting_approval"`,core 5.9.0 W1 改名). The service would then
10
10
  * `setTerminal(failed)` while core had already minted a pending `resource_limit` checkpoint + suspended the
11
11
  * remote VM — ORPHANING both (the run row is terminal, so nothing resumes them). So a resource-limit hit on a
12
12
  * verify/cascade task must FAIL CLEANLY (no suspend), exactly as before the flag. This exclusion is the SAME
@@ -6,7 +6,7 @@
6
6
  * `POST /v1/assistant/tasks/:id/resume`) instead of FAILING; absent ⇒ a limit still FAILS.
7
7
  *
8
8
  * 🔴 EXCLUDE verify/cascade tasks. Core runs their implementation via `runWithVerification`/`runCascade`, which
9
- * map an inner durable suspend to a FAILED result (`errorCode: "unexpected.suspended"`). The service would then
9
+ * map an inner durable suspend to a FAILED result (`errorCode: "suspended.awaiting_approval"`,core 5.9.0 W1 改名). The service would then
10
10
  * `setTerminal(failed)` while core had already minted a pending `resource_limit` checkpoint + suspended the
11
11
  * remote VM — ORPHANING both (the run row is terminal, so nothing resumes them). So a resource-limit hit on a
12
12
  * verify/cascade task must FAIL CLEANLY (no suspend), exactly as before the flag. This exclusion is the SAME
package/dist/runs.d.ts CHANGED
@@ -168,7 +168,7 @@ export declare class TurnAnchorCapture {
168
168
  * Strip the durable-resume capability token from a result before it is persisted to the replayable event
169
169
  * log / run row or returned to the submitter. The token IS the resume credential (token-as-auth, search
170
170
  * [18] Q1b) and must never leave the service. Safe on any result (no-op when absent) — notably core maps a
171
- * NESTED suspend under verify/cascade to status:"failed" + errorCode "unexpected.suspended" while still
171
+ * NESTED suspend under verify/cascade to status:"failed" + errorCode "suspended.awaiting_approval"(core 5.9.0 W1 改名)while still
172
172
  * spreading `checkpointToken`, so that "failed" path (unlike the stream path's explicit suspend handling)
173
173
  * would otherwise leak it into task_event / task_run.result / the sync 200 response. `checkpointGate` is
174
174
  * non-secret and intentionally kept.
package/dist/runs.js CHANGED
@@ -237,7 +237,7 @@ export class TurnAnchorCapture {
237
237
  * Strip the durable-resume capability token from a result before it is persisted to the replayable event
238
238
  * log / run row or returned to the submitter. The token IS the resume credential (token-as-auth, search
239
239
  * [18] Q1b) and must never leave the service. Safe on any result (no-op when absent) — notably core maps a
240
- * NESTED suspend under verify/cascade to status:"failed" + errorCode "unexpected.suspended" while still
240
+ * NESTED suspend under verify/cascade to status:"failed" + errorCode "suspended.awaiting_approval"(core 5.9.0 W1 改名)while still
241
241
  * spreading `checkpointToken`, so that "failed" path (unlike the stream path's explicit suspend handling)
242
242
  * would otherwise leak it into task_event / task_run.result / the sync 200 response. `checkpointGate` is
243
243
  * non-secret and intentionally kept.
@@ -102,6 +102,14 @@ export declare function normalizeLimits(v: unknown, caps: TaskLimitCaps): {
102
102
  maxWalltimeMs?: number;
103
103
  maxOutputTokens?: number;
104
104
  maxTurns?: number;
105
+ approachNotice?: false | {
106
+ at?: [number, number];
107
+ };
108
+ } | undefined;
109
+ /** 166-T3 — approachNotice 单键归一(normalizeLimits/HTTP 门共用判据源):合法 ⇒ 归一值,坏形 ⇒ undefined。
110
+ * 阈值规则对齐 core(prepare-task at 校验):二元组、(0,1] 有限数、a<=b(允许相等)。 */
111
+ export declare function normalizeApproachNotice(raw: unknown): false | {
112
+ at?: [number, number];
105
113
  } | undefined;
106
114
  /**
107
115
  * [854]④ — body.limits 与 env 墙钟的合成规则(resolveSpec 的唯一 limits 装配点;run-local 无 body 车道不经此):
@@ -374,9 +374,28 @@ export function normalizeLimits(v, caps) {
374
374
  ...(() => { const n = pick(o.maxWalltimeMs, caps.timeoutSec !== undefined ? caps.timeoutSec * 1000 : undefined); return n !== undefined ? { maxWalltimeMs: n } : {}; })(),
375
375
  ...(() => { const n = pick(o.maxOutputTokens, caps.maxOutputTokens); return n !== undefined ? { maxOutputTokens: n } : {}; })(),
376
376
  ...(() => { const n = pick(o.maxTurns, caps.maxTurns); return n !== undefined ? { maxTurns: n } : {}; })(),
377
+ // 166-T3:approachNotice(core 默认 ON;false=唯一关闭形,{at:[a,b]} 调阈,core 允 a<=b)。坏形整键
378
+ // DROP(降级为 core 默认阈,方向安全)——不 throw,resume 重放通道同门。
379
+ ...(() => { const n = normalizeApproachNotice(o.approachNotice); return n !== undefined ? { approachNotice: n } : {}; })(),
377
380
  };
378
381
  return Object.keys(out).length > 0 ? out : undefined;
379
382
  }
383
+ /** 166-T3 — approachNotice 单键归一(normalizeLimits/HTTP 门共用判据源):合法 ⇒ 归一值,坏形 ⇒ undefined。
384
+ * 阈值规则对齐 core(prepare-task at 校验):二元组、(0,1] 有限数、a<=b(允许相等)。 */
385
+ export function normalizeApproachNotice(raw) {
386
+ if (raw === false)
387
+ return false;
388
+ if (raw == null || typeof raw !== "object" || Array.isArray(raw))
389
+ return undefined;
390
+ const at = raw.at;
391
+ if (at === undefined)
392
+ return {};
393
+ if (!Array.isArray(at) || at.length !== 2)
394
+ return undefined;
395
+ const [a, b] = at;
396
+ const frac = (n) => typeof n === "number" && Number.isFinite(n) && n > 0 && n <= 1;
397
+ return frac(a) && frac(b) && a <= b ? { at: [a, b] } : undefined;
398
+ }
380
399
  /**
381
400
  * [854]④ — body.limits 与 env 墙钟的合成规则(resolveSpec 的唯一 limits 装配点;run-local 无 body 车道不经此):
382
401
  * - `timeoutSec`:body 给了就用 body(caller 明确配速;已在 normalizeLimits 里被 TASK_TIMEOUT_MAX_SEC 封顶)。
@@ -26,6 +26,14 @@ export interface ToolApprovalFrame {
26
26
  /** [1550] 展示身份(子代 agent 名)——UNTRUSTED-for-display(core 未做秘密脱敏)→ redactSecrets 后渲。
27
27
  * 仅子代 ask 上可能在场(core 未必总能解析出名字)。 */
28
28
  sourceAgentName?: string;
29
+ /** core 5.9.0 W1([2535]):审批上浮的出处链——{parentToolCallId, depth, agentName?}(孙代最内层帧
30
+ * 胜出)。只读展示增强(cli [2516] 验收形=审批卡带子代出处);agentName UNTRUSTED → redactSecrets。
31
+ * 只在子代 ask 上在场(与 fromSubagent 同门)。 */
32
+ delegation?: {
33
+ parentToolCallId: string;
34
+ depth: number;
35
+ agentName?: string;
36
+ };
29
37
  message?: string;
30
38
  /** "tool_approval" only: the tool call's args, secret-redacted, UNTRUSTED-for-display. Absent (with
31
39
  * `argsOmitted: true`) when over the byte cap or unserializable. */
@@ -329,6 +329,16 @@ export class ToolApprovalCoordinator {
329
329
  sourceTaskId: req.sourceTaskId,
330
330
  fromSubagent: true,
331
331
  ...(typeof req.sourceAgentName === "string" ? { sourceAgentName: redactSecrets(req.sourceAgentName) } : {}),
332
+ // core 5.9.0 W1:出处链透传(agentName 同 sourceAgentName 待遇 redact;其余为 core-mint 标识非内容)。
333
+ ...(req.delegation
334
+ ? {
335
+ delegation: {
336
+ parentToolCallId: req.delegation.parentToolCallId,
337
+ depth: req.delegation.depth,
338
+ ...(typeof req.delegation.agentName === "string" ? { agentName: redactSecrets(req.delegation.agentName) } : {}),
339
+ },
340
+ }
341
+ : {}),
332
342
  }
333
343
  : {}),
334
344
  message: typeof req.message === "string" ? redactDeep(req.message) : "",
@@ -25,7 +25,7 @@ type BgNotifExcluded = "kind" | "sessionScoped" | "owner" | "scope" | "descripti
25
25
  type _GuardBgNotif = AssertAllKeysHandled<Exclude<keyof BackgroundChildEvent, BgNotifProjected | BgNotifExcluded>>;
26
26
  type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "createdAt";
27
27
  type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
28
- type AskProjected = "toolName" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName";
28
+ type AskProjected = "toolName" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation";
29
29
  type AskExcluded = "toolCallId" | "preview" | "principal" | "requiresRealApproval";
30
30
  type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
31
31
  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";
@@ -129,6 +129,7 @@ export declare function toolEndEventData(ev: {
129
129
  truncated?: boolean;
130
130
  totalChars?: number;
131
131
  structured?: unknown;
132
+ errorCode?: string;
132
133
  eventId?: string;
133
134
  parentToolCallId?: string;
134
135
  }): Record<string, unknown>;
@@ -156,6 +156,9 @@ export function toolEndEventData(ev) {
156
156
  // core 1.442([1898] RB-210):截断诚实体量(各 block 真实大小之和,不含 JSON 包装开销)。
157
157
  ...(Number.isFinite(ev.totalChars) ? { totalChars: ev.totalChars } : {}),
158
158
  ...(ev.structured !== undefined ? { structured: redactDeep(ev.structured) } : {}),
159
+ // core 5.9.0 W3([2535]):park/死投影结构化标记(引擎闭词表 `gate.parked`/`tool.not_found`,只在
160
+ // isError 时在场)——verbatim 透传,壳按它判别 park 毒化帧而不锚 abort 文案字面。
161
+ ...(typeof ev.errorCode === "string" ? { errorCode: ev.errorCode } : {}),
159
162
  ...identityFields(ev),
160
163
  };
161
164
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "6.0.0",
3
+ "version": "6.2.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,7 +54,7 @@
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.8.0",
57
+ "@sema-agent/core": "^5.9.0",
58
58
  "@sema-agent/registry-core": "^0.14.0",
59
59
  "e2b": "^2.28.0",
60
60
  "libsodium-wrappers": "^0.8.4",
@@ -68,7 +68,7 @@
68
68
  "sharp": "^0.35.3"
69
69
  },
70
70
  "devDependencies": {
71
- "@sema-agent/sdk": "^5.2.0",
71
+ "@sema-agent/sdk": "^6.1.0",
72
72
  "@types/libsodium-wrappers": "^0.7.14",
73
73
  "@types/node": "22.10.2",
74
74
  "@types/pg": "^8.20.0",