@sema-agent/server 7.124.1 → 7.125.1
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/MIGRATION.md +138 -2
- package/USAGE.md +46 -8
- package/dist/audit.d.ts +27 -5
- package/dist/audit.js +42 -8
- package/dist/boot/execution-env.d.ts +33 -1
- package/dist/boot/execution-env.js +31 -2
- package/dist/boot/resolve-spec.js +4 -3
- package/dist/boot/session-faces.d.ts +14 -1
- package/dist/boot/session-faces.js +6 -5
- package/dist/boot/stage-02-web-admin-pull.js +5 -1
- package/dist/boot/stage-05-execution-env.js +6 -2
- package/dist/boot/stage-09-leader.d.ts +1 -1
- package/dist/boot/stage-10-http-server.d.ts +1 -1
- package/dist/boot/web-admin.js +130 -140
- package/dist/capabilities/center-wiring.d.ts +2 -1
- package/dist/capabilities/composite-members.d.ts +1 -0
- package/dist/capabilities/composite-members.js +1 -0
- package/dist/capability-facts.d.ts +28 -3
- package/dist/capability-facts.js +20 -0
- package/dist/config-catalog.js +4 -1
- package/dist/config-d-migration.js +2 -1
- package/dist/config-lkg.d.ts +4 -1
- package/dist/config-lkg.js +16 -12
- package/dist/config-provider.d.ts +4 -1
- package/dist/config-provider.js +1 -0
- package/dist/config-types.d.ts +10 -3
- package/dist/config.js +44 -14
- package/dist/env-facts.d.ts +2 -2
- package/dist/env-facts.js +3 -2
- package/dist/fleet/fleet-bus.d.ts +24 -12
- package/dist/fleet/fleet-bus.js +4 -1
- package/dist/fleet/workspace-snapshot-residual.d.ts +10 -0
- package/dist/fleet/workspace-snapshot-residual.js +35 -0
- package/dist/hooks/hook-runner.d.ts +15 -27
- package/dist/hooks/hook-runner.js +19 -59
- package/dist/http/active-run-conflict.d.ts +3 -0
- package/dist/http/active-run-conflict.js +3 -0
- package/dist/http/admission.js +7 -7
- package/dist/http/approval-sweeps.js +2 -4
- package/dist/http/device-placement.js +1 -0
- package/dist/http/dispatch.js +1 -1
- package/dist/http/durable-run-leg.d.ts +20 -6
- package/dist/http/durable-run-leg.js +4 -4
- package/dist/http/idempotency.d.ts +83 -38
- package/dist/http/idempotency.js +38 -13
- package/dist/http/request-guards.d.ts +18 -0
- package/dist/http/request-guards.js +12 -3
- package/dist/http/resume-legs.js +7 -1
- package/dist/http/route-ctx.d.ts +3 -3
- package/dist/http/route-ctx.js +1 -1
- package/dist/http/routes/a2a-serve.js +27 -11
- package/dist/http/routes/capabilities.js +1 -0
- package/dist/http/routes/fleet.js +2 -0
- package/dist/http/routes/images.js +2 -2
- package/dist/http/routes/leader.d.ts +4 -0
- package/dist/http/routes/leader.js +13 -3
- package/dist/http/routes/observability.js +2 -0
- package/dist/http/routes/runs.d.ts +6 -2
- package/dist/http/routes/runs.js +71 -101
- package/dist/http/routes/session-sync.d.ts +3 -0
- package/dist/http/routes/session-sync.js +30 -1
- package/dist/http/routes/sessions-list.d.ts +11 -0
- package/dist/http/routes/sessions-list.js +6 -6
- package/dist/http/routes/sessions.d.ts +6 -1
- package/dist/http/routes/sessions.js +41 -10
- package/dist/http/routes/tasks.js +63 -21
- package/dist/http/run-read-face.d.ts +26 -0
- package/dist/http/run-read-face.js +67 -0
- package/dist/http/server.d.ts +4 -4
- package/dist/http/server.js +1 -2
- package/dist/http/submit-replay.d.ts +40 -0
- package/dist/http/submit-replay.js +22 -0
- package/dist/leader/endpoint.d.ts +10 -5
- package/dist/leader/endpoint.js +27 -5
- package/dist/observability/cost-quota.d.ts +51 -7
- package/dist/observability/cost-quota.js +35 -17
- package/dist/observability/fail-open.d.ts +1 -1
- package/dist/observability/fail-open.js +1 -1
- package/dist/observability/run-terminal.d.ts +5 -9
- package/dist/observability/run-terminal.js +2 -7
- package/dist/plugins/approval-ask-store-sql.js +1 -1
- package/dist/plugins/caching-session-store.d.ts +7 -2
- package/dist/plugins/caching-session-store.js +6 -0
- package/dist/plugins/device-store-sql.js +1 -1
- package/dist/plugins/ensure-index.js +22 -0
- package/dist/plugins/file-run-store.d.ts +24 -14
- package/dist/plugins/file-run-store.js +52 -12
- package/dist/plugins/leader-run-store-sql.d.ts +1 -11
- package/dist/plugins/leader-run-store-sql.js +1 -5
- package/dist/plugins/local-session-store.d.ts +35 -6
- package/dist/plugins/local-session-store.js +87 -18
- package/dist/plugins/memory-run-store.d.ts +13 -12
- package/dist/plugins/memory-run-store.js +25 -0
- package/dist/plugins/one-time-migrations.js +1 -1
- package/dist/plugins/pg-cost-quota.d.ts +1 -1
- package/dist/plugins/pg-cost-quota.js +2 -5
- package/dist/plugins/pg-pool.js +3 -0
- package/dist/plugins/pg-session-storage.d.ts +11 -1
- package/dist/plugins/pg-session-storage.js +44 -8
- package/dist/plugins/remote-env-k8s.d.ts +127 -12
- package/dist/plugins/remote-env-k8s.js +270 -27
- package/dist/plugins/run-store-sql.d.ts +26 -12
- package/dist/plugins/run-store-sql.js +50 -25
- package/dist/plugins/schema-consistency.d.ts +1 -13
- package/dist/plugins/schema-consistency.js +1 -41
- package/dist/plugins/schema-indexes.js +1 -0
- package/dist/plugins/schema-live-columns.d.ts +20 -0
- package/dist/plugins/schema-live-columns.js +42 -0
- package/dist/plugins/store-contracts.d.ts +40 -2
- package/dist/plugins/tidb-cost-quota.d.ts +1 -1
- package/dist/plugins/tidb-cost-quota.js +2 -5
- package/dist/plugins/tidb-pool.js +14 -3
- package/dist/plugins/tidb-session-store.d.ts +22 -2
- package/dist/plugins/tidb-session-store.js +33 -6
- package/dist/plugins/write-behind-counter.d.ts +12 -5
- package/dist/plugins/write-behind-counter.js +14 -7
- package/dist/prompts-domain-validate.js +3 -2
- package/dist/rules-consent.d.ts +2 -2
- package/dist/rules-consent.js +2 -2
- package/dist/run-local.js +31 -23
- package/dist/runs.d.ts +9 -0
- package/dist/runs.js +8 -0
- package/dist/runtime-governance.d.ts +3 -3
- package/dist/runtime-governance.js +2 -4
- package/dist/security.d.ts +19 -2
- package/dist/session-row.d.ts +53 -0
- package/dist/session-row.js +47 -0
- package/dist/session-sync.d.ts +6 -0
- package/dist/session-sync.js +5 -1
- package/dist/session-titler.d.ts +9 -5
- package/dist/session-titler.js +3 -1
- package/dist/spec-fields.js +2 -2
- package/dist/task-cwd.d.ts +0 -20
- package/dist/task-cwd.js +2 -10
- package/dist/tool-approval.d.ts +5 -7
- package/dist/trace/redact.d.ts +23 -50
- package/dist/trace/redact.js +42 -9
- package/dist/trace/self-minted-wire.d.ts +25 -0
- package/dist/trace/self-minted-wire.js +9 -0
- package/dist/web-admin-client/apply-effective.d.ts +10 -12
- package/dist/web-admin-client/apply-effective.js +2 -3
- package/dist/web-admin-client/candidate-gate.d.ts +69 -0
- package/dist/web-admin-client/candidate-gate.js +27 -0
- package/dist/web-admin-client/http-client.d.ts +2 -1
- package/dist/web-admin-client/http-client.js +2 -1
- package/dist/web-admin-client/pull-state.d.ts +6 -4
- package/dist/web-admin-client/read-warnings.d.ts +29 -5
- package/dist/web-admin-client/read-warnings.js +24 -4
- package/dist/web-admin-client/stage-limits.js +4 -1
- package/dist/workspace-space.d.ts +41 -0
- package/dist/workspace-space.js +5 -0
- package/package.json +3 -3
package/dist/capability-facts.js
CHANGED
|
@@ -3,6 +3,7 @@ import { resolveStreamApprovalGate } from "./tool-approval.js";
|
|
|
3
3
|
import { selfOrchestrationDenial } from "./task-workflow.js";
|
|
4
4
|
import { centerEntitlementSourceWired } from "./runtime-caps-resolver.js";
|
|
5
5
|
import { overwriteWipeIncapable } from "./session-sync.js";
|
|
6
|
+
import { WORKSPACE_PERSIST_DEFAULT, WORKSPACE_SNAPSHOT_KEEP_DEFAULT } from "./workspace-space.js";
|
|
6
7
|
export const CAPABILITY_FACT_WHY = {
|
|
7
8
|
run_store_absent: "no durable run store is wired (DB_BACKEND=mysql|pg|local)",
|
|
8
9
|
subagent_registry_absent: "the sub-agent steer registry is not wired",
|
|
@@ -15,6 +16,7 @@ export const CAPABILITY_FACT_WHY = {
|
|
|
15
16
|
session_leaf_face_absent: "the session store has no leaf-read face (sessionStorage.getLeafId)",
|
|
16
17
|
session_lister_absent: "no session enumerator is wired (session store or run store listSessions)",
|
|
17
18
|
session_fork_face_absent: "the session store has no fork face",
|
|
19
|
+
session_row_faces_absent: "the session store has no session-row faces (sessionFaceOf / sessionSummaryOf / patchSessionMeta)",
|
|
18
20
|
session_purge_absent: "no session purge coordinator is wired",
|
|
19
21
|
session_policy_store_absent: "no session policy store is wired",
|
|
20
22
|
store_backend_absent: "no store backend is wired (DB_BACKEND=memory, multi-tenant bare boot, or half-configured SQL)",
|
|
@@ -58,6 +60,8 @@ export const CAPABILITY_FACT_WHY = {
|
|
|
58
60
|
scheduler_disabled: "the self-wake scheduler is off (SCHEDULER_ENABLED)",
|
|
59
61
|
multi_tenant_deployment: "this is a multi-tenant deployment (REQUIRE_PRINCIPAL=true)",
|
|
60
62
|
not_host_lane: "the execution lane is not the single-user host lane (REMOTE_EXEC=host)",
|
|
63
|
+
not_k8s_lane: "the execution lane is not the k8s pod sandbox (REMOTE_EXEC=k8s)",
|
|
64
|
+
workspace_snapshot_store_absent: "no workspace snapshot store is configured (MINIO_ENDPOINT / MINIO_ACCESS_KEY / MINIO_SECRET_KEY)",
|
|
61
65
|
};
|
|
62
66
|
const no = (why) => ({ available: false, why });
|
|
63
67
|
const yes = (w) => ({ available: true, ...w });
|
|
@@ -123,6 +127,12 @@ export function sessionFork(d) {
|
|
|
123
127
|
return no("session_owner_face_absent");
|
|
124
128
|
return yes({ sessionStorage: s });
|
|
125
129
|
}
|
|
130
|
+
export function sessionPatch(d) {
|
|
131
|
+
const s = d.sessionStorage;
|
|
132
|
+
if (s?.sessionFaceOf === undefined || s.patchSessionMeta === undefined)
|
|
133
|
+
return no("session_row_faces_absent");
|
|
134
|
+
return yes({ sessionStorage: s });
|
|
135
|
+
}
|
|
126
136
|
export function sessionDelete(d) {
|
|
127
137
|
if (d.purgeSession === undefined)
|
|
128
138
|
return no("session_purge_absent");
|
|
@@ -151,6 +161,8 @@ export function sessionSync(d) {
|
|
|
151
161
|
return no("session_export_absent");
|
|
152
162
|
if (s.beginImportStaging === undefined)
|
|
153
163
|
return no("session_import_staging_absent");
|
|
164
|
+
if (s.sessionFaceOf === undefined || s.sessionSummaryOf === undefined || s.patchSessionMeta === undefined)
|
|
165
|
+
return no("session_row_faces_absent");
|
|
154
166
|
if (overwriteWipeIncapable(d.sessionPolicyStore))
|
|
155
167
|
return no("policy_purge_absent");
|
|
156
168
|
return yes({ backend: d.backend, sessionStorage: s, historyStore: d.fileHistoryStore });
|
|
@@ -342,6 +354,14 @@ export function workflowsJournal(d) {
|
|
|
342
354
|
const journal = workflowJournal(d);
|
|
343
355
|
return journal.available ? yes({ store: runs.store, journal: journal.journal }) : journal;
|
|
344
356
|
}
|
|
357
|
+
export function workspaceSpace(c) {
|
|
358
|
+
const rx = c.remoteExec;
|
|
359
|
+
if (rx?.provider !== "k8s")
|
|
360
|
+
return no("not_k8s_lane");
|
|
361
|
+
if (!rx.s3Snapshot)
|
|
362
|
+
return no("workspace_snapshot_store_absent");
|
|
363
|
+
return yes({ policy: { persist: rx.workspacePersist ?? WORKSPACE_PERSIST_DEFAULT, keep: rx.workspaceSnapshotKeep ?? WORKSPACE_SNAPSHOT_KEEP_DEFAULT } });
|
|
364
|
+
}
|
|
345
365
|
export function hostScheduler(c) {
|
|
346
366
|
if (c.schedulerEnabled !== true)
|
|
347
367
|
return no("scheduler_disabled");
|
package/dist/config-catalog.js
CHANGED
|
@@ -2,6 +2,7 @@ import { CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES } fr
|
|
|
2
2
|
import { GLOBAL_SCOPE } from "@sema-agent/settings-schema/api/scopes";
|
|
3
3
|
import { WEB_ADMIN_ENV, DOMAIN_SCHEMAS, buildRetiredEnvWire } from "@sema-agent/settings-schema";
|
|
4
4
|
import { CENTER_CREDENTIAL_RENEW_LEAD_DEFAULT_MS, CENTER_CREDENTIAL_RENEW_LEAD_MAX_MS, CENTER_CREDENTIAL_RENEW_LEAD_MIN_MS } from "./center-credential-renewer.js";
|
|
5
|
+
import { WORKSPACE_PERSIST_DEFAULT, WORKSPACE_PERSIST_WORDS, WORKSPACE_SNAPSHOT_KEEP_DEFAULT, WORKSPACE_SNAPSHOT_KEEP_RANGE } from "./workspace-space.js";
|
|
5
6
|
import { THINKING_FORMATS } from "./model-compat.js";
|
|
6
7
|
import { AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS, CENTER_BASE_URL_RULE, CONFIG_PROVIDER_WORDS, DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS, DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS, FIRST_BYTE_TIMEOUT_BOUNDS, FLEET_LEASE_OUTAGE_MAX_DEFAULT_MS, RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS } from "./config-types.js";
|
|
7
8
|
import { webSearchProviderFromEnv, WEB_SEARCH_PROVIDERS } from "./plugins/web-search.js";
|
|
@@ -193,7 +194,7 @@ export const CONFIG_CATALOG = [
|
|
|
193
194
|
r("K8S_NAMESPACE", "orchestration", "string", "沙箱 pod namespace"),
|
|
194
195
|
r("K8S_RUNTIME_CLASS", "orchestration", "string", "沙箱 pod runtimeClass(Kata 等)"),
|
|
195
196
|
r("K8S_SANDBOX_IMAGE", "orchestration", "string", "k8s 车道沙箱镜像(REMOTE_EXEC=k8s 必需件)"),
|
|
196
|
-
r("K8S_TIMEOUT_MS", "orchestration", "number", "k8s
|
|
197
|
+
r("K8S_TIMEOUT_MS", "orchestration", "number", "k8s 沙箱 pod 寿命(activeDeadlineSeconds,ms;整只 pod 的泄漏护栏,后台任务超时也被它钳;各条命令的超时见工具超时)"),
|
|
197
198
|
r("K8S_TOKEN", "orchestration", "secret", "k8s API token(缺席走 in-cluster SA)", { danger: SEC }),
|
|
198
199
|
r("KUBERNETES_SERVICE_HOST", "orchestration", "string", "平台注入的 in-cluster k8s API host(K8S_API_URL 缺席时用)"),
|
|
199
200
|
r("KUBERNETES_SERVICE_PORT", "orchestration", "string", "平台注入的 in-cluster k8s API 端口(缺省 443)"),
|
|
@@ -479,7 +480,9 @@ export const CONFIG_CATALOG = [
|
|
|
479
480
|
r("WORKFLOW_SIZE_GUIDELINE", "orchestration", "enum", "Workflow 工具卡的规模建议(advisory;typo 拒启)", { enumValues: ["small", "medium", "large", "unrestricted"], configKey: "workflowSizeGuideline" }),
|
|
480
481
|
r("WORKFLOW_TOTAL_TIMEOUT_MS", "orchestration", "number", "workflow 脚本总时限(ms;0/负不是禁用 —— 无禁用臂)", { staticDefault: "3600000" }),
|
|
481
482
|
r("WORKSPACE_FILE_MAX_BYTES", "limitsHttp", "number", "已随 workspace 浏览面退役(S-15):值照常校验/落值,应用于空(重启也不生效)", { envTiming: "inert", configKey: "workspaceFileMaxBytes", center: cLimits("workspaceFileMaxBytes", { timing: "inert" }) }),
|
|
483
|
+
r("WORKSPACE_PERSIST", "orchestration", "enum", "k8s 工作区跨轮保留(opt-in):space = 每条 run 结束把 /workspace 快照进本会话住客的空间、下一条 run 开始恢复最近一份(开着时有已知残窗:提交标记晚于拆环境上界落地 ⇒ 上一条的改动可能被覆盖,见 USAGE §9.3b);off = 每轮新建(缺省)。设了 space 却没配快照存储(MINIO_* 三键)⇒ 不生效,起服一行 workspace_persist_unavailable 点名缺键;前提:会话店读得出会话住客(local / SQL 会话店),内存会话店设 space(且配了快照存储)⇒ 拒启;词表外拒启", { enumValues: WORKSPACE_PERSIST_WORDS, staticDefault: WORKSPACE_PERSIST_DEFAULT, danger: DATA }),
|
|
482
484
|
r("WORKSPACE_SNAPSHOT_BUCKET", "orchestration", "string", "k8s 工作区快照桶", { derivedDefaultNote: "缺席 ⇒ workspaces" }),
|
|
485
|
+
r("WORKSPACE_SNAPSHOT_KEEP", "orchestration", "number", `k8s 工作区每个空间(会话住客)保留的快照份数(整数 [${WORKSPACE_SNAPSHOT_KEEP_RANGE.min}, ${WORKSPACE_SNAPSHOT_KEEP_RANGE.max}];写新快照后删最老的;域外拒启)`, { staticDefault: String(WORKSPACE_SNAPSHOT_KEEP_DEFAULT), danger: DATA }),
|
|
483
486
|
r("WORKSPACE_SNAPSHOT_PREFIX", "orchestration", "string", "k8s 工作区快照 key 前缀"),
|
|
484
487
|
r("WORKSPACE_SNAPSHOT_TTL_SEC", "orchestration", "number", "k8s 工作区快照 presign TTL(秒)"),
|
|
485
488
|
r("WORKTREE_ALLOWED_ROOTS", "orchestration", "csv", "worktree 隔离纵深白名单(缺省 = [repoRoot])", { danger: SEC }),
|
|
@@ -2,7 +2,8 @@ import { stat } from "node:fs/promises";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { RETIRED_DOMAINS, formatConfigPath, isRetiredDomainName } from "@sema-agent/settings-schema";
|
|
4
4
|
import { PORTABLE_DOMAINS, migrateConfigDirectory, nodeConfigDirectoryIo } from "@sema-agent/settings-schema/node";
|
|
5
|
-
|
|
5
|
+
import { REMOTE_EXEC_DOCUMENT } from "@sema-agent/settings-schema/remote-exec";
|
|
6
|
+
const READ_BACK_DOCUMENTS = [...PORTABLE_DOMAINS, ...Object.keys(RETIRED_DOMAINS).filter(isRetiredDomainName), REMOTE_EXEC_DOCUMENT];
|
|
6
7
|
const errnoOf = (err) => (err instanceof Error && "code" in err && typeof err.code === "string" ? err.code : undefined);
|
|
7
8
|
export async function migrateLocalConfigDirectory(root) {
|
|
8
9
|
const dir = join(root, "config.d");
|
package/dist/config-lkg.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type MigrationReceipt } from "@sema-agent/settings-schema/one-time-migrations";
|
|
2
|
+
import type { PromptsDomainFaces } from "./prompts-domain-validate.js";
|
|
2
3
|
/** 落盘信封。`effective`/`etag` 逐字=当时的 fetchEffective 返回(JSON verbatim 面)。`formatVersion` 的语义见头注(4 = 载荷是 settings-schema 11 的形)。 */
|
|
3
4
|
export interface PersistedLkg {
|
|
4
5
|
formatVersion: 4;
|
|
@@ -59,9 +60,11 @@ export interface LkgReadMigration {
|
|
|
59
60
|
* 文件缺席/JSON 坏/信封形不对/worker 或凭证身份不匹配(防同名文件跨 worker / 跨账号串包;`CONFIG_LKG_PATH`
|
|
60
61
|
* 单一路径时这是唯一的一道)/载荷有迁不动的旧形({@link migrateLkgPayload})/prompts 面复判失败。
|
|
61
62
|
* `migrated`:读回时做过一次性迁移(信封 2 → 3 或载荷的 settings-schema 迁移)—— 调用方响亮打一行,并把交回的 4 写回原位。
|
|
63
|
+
* `prompts`:候选判官对这份载荷 prompts 面交出的已判形值(`undefined` = 载荷不带 prompts 键)—— 调用方采纳 prompts 直接用它。
|
|
62
64
|
*/
|
|
63
65
|
export declare function loadLkg(path: string, worker: string | undefined, identity: string): Promise<{
|
|
64
66
|
lkg: PersistedLkg;
|
|
67
|
+
prompts: PromptsDomainFaces | undefined;
|
|
65
68
|
migrated?: LkgReadMigration;
|
|
66
69
|
} | {
|
|
67
70
|
lkg?: undefined;
|
|
@@ -104,7 +107,7 @@ export type LegacyLkgMigration = {
|
|
|
104
107
|
* 2. 旧文件没有(ENOENT / ENOTDIR)或不是 formatVersion 1 信封 ⇒ `none`。旧默认路径上却读不了 / 不是 JSON ⇒
|
|
105
108
|
* `not-migrated`(响亮,不当它不存在);`CONFIG_LKG_PATH` 同路径时这两形由 loadLkg 自己报(不重复打一行)。
|
|
106
109
|
* 3. v1 信封只记 worker:唯一推得出身份的形是拉取令牌(身份 = worker,`pullTokenIdentity`)。此刻身份正是它、worker
|
|
107
|
-
* 对得上、载荷过 {@link migrateLkgPayload}(迁成 11 的形;迁不动 ⇒ 响亮拒、不写箱)与 {@link
|
|
110
|
+
* 对得上、载荷过 {@link migrateLkgPayload}(迁成 11 的形;迁不动 ⇒ 响亮拒、不写箱)与 {@link lkgPayloadJudged} ⇒ **先写后退役**(修复验证 N9):先按新格式把它写进 `binPath`(原子写,
|
|
108
111
|
* 0600),写成了才把旧文件改名 `<legacyPath>.migrated-v1`。写失败 ⇒ `not-migrated`,旧文件原样留在原位,下一次
|
|
109
112
|
* 照常可迁;写成之后改名失败 ⇒ 仍是 `migrated`(`retireError` 响一行),箱已在 ⇒ 下一次按判序 1 不再看旧文件。
|
|
110
113
|
* 同路径(`CONFIG_LKG_PATH`)时新箱的写入会原子替换旧文件本身,所以旧字节在写之前留一份 `.migrated-v1` 副本
|
package/dist/config-lkg.js
CHANGED
|
@@ -3,7 +3,7 @@ import { migrateEffectiveSnapshot } from "@sema-agent/settings-schema/one-time-m
|
|
|
3
3
|
import { formatConfigPath } from "@sema-agent/settings-schema";
|
|
4
4
|
import { join, dirname, resolve } from "node:path";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
|
-
import {
|
|
6
|
+
import { judgeConfigCandidate } from "./web-admin-client/candidate-gate.js";
|
|
7
7
|
import { centerStateBin, pullTokenIdentity } from "./center-credential.js";
|
|
8
8
|
import { configRootOf } from "./sema-config-dir.js";
|
|
9
9
|
export function defaultSkillCacheDir(config) {
|
|
@@ -74,12 +74,12 @@ export async function loadLkg(path, worker, identity) {
|
|
|
74
74
|
const payload = migrateLkgPayload(envelope.effective);
|
|
75
75
|
if ("refused" in payload)
|
|
76
76
|
return { reason: payload.refused };
|
|
77
|
-
const
|
|
78
|
-
if (problem
|
|
79
|
-
return { reason: problem };
|
|
77
|
+
const judged = lkgPayloadJudged(payload.effective, envelope.etag);
|
|
78
|
+
if ("problem" in judged)
|
|
79
|
+
return { reason: judged.problem };
|
|
80
80
|
const lkg = { ...envelope, formatVersion: 4, effective: payload.effective };
|
|
81
81
|
const migrated = formatVersion !== 4 || payload.receipts.length > 0;
|
|
82
|
-
return migrated ? { lkg, migrated: { fromFormat: formatVersion, ...(note !== undefined ? { note } : {}), receipts: payload.receipts } } : { lkg };
|
|
82
|
+
return migrated ? { lkg, prompts: judged.prompts, migrated: { fromFormat: formatVersion, ...(note !== undefined ? { note } : {}), receipts: payload.receipts } } : { lkg, prompts: judged.prompts };
|
|
83
83
|
}
|
|
84
84
|
export function migrateLkgPayload(effective) {
|
|
85
85
|
const m = migrateEffectiveSnapshot(effective);
|
|
@@ -122,7 +122,14 @@ function migrateV2Lkg(lkg) {
|
|
|
122
122
|
(hasRetired ? "; the retired carrier models.droppedIdentities was moved to models.droppedCatalogEntries" : ""),
|
|
123
123
|
};
|
|
124
124
|
}
|
|
125
|
-
function
|
|
125
|
+
function lkgPayloadJudged(effective, etag) {
|
|
126
|
+
const problem = lkgPayloadShapeProblem(effective, etag);
|
|
127
|
+
if (problem !== undefined)
|
|
128
|
+
return { problem };
|
|
129
|
+
const verdict = judgeConfigCandidate({ effective: effective });
|
|
130
|
+
return verdict.verdict === "clean" ? { prompts: verdict.prompts } : { problem: "prompts face fails re-validation" };
|
|
131
|
+
}
|
|
132
|
+
function lkgPayloadShapeProblem(effective, etag) {
|
|
126
133
|
if (effective === null || typeof effective !== "object")
|
|
127
134
|
return "bad effective";
|
|
128
135
|
if (etag !== undefined && typeof etag !== "string")
|
|
@@ -142,9 +149,6 @@ function lkgPayloadProblem(effective, etag) {
|
|
|
142
149
|
return `bad effective.${domain}.${arrayKey}`;
|
|
143
150
|
}
|
|
144
151
|
}
|
|
145
|
-
const promptsRaw = eff.prompts;
|
|
146
|
-
if (promptsRaw !== undefined && !validatePromptsDomain(promptsRaw).ok)
|
|
147
|
-
return "prompts face fails re-validation";
|
|
148
152
|
return undefined;
|
|
149
153
|
}
|
|
150
154
|
export function legacyV1LkgPath(config, worker) {
|
|
@@ -197,9 +201,9 @@ export async function migrateV1Lkg(o) {
|
|
|
197
201
|
const payload = migrateLkgPayload(v1.effective);
|
|
198
202
|
if ("refused" in payload)
|
|
199
203
|
return refuse(`its payload cannot be migrated to the current settings form (${payload.refused})`);
|
|
200
|
-
const
|
|
201
|
-
if (problem
|
|
202
|
-
return refuse(`its payload is unusable (${problem})`);
|
|
204
|
+
const judged = lkgPayloadJudged(payload.effective, v1.etag);
|
|
205
|
+
if ("problem" in judged)
|
|
206
|
+
return refuse(`its payload is unusable (${judged.problem})`);
|
|
203
207
|
const retiredTo = `${o.legacyPath}.migrated-v1`;
|
|
204
208
|
let retireError;
|
|
205
209
|
if (sameFile) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type DroppedConfigEntry } from "./web-admin-client/read-warnings.js";
|
|
1
|
+
import { type DroppedConfigEntry, type UnreadConfigFile } from "./web-admin-client/read-warnings.js";
|
|
2
2
|
import { FileConfigStore } from "@sema-agent/settings-schema/node";
|
|
3
3
|
import { type EffectiveConfig as AgentConfigEffective, type EffectiveReadWarning } from "@sema-agent/settings-schema";
|
|
4
4
|
import { fetchEffective as remoteFetchEffective, fetchSkillContent as remoteFetchSkillContent, type EffectiveConfig } from "./web-admin-client/facade.js";
|
|
@@ -23,6 +23,8 @@ import type { ConfigProviderWord } from "./config-types.js";
|
|
|
23
23
|
/** `droppedEntries`(S-704 ②):schema 逐条丢掉的条目(坐标 + 字段诊断,已脱敏;S-1295 起每行带闭集词 `reasonCode`)—— **不**进候选门,
|
|
24
24
|
* 调用方逐条响亮点名、其余照用;缺席 = 这次读零逐条丢弃(与 `domainErrors` 同一种缺席即无的形)。其中的目录条目同一次折叠另记进
|
|
25
25
|
* `effective.models.droppedCatalogEntries`(`GET /v1/models.droppedEntries` 的源,与 `models` 同世代)。 */
|
|
26
|
+
/** `unreadFiles`:`config.d` 里读腿根本不读的文件(不是域 / 不是便携域)—— **不**进候选门(不是读的域判形失败),调用方一份一行
|
|
27
|
+
* `config_unread_file` 点名、其余照用;缺席 = 没有(本地读腿才会有)。 */
|
|
26
28
|
export type FetchEffectiveResult = {
|
|
27
29
|
effective: EffectiveConfig;
|
|
28
30
|
etag?: string;
|
|
@@ -31,6 +33,7 @@ export type FetchEffectiveResult = {
|
|
|
31
33
|
error: string;
|
|
32
34
|
}>;
|
|
33
35
|
droppedEntries?: DroppedConfigEntry[];
|
|
36
|
+
unreadFiles?: UnreadConfigFile[];
|
|
34
37
|
readWarnings: readonly EffectiveReadWarning[];
|
|
35
38
|
credentialIdentity?: string;
|
|
36
39
|
} | null;
|
package/dist/config-provider.js
CHANGED
|
@@ -107,6 +107,7 @@ export class LocalConfigProvider {
|
|
|
107
107
|
readWarnings,
|
|
108
108
|
...(errors.length > 0 ? { domainErrors: errors } : {}),
|
|
109
109
|
...(droppedEntries.length > 0 ? { droppedEntries } : {}),
|
|
110
|
+
...(folded.unreadFiles.length > 0 ? { unreadFiles: folded.unreadFiles } : {}),
|
|
110
111
|
};
|
|
111
112
|
}
|
|
112
113
|
async fetchSkillContent(contentHash) {
|
package/dist/config-types.d.ts
CHANGED
|
@@ -907,6 +907,12 @@ export interface ServiceConfigFlat {
|
|
|
907
907
|
keyPrefix?: string;
|
|
908
908
|
presignTtlSec?: number;
|
|
909
909
|
};
|
|
910
|
+
/** Workspace persistence by space key (`WORKSPACE_PERSIST`) and snapshots kept per space key (`WORKSPACE_SNAPSHOT_KEEP`).
|
|
911
|
+
* The loader always mints both on this arm; optional only so hand-built configs may omit them. Effective policy =
|
|
912
|
+
* the capability fact `workspaceSpace` (`capability-facts.ts`, the one reading: defaults applied there, absent store ⇒
|
|
913
|
+
* unavailable). */
|
|
914
|
+
workspacePersist?: import("./workspace-space.js").WorkspacePersist;
|
|
915
|
+
workspaceSnapshotKeep?: number;
|
|
910
916
|
} | {
|
|
911
917
|
provider: "ssh";
|
|
912
918
|
host: string;
|
|
@@ -1572,7 +1578,8 @@ export interface ServiceConfigFlat {
|
|
|
1572
1578
|
* per-region via the registry effective-config (shell B3) later. */
|
|
1573
1579
|
sandboxPkgSource?: string;
|
|
1574
1580
|
/** Auto-generate a session title at first submit (one cheap-model call per
|
|
1575
|
-
* session
|
|
1581
|
+
* session; fills a NULL title only — a title the user set via `PATCH /v1/sessions/:id` is never overwritten).
|
|
1582
|
+
* Default ON; `SESSION_AUTO_TITLE=false` opts out. */
|
|
1576
1583
|
sessionAutoTitle: boolean;
|
|
1577
1584
|
/** S-178:上面那一位的来源(同一张四词表)。本键**不进 posture**(缺省恒 true,两形同值)——
|
|
1578
1585
|
* 报来源是为了让运维分辨「这台机器显式关了自动标题」与「它本来就开着」;壳此前注 `false` 的那条
|
|
@@ -2033,10 +2040,10 @@ export interface ServiceConfigFlat {
|
|
|
2033
2040
|
* single task from burning the shared cloud key. Enforced by core (failed + errorCode budget.*). */
|
|
2034
2041
|
maxTaskCostUsd: number;
|
|
2035
2042
|
maxTaskTokens: number;
|
|
2036
|
-
/** Per-principal CUMULATIVE cost ceiling (USD) within a
|
|
2043
|
+
/** Per-principal CUMULATIVE cost ceiling (USD) within a clock-aligned fixed window — blocks a tenant's NEXT task
|
|
2037
2044
|
* once their accumulated spend crosses it. 0/unset = no per-principal quota. */
|
|
2038
2045
|
maxPrincipalCostUsd: number;
|
|
2039
|
-
/**
|
|
2046
|
+
/** Clock-aligned fixed window (seconds, in [1, 1e10]; bucket = floor(now / window)) for the per-principal cost quota. Default 86400 (1 day). */
|
|
2040
2047
|
costQuotaWindowSec: number;
|
|
2041
2048
|
/** Degradation to a cheaper model `to` (a catalog name) instead of hard-failing (core 1.39/1.40):
|
|
2042
2049
|
* - **prospective** (1.40, `atCostFraction`): near-budget — at `atCostFraction × maxCostUsd` switch at
|
package/dist/config.js
CHANGED
|
@@ -20,11 +20,13 @@ import { normalizeSshFingerprint } from "./ssh-host-key.js";
|
|
|
20
20
|
import { buildReadFacePostureNotice, readFaceForPosture } from "./read-face-posture.js";
|
|
21
21
|
import { THINKING_LEVELS, THINKING_FORMATS, buildModelCompat, isThinkingFormat, isThinkingTier } from "./model-compat.js";
|
|
22
22
|
import {} from "./posture-source.js";
|
|
23
|
+
import { COST_QUOTA_WINDOW_SEC_RANGE, costQuotaWindowSecInRange } from "./observability/cost-quota.js";
|
|
23
24
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
24
25
|
import { describeUnmatchableRuleName, ruleEntryToolName, unmatchableRuleNameOf } from "./namespaced-rule-name.js";
|
|
25
26
|
import { bakeOperatorPrincipalsRefusal, bakeRunnerCredentialRefusal, centerIdentityRefusal, halfOpenDirectDoorRefusal, leaderDurableStoreRefusal, operatorPrincipalsRequiredRefusal } from "./config-invariants.js";
|
|
26
27
|
import { declaredSemaConfigDir } from "./sema-config-dir.js";
|
|
27
28
|
import { lkgPathOverrideOf } from "./config-lkg.js";
|
|
29
|
+
import { WORKSPACE_PERSIST_DEFAULT, WORKSPACE_PERSIST_WORDS, WORKSPACE_SNAPSHOT_KEEP_DEFAULT, WORKSPACE_SNAPSHOT_KEEP_RANGE } from "./workspace-space.js";
|
|
28
30
|
import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf, executionLaneRequiredRefusal, isSingleMachineDefaultLane, laneRequiredEnv } from "./execution-lane-caps.js";
|
|
29
31
|
import { MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "@sema-agent/settings-schema/execution-lanes";
|
|
30
32
|
import { AUTONOMY_MODES } from "./runtime-governance.js";
|
|
@@ -1853,19 +1855,7 @@ function parseOrchestrationDomain(ctx) {
|
|
|
1853
1855
|
...((n) => (n !== undefined ? { timeoutMs: n } : {}))(optNumEnvStrict("K8S_TIMEOUT_MS")),
|
|
1854
1856
|
...(process.env.K8S_MEMORY ? { memory: process.env.K8S_MEMORY } : {}),
|
|
1855
1857
|
...(process.env.K8S_CPU ? { cpu: process.env.K8S_CPU } : {}),
|
|
1856
|
-
...(
|
|
1857
|
-
? {
|
|
1858
|
-
s3Snapshot: {
|
|
1859
|
-
endpoint: process.env.MINIO_ENDPOINT,
|
|
1860
|
-
bucket: process.env.WORKSPACE_SNAPSHOT_BUCKET ?? "workspaces",
|
|
1861
|
-
accessKey: process.env.MINIO_ACCESS_KEY,
|
|
1862
|
-
secretKey: process.env.MINIO_SECRET_KEY,
|
|
1863
|
-
...(process.env.MINIO_REGION ? { region: process.env.MINIO_REGION } : {}),
|
|
1864
|
-
...(process.env.WORKSPACE_SNAPSHOT_PREFIX ? { keyPrefix: process.env.WORKSPACE_SNAPSHOT_PREFIX } : {}),
|
|
1865
|
-
...((n) => (n !== undefined ? { presignTtlSec: n } : {}))(optNumEnvStrict("WORKSPACE_SNAPSHOT_TTL_SEC")),
|
|
1866
|
-
},
|
|
1867
|
-
}
|
|
1868
|
-
: {}),
|
|
1858
|
+
...k8sWorkspaceStore(),
|
|
1869
1859
|
}
|
|
1870
1860
|
:
|
|
1871
1861
|
process.env.REMOTE_EXEC === "ssh" && process.env.SSH_HOST && process.env.SSH_USER && process.env.SSH_PRIVATE_KEY
|
|
@@ -2039,7 +2029,14 @@ function parseLimitsHttpDomain() {
|
|
|
2039
2029
|
maxTaskCostUsd: numEnv("MAX_TASK_COST_USD", "0"),
|
|
2040
2030
|
maxTaskTokens: numEnv("MAX_TASK_TOKENS", "0"),
|
|
2041
2031
|
maxPrincipalCostUsd: numEnv("MAX_PRINCIPAL_COST_USD", "0"),
|
|
2042
|
-
costQuotaWindowSec:
|
|
2032
|
+
costQuotaWindowSec: (() => {
|
|
2033
|
+
const sec = numEnv("COST_QUOTA_WINDOW_SEC", "86400");
|
|
2034
|
+
if (!costQuotaWindowSecInRange(sec)) {
|
|
2035
|
+
const { min, max } = COST_QUOTA_WINDOW_SEC_RANGE;
|
|
2036
|
+
throw new Error(`env COST_QUOTA_WINDOW_SEC=${sec} must be in [${min}, ${max}] seconds — it is the length of the clock-aligned per-principal cost-quota window, and its end must be a representable instant; there is no "off" here: MAX_PRINCIPAL_COST_USD=0 (or unset) turns the quota off (the web-admin refuses the same value)`);
|
|
2037
|
+
}
|
|
2038
|
+
return sec;
|
|
2039
|
+
})(),
|
|
2043
2040
|
reapIntervalSec: numEnv("REAP_INTERVAL_SEC", "60"),
|
|
2044
2041
|
runStaleSec: numEnv("REAP_RUN_STALE_SEC", "120"),
|
|
2045
2042
|
toolResultTtlSec: (() => {
|
|
@@ -2385,6 +2382,39 @@ export function attachConfigGroups(flat) {
|
|
|
2385
2382
|
}
|
|
2386
2383
|
return flat;
|
|
2387
2384
|
}
|
|
2385
|
+
function k8sWorkspaceStore() {
|
|
2386
|
+
const workspacePersist = enumEnv("WORKSPACE_PERSIST", WORKSPACE_PERSIST_DEFAULT, WORKSPACE_PERSIST_WORDS);
|
|
2387
|
+
const workspaceSnapshotKeep = numEnvBounded("WORKSPACE_SNAPSHOT_KEEP", String(WORKSPACE_SNAPSHOT_KEEP_DEFAULT), WORKSPACE_SNAPSHOT_KEEP_RANGE.min, WORKSPACE_SNAPSHOT_KEEP_RANGE.max);
|
|
2388
|
+
if (!Number.isInteger(workspaceSnapshotKeep))
|
|
2389
|
+
throw new Error(`env WORKSPACE_SNAPSHOT_KEEP=${workspaceSnapshotKeep} must be an integer (a count of snapshots kept per space key)`);
|
|
2390
|
+
const endpoint = process.env.MINIO_ENDPOINT;
|
|
2391
|
+
const accessKey = process.env.MINIO_ACCESS_KEY;
|
|
2392
|
+
const secretKey = process.env.MINIO_SECRET_KEY;
|
|
2393
|
+
if (!endpoint || !accessKey || !secretKey) {
|
|
2394
|
+
if (workspacePersist === "space") {
|
|
2395
|
+
const missing = [...(endpoint ? [] : ["MINIO_ENDPOINT"]), ...(accessKey ? [] : ["MINIO_ACCESS_KEY"]), ...(secretKey ? [] : ["MINIO_SECRET_KEY"])];
|
|
2396
|
+
CONFIG_NOTICES.push({
|
|
2397
|
+
level: "warn",
|
|
2398
|
+
event: "workspace_persist_unavailable",
|
|
2399
|
+
fields: { missing: missing.join(","), effect: "k8s workspace starts empty every run (no snapshot store); set the missing keys, or drop WORKSPACE_PERSIST=space (the default is off) to silence this line" },
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
2402
|
+
return { workspacePersist, workspaceSnapshotKeep };
|
|
2403
|
+
}
|
|
2404
|
+
return {
|
|
2405
|
+
workspacePersist,
|
|
2406
|
+
workspaceSnapshotKeep,
|
|
2407
|
+
s3Snapshot: {
|
|
2408
|
+
endpoint,
|
|
2409
|
+
bucket: process.env.WORKSPACE_SNAPSHOT_BUCKET ?? "workspaces",
|
|
2410
|
+
accessKey,
|
|
2411
|
+
secretKey,
|
|
2412
|
+
...(process.env.MINIO_REGION ? { region: process.env.MINIO_REGION } : {}),
|
|
2413
|
+
...(process.env.WORKSPACE_SNAPSHOT_PREFIX ? { keyPrefix: process.env.WORKSPACE_SNAPSHOT_PREFIX } : {}),
|
|
2414
|
+
...((n) => (n !== undefined ? { presignTtlSec: n } : {}))(optNumEnvStrict("WORKSPACE_SNAPSHOT_TTL_SEC")),
|
|
2415
|
+
},
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
2388
2418
|
export function loadConfig() {
|
|
2389
2419
|
EXEC_LANE_NOTICE = undefined;
|
|
2390
2420
|
rejectRetiredEnvs();
|
package/dist/env-facts.d.ts
CHANGED
|
@@ -101,8 +101,8 @@ warn: (msg: string, meta?: object) => void): Promise<string | undefined>;
|
|
|
101
101
|
* 这条豁免目录算不算绝对」——引擎自本版起按**树的家族**判(`isAbsoluteForFamily`,家族从树的 root/cwd 拼法
|
|
102
102
|
* 读,不看宿主 OS),本函数用的是 Node 宿主口径。**posix 家族两者逐字同判**(2026-09-12 实测六形全同),
|
|
103
103
|
* 而本闸又只在 `hostSemanticsLane` 上场(路径就是这台 worker 自己的盘)⇒ 今天零分歧,不改。**分歧只在 win
|
|
104
|
-
* 家族的 `/`-开头无盘符那一类**(Node 说绝对、引擎说未完全限定)
|
|
105
|
-
*
|
|
104
|
+
* 家族的 `/`-开头无盘符那一类**(Node 说绝对、引擎说未完全限定)。同一条接缝的读数钉在 `test/task-cwd.test.ts`「[ref] 路径家族接缝」格
|
|
105
|
+
* (cwd 那一侧的判官是契约包的 `isAbsoluteHostPath`,posix 口径;那里是唯一成文点,这里不复述第二份)。 */
|
|
106
106
|
export declare function acceptShellScratchpadDir(raw: unknown, opts: {
|
|
107
107
|
requirePrincipal: boolean;
|
|
108
108
|
hostSemanticsLane: boolean;
|
package/dist/env-facts.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { isAbsolute, join, resolve, sep } from "node:path";
|
|
2
2
|
import { mkdir, readdir, rm, stat } from "node:fs/promises";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
-
import {
|
|
4
|
+
import { isAbsoluteHostPath } from "@sema-agent/settings-schema";
|
|
5
|
+
import { dirCoversPath, normalizeAbsolutePathLexically } from "./task-cwd.js";
|
|
5
6
|
const MAX_STR = 64;
|
|
6
7
|
const MAX_CAPS = 16;
|
|
7
8
|
const MAX_PATH = 160;
|
|
@@ -118,7 +119,7 @@ export function screenShellScratchpadDir(raw, opts) {
|
|
|
118
119
|
return canonical;
|
|
119
120
|
}
|
|
120
121
|
export function scratchpadCoveringTaskRoot(args) {
|
|
121
|
-
if (!args.cwdHonored || !
|
|
122
|
+
if (!args.cwdHonored || !isAbsoluteHostPath(args.cwd))
|
|
122
123
|
return undefined;
|
|
123
124
|
const candidates = [];
|
|
124
125
|
const shell = screenShellScratchpadDir(args.shellScratchpadDir, { requirePrincipal: args.requirePrincipal, hostSemanticsLane: true, warn: () => { } });
|
|
@@ -62,7 +62,9 @@ export interface FleetTaskRow {
|
|
|
62
62
|
/** Owning principal (= the run's scope). The `/v1/fleet/stream` route filters to the caller's scope (owner-gated)
|
|
63
63
|
* unless the caller is fleet-wide (operator / trace-token). NOT rendered — a tenant-isolation field. */
|
|
64
64
|
scope?: string;
|
|
65
|
-
/** Fleet per-session scope: the
|
|
65
|
+
/** Fleet per-session scope: the **originating session id** — the id the caller submitted under, which is what a
|
|
66
|
+
* shell sends as `?session=`. It is NOT the run's tree root (`rootTaskId`, the canonical taskId that forwarded
|
|
67
|
+
* subagents nest under — a different quantity, see {@link fleetRunPublisher}'s `run` param). The
|
|
66
68
|
* `/v1/fleet/stream` route ALSO filters to the caller's `?session=` when present, so a TUI footer shows only the
|
|
67
69
|
* CURRENT session's rows, not OTHER sessions of the same principal (the "他会话 token" leak). NOT rendered — a
|
|
68
70
|
* scoping field like `scope`. Absent ⇒ the row is visible to any same-principal caller (back-compat, unscoped). */
|
|
@@ -128,6 +130,11 @@ export interface FleetTaskRow {
|
|
|
128
130
|
turns?: number;
|
|
129
131
|
costMicroUsd?: number;
|
|
130
132
|
};
|
|
133
|
+
/** 顶层 run 的终局残局键(同上「只在终态帧出现」):k8s 车道工作区跨轮保留开着时,这条 run 结束的工作区快照
|
|
134
|
+
* **没存上**(上传或 `latest` 指针写失败)⇒ `"failed"`;下一条 run 恢复的是上一份。成功 / 没开保留 / 不是 k8s
|
|
135
|
+
* 车道 / 这条 run 没碰过沙箱 / 结局晚于终态帧(只留运维行)⇒ 缺席。只承载本 run 在终态帧之前落定的结局。
|
|
136
|
+
* 单成员闭集;源 = `workspace-snapshot-residual.ts`。ADDITIVE / tolerate-absent。 */
|
|
137
|
+
workspaceSnapshot?: "failed";
|
|
131
138
|
resumable?: boolean;
|
|
132
139
|
editedFiles?: Array<{
|
|
133
140
|
path: string;
|
|
@@ -179,7 +186,8 @@ export interface FleetWorkflowRow {
|
|
|
179
186
|
description?: string;
|
|
180
187
|
/** Owning principal (= the workflow run's scope). Same owner-gate as FleetTaskRow. NOT rendered. */
|
|
181
188
|
scope?: string;
|
|
182
|
-
/** #3 fleet per-session scope (
|
|
189
|
+
/** #3 fleet per-session scope (the originating session id, same quantity as {@link FleetTaskRow.sessionId}): the
|
|
190
|
+
* `/v1/fleet/stream` `?session=` filter. NOT rendered. */
|
|
183
191
|
sessionId?: string;
|
|
184
192
|
status: string;
|
|
185
193
|
doneCount?: number;
|
|
@@ -556,7 +564,13 @@ export declare function fleetRunPublisher(bus: FleetEventBus | undefined, run: {
|
|
|
556
564
|
scope: string;
|
|
557
565
|
name: string;
|
|
558
566
|
description?: string;
|
|
567
|
+
/** 嵌子代树的根:这条腿的 canonical core taskId(`spec.taskId ?? sessionId`)。转发子代的 `parentTaskId` 与它相等
|
|
568
|
+
* ⇒ 挂在 run 行下,否则挂在那个子代的行下。**只管嵌套**,不是行的会话键。 */
|
|
559
569
|
rootTaskId?: string;
|
|
570
|
+
/** 发起会话 id —— 行上 `sessionId`(`GET /v1/fleet/stream?session=` 的过滤键)的**唯一来源**。
|
|
571
|
+
* 与 `rootTaskId` 是两个量(交互 / bg / 续跑三条腿的树根都是 taskId,不是会话 id)。
|
|
572
|
+
* 缺席 = 没有会话的 run:行不打键(既有 UNTAGGED 语义:同 principal 全会话可见,内容降权)。 */
|
|
573
|
+
sessionId?: string;
|
|
560
574
|
}): {
|
|
561
575
|
onStart: () => void;
|
|
562
576
|
onEvent: (ev: {
|
|
@@ -592,11 +606,7 @@ export declare function fleetRunPublisher(bus: FleetEventBus | undefined, run: {
|
|
|
592
606
|
/** [ref]①/(a) 案:`residuals` = 顶层 run 的终局残局键(有源才传,见 FleetTaskRow 上的键注)。
|
|
593
607
|
* 只有**真终态**(completed/failed/killed)帧带残局——parked(suspended/needs_review)的行还活着,
|
|
594
608
|
* 没有「终局」可言,误传也不上帧。 */
|
|
595
|
-
onTerminal: (status: RunStatusForFleet, residuals?:
|
|
596
|
-
usage?: FleetTaskRow["usage"];
|
|
597
|
-
stoppedBy?: string;
|
|
598
|
-
transcriptId?: string;
|
|
599
|
-
}) => void;
|
|
609
|
+
onTerminal: (status: RunStatusForFleet, residuals?: FleetRunResiduals) => void;
|
|
600
610
|
};
|
|
601
611
|
/** The run-scoped publisher handle (returned by `fleetRunPublisher`). Threaded into `runInBackground` so a
|
|
602
612
|
* background run TRANSITIONS its fleet row exactly like the sync POST /v1/runs leg does. */
|
|
@@ -610,6 +620,12 @@ export type FleetRunPublisher = ReturnType<typeof fleetRunPublisher>;
|
|
|
610
620
|
* 留着旧形不会红 —— 那正是危险所在:`TaskResult` 上已经没有 `errorCode` 这个键了,可结构性可选参数照样
|
|
611
621
|
* 编译通过,于是 `stoppedBy:"user"` 会**静默地永远不再铸**(取消的 run 在舰队行上从此看不出是被人杀的)。
|
|
612
622
|
* 终局事实一律走因由,`failureCodeOf` 是本仓读「这次失败带的是哪个码」的唯一形。 */
|
|
623
|
+
export type FleetRunResiduals = {
|
|
624
|
+
usage?: FleetTaskRow["usage"];
|
|
625
|
+
stoppedBy?: string;
|
|
626
|
+
transcriptId?: string;
|
|
627
|
+
workspaceSnapshot?: "failed";
|
|
628
|
+
};
|
|
613
629
|
export declare function fleetRunResiduals(result: {
|
|
614
630
|
sessionId?: string;
|
|
615
631
|
terminal?: TerminalCause;
|
|
@@ -619,11 +635,7 @@ export declare function fleetRunResiduals(result: {
|
|
|
619
635
|
toolCalls?: number;
|
|
620
636
|
costMicroUsd?: number;
|
|
621
637
|
};
|
|
622
|
-
} | null | undefined):
|
|
623
|
-
usage?: FleetTaskRow["usage"];
|
|
624
|
-
stoppedBy?: string;
|
|
625
|
-
transcriptId?: string;
|
|
626
|
-
};
|
|
638
|
+
} | null | undefined): FleetRunResiduals;
|
|
627
639
|
/**
|
|
628
640
|
* [ref]-B (core 1.240.0) — the PROCESS-LEVEL background-child fleet publisher, wired once
|
|
629
641
|
* onto `RunnerDeps.onBackgroundChildEvent`. Unlike {@link fleetRunPublisher} (per-leg: dies at leg settle —
|
package/dist/fleet/fleet-bus.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { DURABLE_AGENT_HANDLE_RE, deriveAgentDisplayStatus, isDelegatedAgentTerminal } from "@sema-agent/core";
|
|
3
3
|
import { persistedPlaneOf } from "../terminal.js";
|
|
4
|
+
import { takeWorkspaceSnapshotFailure } from "./workspace-snapshot-residual.js";
|
|
4
5
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
5
6
|
import { redactSecrets } from "../trace/redact.js";
|
|
6
7
|
import { buildRunObjectivePreview } from "../user-authored-text.js";
|
|
@@ -200,7 +201,7 @@ export function fleetRunPublisher(bus, run) {
|
|
|
200
201
|
return { onStart: () => undefined, onEvent: () => undefined, onForwardEvent: () => undefined, onChildTerminal: () => false, onTerminal: () => undefined };
|
|
201
202
|
let tokens = 0;
|
|
202
203
|
const startedAt = Date.now();
|
|
203
|
-
const sessionField = run.
|
|
204
|
+
const sessionField = run.sessionId ? { sessionId: run.sessionId } : {};
|
|
204
205
|
const children = new Set();
|
|
205
206
|
const childStartedAt = new Map();
|
|
206
207
|
const childByToolCall = new Map();
|
|
@@ -332,6 +333,7 @@ export function fleetRunPublisher(bus, run) {
|
|
|
332
333
|
...(residuals.usage ? { usage: residuals.usage } : {}),
|
|
333
334
|
...(residuals.stoppedBy ? { stoppedBy: residuals.stoppedBy } : {}),
|
|
334
335
|
...(residuals.transcriptId ? { transcriptId: residuals.transcriptId } : {}),
|
|
336
|
+
...(residuals.workspaceSnapshot ? { workspaceSnapshot: residuals.workspaceSnapshot } : {}),
|
|
335
337
|
}
|
|
336
338
|
: {};
|
|
337
339
|
bus.publishTask({ id: run.runId, status: fleetStatus, tokens, elapsedMs: Date.now() - startedAt, ...res });
|
|
@@ -362,6 +364,7 @@ export function fleetRunResiduals(result) {
|
|
|
362
364
|
...(Object.keys(usage).length > 0 ? { usage } : {}),
|
|
363
365
|
...(persistedPlaneOf(result).errorCode === "cancelled" ? { stoppedBy: "user" } : {}),
|
|
364
366
|
...(result.sessionId ? { transcriptId: result.sessionId } : {}),
|
|
367
|
+
...(takeWorkspaceSnapshotFailure(result.sessionId) ? { workspaceSnapshot: "failed" } : {}),
|
|
365
368
|
};
|
|
366
369
|
}
|
|
367
370
|
export function fleetBackgroundChildPublisher(bus, log) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open the outcome seat of the env being built for a run of `sessionId` (call it when the env is built, before its
|
|
3
|
+
* connect). Closes the session's earlier seats and drops what they left pending. The returned function is the env's
|
|
4
|
+
* `onWorkspaceSnapshot` seat — a closure ⇒ `create*`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createRunWorkspaceSnapshotSeat(sessionId: string): (outcome: "saved" | "failed") => void;
|
|
7
|
+
/** Take-once, at settle: `true` when this session's run reported a failed run-end snapshot that no settle has carried yet.
|
|
8
|
+
* Closes the session's seat (see the header: the frame ends "before the frame"). */
|
|
9
|
+
export declare function takeWorkspaceSnapshotFailure(sessionId: string | undefined): boolean;
|
|
10
|
+
//# sourceMappingURL=workspace-snapshot-residual.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const pending = new Map();
|
|
2
|
+
const seats = new Map();
|
|
3
|
+
const CAP = 1024;
|
|
4
|
+
let generation = 0;
|
|
5
|
+
function bounded(map) {
|
|
6
|
+
while (map.size > CAP) {
|
|
7
|
+
const oldest = map.keys().next().value;
|
|
8
|
+
if (oldest === undefined)
|
|
9
|
+
return;
|
|
10
|
+
map.delete(oldest);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function createRunWorkspaceSnapshotSeat(sessionId) {
|
|
14
|
+
const mine = ++generation;
|
|
15
|
+
seats.delete(sessionId);
|
|
16
|
+
seats.set(sessionId, mine);
|
|
17
|
+
bounded(seats);
|
|
18
|
+
pending.delete(sessionId);
|
|
19
|
+
return (outcome) => {
|
|
20
|
+
if (seats.get(sessionId) !== mine)
|
|
21
|
+
return;
|
|
22
|
+
pending.delete(sessionId);
|
|
23
|
+
if (outcome === "saved")
|
|
24
|
+
return;
|
|
25
|
+
pending.set(sessionId, true);
|
|
26
|
+
bounded(pending);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function takeWorkspaceSnapshotFailure(sessionId) {
|
|
30
|
+
if (sessionId === undefined)
|
|
31
|
+
return false;
|
|
32
|
+
seats.delete(sessionId);
|
|
33
|
+
return pending.delete(sessionId);
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=workspace-snapshot-residual.js.map
|