@sema-agent/server 7.92.1 → 7.93.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/README.md +1 -0
- package/README.zh-CN.md +1 -0
- package/dist/approval-ask-machine.d.ts +1 -1
- package/dist/approval-card.d.ts +10 -7
- package/dist/approval-card.js +3 -3
- package/dist/bench/s1/arms.js +5 -6
- package/dist/bench/s1/live-deps.js +2 -2
- package/dist/boot/resolve-spec.js +1 -0
- package/dist/boot/stage-06-runners.js +3 -2
- package/dist/boot/stores.js +9 -0
- package/dist/capabilities/mcp-probe.d.ts +290 -0
- package/dist/capabilities/mcp-probe.js +318 -0
- package/dist/http/resume-legs.js +12 -10
- package/dist/http/route-ctx.d.ts +3 -0
- package/dist/http/routes/capabilities.d.ts +17 -0
- package/dist/http/routes/capabilities.js +93 -1
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/rules.js +49 -13
- package/dist/http/routes/runs.js +3 -2
- package/dist/http/routes/session-sync.js +24 -12
- package/dist/http/routes/sessions.js +12 -8
- package/dist/http/routes/tasks.js +13 -13
- package/dist/http/send.d.ts +11 -0
- package/dist/http/send.js +9 -0
- package/dist/http/server.js +3 -2
- package/dist/http/wire-types.d.ts +17 -1
- package/dist/leader/fanout.js +3 -3
- package/dist/leader/leader.js +4 -4
- package/dist/leader/repair-wire.js +2 -2
- package/dist/leader/wire.js +2 -2
- package/dist/observability/metrics.js +2 -0
- package/dist/permission-rule-vocab.d.ts +9 -9
- package/dist/plugins/file-run-store.js +3 -3
- package/dist/plugins/memory-run-store.js +3 -3
- package/dist/plugins/remote-env-host.d.ts +5 -0
- package/dist/plugins/remote-env-host.js +39 -37
- package/dist/plugins/run-store-sql.js +3 -3
- package/dist/plugins/schema-consistency.d.ts +88 -0
- package/dist/plugins/schema-consistency.js +202 -0
- package/dist/plugins/schema-ensure-legs.d.ts +55 -0
- package/dist/plugins/schema-ensure-legs.js +87 -0
- package/dist/plugins/session-policy-store-sql.js +23 -5
- package/dist/request-key-closure.js +1 -1
- package/dist/rules-consent.d.ts +42 -4
- package/dist/rules-consent.js +23 -7
- package/dist/run-local.js +2 -2
- package/dist/runs.js +14 -14
- package/dist/task-mcp.d.ts +32 -0
- package/dist/task-mcp.js +7 -0
- package/dist/terminal.d.ts +58 -4
- package/dist/terminal.js +28 -6
- package/dist/tool-approval.d.ts +22 -1
- package/dist/tool-approval.js +1 -0
- package/dist/trace/core-keyset-guard.d.ts +3 -3
- package/dist/trace/ledger-sink.js +3 -2
- package/dist/trace/project.d.ts +24 -0
- package/dist/trace/project.js +40 -37
- package/dist/trace/wire-projection-faces.d.ts +209 -0
- package/dist/trace/wire-projection-faces.js +62 -0
- package/package.json +3 -3
package/dist/rules-consent.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type DirectWritableRuleBehavior } from "./permission-rule-vocab.js";
|
|
2
|
-
import { type RuleOwner, type ImportPreview, type RedeemedBatchMember, type ImportedSettingsLayer, type PersistedRule, type RuleBehavior, type RuleCandidate, type EffectivePermissionRule, type RemoveResult, type EditedRuleTextPrecheck, type RuleScope, type RuleOffer } from "@sema-agent/core";
|
|
1
|
+
import { type DirectWritableRuleBehavior, type RemovalLiveness } from "./permission-rule-vocab.js";
|
|
2
|
+
import { type RuleOwner, type ImportPreview, type RedeemedBatchMember, type ImportedSettingsLayer, type PersistedRule, type RuleBehavior, type RuleCandidate, type EffectivePermissionRule, type RemoveResult, type RuleWriteCommitted, type EditedRuleTextPrecheck, type RuleScope, type RuleOffer } from "@sema-agent/core";
|
|
3
3
|
import type { DurableRulePartitionProvider, PermissionRuleStoreProvider, RuleApprovalRecordStore } from "@sema-agent/core";
|
|
4
4
|
import { type RuleImportTicket, type RuleTicketPurpose, type RuleTicketReclaimResult, type RuleTicketRedeemResult, type RuleTicketSnapshot } from "./plugins/permission-rule-store-sql.js";
|
|
5
5
|
/**
|
|
@@ -599,18 +599,56 @@ export type RuleWritten =
|
|
|
599
599
|
{
|
|
600
600
|
status: "persisted" | "no-op";
|
|
601
601
|
rev: number;
|
|
602
|
+
stillLive: Extract<RemovalLiveness, "yes">;
|
|
602
603
|
row: PersistedRuleWireRow;
|
|
603
604
|
}
|
|
605
|
+
/** 同上两个词,但**写后读回时它已经不在了** —— 一次合法并发(自己的 / operator 的撤销挤在了「引擎落了
|
|
606
|
+
* dot」与「读回那一行」之间)。
|
|
607
|
+
*
|
|
608
|
+
* 🔴 B3(core fable 复审 2026-09-20):这一形**不是** `indeterminate`。core 已经答了 `added` 并把 `rev`
|
|
609
|
+
* 交到手里 —— 「写没写」这一问有答案;没答案的是「它现在还站着吗」,而那是**另一个问题**,撤销面早就有
|
|
610
|
+
* 一只键在回答它({@link RemovalLiveness} 三词)。折成 `indeterminate` 的代价有名字:那条腿的处置是
|
|
611
|
+
* 「retry」,而照着重试会把**刚刚被人撤掉的收紧重新立起来**(core `AddResult.failed` 头注逐字警告过
|
|
612
|
+
* 这条路)。
|
|
613
|
+
*
|
|
614
|
+
* 🔴 `row` 在这一支**缺席**,而缺席由 `stillLive` 判别(不是「未知」):没有活行可端的时候编一行出来
|
|
615
|
+
* 才是谎 —— 亲读 core 的 `AddResult`,`added` 臂只有 `{status, rev, dot}`,**刻意**不带行(头注:之后
|
|
616
|
+
* 落地的撤销是「`list()` 答的普通后续事件」),而本仓的行形带着 `status: "live"` 这一格,给一行已经
|
|
617
|
+
* 不在的规则铸它就是第二个谎。 */
|
|
618
|
+
/** 🔴 `"unknown"`(7.93.0 合并复审 codex 验真后加):引擎已确认 `added`(`rev` 在手),而**写后读回本身抛了**
|
|
619
|
+
* (店读不出来)。「写没写」有答案、「现在站不站着」没人观察 —— 与撤销口 `no-op` + `unknown` 同词同义:
|
|
620
|
+
* **不要重试**(重发只会 `no-op`),用 `GET /v1/rules` 对账。此前这一形落域外通用 500 `internal.error`,
|
|
621
|
+
* 与「写前读库失败」(零写,重发安全)走同一个异常出口 —— 两种强度一句话,正是 `committed` 要拆开的那对。 */
|
|
622
|
+
| {
|
|
623
|
+
status: "persisted" | "no-op";
|
|
624
|
+
rev: number;
|
|
625
|
+
stillLive: Exclude<RemovalLiveness, "yes">;
|
|
626
|
+
row?: undefined;
|
|
627
|
+
}
|
|
604
628
|
/** 裁定性不收(内容判据 / 方向判据)。`detail` 是给人看的那一半,`reason` 是给客户端分流的那一半。 */
|
|
605
629
|
| {
|
|
606
630
|
status: "rejected";
|
|
607
631
|
reason: LocalImportRefusalCode;
|
|
608
632
|
detail: string;
|
|
609
633
|
}
|
|
610
|
-
/**
|
|
634
|
+
/** 终态写失败。`detail` 是给人看的那一半;`committed`({@link RuleWriteCommitted},core 的两词闭集)是
|
|
635
|
+
* 给机器分流的那一半 —— 它回答的是**调用方下一步该做什么**。
|
|
636
|
+
*
|
|
637
|
+
* 🔴 **词从 core 的 `AddResult.failed` 原样接过来,不从报文里猜**(core 7.26.0 [ref])。此前本臂叫
|
|
638
|
+
* `indeterminate` 且只带 `detail`:core 把「店自己答了、确知零写」与「apply 抛了、落没落不知道」两种强度
|
|
639
|
+
* **只写在散文里**,本仓于是把两者一律折成「不确定」+ 端点 503「retry」。代价是那句 retry 对两种强度**一句
|
|
640
|
+
* 都不对**:`"no"` 的那半确知零写、重发同一份体是安全的(而旧文案让客户端以为要先对账);`"unknown"` 的那半
|
|
641
|
+
* 可能已经落了,而盲重试会**再铸一枚 dot**、把刚被人撤掉的收紧重新立起来(core `AddResult.failed` 头注逐字
|
|
642
|
+
* 警告过这条路)——那正是本臂最需要说清的一句话,旧形却把它和安全的那半说成了同一句。
|
|
643
|
+
*
|
|
644
|
+
* 🔴 臂名随词改成 core 的 `failed`(不再叫 `indeterminate`):拿到判别位之后,「不确定」对 `"no"` 这半是
|
|
645
|
+
* **假的**,而一个名字与它所含的一半相反的臂,下一个读它的人一定读错。两个词表合成一个,规则少一条。
|
|
646
|
+
*
|
|
647
|
+
* **「写落了随后被撤」不在本臂** —— 那一形有答案,见上面 `stillLive:"no"`。 */
|
|
611
648
|
| {
|
|
612
|
-
status: "
|
|
649
|
+
status: "failed";
|
|
613
650
|
detail: string;
|
|
651
|
+
committed: RuleWriteCommitted;
|
|
614
652
|
};
|
|
615
653
|
export interface RuleConsentLane {
|
|
616
654
|
/** 卡道兑付口:一次 ask 决议携规则确认 ⇒ prepare→confirm→redeem 一气呵成。 */
|
package/dist/rules-consent.js
CHANGED
|
@@ -736,10 +736,17 @@ export function createRuleConsentLane(stores, opts) {
|
|
|
736
736
|
adds: [],
|
|
737
737
|
};
|
|
738
738
|
const rowOf = (rows) => rows.find((r) => sameWireRuleIdentity(r, { behavior: input.behavior, rule: written.rule.rule, scope: row.scope }));
|
|
739
|
-
|
|
739
|
+
let before;
|
|
740
|
+
try {
|
|
741
|
+
before = await lane.listRules(input.principal);
|
|
742
|
+
}
|
|
743
|
+
catch (e) {
|
|
744
|
+
localImportLogger.warn("direct_rule_write_pre_read_failed", { behavior: input.behavior, err: e instanceof Error ? e.message : String(e) });
|
|
745
|
+
return { status: "failed", detail: `the rule store could not be read before writing: ${e instanceof Error ? e.message : String(e)}`, committed: "no" };
|
|
746
|
+
}
|
|
740
747
|
const standing = rowOf(before.rows);
|
|
741
748
|
if (standing !== undefined)
|
|
742
|
-
return { status: "no-op", rev: before.rev, row: standing };
|
|
749
|
+
return { status: "no-op", rev: before.rev, stillLive: "yes", row: standing };
|
|
743
750
|
const screened = screenLocalImportRow(row);
|
|
744
751
|
if ("reason" in screened)
|
|
745
752
|
return { status: "rejected", reason: screened.reason, detail: screened.detail };
|
|
@@ -750,23 +757,32 @@ export function createRuleConsentLane(stores, opts) {
|
|
|
750
757
|
case "no-op":
|
|
751
758
|
break;
|
|
752
759
|
case "failed":
|
|
753
|
-
return { status: "
|
|
760
|
+
return { status: "failed", detail: added.error, committed: added.committed };
|
|
754
761
|
default: {
|
|
755
762
|
const unknown = added;
|
|
756
763
|
throw new Error(`unknown addPersistedRule outcome: ${JSON.stringify(unknown)}`);
|
|
757
764
|
}
|
|
758
765
|
}
|
|
759
|
-
const
|
|
766
|
+
const status = added.status === "no-op" ? "no-op" : "persisted";
|
|
767
|
+
let readBack;
|
|
768
|
+
try {
|
|
769
|
+
readBack = await lane.listRules(input.principal);
|
|
770
|
+
}
|
|
771
|
+
catch (e) {
|
|
772
|
+
localImportLogger.warn("direct_rule_write_read_back_failed", { behavior: input.behavior, scope: row.scope, rev: added.rev, wrote: status === "persisted", err: e instanceof Error ? e.message : String(e) });
|
|
773
|
+
return { status, rev: added.rev, stillLive: "unknown" };
|
|
774
|
+
}
|
|
760
775
|
const landed = rowOf(readBack.rows);
|
|
761
776
|
if (landed === undefined) {
|
|
762
777
|
localImportLogger.warn("direct_rule_write_vanished_before_read_back", {
|
|
763
778
|
behavior: input.behavior,
|
|
764
779
|
scope: row.scope,
|
|
765
|
-
note: "the store reported this rule as
|
|
780
|
+
note: "the store reported this rule as in effect, and it was already gone when the row was read back — a revocation landed in between",
|
|
781
|
+
wrote: status === "persisted",
|
|
766
782
|
});
|
|
767
|
-
return { status:
|
|
783
|
+
return { status, rev: added.rev, stillLive: "no" };
|
|
768
784
|
}
|
|
769
|
-
return { status: added.
|
|
785
|
+
return { status, rev: added.rev, stillLive: "yes", row: landed };
|
|
770
786
|
},
|
|
771
787
|
};
|
|
772
788
|
return lane;
|
package/dist/run-local.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { DEFAULT_MODEL_REF } from "./model-select.js";
|
|
3
|
-
import {
|
|
3
|
+
import { outwardPlane } from "./terminal.js";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { realpathSync } from "node:fs";
|
|
@@ -535,7 +535,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
535
535
|
print(result.result);
|
|
536
536
|
}
|
|
537
537
|
else {
|
|
538
|
-
const plane =
|
|
538
|
+
const plane = outwardPlane(result.terminal);
|
|
539
539
|
const salvage = result.result || result.salvagedOutput || plane.blockedReason || "";
|
|
540
540
|
if (salvage)
|
|
541
541
|
print(salvage);
|
package/dist/runs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { runWithVerification, runCascade, defaultTaskRegistry,
|
|
2
|
-
import { failureCodeOf,
|
|
1
|
+
import { runWithVerification, runCascade, defaultTaskRegistry, DURABLE_AGENT_HANDLE_RE } from "@sema-agent/core";
|
|
2
|
+
import { failureCodeOf, sealResultForExit, outwardPlane } from "./terminal.js";
|
|
3
3
|
import { GOVERNANCE_HTTP_STATUS } from "./governance-http-status.js";
|
|
4
4
|
import { redactErrorMessage } from "./observability/run-terminal.js";
|
|
5
5
|
import { withPrincipal } from "./observability/principal-context.js";
|
|
@@ -311,10 +311,10 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
311
311
|
await flush();
|
|
312
312
|
evictIfConflict(runner, spec.sessionId, result);
|
|
313
313
|
await flushPromptManifest();
|
|
314
|
-
const safe = await withModelUsage(
|
|
314
|
+
const safe = await withModelUsage(sealResultForExit(result));
|
|
315
315
|
await append("done", { result: safe });
|
|
316
316
|
reached = { kind: "done", result: safe };
|
|
317
|
-
const plane =
|
|
317
|
+
const plane = outwardPlane(safe.terminal);
|
|
318
318
|
await runStore.setTerminal(taskId, plane.status, safe, plane.errorMessage ?? null);
|
|
319
319
|
recordDone(safe, plane.status);
|
|
320
320
|
return;
|
|
@@ -324,10 +324,10 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
324
324
|
await flush();
|
|
325
325
|
evictIfConflict(runner, spec.sessionId, result);
|
|
326
326
|
await flushPromptManifest();
|
|
327
|
-
const safe = await withModelUsage(
|
|
327
|
+
const safe = await withModelUsage(sealResultForExit(result));
|
|
328
328
|
await append("done", { result: safe });
|
|
329
329
|
reached = { kind: "done", result: safe };
|
|
330
|
-
const plane =
|
|
330
|
+
const plane = outwardPlane(safe.terminal);
|
|
331
331
|
await runStore.setTerminal(taskId, plane.status, safe, plane.errorMessage ?? null);
|
|
332
332
|
recordDone(safe, plane.status);
|
|
333
333
|
return;
|
|
@@ -358,7 +358,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
358
358
|
}
|
|
359
359
|
if (ev.type === "done") {
|
|
360
360
|
await flush();
|
|
361
|
-
const parkStatus = ev.result.terminal.kind === "paused" ?
|
|
361
|
+
const parkStatus = ev.result.terminal.kind === "paused" ? outwardPlane(ev.result.terminal).status : undefined;
|
|
362
362
|
if (parkStatus === "suspended") {
|
|
363
363
|
await sink.appendParked("suspended", await graftParkToolCallId(ev.result, parkCheckpointReader));
|
|
364
364
|
await flushModelUsage();
|
|
@@ -376,10 +376,10 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
376
376
|
{
|
|
377
377
|
const settled = cancelledAtDone ? cancelledResult(ev.result) : ev.result;
|
|
378
378
|
settledAsCancel = failureCodeOf(settled.terminal) === CANCELLED_CODE;
|
|
379
|
-
const safe = await withModelUsage(
|
|
379
|
+
const safe = await withModelUsage(sealResultForExit(settled));
|
|
380
380
|
await sink.appendDone(safe);
|
|
381
381
|
reached = { kind: "done", result: safe };
|
|
382
|
-
const plane =
|
|
382
|
+
const plane = outwardPlane(safe.terminal);
|
|
383
383
|
await runStore.setTerminal(taskId, plane.status, safe, plane.errorMessage ?? null);
|
|
384
384
|
recordDone(safe, failureCodeOf(safe.terminal) === CANCELLED_CODE ? CANCELLED_CODE : plane.status);
|
|
385
385
|
}
|
|
@@ -436,7 +436,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
436
436
|
recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=append-clean-cancel ${errText(e)}`);
|
|
437
437
|
}
|
|
438
438
|
reached = { kind: "done", result: c };
|
|
439
|
-
await runStore.setTerminal(taskId, "failed", c,
|
|
439
|
+
await runStore.setTerminal(taskId, "failed", c, outwardPlane(c.terminal).errorMessage ?? null);
|
|
440
440
|
recordDone(c, CANCELLED_CODE);
|
|
441
441
|
}
|
|
442
442
|
}
|
|
@@ -450,7 +450,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
450
450
|
recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=flush-catch ${errText(e)}`);
|
|
451
451
|
}
|
|
452
452
|
if (reached?.kind === "done") {
|
|
453
|
-
const plane =
|
|
453
|
+
const plane = outwardPlane(reached.result.terminal);
|
|
454
454
|
settledAsCancel = failureCodeOf(reached.result.terminal) === CANCELLED_CODE;
|
|
455
455
|
await runStore.setTerminal(taskId, plane.status, reached.result, plane.errorMessage ?? null);
|
|
456
456
|
}
|
|
@@ -469,7 +469,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
469
469
|
catch (e) {
|
|
470
470
|
recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=append-thrown-cancel ${errText(e)}`);
|
|
471
471
|
}
|
|
472
|
-
await runStore.setTerminal(taskId, "failed", c,
|
|
472
|
+
await runStore.setTerminal(taskId, "failed", c, outwardPlane(c.terminal).errorMessage ?? null);
|
|
473
473
|
}
|
|
474
474
|
else {
|
|
475
475
|
settledAsCancel = false;
|
|
@@ -486,7 +486,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
486
486
|
catch {
|
|
487
487
|
}
|
|
488
488
|
if (reached?.kind === "done")
|
|
489
|
-
recordDone(reached.result, failureCodeOf(reached.result.terminal) === CANCELLED_CODE ? CANCELLED_CODE :
|
|
489
|
+
recordDone(reached.result, failureCodeOf(reached.result.terminal) === CANCELLED_CODE ? CANCELLED_CODE : outwardPlane(reached.result.terminal).status);
|
|
490
490
|
else if (reached?.kind === "suspended") {
|
|
491
491
|
metrics?.addGauge("runs_active", -1);
|
|
492
492
|
metrics?.inc("tasks_total", { status: "suspended" });
|
|
@@ -503,7 +503,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
503
503
|
unregisterEngineNotice();
|
|
504
504
|
if (fleetPublisher) {
|
|
505
505
|
if (reached?.kind === "done")
|
|
506
|
-
fleetPublisher.onTerminal(failureCodeOf(reached.result.terminal) === CANCELLED_CODE ? CANCELLED_CODE :
|
|
506
|
+
fleetPublisher.onTerminal(failureCodeOf(reached.result.terminal) === CANCELLED_CODE ? CANCELLED_CODE : outwardPlane(reached.result.terminal).status, fleetRunResiduals(reached.result));
|
|
507
507
|
else if (reached?.kind === "suspended")
|
|
508
508
|
fleetPublisher.onTerminal("suspended");
|
|
509
509
|
else if (reached?.kind === "needs_review")
|
package/dist/task-mcp.d.ts
CHANGED
|
@@ -41,6 +41,38 @@ export declare const MAX_REQUEST_MCP_SERVERS = 32;
|
|
|
41
41
|
* 有界点名两头都占:该说的仍然说得出,量级钉死在与挂载同阶。
|
|
42
42
|
*/
|
|
43
43
|
export declare const MAX_REQUEST_MCP_DROP_NAMES = 32;
|
|
44
|
+
/**
|
|
45
|
+
* S-481 —— 一条 `mcpServers[]` 条目在 wire 上的**键闭集**,与它两种传输各自的键闭集。
|
|
46
|
+
*
|
|
47
|
+
* ## 属主为什么在这里
|
|
48
|
+
* 这个文件就是「请求腿的 MCP 条目形」的家({@link normalizeServer} 逐键读的正是这些)。S-481 的探测口
|
|
49
|
+
* ({@link import("./capabilities/mcp-probe.js").readMcpProbeBody})要的是**同一个**词表 —— 它在自己那边
|
|
50
|
+
* 再写一份就是一份手抄投影([ref]:任何仓第一排查点),而两份哪天分岔,窄的那一份会把**合法**的键当成
|
|
51
|
+
* 拼写错误 400 掉。
|
|
52
|
+
*
|
|
53
|
+
* ## 🔴 两向钉在 core 的 `McpServerSpec` 上(不是手抄)
|
|
54
|
+
* 下面六道编译期门:型上多一个键而表没跟 ⇒ 红;表里写一个型上没有的词(改名 / 拼错 / 删键后的残条目)
|
|
55
|
+
* ⇒ 也红。于是这三张表是从 core 的型**派生**出来的数据,而不是誰记得去同步的清单。
|
|
56
|
+
*
|
|
57
|
+
* ## 两个消费点的处置**刻意不同**(同一张表,不同的门)
|
|
58
|
+
* · 提交腿({@link normalizeServer})对词表外的键**宽容**(读不认识的键就当没有)—— 那条腿要能安全地
|
|
59
|
+
* 重放存量 body,一个 4xx 打在 resume 上会永久砖死一条 parked 任务(本仓三处 `opts.leg` 先例);
|
|
60
|
+
* · 探测口**响亮拒**(400,点名 `mcpServers[i].<键>`)—— 它问的是「这台到底能不能用」,把一个拼错的键
|
|
61
|
+
* 悄悄忽略再回一份好看的名单,正是那条面要根除的假答案。
|
|
62
|
+
* 两个门,一个词表。表在这里,门在各自的腿上。
|
|
63
|
+
*/
|
|
64
|
+
export declare const MCP_SERVER_ENTRY_KEYS: readonly ["allowTools", "contentOrigin", "elicitation", "name", "source", "toolAxes", "toolFaces", "transport"];
|
|
65
|
+
/** `transport.kind:"stdio"` 的键闭集(两向钉在 core 那一支上)。 */
|
|
66
|
+
export declare const STDIO_MCP_TRANSPORT_KEYS: readonly ["args", "command", "env", "kind"];
|
|
67
|
+
/** `transport.kind:"http"` 的键闭集(同上)。 */
|
|
68
|
+
export declare const HTTP_MCP_TRANSPORT_KEYS: readonly ["headers", "kind", "principalHeader", "url"];
|
|
69
|
+
/** 条目层:`{}` 与缺席等价的可选 Record 键。 */
|
|
70
|
+
export declare const MCP_ENTRY_EMPTY_RECORD_KEYS: readonly ["toolAxes", "toolFaces"];
|
|
71
|
+
/** 条目层:`[]` 与缺席等价的可选数组键。 */
|
|
72
|
+
export declare const MCP_ENTRY_EMPTY_ARRAY_KEYS: readonly ["allowTools"];
|
|
73
|
+
/** 传输层(两种合起来):`{}` / `[]` 与缺席等价的键。 */
|
|
74
|
+
export declare const MCP_TRANSPORT_EMPTY_RECORD_KEYS: readonly ["env", "headers"];
|
|
75
|
+
export declare const MCP_TRANSPORT_EMPTY_ARRAY_KEYS: readonly ["args"];
|
|
44
76
|
/**
|
|
45
77
|
* Does THIS deployment honor caller-supplied per-request MCP servers? TRUE for a SINGLE-USER deployment
|
|
46
78
|
* (`requirePrincipal !== true`) — the requester is the super-admin of their own worker, so their `.mcp.json` is
|
package/dist/task-mcp.js
CHANGED
|
@@ -8,6 +8,13 @@ import { readMcpToolFaces } from "./mcp-tool-faces.js";
|
|
|
8
8
|
import { isToolAxisIrreversibility, isToolEffect } from "./tool-axis-words.js";
|
|
9
9
|
export const MAX_REQUEST_MCP_SERVERS = 32;
|
|
10
10
|
export const MAX_REQUEST_MCP_DROP_NAMES = MAX_REQUEST_MCP_SERVERS;
|
|
11
|
+
export const MCP_SERVER_ENTRY_KEYS = ["allowTools", "contentOrigin", "elicitation", "name", "source", "toolAxes", "toolFaces", "transport"];
|
|
12
|
+
export const STDIO_MCP_TRANSPORT_KEYS = ["args", "command", "env", "kind"];
|
|
13
|
+
export const HTTP_MCP_TRANSPORT_KEYS = ["headers", "kind", "principalHeader", "url"];
|
|
14
|
+
export const MCP_ENTRY_EMPTY_RECORD_KEYS = ["toolAxes", "toolFaces"];
|
|
15
|
+
export const MCP_ENTRY_EMPTY_ARRAY_KEYS = ["allowTools"];
|
|
16
|
+
export const MCP_TRANSPORT_EMPTY_RECORD_KEYS = ["env", "headers"];
|
|
17
|
+
export const MCP_TRANSPORT_EMPTY_ARRAY_KEYS = ["args"];
|
|
11
18
|
export function mcpInjectionHonored(config) {
|
|
12
19
|
if (config.lockedConfigKeys?.includes("mcp") === true)
|
|
13
20
|
return false;
|
package/dist/terminal.d.ts
CHANGED
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
* (run 行的 `status` / `error` 列、`tasks_total{status}` 计量标签、fleet 行的终态词)。出口之外不拼。
|
|
17
17
|
*
|
|
18
18
|
* wire 面(HTTP 任务体、SSE `done.result`)**就是结果本身**,所以它跟着 core 一起丢八键、带 `terminal`
|
|
19
|
-
* —— 那里没有投影,只有转发(见 {@link
|
|
19
|
+
* —— 那里没有投影,只有转发(见 {@link sealResultForExit} 的例外)。
|
|
20
20
|
*/
|
|
21
|
-
import { type PausedCause, type TaskStatus, type TerminalCause } from "@sema-agent/core";
|
|
21
|
+
import { terminalProjection, type PausedCause, type TaskStatus, type TerminalCause } from "@sema-agent/core";
|
|
22
22
|
/**
|
|
23
23
|
* 这次失败带的**机器码**(`terminal.failed.code`);非失败终局 ⇒ `undefined`。
|
|
24
24
|
*
|
|
@@ -31,7 +31,46 @@ import { type PausedCause, type TaskStatus, type TerminalCause } from "@sema-age
|
|
|
31
31
|
*/
|
|
32
32
|
export declare function failureCodeOf(terminal: TerminalCause): string | undefined;
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
34
|
+
* 本仓**对外机器码**的词法(S-548)。附录 A 那张表、core 的点名族(`limits.*` / `config.*` /
|
|
35
|
+
* `resume_at.*` / `output.*` / brain 扁平码)、工具层自注册码 —— 全体成员都是这一形:**小写**的
|
|
36
|
+
* `a-z0-9_`,用 `.` 分族。门 {@link OUTWARD_CODE_SHAPE} 在 `test/terminal-store-error-fold.test.ts`
|
|
37
|
+
* 里对**目录里真有的每一个码**断言这条词法(判据由 catalog 机器派生,不是手抄的一张清单)。
|
|
38
|
+
*/
|
|
39
|
+
export declare const OUTWARD_CODE_SHAPE: RegExp;
|
|
40
|
+
/** 域外失败码的**唯一**落点(附录外的新码;原码进日志与 message,不丢)。 */
|
|
41
|
+
export declare const STORE_ERROR_CODE = "internal.store_error";
|
|
42
|
+
/**
|
|
43
|
+
* 一个失败码要上 wire 之前的**归一**(S-548)。在词法内 ⇒ 原样;词法外 ⇒ {@link STORE_ERROR_CODE}。
|
|
44
|
+
*
|
|
45
|
+
* ── 病(test T-47,三库一致)────────────────────────────────────────────────────────────────────
|
|
46
|
+
* 本仓的 SQL 店把**驱动原生错误**原样抛回引擎,而 core 的 `errorCodeOf` 沿 `err.code`/`cause` 链取
|
|
47
|
+
* **第一个非空字符串**当这次失败的机器码 —— 于是 mysql2 的 `ER_BAD_FIELD_ERROR` / node-pg 的 SQLSTATE
|
|
48
|
+
* `42703` **逐字**成了 `POST /v1/tasks` 回给消费端的 `terminal.failed.code`。消费端拿到的是一个它的码表里
|
|
49
|
+
* 根本没有、而且随数据库厂商/版本变化的串。
|
|
50
|
+
*
|
|
51
|
+
* ── 为什么判据是**词法**而不是「在不在附录 A 那张表里」────────────────────────────────────────
|
|
52
|
+
* 亲读实现证伪了白名单形:`output.invalid` / `suspend.loop` / `resume_at.*` 这些**合法的引擎终局码**
|
|
53
|
+
* 今天**不在**附录 A(那张表的辖域是 4xx/5xx 错误体,不是 run 终局),白名单会把它们一起折掉 ——
|
|
54
|
+
* 那不仅丢诊断,还会当场打死 `resumeAtHttpStatus` 的 404/422 分诊腿。词法判据则是一条**正面**规则:
|
|
55
|
+
* 「本仓与引擎的码全是小写点分」,而驱动码全是 SCREAMING_SNAKE(`ER_*` / `PROTOCOL_*` / `ECONNRESET`)
|
|
56
|
+
* 或五位 SQLSTATE(`42703`)—— 两个字符集不相交,一条规则覆盖整类,**零逐码特判**。
|
|
57
|
+
*
|
|
58
|
+
* ⚠️ **辖域如实**:本臂触发条件是「码出了本仓的词法」,所以任何依赖铸出的域外码都落这里,不只 SQL 驱动。
|
|
59
|
+
* 今天可观察的产出方**只有**耐久店那一层(引擎自己的传输/brain 错误在 core 侧已被分类成小写码),故码名
|
|
60
|
+
* 取 `internal.store_error`;原码逐字进 message 与 warn 行,归因从不丢失。
|
|
61
|
+
*/
|
|
62
|
+
export declare function outwardFailureCode(code: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* **一条结果离开引擎、进入本仓(可重放账本 / run 行 / wire)的唯一封口** —— 两件事,一个出口:
|
|
65
|
+
* ①摘掉**耐久恢复能力凭据**(`CheckpointToken`);②把**域外的失败码**折成本仓的 `internal.store_error`
|
|
66
|
+
* ({@link outwardFailureCode},S-548)。
|
|
67
|
+
*
|
|
68
|
+
* 🔴 为什么两件事合在一只函数里而不是并列两只(7.92.2 改名 `stripResumeToken` → `sealResultForExit`):
|
|
69
|
+
* 它们是**同一个出口上的同一件事** —— 「这条结果可以见人了吗」。并列两只 = 六个结算点各自要记得调两次,
|
|
70
|
+
* 而漏调的那一次不会红、只会在某天的 wire 上露出一个不该露的东西(凭据或驱动码)。名字跟着职责改:
|
|
71
|
+
* 旧名只说了摘凭据那一半,一个只做一半的名字会让下一个人在另一半上另开出口。
|
|
72
|
+
*
|
|
73
|
+
* ── ① 摘凭据 ──
|
|
35
74
|
* token 就是 resume 凭据(token-as-auth,search [18] Q1b),**永不离开本服务**;非密的 `gate` /
|
|
36
75
|
* `checkpointId` / `restoreMode` 有意保留(对账位)。
|
|
37
76
|
*
|
|
@@ -49,9 +88,24 @@ export declare function failureCodeOf(terminal: TerminalCause): string | undefin
|
|
|
49
88
|
*
|
|
50
89
|
* 对任何输入安全(非普通对象 / 无 `terminal` / 因由不带 token ⇒ **同一引用**原样返回,不重塑)。
|
|
51
90
|
*/
|
|
52
|
-
export declare function
|
|
91
|
+
export declare function sealResultForExit<T extends {
|
|
53
92
|
terminal: TerminalCause;
|
|
54
93
|
}>(result: T): T;
|
|
94
|
+
/**
|
|
95
|
+
* **投影出口的唯一属主**(S-548)—— core 的 `terminalProjection` + 对外码归一,一次。
|
|
96
|
+
*
|
|
97
|
+
* `docs/ASSISTANT-WIRE-CONTRACT.md` §5.0 说清了终局有**两张脸**:①结果**本身**(`done.result` / 200 体 /
|
|
98
|
+
* `GET /v1/runs/:id → result`,零投影)与②**五词投影**(run 行的 `status` / `error_code` 列、fleet 终态词、
|
|
99
|
+
* 决策腿 200 体的三格平面词)。两张脸各有一个属主:①是 {@link sealResultForExit},②就是本函数。
|
|
100
|
+
* **两个属主都折码**,否则同一次失败在两张脸上说两个不同的码 —— 而缺的那一张正是 S-548 修前仍在泄漏的
|
|
101
|
+
* 那条路(codex 对抗复审本轮 [medium],亲读证实:决策腿的 200 体从**未封口的** result 投影 `errorCode`,
|
|
102
|
+
* 于是驱动原生码照旧上 wire,尽管同一段代码落库那份已经折过)。
|
|
103
|
+
*
|
|
104
|
+
* ⇒ 纪律:**`src/` 里除本文件外不许再直接调 core 的 `terminalProjection`**(门在
|
|
105
|
+
* `test/terminal-store-error-fold.test.ts` ④)。折叠**幂等**(折过的码再折还是它自己),所以对一个已经封口的
|
|
106
|
+
* 结果调本函数是安全的 no-op —— 调用方不需要记得"这个 result 封过了没有"。
|
|
107
|
+
*/
|
|
108
|
+
export declare function outwardPlane(terminal: TerminalCause): ReturnType<typeof terminalProjection>;
|
|
55
109
|
/**
|
|
56
110
|
* 这条腿的结果里**藏着的那次耐久暂停** —— 嵌套边界(verify / cascade)遇到自己驱动不了的耐久 park 时,
|
|
57
111
|
* 在**它的**边界上如实报 `failed`(码是 `unexpected.suspended` / `unexpected.needs_review`),而把那次
|
package/dist/terminal.js
CHANGED
|
@@ -3,15 +3,37 @@ import { recordFailOpen } from "./observability/fail-open.js";
|
|
|
3
3
|
export function failureCodeOf(terminal) {
|
|
4
4
|
return terminal.kind === "failed" ? terminal.code : undefined;
|
|
5
5
|
}
|
|
6
|
-
export
|
|
6
|
+
export const OUTWARD_CODE_SHAPE = /^[a-z][a-z0-9_]*(?:\.[a-z0-9_]+)*$/;
|
|
7
|
+
export const STORE_ERROR_CODE = "internal.store_error";
|
|
8
|
+
export function outwardFailureCode(code) {
|
|
9
|
+
return OUTWARD_CODE_SHAPE.test(code) ? code : STORE_ERROR_CODE;
|
|
10
|
+
}
|
|
11
|
+
export function sealResultForExit(result) {
|
|
7
12
|
const t = result?.terminal;
|
|
8
13
|
if (t === null || typeof t !== "object")
|
|
9
14
|
return result;
|
|
10
15
|
if (t.kind === "paused")
|
|
11
16
|
return { ...result, terminal: stripPausedToken(t) };
|
|
12
|
-
if (t.kind
|
|
13
|
-
return
|
|
14
|
-
|
|
17
|
+
if (t.kind !== "failed")
|
|
18
|
+
return result;
|
|
19
|
+
let terminal = t;
|
|
20
|
+
if (t.nestedPause !== undefined)
|
|
21
|
+
terminal = { ...terminal, nestedPause: stripPausedToken(t.nestedPause) };
|
|
22
|
+
if (t.code !== undefined) {
|
|
23
|
+
const outward = outwardFailureCode(t.code);
|
|
24
|
+
if (outward !== t.code) {
|
|
25
|
+
console.warn(`[terminal] outward_failure_code_folded original=${t.code} outward=${outward}`, { message: t.message });
|
|
26
|
+
terminal = { ...terminal, code: outward, message: `${t.message ?? "the durable store failed"} [original code: ${t.code}]` };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return terminal === t ? result : { ...result, terminal };
|
|
30
|
+
}
|
|
31
|
+
export function outwardPlane(terminal) {
|
|
32
|
+
const plane = terminalProjection(terminal);
|
|
33
|
+
if (plane.errorCode === undefined)
|
|
34
|
+
return plane;
|
|
35
|
+
const outward = outwardFailureCode(plane.errorCode);
|
|
36
|
+
return outward === plane.errorCode ? plane : { ...plane, errorCode: outward };
|
|
15
37
|
}
|
|
16
38
|
function stripPausedToken(paused) {
|
|
17
39
|
const { token: _drop, ...rest } = paused;
|
|
@@ -30,7 +52,7 @@ export function persistedPlaneOf(result) {
|
|
|
30
52
|
if (r.terminal !== null && typeof r.terminal === "object") {
|
|
31
53
|
let projected;
|
|
32
54
|
try {
|
|
33
|
-
projected =
|
|
55
|
+
projected = outwardPlane(r.terminal);
|
|
34
56
|
}
|
|
35
57
|
catch (e) {
|
|
36
58
|
recordFailOpen("server.terminal.persisted-plane-malformed", e instanceof Error ? e.message : String(e));
|
|
@@ -47,7 +69,7 @@ export function persistedPlaneOf(result) {
|
|
|
47
69
|
}
|
|
48
70
|
return {
|
|
49
71
|
...(typeof r.status === "string" ? { status: r.status } : {}),
|
|
50
|
-
...(typeof r.errorCode === "string" ? { errorCode: r.errorCode } : {}),
|
|
72
|
+
...(typeof r.errorCode === "string" ? { errorCode: outwardFailureCode(r.errorCode) } : {}),
|
|
51
73
|
...(typeof r.errorMessage === "string" ? { errorMessage: r.errorMessage } : {}),
|
|
52
74
|
...(typeof r.blockedReason === "string" ? { blockedReason: r.blockedReason } : {}),
|
|
53
75
|
};
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -124,6 +124,27 @@ export interface ToolApprovalFrame {
|
|
|
124
124
|
* 本键让 live-only 部署(店缺席、帧无卡)也能读到出身位;安全类标记不得依赖店在场([ref] 结构性
|
|
125
125
|
* 缺口成文)。消费语义(cli 挂点已预埋):在场 ⇒ 一切自动放行让位(含记住的规则与 bypass 姿态)。 */
|
|
126
126
|
requiresRealApproval?: true;
|
|
127
|
+
/**
|
|
128
|
+
* **[ref]**(core 7.26.0;**ADDITIVE**,`"tool_approval"` only)—— **这一问没有任何规则能清掉**
|
|
129
|
+
* (`AskRequest.mandated`:org / 治理层、保护判官、hook 的筛查权、被祖先的应答者裁决的问…… 由引擎在闸站
|
|
130
|
+
* 盖章,策略方不可自填、只可收紧)。壳据它渲染「规则清不掉这一问」,并让**声明了自动应答姿态**的席位知道
|
|
131
|
+
* 哪些卡它不该自动答。
|
|
132
|
+
*
|
|
133
|
+
* 🔴 **别拿 `ruleOffersAbsence:"mandated"` 顶替它**(core 头注逐字警告过这条路):那只键回答的是**规则报价
|
|
134
|
+
* 车道**有什么可给,而那条车道对它不说话的工具、**没接规则店的部署**(本仓的 live-only 形)、装不下规则的
|
|
135
|
+
* 任务都是结构性沉默 —— 它的缺席**不是**断言。本键是这一问**自己**的属性,凡强制皆在场。读错的代价 core
|
|
136
|
+
* 实测过:同一条根外读命令在 shell 工具上是强制问、在 monitor 工具上看起来是普通问。
|
|
137
|
+
*
|
|
138
|
+
* 🔴 **它不是 `requiresRealApproval`**:强制只说「没有规则能清掉」,**不**说「没有应答者可以答」——
|
|
139
|
+
* 一个人、一只 hook、一张一揽子 `onAsk:"allow"` 席照常答得了强制卡。两键别互推。
|
|
140
|
+
*
|
|
141
|
+
* 🔴 **真才带,缺席绝不编 `false`**(`governanceForced` / `requiresRealApproval` 同形)。缺席同时覆盖
|
|
142
|
+
* 「不是强制」「老引擎」「这是一张从 park 行上重铸的耐久卡」三形 —— **耐久面上本仓不造孪生座**:core 的
|
|
143
|
+
* 寄存行(`PendingAction`)没有这一位(KL-997a 亲读登记),行上强制表现为 `ruleOffersAbsence:"mandated"`
|
|
144
|
+
* 且无 `ruleOffers`;在 `card_json` 侧自铸一个就是本仓成了这个事实的第二个源(而 park 那侧根本没有输入)。
|
|
145
|
+
* 🔴 echo-only:server 不据它做任何裁决(强制的判定属主在 core 的闸站)。
|
|
146
|
+
*/
|
|
147
|
+
mandated?: true;
|
|
127
148
|
/**
|
|
128
149
|
* **S-125③/[ref]**(core 7.5.0;**ADDITIVE**,`"tool_approval"` only)—— 这只 ask 的**出身**:
|
|
129
150
|
* 谁提的这一问,一个 core 闭集词(`ASK_ORIGINS` **11 员**,core 7.14.0 现值:content_question /
|
|
@@ -179,7 +200,7 @@ export interface ToolApprovalFrame {
|
|
|
179
200
|
*
|
|
180
201
|
* 🔴 **原样回传**(core 契约 `shell.read_boundary.grant_candidate_is_the_grant`):`dir` 是绝对、已做词法
|
|
181
202
|
* 折叠、用读边界自己的拼法写的串;**显示的串就是要加的串**。壳可以为了好看渲一个缩短形,回传必须是
|
|
182
|
-
* 本键里的原串。⇒ 本仓的姿势是「整只逐字带,或整只丢」,**绝不截**(
|
|
203
|
+
* 本键里的原串。⇒ 本仓的姿势是「整只逐字带,或整只丢」,**绝不截**(界见 core 的 `READ_ROOT_CANDIDATE_DIR_MAX`)。
|
|
183
204
|
*
|
|
184
205
|
* 🔴 **echo-only,零判读**:server 不从命令文本重推目录,也不复核它真不真能清掉这只 ask —— core 已经
|
|
185
206
|
* 把提议的根放回读边界又走了一遍才递出来,下游再判一遍就是同一事实的第二个判官(与 `origin` /
|
package/dist/tool-approval.js
CHANGED
|
@@ -854,6 +854,7 @@ export class ToolApprovalCoordinator {
|
|
|
854
854
|
message: typeof req.message === "string" ? redactDeep(req.message) : "",
|
|
855
855
|
...(governanceForced ? { governanceForced: true } : {}),
|
|
856
856
|
...(req.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
857
|
+
...(req.mandated === true ? { mandated: true } : {}),
|
|
857
858
|
...(typeof req.origin === "string" && req.origin.length > 0 ? { origin: req.origin } : {}),
|
|
858
859
|
...(typeof req.persistedRuleShadowed === "string" && req.persistedRuleShadowed !== ""
|
|
859
860
|
? { persistedRuleShadowed: redactSecrets(req.persistedRuleShadowed).slice(0, MAX_RULE_TEXT_CHARS) }
|
|
@@ -26,7 +26,7 @@ import type { ToolApprovalFrame } from "../tool-approval.js";
|
|
|
26
26
|
* 本文件只有类型、不产 JS,所以被 `import type` 引用不新增任何运行期边。 */
|
|
27
27
|
export type AssertAllKeysHandled<T extends never> = T;
|
|
28
28
|
type NotificationProjected = "task_id" | "task_type" | "toolUseId" | "status" | "summary" | "result" | "output_file" | "usage" | "sessionId" | "seq" | "lines" | "stoppedBy" | "source" | "exitCode" | "partial" | "diagnostics" | "recentSteps" | "editedFiles" | "resumable" | "completionId" | "error" | "errorCode" | "apiFailure" | "agentMessage" | "_sema_provenance";
|
|
29
|
-
type NotificationExcluded = "
|
|
29
|
+
type NotificationExcluded = (typeof import("./wire-projection-faces.js").WIRE_PROJECTION_FACES)["task_notification"]["excluded"][number];
|
|
30
30
|
type _GuardNotification = AssertAllKeysHandled<Exclude<keyof TaskNotificationPayload, NotificationProjected | NotificationExcluded>>;
|
|
31
31
|
type _GuardNotificationReverse = AssertAllKeysHandled<Exclude<NotificationProjected | NotificationExcluded, keyof TaskNotificationPayload>>;
|
|
32
32
|
type ProvenanceProjected = "kind" | "from" | "taskId" | "seq" | "agentType";
|
|
@@ -43,7 +43,7 @@ type _GuardBgNotifReverse = AssertAllKeysHandled<Exclude<BgNotifProjected | BgNo
|
|
|
43
43
|
type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
|
|
44
44
|
type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
|
|
45
45
|
type _GuardRosterReverse = AssertAllKeysHandled<Exclude<RosterProjected, keyof RosterEntry>>;
|
|
46
|
-
type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "ruleStoreUnreadable" | "readRootCandidate";
|
|
46
|
+
type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "ruleStoreUnreadable" | "readRootCandidate" | "mandated";
|
|
47
47
|
type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason" | "hasBidiControls" | "previewWithheld" | "execCwd";
|
|
48
48
|
type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
|
|
49
49
|
type _GuardAskReverse = AssertAllKeysHandled<Exclude<AskProjected | AskExcluded, keyof AskRequest>>;
|
|
@@ -134,7 +134,7 @@ type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "seve
|
|
|
134
134
|
type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "preview" | "principal" | "sourceTaskId" | "contentKind" | "restoreMode" | "checkpointId" | "previewWithheld";
|
|
135
135
|
type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
|
|
136
136
|
type _GuardInboxSummaryReverse = AssertAllKeysHandled<Exclude<SummaryProjected | SummaryExcluded, keyof CheckpointSummary>>;
|
|
137
|
-
type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "writeFace" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "autoModeRequested" | "skillListingBudgetFraction" | "skillListingMaxDescChars" | "excludeAllTools";
|
|
137
|
+
type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "writeFace" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "approverPosture" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "autoModeRequested" | "skillListingBudgetFraction" | "skillListingMaxDescChars" | "excludeAllTools";
|
|
138
138
|
type TaskSpecUnwired = "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars";
|
|
139
139
|
type _GuardTaskSpec = AssertAllKeysHandled<Exclude<keyof TaskSpec, TaskSpecWired | TaskSpecUnwired>>;
|
|
140
140
|
type _GuardTaskSpecReverse = AssertAllKeysHandled<Exclude<TaskSpecWired | TaskSpecUnwired, keyof TaskSpec>>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {} from "@sema-agent/core";
|
|
2
|
+
import { outwardPlane } from "../terminal.js";
|
|
2
3
|
import { modelTextEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, toolDisclosureEventData, humanInputEventData, turnEndEventData, contextUsageEventData } from "./project.js";
|
|
3
4
|
import { NotifiedKeys, taskNotificationStreamKey } from "../orchestration/workflow-completion-inbox.js";
|
|
4
5
|
import { isParkLedgerEventType } from "./ledger-events.js";
|
|
@@ -164,7 +165,7 @@ export function createLedgerSink(opts) {
|
|
|
164
165
|
const onDone = async (result) => {
|
|
165
166
|
await flush();
|
|
166
167
|
if (result.terminal.kind === "paused") {
|
|
167
|
-
const parkKind =
|
|
168
|
+
const parkKind = outwardPlane(result.terminal).status;
|
|
168
169
|
if (isParkLedgerEventType(parkKind)) {
|
|
169
170
|
await appendParked(parkKind, result);
|
|
170
171
|
return;
|
package/dist/trace/project.d.ts
CHANGED
|
@@ -11,6 +11,17 @@
|
|
|
11
11
|
* `output` (+ `truncated`) — redacted + size-bounded at append (runs.ts) and read straight off the event here (the
|
|
12
12
|
* old session-join `toolOutput` hook is gone, there's a real source now); `eventId`/`parentToolCallId` (§E2) ride
|
|
13
13
|
* each content event, additive / tolerate-absent.
|
|
14
|
+
*
|
|
15
|
+
* 🔴 **本族 `*EventData` 构造器的入参型不是白名单**(S-545 起,别再照旧读法维护它)。
|
|
16
|
+
* 每只构造器的形参型是**这条投影的本地入参形**,故意比 core 的分支型松:三条腿里有两条(durable 账本 /
|
|
17
|
+
* resume 回放)手里只有磁盘上的 `Record<string, unknown>`,窄值型在那里结构上不成立,所以 `unknown` +
|
|
18
|
+
* 内部逐层窄化是**有意**的,不是欠账;核过的 core 键在场性由投影体自己的条件展开臂决定。
|
|
19
|
+
* 「这一版 core 的键有没有真的上 wire」这件事的判据**不在形参型上** —— 它在
|
|
20
|
+
* `trace/wire-projection-faces.ts` 的登记表 + `test/wire-whitelist-exhaustiveness.test.ts`:门把 core 型的
|
|
21
|
+
* 键集从**安装态 `.d.ts`** 机器派生出来,喂一枚全键 fixture 真跑一遍本族每只构造器,再拿**投影体**的键集
|
|
22
|
+
* 双向对账(多投一枚 / 漏投一枚各自一条变异负控)。旧形只比形参签名,于是「入参型里写着但投影体里从来
|
|
23
|
+
* 没有」这一类(`human_input.principal` 就是一例)与「忘了投」逐字同形 —— 那正是被替掉的盲区。
|
|
24
|
+
* 新加一只构造器 ⇒ 同批往登记表加一行,否则普查格红。
|
|
14
25
|
*/
|
|
15
26
|
import type { GateOutcome, McpDelivered, TaskResult, SystemInjectionPriority } from "@sema-agent/core";
|
|
16
27
|
import type { RunEvent, RunRecord } from "../plugins/store-contracts.js";
|
|
@@ -650,6 +661,19 @@ export declare function workspaceChangedEventData(ev: {
|
|
|
650
661
|
* 都红不了(2026-09-04 实测:该钉 26/26 全绿,红出 `autoMode` 的是编译期门)。段级 + 成员级的**编译期**门在
|
|
651
662
|
* `core-keyset-guard.ts` ⑥b —— 那才是「core 加了段/加了成员」的可靠红点,两道并用缺一不可。
|
|
652
663
|
*/
|
|
664
|
+
/**
|
|
665
|
+
* 🔴 **`wiring_manifest.mcp[]` 一行上 wire 的形 —— 全仓唯一属主**(S-481 从 {@link wiringManifestEventData}
|
|
666
|
+
* 的内联 IIFE **逐字**提出来,零行为变化;既有钉在 `test/wiring-manifest-projection.test.ts`)。
|
|
667
|
+
*
|
|
668
|
+
* 为什么必须是一只具名函数:S-481 又开了两条读面(`GET /v1/capabilities/mcp` /
|
|
669
|
+
* `POST /v1/capabilities/mcp/probe`),它们发的是**同一种**行。留在内联里,那两条面只能各自再挑一遍键
|
|
670
|
+
* —— 而契约 §G.7 第 3 条(`error` 远端原话**有意不投**,理由是「再脱一遍就是同一语义面的第二个写者」)
|
|
671
|
+
* 这种**裁定**一旦有两个实现,下一个人只会改到其中一个。
|
|
672
|
+
*
|
|
673
|
+
* 逐条目挑键(不是整只判形):`name` / `status` 缺一 ⇒ 丢**这一条**;数组本身非数组 ⇒ 整段缺席。
|
|
674
|
+
* 逐键判据与失败方向的理由全在下面各行的注里,`liveness` 那一格的裁定在 §G.7 第 2bis 条。
|
|
675
|
+
*/
|
|
676
|
+
export declare function projectWiringManifestMcpRows(mcpIn: unknown): Array<Record<string, unknown>> | undefined;
|
|
653
677
|
/**
|
|
654
678
|
* `tool_roster_delta`(core 7.8.0 [ref] ⑪ 新 `TaskEvent` 臂)—— **运行期**名册变更:整只变更后名册
|
|
655
679
|
* (`delta.roster`)+ 一段只有持有 `fromDigest` 的消费者才用得上的摘要。
|