@sema-agent/server 7.136.0 → 7.138.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/MIGRATION.md +501 -139
- package/README.md +1 -0
- package/README.zh-CN.md +1 -0
- package/USAGE.md +149 -34
- package/deploy/sema-up/README.md +14 -0
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
- package/dist/approval-card.d.ts +21 -14
- package/dist/approval-card.js +1 -0
- package/dist/approval-content-kind.d.ts +6 -0
- package/dist/approval-content-kind.js +7 -0
- package/dist/approval-gate-currency.d.ts +48 -6
- package/dist/approval-gate-currency.js +16 -9
- package/dist/approval-hmac.d.ts +13 -1
- package/dist/approval-hmac.js +1 -1
- package/dist/approval.js +3 -3
- package/dist/audit.js +1 -0
- package/dist/auth-bridge.js +2 -2
- package/dist/background-agent-name.d.ts +5 -0
- package/dist/background-agent-name.js +5 -0
- package/dist/boot/budget-tracing.d.ts +1 -12
- package/dist/boot/deployment-mods.d.ts +46 -16
- package/dist/boot/deployment-mods.js +82 -6
- package/dist/boot/execution-env.d.ts +24 -1
- package/dist/boot/execution-env.js +69 -6
- package/dist/boot/governance-seams.d.ts +10 -17
- package/dist/boot/governance-seams.js +5 -17
- package/dist/boot/leader.d.ts +3 -0
- package/dist/boot/leader.js +1 -0
- package/dist/boot/org-delivery.js +15 -2
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +15 -1
- package/dist/boot/resolve-spec.d.ts +7 -0
- package/dist/boot/resolve-spec.js +36 -26
- package/dist/boot/retention-lane.d.ts +11 -5
- package/dist/boot/retention-lane.js +2 -2
- package/dist/boot/runner-deps.d.ts +7 -3
- package/dist/boot/runner-deps.js +1 -0
- package/dist/boot/stage-03-store-backend.d.ts +1 -1
- package/dist/boot/stage-04-stores.d.ts +2 -13
- package/dist/boot/stage-05-execution-env.d.ts +6 -13
- package/dist/boot/stage-05-execution-env.js +17 -5
- package/dist/boot/stage-06-runners.d.ts +6 -13
- package/dist/boot/stage-06-runners.js +10 -3
- package/dist/boot/stage-07-capability-layer.d.ts +7 -13
- package/dist/boot/stage-07-capability-layer.js +3 -3
- package/dist/boot/stage-08-reapers.d.ts +7 -13
- package/dist/boot/stage-08-reapers.js +2 -1
- package/dist/boot/stage-09-leader.d.ts +7 -13
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +7 -13
- package/dist/boot/stage-10-http-server.js +6 -3
- package/dist/boot/stores.d.ts +1 -1
- package/dist/boot/web-admin.d.ts +5 -2
- package/dist/boot/web-admin.js +3 -3
- package/dist/brain.js +5 -5
- package/dist/broker/broker.d.ts +89 -0
- package/dist/broker/broker.js +287 -0
- package/dist/broker/constants.d.ts +39 -0
- package/dist/broker/constants.js +15 -0
- package/dist/broker/http1.d.ts +111 -0
- package/dist/broker/http1.js +256 -0
- package/dist/broker/lease-book.d.ts +87 -0
- package/dist/broker/lease-book.js +142 -0
- package/dist/broker/lease-file.d.ts +60 -0
- package/dist/broker/lease-file.js +127 -0
- package/dist/broker/main.d.ts +2 -0
- package/dist/broker/main.js +20 -0
- package/dist/budget.d.ts +17 -15
- package/dist/budget.js +2 -2
- package/dist/capabilities/composite-members.d.ts +2 -1
- package/dist/capabilities/composite-members.js +2 -1
- package/dist/capabilities/engine-capabilities.js +1 -0
- package/dist/capabilities/mcp-probe.d.ts +36 -11
- package/dist/capabilities/mcp-probe.js +44 -5
- package/dist/capabilities/scenarios.d.ts +23 -8
- package/dist/capabilities/scenarios.js +13 -8
- package/dist/capabilities/skills.d.ts +28 -5
- package/dist/capabilities/skills.js +8 -0
- package/dist/capability-facts.d.ts +6 -0
- package/dist/capability-facts.js +5 -0
- package/dist/center-credential-renewer.js +5 -1
- package/dist/checkpoint-ctx-seal-m28.d.ts +3 -10
- package/dist/checkpoint-ctx-seal-m28.js +1 -1
- package/dist/checkpoint-identity.d.ts +13 -6
- package/dist/config-catalog.js +16 -7
- package/dist/config-types.d.ts +24 -7
- package/dist/config.d.ts +1 -1
- package/dist/config.js +42 -13
- package/dist/deployment-governance.d.ts +2 -2
- package/dist/deployment-governance.js +7 -9
- package/dist/deployment-shape.d.ts +6 -6
- package/dist/device-ws-protocol.d.ts +1 -0
- package/dist/device-ws-protocol.js +2 -1
- package/dist/file-history-disclosure.d.ts +4 -2
- package/dist/file-history-disclosure.js +2 -0
- package/dist/gate-settlement.d.ts +71 -0
- package/dist/gate-settlement.js +40 -0
- package/dist/hooks/classic-stations.d.ts +12 -54
- package/dist/hooks/classic-stations.js +1 -0
- package/dist/hooks/command-spawn.d.ts +2 -2
- package/dist/hooks/hook-runner.js +14 -14
- package/dist/http/admission.js +14 -9
- package/dist/http/approval-sweeps.js +4 -3
- package/dist/http/caller-trust.d.ts +2 -1
- package/dist/http/caller-trust.js +1 -3
- package/dist/http/checkpoint-ctx.d.ts +48 -7
- package/dist/http/checkpoint-ctx.js +40 -0
- package/dist/http/dispatch.d.ts +4 -0
- package/dist/http/dispatch.js +18 -10
- package/dist/http/durable-run-leg.d.ts +5 -1
- package/dist/http/durable-run-leg.js +5 -3
- package/dist/http/health-self-report.d.ts +2 -2
- package/dist/http/memory-compliance-failure-reply.js +3 -0
- package/dist/http/principal-gate.d.ts +15 -10
- package/dist/http/principal-gate.js +48 -9
- package/dist/http/resume-legs.d.ts +7 -31
- package/dist/http/resume-legs.js +185 -126
- package/dist/http/route-ctx.d.ts +38 -53
- package/dist/http/route-ctx.js +2 -5
- package/dist/http/routes/a2a-serve.js +10 -4
- package/dist/http/routes/agents-roster.d.ts +1 -1
- package/dist/http/routes/agents-roster.js +5 -1
- package/dist/http/routes/approvals-assistant.js +24 -10
- package/dist/http/routes/capabilities.js +23 -11
- package/dist/http/routes/mcp-reconnect.d.ts +3 -2
- package/dist/http/routes/mcp-reconnect.js +7 -7
- package/dist/http/routes/memory-consolidation.d.ts +1 -1
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/rules.js +3 -3
- package/dist/http/routes/runs.js +13 -8
- package/dist/http/routes/session-sync.js +42 -6
- package/dist/http/routes/sessions.js +6 -3
- package/dist/http/routes/side-query.js +2 -3
- package/dist/http/routes/tasks.js +794 -780
- package/dist/http/server.d.ts +35 -25
- package/dist/http/stored-body-replay.d.ts +3 -3
- package/dist/http/stored-body-replay.js +6 -1
- package/dist/http/wire-types.d.ts +66 -33
- package/dist/identity-source.d.ts +65 -0
- package/dist/identity-source.js +45 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/leader/wire.d.ts +7 -0
- package/dist/leader/wire.js +5 -4
- package/dist/mcp-assertion-header.d.ts +51 -40
- package/dist/mcp-assertion-header.js +10 -11
- package/dist/mcp-authorization-face.d.ts +27 -0
- package/dist/mcp-authorization-face.js +15 -0
- package/dist/memory-operator-faces.d.ts +13 -4
- package/dist/memory-operator-faces.js +1 -17
- package/dist/memory-store-refusal.d.ts +3 -3
- package/dist/memory-sync-client.js +72 -57
- package/dist/memory-sync.d.ts +1 -1
- package/dist/model-entry-refusal.d.ts +2 -0
- package/dist/model-entry-refusal.js +11 -0
- package/dist/model-route-gap.d.ts +6 -22
- package/dist/model-route-gap.js +7 -5
- package/dist/model-select.d.ts +6 -17
- package/dist/model-select.js +0 -3
- package/dist/mods-declaration.d.ts +110 -0
- package/dist/mods-declaration.js +109 -0
- package/dist/namespaced-rule-name.d.ts +1 -1
- package/dist/observability/err-text.d.ts +5 -0
- package/dist/observability/err-text.js +9 -0
- package/dist/observability/fail-open.d.ts +17 -1
- package/dist/observability/fail-open.js +17 -1
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/run-terminal.js +1 -0
- package/dist/observability/secret-env-scrub.d.ts +1 -1
- package/dist/observability/secret-env-scrub.js +1 -1
- package/dist/operator-ask.d.ts +2 -2
- package/dist/org-delivery-gate.js +1 -1
- package/dist/org-memory-admission.d.ts +2 -2
- package/dist/org-memory-admission.js +39 -2
- package/dist/parked-decide.d.ts +15 -2
- package/dist/parked-decide.js +17 -10
- package/dist/peer-directory.js +2 -0
- package/dist/peer-seat.d.ts +150 -0
- package/dist/peer-seat.js +239 -0
- package/dist/permission-rule-list.d.ts +2 -2
- package/dist/permission-rule-vocab.d.ts +7 -7
- package/dist/permission-rule-vocab.js +3 -3
- package/dist/plain-string-record.d.ts +38 -0
- package/dist/plain-string-record.js +38 -0
- package/dist/plugins/background-agent-store-sql.js +6 -3
- package/dist/plugins/caching-session-store.d.ts +5 -0
- package/dist/plugins/caching-session-store.js +6 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +34 -29
- package/dist/plugins/checkpoint-store-sql.js +14 -14
- package/dist/plugins/file-history-store-sql.js +10 -16
- package/dist/plugins/file-run-store.js +8 -2
- package/dist/plugins/k8s-exec-protocol.d.ts +69 -13
- package/dist/plugins/k8s-exec-protocol.js +116 -28
- package/dist/plugins/k8s-pod-delete-failure.d.ts +25 -0
- package/dist/plugins/k8s-pod-delete-failure.js +17 -0
- package/dist/plugins/k8s-pod-reaper.d.ts +106 -0
- package/dist/plugins/k8s-pod-reaper.js +122 -0
- package/dist/plugins/local-checkpoint-store.d.ts +4 -17
- package/dist/plugins/local-checkpoint-store.js +3 -4
- package/dist/plugins/local-session-store.d.ts +66 -27
- package/dist/plugins/local-session-store.js +60 -28
- package/dist/plugins/mailbox-store-sql.d.ts +11 -3
- package/dist/plugins/mailbox-store-sql.js +103 -15
- package/dist/plugins/memory-run-store.js +2 -0
- package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
- package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
- package/dist/plugins/one-time-migrations.d.ts +34 -12
- package/dist/plugins/one-time-migrations.js +51 -9
- package/dist/plugins/permission-rule-store-file.js +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +17 -17
- package/dist/plugins/permission-rule-store-sql.js +37 -30
- package/dist/plugins/pg-pool.js +4 -0
- package/dist/plugins/pg-session-storage.d.ts +10 -2
- package/dist/plugins/pg-session-storage.js +20 -7
- package/dist/plugins/remote-env-adb.d.ts +6 -1
- package/dist/plugins/remote-env-adb.js +2 -1
- package/dist/plugins/remote-env-device.d.ts +6 -1
- package/dist/plugins/remote-env-device.js +5 -1
- package/dist/plugins/remote-env-e2b.d.ts +5 -0
- package/dist/plugins/remote-env-e2b.js +2 -1
- package/dist/plugins/remote-env-file-error.d.ts +8 -1
- package/dist/plugins/remote-env-file-error.js +7 -1
- package/dist/plugins/remote-env-host.d.ts +10 -2
- package/dist/plugins/remote-env-host.js +8 -2
- package/dist/plugins/remote-env-k8s.d.ts +140 -11
- package/dist/plugins/remote-env-k8s.js +277 -15
- package/dist/plugins/remote-env-local-docker.d.ts +5 -0
- package/dist/plugins/remote-env-local-docker.js +2 -1
- package/dist/plugins/remote-env-ssh.d.ts +6 -1
- package/dist/plugins/remote-env-ssh.js +2 -1
- package/dist/plugins/retention-store-sql.d.ts +14 -2
- package/dist/plugins/retention-store-sql.js +6 -0
- package/dist/plugins/run-store-sql.js +10 -4
- package/dist/plugins/session-fork.d.ts +10 -13
- package/dist/plugins/session-fork.js +5 -25
- package/dist/plugins/session-ownership.d.ts +24 -3
- package/dist/plugins/session-ownership.js +24 -5
- package/dist/plugins/session-workspace-cell.d.ts +17 -0
- package/dist/plugins/session-workspace-cell.js +38 -0
- package/dist/plugins/sql-errors.d.ts +23 -2
- package/dist/plugins/sql-errors.js +15 -0
- package/dist/plugins/store-backend.js +7 -7
- package/dist/plugins/store-contracts.d.ts +10 -1
- package/dist/plugins/store-fault.d.ts +2 -2
- package/dist/plugins/store-fault.js +4 -2
- package/dist/plugins/tidb-pool.js +13 -0
- package/dist/plugins/tidb-session-store.d.ts +17 -2
- package/dist/plugins/tidb-session-store.js +18 -7
- package/dist/plugins/tool-result-store-sql.d.ts +13 -6
- package/dist/plugins/tool-result-store-sql.js +8 -7
- package/dist/plugins/worktree-isolation.d.ts +3 -2
- package/dist/plugins/worktree-isolation.js +1 -7
- package/dist/project-identity.d.ts +1 -14
- package/dist/project-identity.js +0 -4
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +31 -2
- package/dist/request-value-closure.js +7 -1
- package/dist/rule-entry-list.d.ts +3 -2
- package/dist/rule-entry-list.js +2 -2
- package/dist/rules-consent.js +12 -7
- package/dist/run-local.d.ts +2 -2
- package/dist/run-local.js +12 -6
- package/dist/runs.d.ts +7 -1
- package/dist/runs.js +8 -2
- package/dist/runtime-governance.d.ts +5 -5
- package/dist/sandbox-git-lease.d.ts +49 -0
- package/dist/sandbox-git-lease.js +60 -0
- package/dist/sandbox-git-plane.d.ts +84 -0
- package/dist/sandbox-git-plane.js +105 -0
- package/dist/security.d.ts +16 -4
- package/dist/security.js +8 -3
- package/dist/server-secret-env.d.ts +12 -3
- package/dist/server-secret-env.js +18 -2
- package/dist/session-error-wire.d.ts +14 -6
- package/dist/session-error-wire.js +2 -1
- package/dist/session-incarnation.d.ts +4 -5
- package/dist/session-incarnation.js +2 -2
- package/dist/session-policy-wire.d.ts +1 -1
- package/dist/session-policy-wire.js +2 -2
- package/dist/session-sync.d.ts +1 -1
- package/dist/session-workspace.d.ts +130 -0
- package/dist/session-workspace.js +83 -0
- package/dist/spec-fields.d.ts +44 -14
- package/dist/spec-fields.js +33 -12
- package/dist/standing-answer-grant.d.ts +2 -13
- package/dist/standing-answer-grant.js +0 -7
- package/dist/stored-request-prompt-profile-m30.d.ts +49 -0
- package/dist/stored-request-prompt-profile-m30.js +25 -0
- package/dist/task-a2a.d.ts +44 -14
- package/dist/task-a2a.js +46 -20
- package/dist/task-mcp.d.ts +34 -21
- package/dist/task-mcp.js +46 -22
- package/dist/task-settings.d.ts +2 -2
- package/dist/tool-approval.d.ts +43 -9
- package/dist/tool-approval.js +57 -15
- package/dist/tool-rule-names.d.ts +5 -0
- package/dist/trace/core-keyset-guard.d.ts +34 -19
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +4 -0
- package/dist/trace/exit-seat.d.ts +20 -1
- package/dist/trace/exit-seat.js +5 -2
- package/dist/trace/extension-call-face.d.ts +33 -0
- package/dist/trace/extension-call-face.js +39 -0
- package/dist/trace/ledger-sink.d.ts +4 -5
- package/dist/trace/ledger-sink.js +3 -7
- package/dist/trace/project.d.ts +62 -10
- package/dist/trace/project.js +49 -9
- package/dist/trace/redact.d.ts +18 -6
- package/dist/trace/redact.js +154 -60
- package/dist/trace/self-minted-wire.d.ts +500 -7
- package/dist/trace/self-minted-wire.js +182 -1
- package/dist/trace/wire-projection-faces.d.ts +7 -5
- package/dist/trace/wire-projection-faces.js +9 -4
- package/dist/user-authored-text.d.ts +2 -2
- package/dist/user-authored-text.js +16 -1
- package/dist/web-admin-client/apply-effective.js +0 -1
- package/dist/web-admin-client/center-request.d.ts +23 -4
- package/dist/web-admin-client/center-request.js +42 -8
- package/dist/web-admin-client/http-client.js +6 -6
- package/dist/web-admin-client/skills-mcp.d.ts +8 -4
- package/dist/web-admin-client/skills-mcp.js +31 -18
- package/dist/web-admin-client/types.d.ts +10 -5
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
- package/package.json +3 -3
- package/wire-changes/server@7.137.0.json +825 -0
- package/wire-changes/server@7.138.0.json +446 -0
package/dist/parked-decide.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type { RebuiltInheritedGate } from "./boot/parked-revive-gate.js";
|
|
|
16
16
|
import { type WorkflowRunParkKey, type WorkflowAgentSessionIndex } from "./orchestration/workflow-agent-session-index.js";
|
|
17
17
|
import type { WorkflowParkDecisionStore, WorkflowParkOutcome } from "./orchestration/workflow-park-decision.js";
|
|
18
18
|
import { type HeadRead } from "./session-incarnation.js";
|
|
19
|
+
import type { GateIdentity } from "./checkpoint-identity.js";
|
|
19
20
|
export interface ParkedAgentMatch {
|
|
20
21
|
handle: string;
|
|
21
22
|
row: BackgroundAgentRecord;
|
|
@@ -224,6 +225,18 @@ export interface ParkedDecideRequest {
|
|
|
224
225
|
* 的挂点(调用方自吞错,绝不抛落赎回)。claim 败/判别 miss 恒不触发。 */
|
|
225
226
|
onDecideCommitted?: () => Promise<void>;
|
|
226
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* 后台子代赎回腿({@link decideParkedAgent})收的请求 = 两条车道共用的那一份 + 选中这张卡的**门身份片段**。workflow 车道是投递、不收它
|
|
230
|
+
* (那条车道的回执不带结清证据,身份在那里不适用 —— 不捕获,也就不为它记「缺身份」)。
|
|
231
|
+
*/
|
|
232
|
+
export interface ParkedAgentDecideRequest extends ParkedDecideRequest {
|
|
233
|
+
/**
|
|
234
|
+
* 调用方在执行之前从选中的那一行捕获、已过信任边界(`checkpoint-identity.ts` 的 `durableRowIdentity`,face `decision-receipt`;读不出 ⇒ `{}`)。
|
|
235
|
+
* 本腿的两个结清边界(复活成功 / 不可复活 deny 作废)把它随结清证据上体(`gate-settlement.ts`)。必填:身份只有这一个来源 ——
|
|
236
|
+
* agent 行上没有这一格,本腿也不另读「当前待决门」(受理之后它可能已是下一张卡);认领到的 token 不是选中的那一张 ⇒ 拒,不替它作证。
|
|
237
|
+
*/
|
|
238
|
+
readonly selectedGate: GateIdentity;
|
|
239
|
+
}
|
|
227
240
|
export interface ParkedDecideResult {
|
|
228
241
|
status: number;
|
|
229
242
|
body: Record<string, unknown>;
|
|
@@ -341,8 +354,8 @@ export declare function planWorkflowParkedDecide(origin: Extract<ParkOrigin, {
|
|
|
341
354
|
* ([ref] Q2:isError 入参级早退 + throw 两形,已核幂等/双滚=无害 lost)。
|
|
342
355
|
*
|
|
343
356
|
* 200 是**受理**语义(drive 是 core 内 fire-and-forget):失败经行/notification 面 + REOPENED
|
|
344
|
-
* re-park 诚实可见,操作员可再决。
|
|
357
|
+
* re-park 诚实可见,操作员可再决。200 体 `{ taskId, status: "resuming", decision }` + 结清证据(`gate-settlement.ts` 的边界 ④)。
|
|
345
358
|
*/
|
|
346
|
-
export declare function decideParkedAgent(deps: ParkedDecideDeps, req:
|
|
359
|
+
export declare function decideParkedAgent(deps: ParkedDecideDeps, req: ParkedAgentDecideRequest, match: ParkedAgentMatch): Promise<ParkedDecideResult>;
|
|
347
360
|
export {};
|
|
348
361
|
//# sourceMappingURL=parked-decide.d.ts.map
|
package/dist/parked-decide.js
CHANGED
|
@@ -4,9 +4,11 @@ import { routeWorkflowPark } from "./orchestration/workflow-agent-session-index.
|
|
|
4
4
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
5
5
|
import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
|
|
6
6
|
import { backgroundScopesForCheckpointScope, decodeCheckpointScope, ownerOfCheckpointScope } from "./security.js";
|
|
7
|
-
import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
|
|
7
|
+
import { ASK_USER_QUESTION_TOOL_NAME, isEngineQuestionRow } from "./approval-content-kind.js";
|
|
8
8
|
import { sessionFaceOfHead } from "./session-incarnation.js";
|
|
9
9
|
import { refusalBody, resumeRefusalFacts } from "./http/refusal-body.js";
|
|
10
|
+
import { backgroundAgentNameOf } from "./background-agent-name.js";
|
|
11
|
+
import { buildSettlementEvidence } from "./gate-settlement.js";
|
|
10
12
|
export async function findParkOrigin(sources, cp) {
|
|
11
13
|
if (sources.agentStore !== undefined) {
|
|
12
14
|
let match;
|
|
@@ -111,7 +113,7 @@ function mintReviveArgs(row, knownAgentTypes) {
|
|
|
111
113
|
return {
|
|
112
114
|
prompt: row.description ?? "resume parked agent",
|
|
113
115
|
run_in_background: true,
|
|
114
|
-
name: row
|
|
116
|
+
name: backgroundAgentNameOf(row),
|
|
115
117
|
description: row.description ?? "revived agent",
|
|
116
118
|
...(row.agentType !== undefined && knownAgentTypes.has(row.agentType) ? { subagent_type: row.agentType } : {}),
|
|
117
119
|
};
|
|
@@ -120,12 +122,13 @@ export function buildParkedResumeOutcome(req) {
|
|
|
120
122
|
const pa = req.pendingAction;
|
|
121
123
|
const persistedCallId = pa?.kind === "tool_approval" ? (pa.toolCallId ?? "") : "";
|
|
122
124
|
const persistedHash = pa?.kind === "tool_approval" ? (pa.boundInputHash ?? "") : "";
|
|
125
|
+
const engineQuestion = isEngineQuestionRow(req.pendingAction);
|
|
123
126
|
return {
|
|
124
127
|
gate: "policy_ask",
|
|
125
128
|
boundCallId: req.binding?.boundCallId ?? persistedCallId,
|
|
126
129
|
boundInputHash: req.binding?.boundInputHash ?? persistedHash,
|
|
127
130
|
decision: req.decision === "approve" ? "allow" : "deny",
|
|
128
|
-
...(req.decision === "approve" && req.binding?.updatedInput !== undefined ? { updatedInput: req.binding.updatedInput } : {}),
|
|
131
|
+
...(req.decision === "approve" && !engineQuestion && req.binding?.updatedInput !== undefined ? { updatedInput: req.binding.updatedInput } : {}),
|
|
129
132
|
...(req.binding?.feedback !== undefined ? { feedback: req.binding.feedback } : {}),
|
|
130
133
|
...(req.answer !== undefined ? { answer: req.answer } : {}),
|
|
131
134
|
...(req.reason !== undefined ? { reason: req.reason } : {}),
|
|
@@ -231,10 +234,10 @@ function sessionGone(taskId) {
|
|
|
231
234
|
}),
|
|
232
235
|
};
|
|
233
236
|
}
|
|
237
|
+
function gateMoved(taskId) {
|
|
238
|
+
return { status: 409, body: refusalBody({ errorCode: "decide.gate_moved", message: "the approval gate moved under this decide (re-parked or already settled) — re-check the pending list", facts: { taskId } }) };
|
|
239
|
+
}
|
|
234
240
|
function revivabilityOf(row, taskId) {
|
|
235
|
-
if (row.name === undefined) {
|
|
236
|
-
return { kind: "unrevivable", approveRefusal: { status: 409, body: refusalBody({ errorCode: "decide.row_unrevivable", message: "parked row carries no agent name — the revive path requires a named agent, so this approval cannot be approved; deny it to close the card (the agent then ends as failed)", facts: { taskId } }) } };
|
|
237
|
-
}
|
|
238
241
|
if (row.rootSessionId === undefined) {
|
|
239
242
|
return { kind: "unrevivable", approveRefusal: { status: 409, body: refusalBody({ errorCode: "decide.row_unrevivable", message: "this agent's row carries no root session anchor — the revive path cannot tell which session this approval belongs to, so it cannot be approved; deny it to close the card (the agent then ends as failed)", facts: { taskId } }) } };
|
|
240
243
|
}
|
|
@@ -243,7 +246,7 @@ function revivabilityOf(row, taskId) {
|
|
|
243
246
|
async function voidUnrevivableCard(deps, req, match) {
|
|
244
247
|
const voided = await deps.checkpointStore.expire(req.token, req.scope);
|
|
245
248
|
if (!voided) {
|
|
246
|
-
return
|
|
249
|
+
return gateMoved(match.handle);
|
|
247
250
|
}
|
|
248
251
|
try {
|
|
249
252
|
await deps.registry.reconcileParkedAgents({ agentStore: deps.agentStore, checkpointStore: deps.checkpointStore }, match.row.scope, Date.now());
|
|
@@ -251,7 +254,7 @@ async function voidUnrevivableCard(deps, req, match) {
|
|
|
251
254
|
catch (e) {
|
|
252
255
|
quiet(deps.warn, "parked_unrevivable_reconcile_failed", { taskId: match.handle, error: e instanceof Error ? e.message : String(e) });
|
|
253
256
|
}
|
|
254
|
-
return { status: 200, body: { taskId: match.handle, status: "failed", decision: "deny" } };
|
|
257
|
+
return { status: 200, body: { taskId: match.handle, status: "failed", decision: "deny", ...buildSettlementEvidence({ at: "card_voided", gate: req.selectedGate }) } };
|
|
255
258
|
}
|
|
256
259
|
export async function decideParkedAgent(deps, req, match) {
|
|
257
260
|
const row = match.row;
|
|
@@ -281,7 +284,7 @@ export async function decideParkedAgent(deps, req, match) {
|
|
|
281
284
|
return { status: 409, body: refusalBody({ errorCode: "decide.claim_lost", message: "the parked agent is already claimed — either a concurrent decide won (re-check the pending list), or a stale claim is pending automatic cleanup (retry after the stale-claim window)", facts: { taskId: match.handle } }) };
|
|
282
285
|
case "binding_broken":
|
|
283
286
|
case "checkpoint_not_pending":
|
|
284
|
-
return
|
|
287
|
+
return gateMoved(match.handle);
|
|
285
288
|
default:
|
|
286
289
|
return { status: 409, body: refusalBody({ errorCode: "decide.not_parked", message: "the parked agent moved under this decide (stopped or already decided)", facts: { taskId: match.handle } }) };
|
|
287
290
|
}
|
|
@@ -305,6 +308,10 @@ export async function decideParkedAgent(deps, req, match) {
|
|
|
305
308
|
return "retry";
|
|
306
309
|
}
|
|
307
310
|
};
|
|
311
|
+
if (ticket.token !== req.token) {
|
|
312
|
+
await rollback("claimed_token_moved");
|
|
313
|
+
return gateMoved(match.handle);
|
|
314
|
+
}
|
|
308
315
|
let faceAfter;
|
|
309
316
|
try {
|
|
310
317
|
faceAfter = sessionFaceOfHead(root, await deps.headOf(root));
|
|
@@ -407,6 +414,6 @@ export async function decideParkedAgent(deps, req, match) {
|
|
|
407
414
|
}),
|
|
408
415
|
};
|
|
409
416
|
}
|
|
410
|
-
return { status: 200, body: { taskId: match.handle, status: "resuming", decision: req.decision } };
|
|
417
|
+
return { status: 200, body: { taskId: match.handle, status: "resuming", decision: req.decision, ...buildSettlementEvidence({ at: "agent_revived", gate: req.selectedGate }) } };
|
|
411
418
|
}
|
|
412
419
|
//# sourceMappingURL=parked-decide.js.map
|
package/dist/peer-directory.js
CHANGED
|
@@ -85,6 +85,7 @@ export function createPeerDirectory(ctx) {
|
|
|
85
85
|
startedAt: Number.isFinite(started) ? started : 0,
|
|
86
86
|
updatedAt: Number.isFinite(updated) ? updated : 0,
|
|
87
87
|
liveness: "live",
|
|
88
|
+
where: "hosted",
|
|
88
89
|
...(tempo !== undefined ? { tempo } : {}),
|
|
89
90
|
inboundPosture: posture,
|
|
90
91
|
});
|
|
@@ -125,6 +126,7 @@ export function createPeerDirectory(ctx) {
|
|
|
125
126
|
startedAt: t.retiredAt,
|
|
126
127
|
updatedAt: t.retiredAt,
|
|
127
128
|
liveness: "deleted",
|
|
129
|
+
where: "hosted",
|
|
128
130
|
diedAt: t.retiredAt,
|
|
129
131
|
inboundPosture: posture,
|
|
130
132
|
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { A2aServerSpec, McpServerSpec, PeerAnswer, PeerQuestion, PeerSeatTarget } from "@sema-agent/core";
|
|
2
|
+
import { type Logger } from "./observability/logger.js";
|
|
3
|
+
import { type SafeText } from "./trace/exit-seat.js";
|
|
4
|
+
type StdioArm = Extract<McpServerSpec["transport"], {
|
|
5
|
+
kind: "stdio";
|
|
6
|
+
}>;
|
|
7
|
+
type HttpArm = Extract<McpServerSpec["transport"], {
|
|
8
|
+
kind: "http";
|
|
9
|
+
}>;
|
|
10
|
+
/** The pieces a declaration's sends carry, as written: header name → value (http MCP, A2A) or variable → value (stdio MCP). */
|
|
11
|
+
export type PeerPieces = Readonly<Record<string, string>>;
|
|
12
|
+
/**
|
|
13
|
+
* An MCP server declaration before the desk has seen it: the engine's declaration without its credential reference, beside the
|
|
14
|
+
* pieces. `credentials?: never` sits on each transport arm (where the engine keeps the reference) so that a declaration that already
|
|
15
|
+
* carries one cannot be passed back in as a draft: `Omit` alone would not refuse it, because excess members are judged only on fresh
|
|
16
|
+
* object literals. Without `exactOptionalPropertyTypes` an explicit `credentials: undefined` still passes; it carries no reference.
|
|
17
|
+
*/
|
|
18
|
+
export type McpDraftSpec = Omit<McpServerSpec, "transport"> & {
|
|
19
|
+
transport: (Omit<StdioArm, "credentials"> & {
|
|
20
|
+
credentials?: never;
|
|
21
|
+
}) | (Omit<HttpArm, "credentials"> & {
|
|
22
|
+
credentials?: never;
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
/** An MCP draft: the declaration and the pieces its sends carry (absent or empty ⇒ nothing to register). */
|
|
26
|
+
export interface McpDraft {
|
|
27
|
+
readonly spec: McpDraftSpec;
|
|
28
|
+
readonly pieces?: PeerPieces;
|
|
29
|
+
}
|
|
30
|
+
/** An A2A peer declaration before the desk has seen it; the engine keeps an A2A peer's reference at the top level. */
|
|
31
|
+
export type A2aDraftSpec = Omit<A2aServerSpec, "credentials"> & {
|
|
32
|
+
credentials?: never;
|
|
33
|
+
};
|
|
34
|
+
/** An A2A draft: the declaration and the headers its sends carry (absent or empty ⇒ nothing to register). */
|
|
35
|
+
export interface A2aDraft {
|
|
36
|
+
readonly spec: A2aDraftSpec;
|
|
37
|
+
readonly pieces?: PeerPieces;
|
|
38
|
+
}
|
|
39
|
+
export type PeerDraft = McpDraft | A2aDraft;
|
|
40
|
+
/** The reference {@link PeerDesk.declare} mints (the `handle` kind of the reference the engine asks with): opaque, the same for the same
|
|
41
|
+
* content within one process. */
|
|
42
|
+
export type PeerHandle = Extract<NonNullable<PeerSeatTarget["credentials"]>, {
|
|
43
|
+
kind: "handle";
|
|
44
|
+
}>;
|
|
45
|
+
/**
|
|
46
|
+
* Who a send goes to, as the seat binds an answer to it: the engine's question target without the reference itself and without an
|
|
47
|
+
* A2A send's `destination` (the engine owns which declared origin a send may reach). An http MCP server = name, url, mount
|
|
48
|
+
* fingerprint; a stdio one = name, the whole start command (absent `args` ≡ `[]`), fingerprint; an A2A peer = name, url, `cardUrl`.
|
|
49
|
+
*/
|
|
50
|
+
export type PeerBinding = {
|
|
51
|
+
readonly protocol: "mcp";
|
|
52
|
+
readonly transport: "http";
|
|
53
|
+
readonly server: string;
|
|
54
|
+
readonly url: string;
|
|
55
|
+
readonly fingerprint?: string;
|
|
56
|
+
} | {
|
|
57
|
+
readonly protocol: "mcp";
|
|
58
|
+
readonly transport: "stdio";
|
|
59
|
+
readonly server: string;
|
|
60
|
+
readonly command: string;
|
|
61
|
+
readonly args: readonly string[];
|
|
62
|
+
readonly fingerprint?: string;
|
|
63
|
+
} | {
|
|
64
|
+
readonly protocol: "a2a";
|
|
65
|
+
readonly peer: string;
|
|
66
|
+
readonly url: string;
|
|
67
|
+
readonly cardUrl?: string;
|
|
68
|
+
};
|
|
69
|
+
/** Why the seat refused a question. Closed: a new word is a new arm of `answer`'s switch. */
|
|
70
|
+
export type PeerSeatRefusalReason = "handle_not_held" | "binding_mismatch" | "kind_env" | "kind_vault" | "kind_assertion" | "kind_unknown";
|
|
71
|
+
/**
|
|
72
|
+
* The desk's own failures, each loud:
|
|
73
|
+
* · `peer_lease_released` — a released lease was used to declare or to extend (a scope released before its last use);
|
|
74
|
+
* · `peer_lease_foreign` — a lease another desk opened was used here (its release would never reach this desk's entries);
|
|
75
|
+
* · `peer_lease_scope_unknown` — a lease was asked for with a scope word outside {@link PEER_LEASE_SCOPES};
|
|
76
|
+
* · `peer_binding_conflict` — two different contents were minted one id (only a digest collision does that); the entry stands;
|
|
77
|
+
* · `peer_seat_refused` — the seat refused a question (the engine reads any throw as the seat not furnishing that send).
|
|
78
|
+
* No message names a value or an id.
|
|
79
|
+
*/
|
|
80
|
+
export type PeerDeskErrorCode = "peer_lease_released" | "peer_lease_foreign" | "peer_lease_scope_unknown" | "peer_binding_conflict" | "peer_seat_refused";
|
|
81
|
+
export declare class PeerDeskError extends Error {
|
|
82
|
+
readonly code: PeerDeskErrorCode;
|
|
83
|
+
constructor(code: PeerDeskErrorCode, message: string);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Which host scope a lease is opened for: the request funnel of a fresh submission, the funnel of a resumed leg, a probe walk, the
|
|
87
|
+
* roster (held for the process's life). A closed set, so the desk's own error text can name a scope without echoing caller text.
|
|
88
|
+
*/
|
|
89
|
+
export declare const PEER_LEASE_SCOPES: readonly ["fresh", "resume", "probe", "roster"];
|
|
90
|
+
export type PeerLeaseScope = (typeof PEER_LEASE_SCOPES)[number];
|
|
91
|
+
/** A host holder. `release` is idempotent; `extend` opens a further lease that holds every entry this one holds now. */
|
|
92
|
+
export interface PeerLease {
|
|
93
|
+
/** A further lease holding what this one holds at this moment; this one released ⇒ throws `peer_lease_released`. */
|
|
94
|
+
extend(): PeerLease;
|
|
95
|
+
/** Stop holding. Entries no other holder holds are dropped now. A second call does nothing. */
|
|
96
|
+
release(): void;
|
|
97
|
+
}
|
|
98
|
+
/** What the engine asks: the pieces for one send. The seat holds nothing itself — an entry's holders are host leases only. */
|
|
99
|
+
export interface PeerDeskSeat {
|
|
100
|
+
/** The pieces for one send, or a throw (refused: logged as one `peer_seat_refused` row naming protocol, peer and reason only). */
|
|
101
|
+
answer(question: PeerQuestion): Promise<PeerAnswer>;
|
|
102
|
+
}
|
|
103
|
+
/** Live entries and the host leases holding them, summed across entries — the registry's whole observable state. */
|
|
104
|
+
export interface PeerDeskSize {
|
|
105
|
+
readonly entries: number;
|
|
106
|
+
readonly holders: number;
|
|
107
|
+
}
|
|
108
|
+
export interface PeerDesk {
|
|
109
|
+
readonly seat: PeerDeskSeat;
|
|
110
|
+
/**
|
|
111
|
+
* Register a draft's pieces under `lease` and mint the reference the declaration carries to the engine. No pieces ⇒ nothing is
|
|
112
|
+
* registered and nothing is minted (`undefined`: the declaration goes without a reference). The same content (binding, lane,
|
|
113
|
+
* pieces by name) mints the same id in one process, so two legs declaring one body share one entry and each holds it.
|
|
114
|
+
*/
|
|
115
|
+
declare(draft: PeerDraft, lease: PeerLease): PeerHandle | undefined;
|
|
116
|
+
/** Open a host lease for `scope` (named in the desk's own error text; a word outside the set ⇒ throws `peer_lease_scope_unknown`). */
|
|
117
|
+
lease(scope: PeerLeaseScope): PeerLease;
|
|
118
|
+
size(): PeerDeskSize;
|
|
119
|
+
}
|
|
120
|
+
export interface PeerDeskOptions {
|
|
121
|
+
/** Where refusal rows go (default: a JSON-line logger). */
|
|
122
|
+
readonly log?: Logger;
|
|
123
|
+
}
|
|
124
|
+
/** The binding of a draft, or of the target the engine asks about; both read the same three rows. */
|
|
125
|
+
export declare function bindingOf(peer: PeerDraft | PeerSeatTarget): PeerBinding;
|
|
126
|
+
export declare function createPeerDesk(opts?: PeerDeskOptions): PeerDesk;
|
|
127
|
+
/**
|
|
128
|
+
* **The one minting point** every entrance calls — the roster (under the roster lease), the request surface (under the leg's host lease) and
|
|
129
|
+
* the probe walk: register `draft`'s pieces on `desk` under `lease` and hand back the declaration the engine gets, carrying the minted
|
|
130
|
+
* reference where the engine keeps it (an MCP server's transport arm, an A2A peer's top level) and no piece value anywhere. A draft with no
|
|
131
|
+
* pieces registers nothing and goes as drafted (no reference member). Where the reference sits is written here and nowhere else.
|
|
132
|
+
*/
|
|
133
|
+
export declare function mintDeclaration(desk: Pick<PeerDesk, "declare">, draft: McpDraft, lease: PeerLease): McpServerSpec;
|
|
134
|
+
export declare function mintDeclaration(desk: Pick<PeerDesk, "declare">, draft: A2aDraft, lease: PeerLease): A2aServerSpec;
|
|
135
|
+
/**
|
|
136
|
+
* **The one exit** for an engine judge's sentence about a peer declaration (an MCP server's or an A2A peer's `toolAxes` / `toolFaces` /
|
|
137
|
+
* `sessionMetaKey` / start bound …) on its way into this server's refusal text or an operator line: the engine words what the declaring side
|
|
138
|
+
* wrote (a tool name, a key — which a mistake can make a credential), so the sentence leaves through the exit seat's `peer-declaration-sentence`
|
|
139
|
+
* row (credential shapes become marks, the sentence stays the engine's). The request surface's judges, the replay reread lines and the roster's
|
|
140
|
+
* skip lines all use this one.
|
|
141
|
+
*/
|
|
142
|
+
export declare function peerSentence(problem: string): SafeText;
|
|
143
|
+
/**
|
|
144
|
+
* Hold `lease`'s entries for as long as `drive` runs: a further lease is opened before `drive` is called (synchronously, so before
|
|
145
|
+
* the caller's own scope can release) and released when `drive` settles — resolved, rejected or thrown synchronously. A released
|
|
146
|
+
* lease rejects with `peer_lease_released` and `drive` is not called.
|
|
147
|
+
*/
|
|
148
|
+
export declare function heldFor<T>(lease: PeerLease, drive: () => Promise<T>): Promise<T>;
|
|
149
|
+
export {};
|
|
150
|
+
//# sourceMappingURL=peer-seat.d.ts.map
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { createHmac, randomBytes } from "node:crypto";
|
|
2
|
+
import { createLogger } from "./observability/logger.js";
|
|
3
|
+
import { seatText } from "./trace/exit-seat.js";
|
|
4
|
+
export class PeerDeskError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = "PeerDeskError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export const PEER_LEASE_SCOPES = ["fresh", "resume", "probe", "roster"];
|
|
13
|
+
export function bindingOf(peer) {
|
|
14
|
+
if ("spec" in peer) {
|
|
15
|
+
const spec = peer.spec;
|
|
16
|
+
if (!("transport" in spec))
|
|
17
|
+
return a2aBinding(spec.name, spec.url, spec.cardUrl);
|
|
18
|
+
const t = spec.transport;
|
|
19
|
+
switch (t.kind) {
|
|
20
|
+
case "http":
|
|
21
|
+
return httpBinding(spec.name, t.url, spec.fingerprint);
|
|
22
|
+
case "stdio":
|
|
23
|
+
return stdioBinding(spec.name, t.command, t.args ?? [], spec.fingerprint);
|
|
24
|
+
default: {
|
|
25
|
+
const unknownArm = t;
|
|
26
|
+
throw new Error(`peer desk: an MCP draft with a transport this desk does not know (${discriminantsOf(unknownArm)})`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
switch (peer.protocol) {
|
|
31
|
+
case "mcp":
|
|
32
|
+
switch (peer.transport) {
|
|
33
|
+
case "http":
|
|
34
|
+
return httpBinding(peer.server, peer.url, peer.fingerprint);
|
|
35
|
+
case "stdio":
|
|
36
|
+
return stdioBinding(peer.server, peer.command, peer.args, peer.fingerprint);
|
|
37
|
+
default: {
|
|
38
|
+
const unknownArm = peer;
|
|
39
|
+
throw new Error(`peer desk: a question about an MCP transport this desk does not know (${discriminantsOf(unknownArm)})`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
case "a2a":
|
|
43
|
+
return a2aBinding(peer.peer, peer.url, peer.cardUrl);
|
|
44
|
+
default: {
|
|
45
|
+
const unknownArm = peer;
|
|
46
|
+
throw new Error(`peer desk: a question about a protocol this desk does not know (${discriminantsOf(unknownArm)})`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function httpBinding(server, url, fingerprint) {
|
|
51
|
+
return Object.freeze({ protocol: "mcp", transport: "http", server, url, ...(fingerprint !== undefined ? { fingerprint } : {}) });
|
|
52
|
+
}
|
|
53
|
+
function stdioBinding(server, command, args, fingerprint) {
|
|
54
|
+
return Object.freeze({ protocol: "mcp", transport: "stdio", server, command, args: Object.freeze([...args]), ...(fingerprint !== undefined ? { fingerprint } : {}) });
|
|
55
|
+
}
|
|
56
|
+
function a2aBinding(peer, url, cardUrl) {
|
|
57
|
+
return Object.freeze({ protocol: "a2a", peer, url, ...(cardUrl !== undefined ? { cardUrl } : {}) });
|
|
58
|
+
}
|
|
59
|
+
function discriminantsOf(value) {
|
|
60
|
+
if (typeof value !== "object" || value === null)
|
|
61
|
+
return typeof value;
|
|
62
|
+
const at = (key) => {
|
|
63
|
+
const v = Reflect.get(value, key);
|
|
64
|
+
return typeof v === "string" ? JSON.stringify(v.slice(0, 40)) : typeof v;
|
|
65
|
+
};
|
|
66
|
+
return `protocol ${at("protocol")}, transport ${at("transport")}, kind ${at("kind")}`;
|
|
67
|
+
}
|
|
68
|
+
function laneOf(binding) {
|
|
69
|
+
return binding.protocol === "a2a" ? "a2a" : binding.transport;
|
|
70
|
+
}
|
|
71
|
+
function rowsOf(record) {
|
|
72
|
+
return Object.entries(record).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
73
|
+
}
|
|
74
|
+
function canonicalOf(binding, lane, pieces) {
|
|
75
|
+
return JSON.stringify([rowsOf(binding), lane, rowsOf(pieces)]);
|
|
76
|
+
}
|
|
77
|
+
function sameBinding(a, b) {
|
|
78
|
+
return JSON.stringify(rowsOf(a)) === JSON.stringify(rowsOf(b));
|
|
79
|
+
}
|
|
80
|
+
const ID_KEY = randomBytes(32);
|
|
81
|
+
function mintId(canonical) {
|
|
82
|
+
return `h1.${createHmac("sha256", ID_KEY).update(canonical).digest("base64url")}`;
|
|
83
|
+
}
|
|
84
|
+
function copyPieces(given) {
|
|
85
|
+
if (given === undefined)
|
|
86
|
+
return undefined;
|
|
87
|
+
const names = Object.keys(given);
|
|
88
|
+
if (names.length === 0)
|
|
89
|
+
return undefined;
|
|
90
|
+
const copy = Object.create(null);
|
|
91
|
+
for (const name of names)
|
|
92
|
+
copy[name] = given[name];
|
|
93
|
+
return Object.freeze(copy);
|
|
94
|
+
}
|
|
95
|
+
export function createPeerDesk(opts = {}) {
|
|
96
|
+
const log = opts.log ?? createLogger();
|
|
97
|
+
const table = new Map();
|
|
98
|
+
const leases = new WeakMap();
|
|
99
|
+
let lastLeaseId = 0;
|
|
100
|
+
const entryHeld = (id) => {
|
|
101
|
+
const entry = table.get(id);
|
|
102
|
+
if (entry === undefined)
|
|
103
|
+
throw new Error("peer desk: a live lease holds an entry the table no longer has");
|
|
104
|
+
return entry;
|
|
105
|
+
};
|
|
106
|
+
const dropIfUnheld = (id, entry) => {
|
|
107
|
+
if (entry.hosts.size === 0)
|
|
108
|
+
table.delete(id);
|
|
109
|
+
};
|
|
110
|
+
const liveState = (lease) => {
|
|
111
|
+
const state = leases.get(lease);
|
|
112
|
+
if (state === undefined)
|
|
113
|
+
throw new PeerDeskError("peer_lease_foreign", "peer desk: a lease another desk opened was used here");
|
|
114
|
+
const held = state.held;
|
|
115
|
+
if (held === undefined)
|
|
116
|
+
throw new PeerDeskError("peer_lease_released", `peer desk: a "${state.scope}" lease was used after it was released`);
|
|
117
|
+
return { id: state.id, scope: state.scope, held };
|
|
118
|
+
};
|
|
119
|
+
const openLease = (scope, holds) => {
|
|
120
|
+
const state = { id: ++lastLeaseId, scope, held: new Set(holds) };
|
|
121
|
+
for (const id of holds)
|
|
122
|
+
entryHeld(id).hosts.add(state.id);
|
|
123
|
+
const lease = Object.freeze({
|
|
124
|
+
extend: () => openLease(state.scope, liveState(lease).held),
|
|
125
|
+
release: () => {
|
|
126
|
+
const held = state.held;
|
|
127
|
+
if (held === undefined)
|
|
128
|
+
return;
|
|
129
|
+
state.held = undefined;
|
|
130
|
+
for (const id of held) {
|
|
131
|
+
const entry = entryHeld(id);
|
|
132
|
+
entry.hosts.delete(state.id);
|
|
133
|
+
dropIfUnheld(id, entry);
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
leases.set(lease, state);
|
|
138
|
+
return lease;
|
|
139
|
+
};
|
|
140
|
+
const refuse = (target, reason) => {
|
|
141
|
+
const peer = target.protocol === "a2a" ? target.peer : target.server;
|
|
142
|
+
log.warn("peer_seat_refused", { protocol: target.protocol, peer, reason });
|
|
143
|
+
throw new PeerDeskError("peer_seat_refused", `peer desk: the seat refused this send (${reason})`);
|
|
144
|
+
};
|
|
145
|
+
const carry = (entry) => {
|
|
146
|
+
switch (entry.lane) {
|
|
147
|
+
case "http":
|
|
148
|
+
case "a2a":
|
|
149
|
+
return { kind: "carry", headers: entry.pieces };
|
|
150
|
+
case "stdio":
|
|
151
|
+
return { kind: "carry", env: entry.pieces };
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const seat = Object.freeze({
|
|
155
|
+
answer: async (question) => {
|
|
156
|
+
const target = question.peer;
|
|
157
|
+
const ref = target.credentials;
|
|
158
|
+
if (ref === undefined)
|
|
159
|
+
return { kind: "carry" };
|
|
160
|
+
switch (ref.kind) {
|
|
161
|
+
case "handle": {
|
|
162
|
+
const entry = table.get(ref.id);
|
|
163
|
+
if (entry === undefined)
|
|
164
|
+
return refuse(target, "handle_not_held");
|
|
165
|
+
if (!sameBinding(entry.binding, bindingOf(target)))
|
|
166
|
+
return refuse(target, "binding_mismatch");
|
|
167
|
+
return carry(entry);
|
|
168
|
+
}
|
|
169
|
+
case "env":
|
|
170
|
+
return refuse(target, "kind_env");
|
|
171
|
+
case "vault":
|
|
172
|
+
return refuse(target, "kind_vault");
|
|
173
|
+
case "assertion":
|
|
174
|
+
return refuse(target, "kind_assertion");
|
|
175
|
+
default: {
|
|
176
|
+
const unknownKind = ref;
|
|
177
|
+
void unknownKind;
|
|
178
|
+
return refuse(target, "kind_unknown");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
return Object.freeze({
|
|
184
|
+
seat,
|
|
185
|
+
declare: (draft, lease) => {
|
|
186
|
+
const pieces = copyPieces(draft.pieces);
|
|
187
|
+
const binding = pieces === undefined ? undefined : bindingOf(draft);
|
|
188
|
+
const holder = liveState(lease);
|
|
189
|
+
if (pieces === undefined || binding === undefined)
|
|
190
|
+
return undefined;
|
|
191
|
+
const lane = laneOf(binding);
|
|
192
|
+
const canonical = canonicalOf(binding, lane, pieces);
|
|
193
|
+
const id = mintId(canonical);
|
|
194
|
+
const known = table.get(id);
|
|
195
|
+
if (known === undefined) {
|
|
196
|
+
table.set(id, { binding, lane, pieces, hosts: new Set([holder.id]) });
|
|
197
|
+
}
|
|
198
|
+
else if (canonicalOf(known.binding, known.lane, known.pieces) !== canonical) {
|
|
199
|
+
throw new PeerDeskError("peer_binding_conflict", "peer desk: two different declarations were minted one id; the registered one stands");
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
known.hosts.add(holder.id);
|
|
203
|
+
}
|
|
204
|
+
holder.held.add(id);
|
|
205
|
+
return Object.freeze({ kind: "handle", id });
|
|
206
|
+
},
|
|
207
|
+
lease: (scope) => {
|
|
208
|
+
if (!PEER_LEASE_SCOPES.includes(scope))
|
|
209
|
+
throw new PeerDeskError("peer_lease_scope_unknown", "peer desk: a lease was asked for with a scope outside the closed set");
|
|
210
|
+
return openLease(scope, new Set());
|
|
211
|
+
},
|
|
212
|
+
size: () => {
|
|
213
|
+
let holders = 0;
|
|
214
|
+
for (const entry of table.values())
|
|
215
|
+
holders += entry.hosts.size;
|
|
216
|
+
return { entries: table.size, holders };
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
export function mintDeclaration(desk, draft, lease) {
|
|
221
|
+
const credentials = desk.declare(draft, lease);
|
|
222
|
+
const spec = draft.spec;
|
|
223
|
+
if ("transport" in spec)
|
|
224
|
+
return credentials === undefined ? spec : { ...spec, transport: { ...spec.transport, credentials } };
|
|
225
|
+
return credentials === undefined ? spec : { ...spec, credentials };
|
|
226
|
+
}
|
|
227
|
+
export function peerSentence(problem) {
|
|
228
|
+
return seatText("peer-declaration-sentence", problem);
|
|
229
|
+
}
|
|
230
|
+
export async function heldFor(lease, drive) {
|
|
231
|
+
const own = lease.extend();
|
|
232
|
+
try {
|
|
233
|
+
return await drive();
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
own.release();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=peer-seat.js.map
|
|
@@ -40,7 +40,7 @@ export declare function buildRuleListJudgeOptions(pathBases: PathRuleBases | und
|
|
|
40
40
|
* `namespacedCoveringEntries/Hit`。同族分家,差的只是解析器。
|
|
41
41
|
*
|
|
42
42
|
* ⇒ **规则表的 policy 面现在同源**:两种态度都由 `createPermissionRulePolicy` 的 covering 车道解,底下是
|
|
43
|
-
* core 的**同一只** `
|
|
43
|
+
* core 的**同一只** `ruleNameCovers`(协议无关:`mcp__`/`a2a__` 同解;peer 段精确比;工具段 `*`/glob 覆盖)。
|
|
44
44
|
* 本仓**不自写第二套 glob** —— 那正是当年分家的成因。
|
|
45
45
|
*
|
|
46
46
|
* ⚠️ 「同源」的**射程**说准了免得下一个人当成全仓结论(codex 交叉轮 finding-1 追出来的边):
|
|
@@ -51,7 +51,7 @@ export declare function buildRuleListJudgeOptions(pathBases: PathRuleBases | und
|
|
|
51
51
|
* 所以各表收 `mcp__figma__*`(等价、且被推荐)、拒 `mcp__figma`。
|
|
52
52
|
* · **同源的射程已扩到 operator 名单腿**([ref] 孪生修,[ref]):`createDurableAskPolicy`(`src/approval.ts`)的
|
|
53
53
|
* deny/neverAuto/require 三集合过去是逐字 `Set.has`,自 core 5.60.1 谓词根导出后改成「精确 ∪ covering」,用的是
|
|
54
|
-
* **同一只** `
|
|
54
|
+
* **同一只** `ruleNameCovers`。⇒ env `APPROVAL_*` 腿 / 中心下发 `runtime.approvalRequire` 腿 / 规则表三家
|
|
55
55
|
* 现在对 covering 拼法的解读逐字一致,`mcp__figma__*` 在哪条腿上都是真判。
|
|
56
56
|
* · **两段形为什么仍不放开**(理由已换,别再引「approval.ts 还不认」):它一名两义——「整台服务器」与「三段名写漏一段」
|
|
57
57
|
* 在 boot 期不可分辨,所以各条腿一律响亮拒 + 指引给无歧义的 `mcp__figma__*`。放宽是各腿同时变宽的 behavior-facing
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { RuleBehavior } from "@sema-agent/core";
|
|
2
2
|
/**
|
|
3
|
-
* `
|
|
4
|
-
* (`
|
|
5
|
-
*
|
|
6
|
-
* `pathTarget` 的工具,本仓会静默拒收一整类合法行(正是本文件顶注说的那个病)。
|
|
3
|
+
* 一张审批卡的**一键整工具同意**能落在这只工具名上吗 —— 仅当没有任何规则词替这个名字说话:它自己没有规则文法(`ruleToolGrammarOf` 答 undefined),
|
|
4
|
+
* 也不在 shell 面上(`COARSE_SHELL_TOOLS`:`Bash` 与 `Monitor`,它们的命令按 `Bash(…)` 规则判)。引擎为卡铸整工具候选恰在这个条件下(没有规则文法、
|
|
5
|
+
* 也不回答任何规则词的调用);一张 shell 卡上的一键整工具同意 = 对每一个程序的常驻许可,路径 / 名字 / 主机工具的卡各有自己的那一形。
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
* (
|
|
7
|
+
* 读者:卡素材铸点的派生事实一致性(`tool-approval.ts`:整工具候选的工具过不了 ⇒ 丢整只候选,卡上不渲一格按不动的「不再询问」)。
|
|
8
|
+
* 卡编辑面不读它:引擎的编辑门对任何整工具规则一律拒(编辑只改命令规则),编辑面的形闭集(`rules-consent.ts`)跟着恒拒 —— 那张闭集里「shell 卡上一键整只 shell」
|
|
9
|
+
* 的理由就是本谓词的理由,不另写一份判据。方向 fail-closed:答不出 ⇒ 不是。
|
|
10
10
|
*/
|
|
11
|
-
export declare function
|
|
11
|
+
export declare function cardTakesWholeToolConsent(tool: string): boolean;
|
|
12
12
|
/**
|
|
13
13
|
* [ref] —— `RuleBehavior` 每一员在**无同意仪式的直接写口**(`POST /v1/rules`)上的逐员表态。
|
|
14
14
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ruleToolGrammarOf } from "@sema-agent/core";
|
|
2
|
-
export function
|
|
3
|
-
return ruleToolGrammarOf(
|
|
1
|
+
import { COARSE_SHELL_TOOLS, ruleToolGrammarOf } from "@sema-agent/core";
|
|
2
|
+
export function cardTakesWholeToolConsent(tool) {
|
|
3
|
+
return ruleToolGrammarOf(tool) === undefined && !COARSE_SHELL_TOOLS.includes(tool);
|
|
4
4
|
}
|
|
5
5
|
export const DIRECT_RULE_WRITE_ADMITS = {
|
|
6
6
|
allow: false,
|
|
@@ -22,4 +22,42 @@ export type PlainStringRecordRead = {
|
|
|
22
22
|
* 数据属性取值,访问器调一次。先判每个成员的值(`__proto__` 名的成员也判:它的值不是串 ⇒ 整只不交,与 7.131.x 同),再剔 `__proto__` 名。调用方的对象不被交出去、也不被改。
|
|
23
23
|
*/
|
|
24
24
|
export declare function readPlainStringRecord(v: unknown): PlainStringRecordRead;
|
|
25
|
+
/**
|
|
26
|
+
* 请求面一份声明里每个键的**去处**(闭集;{@link splitDeclaration} 对它穷尽 `switch`):
|
|
27
|
+
* · `engine` —— 进交给引擎的声明;
|
|
28
|
+
* · `seat` —— 进草稿的件:由部署的对端座在发送时按原字节作答,交给引擎的声明里不带(只有值是「名 → 串」记录的键能标它);
|
|
29
|
+
* · `withheld` —— 承载、不发送:声明与件两边都不进(由声明这一格的属主另说一行)。
|
|
30
|
+
*/
|
|
31
|
+
export type DeclarationKeyDisposition = "engine" | "seat" | "withheld";
|
|
32
|
+
/**
|
|
33
|
+
* 请求形 `R` 的**处置表**:`R` 的每个键(可选键也算)恰一格 —— 写成 `as const satisfies DeclarationDispositionTable<R>` 的对象字面量,于是请求形多一个键而表没写 ⇒ 缺属性编译红,
|
|
34
|
+
* 表里写一个请求形上没有的词 ⇒ 多余属性编译红(三道编译格的第一道,两向)。只有值是「名 → 串」记录的键可以标 `seat`(别的键标它 ⇒ 编译红)。
|
|
35
|
+
* 表的书写序就是键闭集的序({@link declarationKeys}):受理门按它判序。
|
|
36
|
+
*/
|
|
37
|
+
export type DeclarationDispositionTable<R> = {
|
|
38
|
+
readonly [K in keyof Required<R>]: NonNullable<R[K]> extends Readonly<Record<string, string>> ? DeclarationKeyDisposition : Exclude<DeclarationKeyDisposition, "seat">;
|
|
39
|
+
};
|
|
40
|
+
/** 处置表里标 `engine` 的键。 */
|
|
41
|
+
export type EngineKeysOf<T> = {
|
|
42
|
+
[K in keyof T]: T[K] extends "engine" ? K : never;
|
|
43
|
+
}[keyof T];
|
|
44
|
+
/**
|
|
45
|
+
* 三道编译格的后两道(引擎一侧)的**缺口**,一个型名、两向;用处写成 `AssertAllKeysHandled<EngineSideGap<…>>`(缺口非空 ⇒ 编译红,点名那个键):
|
|
46
|
+
* ② 引擎形上没标 `engine` 的键恰是 `Unbound`(引擎形长出一个键而表没跟 ⇒ 缺口里有它);
|
|
47
|
+
* ③ 标 `engine` 的键与 `Unbound` 都仍在引擎形上(引擎退役一个键、或退役了 `Unbound` 那一位 ⇒ 缺口里有它)。`Unbound` = 引擎形上请求面不写、由宿主铸的位(凭据引用)。
|
|
48
|
+
* 只证键集,不证值型与运行期转换完备(值型由 {@link splitDeclaration} 的返回型交给声明型去判)。
|
|
49
|
+
*/
|
|
50
|
+
export type EngineSideGap<T, EngineArm, Unbound extends PropertyKey> = Exclude<keyof EngineArm, EngineKeysOf<T> | Unbound> | Exclude<EngineKeysOf<T> | Unbound, keyof EngineArm>;
|
|
51
|
+
/** 一张处置表的键,按表的书写序(冻结;受理门的键闭集与判序读它,不另写第二份清单)。 */
|
|
52
|
+
export declare function declarationKeys<T extends object>(table: T): ReadonlyArray<keyof T & string>;
|
|
53
|
+
/**
|
|
54
|
+
* 按处置表拆一份请求面声明:标 `engine` 的成员进 `engine`(交给引擎的声明那一部分),标 `seat` 的那一张记录进 `pieces`(空记录 = 没有件,`pieces` 缺席),
|
|
55
|
+
* 标 `withheld` 的成员两边都不进;值为 `undefined` 的成员按缺席读。
|
|
56
|
+
* 请求面声明的唯一拆法 —— MCP 两臂(`toMcpDraft`)与 A2A 对端(`toA2aDraft`)同调这一只,所以「哪个键去哪」只有表一处说,代码里没有第二份键名。
|
|
57
|
+
* 运行期漏臂(响亮):成员不在表里(表的型挡住了写得出的形,只有绕过型的调用方走得到)/ 一份声明有两张标 `seat` 的记录 ⇒ 抛;两句都不带值。
|
|
58
|
+
*/
|
|
59
|
+
export declare function splitDeclaration<R extends object, T extends DeclarationDispositionTable<R>>(declaration: R, table: T): {
|
|
60
|
+
readonly engine: Pick<R, EngineKeysOf<T> & keyof R>;
|
|
61
|
+
readonly pieces?: Readonly<Record<string, string>>;
|
|
62
|
+
};
|
|
25
63
|
//# sourceMappingURL=plain-string-record.d.ts.map
|