@sema-agent/server 7.101.1 → 7.102.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 +23 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +58 -4
- package/dist/approval-card.d.ts +35 -2
- package/dist/approval-card.js +19 -1
- package/dist/background-session-reap.d.ts +55 -0
- package/dist/background-session-reap.js +90 -0
- package/dist/boot/config-center.d.ts +16 -4
- package/dist/boot/config-center.js +44 -15
- package/dist/boot/exit-boundary.js +17 -0
- package/dist/boot/runner-deps.js +1 -0
- package/dist/boot/runtime-caps.js +3 -2
- package/dist/boot/session-faces.js +3 -4
- package/dist/boot/shutdown.d.ts +2 -1
- package/dist/boot/shutdown.js +33 -12
- package/dist/boot/stage-07-capability-layer.d.ts +1 -1
- package/dist/boot/stage-07-capability-layer.js +6 -4
- package/dist/boot/stage-08-reapers.d.ts +1 -1
- package/dist/boot/stage-09-leader.d.ts +1 -1
- package/dist/boot/stage-10-http-server.d.ts +1 -1
- package/dist/boot/stage-10-http-server.js +7 -3
- package/dist/capabilities/builtin-tools.d.ts +14 -1
- package/dist/capabilities/builtin-tools.js +3 -0
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capabilities/scenarios.js +3 -3
- package/dist/center-credential-renewer.d.ts +76 -0
- package/dist/center-credential-renewer.js +359 -0
- package/dist/center-credential.d.ts +76 -22
- package/dist/center-credential.js +119 -43
- package/dist/config-catalog.js +9 -4
- package/dist/config-center/apply-effective.js +5 -12
- package/dist/config-center/center-request.d.ts +13 -0
- package/dist/config-center/center-request.js +23 -3
- package/dist/config-center/facade.d.ts +1 -1
- package/dist/config-center/http-client.js +2 -5
- package/dist/config-center/read-warnings.d.ts +19 -48
- package/dist/config-center/read-warnings.js +7 -49
- package/dist/config-center/restart-signal.d.ts +8 -11
- package/dist/config-center/types.d.ts +4 -2
- package/dist/config-provider.js +1 -48
- package/dist/config-types.d.ts +77 -5
- package/dist/config-types.js +5 -0
- package/dist/config.js +49 -13
- package/dist/device-store.d.ts +1 -1
- package/dist/fleet-client.d.ts +21 -7
- package/dist/fleet-client.js +41 -12
- package/dist/fleet-lease.d.ts +1 -1
- package/dist/http/approval-sweeps.js +5 -20
- package/dist/http/dispatch.js +20 -14
- package/dist/http/readiness.d.ts +87 -0
- package/dist/http/readiness.js +54 -0
- package/dist/http/resume-legs.js +5 -4
- package/dist/http/route-table.d.ts +7 -0
- package/dist/http/route-table.js +6 -0
- package/dist/http/routes/a2a-serve.js +6 -14
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/http/routes/background.d.ts +80 -0
- package/dist/http/routes/background.js +94 -0
- package/dist/http/routes/capabilities.js +2 -0
- package/dist/http/server.d.ts +21 -8
- package/dist/http/server.js +2 -2
- package/dist/model-entry-refusal.d.ts +11 -10
- package/dist/model-entry-refusal.js +13 -10
- package/dist/observability/fail-open.d.ts +2 -2
- package/dist/observability/fail-open.js +2 -2
- package/dist/parked-decide.d.ts +11 -5
- package/dist/parked-decide.js +4 -1
- package/dist/parked-revive-surface.d.ts +47 -0
- package/dist/parked-revive-surface.js +20 -0
- package/dist/plugins/background-shell-support.d.ts +20 -1
- package/dist/plugins/background-shell-support.js +26 -0
- package/dist/plugins/host-platform.d.ts +11 -1
- package/dist/plugins/host-platform.js +11 -0
- package/dist/plugins/remote-env-host.d.ts +14 -1
- package/dist/plugins/remote-env-host.js +18 -5
- package/dist/tool-approval.d.ts +30 -1
- package/dist/tool-approval.js +31 -8
- package/dist/trace/core-keyset-guard.d.ts +3 -3
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/package.json +3 -3
|
@@ -7,7 +7,7 @@ import { openSync, closeSync, readSync, statSync, truncateSync, unlinkSync, mkdi
|
|
|
7
7
|
import { StringDecoder } from "node:string_decoder";
|
|
8
8
|
import { armPipeDestroyGrace, numEnvOr, SYMLINK_CHAIN_MAX_HOPS } from "./remote-shell.js";
|
|
9
9
|
import { hostBackgroundShellEnabled, hostExecSpoolEnabled } from "../config.js";
|
|
10
|
-
import { resolveHostShell, hostShell, spawnGroupOptions, killTreeHard, killTreeSoft, collapseWin32EnvKeys, adoptHostForegroundChild } from "./host-platform.js";
|
|
10
|
+
import { resolveHostShell, hostShell, spawnGroupOptions, killTreeHard, killTreeSoft, hardKillGroupNow, collapseWin32EnvKeys, adoptHostForegroundChild } from "./host-platform.js";
|
|
11
11
|
import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
|
|
12
12
|
import { FileError, ExecutionError, RemoteExecutionError, RollingTailBuffer, markTruncated, SchedulerError, BackgroundShellError, } from "@sema-agent/core";
|
|
13
13
|
import { recordSecretEnvScrub } from "../observability/secret-env-scrub.js";
|
|
@@ -21,7 +21,7 @@ const HOST_BG_DEFAULT_TIMEOUT_SEC = numEnvOr("HOST_BG_DEFAULT_TIMEOUT_SEC", 300,
|
|
|
21
21
|
const HOST_BG_MAX_TIMEOUT_SEC = numEnvOr("HOST_BG_MAX_TIMEOUT_SEC", 1800, HOST_BG_DEFAULT_TIMEOUT_SEC);
|
|
22
22
|
const HOST_BG_READ_CAP = numEnvOr("HOST_BG_READ_CAP", 1024 * 1024, 64 * 1024);
|
|
23
23
|
const HOST_BG_FILE_CAP = numEnvOr("HOST_BG_FILE_CAP", 64 * 1024 * 1024, HOST_BG_READ_CAP);
|
|
24
|
-
const HOST_BG_KILL_GRACE_MS = numEnvOr("HOST_BG_KILL_GRACE_MS", 1000, 0);
|
|
24
|
+
export const HOST_BG_KILL_GRACE_MS = numEnvOr("HOST_BG_KILL_GRACE_MS", 1000, 0);
|
|
25
25
|
const HOST_BG_MEM_CAP = numEnvOr("HOST_BG_MEM_CAP", 8 * 1024 * 1024, 64 * 1024);
|
|
26
26
|
const ok = (value) => ({ ok: true, value });
|
|
27
27
|
const UNC_SPELLING = /^[\\/]{2}[^\\/]+[\\/]+[^\\/]/;
|
|
@@ -253,7 +253,7 @@ export class RemoteHostExecutionEnv {
|
|
|
253
253
|
await fs.rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
254
254
|
throw new BackgroundShellError("spawn_failed", "host background spawn produced no pid");
|
|
255
255
|
}
|
|
256
|
-
return { pid, dir, outPath, errPath, stdoutCursor: 0, stderrCursor: 0 };
|
|
256
|
+
return { pid, child, dir, outPath, errPath, stdoutCursor: 0, stderrCursor: 0 };
|
|
257
257
|
});
|
|
258
258
|
},
|
|
259
259
|
async read(state) {
|
|
@@ -334,6 +334,16 @@ export class RemoteHostExecutionEnv {
|
|
|
334
334
|
if (state.dir)
|
|
335
335
|
await fs.rm(state.dir, { recursive: true, force: true }).catch(() => { });
|
|
336
336
|
},
|
|
337
|
+
killNow(state) {
|
|
338
|
+
if (state.hardKilled === true)
|
|
339
|
+
return "already_gone";
|
|
340
|
+
if (state.child.exitCode !== null || state.child.signalCode !== null)
|
|
341
|
+
return "already_gone";
|
|
342
|
+
const outcome = hardKillGroupNow(state.pid);
|
|
343
|
+
if (outcome === "killed")
|
|
344
|
+
state.hardKilled = true;
|
|
345
|
+
return outcome;
|
|
346
|
+
},
|
|
337
347
|
};
|
|
338
348
|
}
|
|
339
349
|
spawnBackground(command, options) {
|
|
@@ -362,7 +372,7 @@ export class RemoteHostExecutionEnv {
|
|
|
362
372
|
child.off("close", onClose);
|
|
363
373
|
child.off("error", onErrEvt);
|
|
364
374
|
};
|
|
365
|
-
return { pid, stdoutCursor: 0, stderrCursor: 0, mem, detachCleanup };
|
|
375
|
+
return { pid, child, stdoutCursor: 0, stderrCursor: 0, mem, detachCleanup };
|
|
366
376
|
});
|
|
367
377
|
return adopted?.shellId;
|
|
368
378
|
}
|
|
@@ -372,6 +382,9 @@ export class RemoteHostExecutionEnv {
|
|
|
372
382
|
killBackground(shellId) {
|
|
373
383
|
return this.bgManager.kill(shellId);
|
|
374
384
|
}
|
|
385
|
+
killBackgroundNow(shellId) {
|
|
386
|
+
return this._bgManager?.killNow(shellId) ?? "already_gone";
|
|
387
|
+
}
|
|
375
388
|
disposeBackgroundShells(opts) {
|
|
376
389
|
return this._bgManager ? this._bgManager.dispose(opts) : Promise.resolve();
|
|
377
390
|
}
|
|
@@ -782,7 +795,7 @@ export class RemoteHostExecutionEnv {
|
|
|
782
795
|
child.off("close", onClose);
|
|
783
796
|
child.off("error", onErrEvt);
|
|
784
797
|
};
|
|
785
|
-
return { pid, dir: spool.dir, outPath: spool.outPath, errPath: spool.errPath, stdoutCursor: cursors.out, stderrCursor: cursors.err, detachCleanup, ...(fgRemovedBytes > 0 ? { preSpoolBase: fgRemovedBytes, pendingDropped: fgRemovedBytes } : {}) };
|
|
798
|
+
return { pid, child, dir: spool.dir, outPath: spool.outPath, errPath: spool.errPath, stdoutCursor: cursors.out, stderrCursor: cursors.err, detachCleanup, ...(fgRemovedBytes > 0 ? { preSpoolBase: fgRemovedBytes, pendingDropped: fgRemovedBytes } : {}) };
|
|
786
799
|
});
|
|
787
800
|
return adopted?.shellId;
|
|
788
801
|
}
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HostDecidedBy, HostSettledBy } from "./host-decision.js";
|
|
2
2
|
import type { IncomingMessage } from "node:http";
|
|
3
|
-
import { type AskRequest, type AskOutcome, type CheckpointGate, type PendingAction, type ReadRootGrantCandidate, type RuleOffer } from "@sema-agent/core";
|
|
3
|
+
import { type AskRequest, type AskOutcome, type CheckpointGate, type PendingAction, type PersistedRuleMandate, type ReadRootGrantCandidate, type RuleOffer } from "@sema-agent/core";
|
|
4
4
|
import { type CardRulePersisted, type RuleConsentLane } from "./rules-consent.js";
|
|
5
5
|
import { type DenialLimitFallback, type RuleOffersAbsence, type ApprovalRequestFrame, type ApprovalRevokeFrame, type RuleEvidenceProjection } from "./approval-card.js";
|
|
6
6
|
import { type ApprovalAskStore, type AskRow } from "./plugins/approval-ask-store-sql.js";
|
|
@@ -244,6 +244,16 @@ export interface ToolApprovalFrame {
|
|
|
244
244
|
* 席照常答得了」说的是**这一次调用的应答者**,本仓不改它。
|
|
245
245
|
*/
|
|
246
246
|
mandated?: true;
|
|
247
|
+
/**
|
|
248
|
+
* **S-721(core 7.32.0 #1092;ADDITIVE,`"tool_approval"` only)** —— 强制位**站在哪一个词上**:core `PERSISTED_RULE_MANDATES`
|
|
249
|
+
* 六词之一(`operator_always` / `tool_marks` / `probe_mandate` / `probe_unanswered` / `write_protection` /
|
|
250
|
+
* `write_protection_unresolved`,词义属主在 core)。只在 `mandated: true` 的**整因**是那一个词时在场;另一扇门(治理 / hook /
|
|
251
|
+
* 祖先)或不止一个理由 ⇒ 缺席 —— **缺席从不命名一个词**,也不等于「不强制」(读 `mandated`)。值经 `approval-card.ts` 的
|
|
252
|
+
* `readClosedWordSeat(req, "mandate")` 窄读(闭集筛 = core `isPersistedRuleMandate`,本仓不抄词表;集外词读作缺席 + 计一次)。echo-only:
|
|
253
|
+
* 本仓不按词分支。耐久面同一个词:运维队列行(`parkRowFacts`,park 行孪生座)与 inbox 行(core `CheckpointSummary.mandate`)。
|
|
254
|
+
* 与 `mandated` 同一条:`card_json` 卡面不带本键(`ApprovalCardSchema` `.strict()`)。
|
|
255
|
+
*/
|
|
256
|
+
mandate?: PersistedRuleMandate;
|
|
247
257
|
/**
|
|
248
258
|
* **S-125③/[ref]**(core 7.5.0;**ADDITIVE**,`"tool_approval"` only)—— 这只 ask 的**出身**:
|
|
249
259
|
* 谁提的这一问,一个 core 闭集词(`ASK_ORIGINS` **11 员**,core 7.14.0 现值:content_question /
|
|
@@ -263,6 +273,12 @@ export interface ToolApprovalFrame {
|
|
|
263
273
|
* 🔴 **不枚举词表**(`string` 而不是本仓自铸的联合):词表单一属主在 core,在这里抄一份会把 core 加的
|
|
264
274
|
* 新词吞成缺席 —— 与 `wiring_manifest.autoMode.reason` 的透传纪律逐字同规。缺席 = 老引擎 / 非 ask 路径。
|
|
265
275
|
*
|
|
276
|
+
* 同一个词的其余四面(S-755 起全部在场):`livePending` 行顶层 / `live_pending` 帧(条目铸定时与本键**同一次**读定);运维队列行
|
|
277
|
+
* (`GET /v1/approvals` 的 `pending[]` + `/stream` 的 `pending` 帧,读 park 行孪生座 `PendingAction.tool_approval.origin`,经
|
|
278
|
+
* `approval-card.ts` 的 `readClosedWordSeat(…, "origin")` —— 那一面读的是持久层原字节、中间没有 core 投影,所以亲手调 core 的
|
|
279
|
+
* `isAskOrigin`);inbox 行(core `CheckpointSummary.origin`,core 投影处已筛)。本键读的是引擎在门上刚盖好的值(盖章点即 core
|
|
280
|
+
* 的筛点),所以只过「非空串」形门、不再筛第二遍 —— 对 core 盖得出的每一个词,五面逐字同值。
|
|
281
|
+
*
|
|
266
282
|
* ⚠️ 与 {@link governanceForced} **不是**一回事,别互相顶替:本键答「哪一类权威提的问」,那一键答
|
|
267
283
|
* 「**本部署运维治理层**是不是这只 ask 的门」。`origin: "policy"` 覆盖的是「任何部署 ToolPolicy 的
|
|
268
284
|
* ask」,治理策略产的与普通策略产的在这一个词上同形 ⇒ 顶替不了(逐字论证见 `governance-ask-marks.ts` 顶注)。
|
|
@@ -530,6 +546,13 @@ export interface LivePendingRow {
|
|
|
530
546
|
* 值 = 条目上那一位(`askBroadcast` 铸条目时取自这一次过闸的 `req.mandated`,与 {@link frame} 上的同名键同源)。此前只在
|
|
531
547
|
* `frame` 里:同一个 `GET /v1/approvals` 响应的 durable `pending[]` 行顶层带它(S-663 二段),live 行却要进帧里读。 */
|
|
532
548
|
mandated?: true;
|
|
549
|
+
/** S-721(core 7.32.0 #1092):强制位站在哪一个词上 —— 与 `mandated` 同层同形(在场逐字,缺席不编词),值 = 条目上那一个词
|
|
550
|
+
* (与 {@link frame} 上同名键同源;帧型该键顶注是全文)。 */
|
|
551
|
+
mandate?: PersistedRuleMandate;
|
|
552
|
+
/** S-755(cli L-695 / [ref]):ask 的**出身词**(谁提的这一问,core `ASK_ORIGINS` 闭集一词)—— 与 `mandate` 同层同形(在场逐字,
|
|
553
|
+
* 缺席不编词),值 = 条目上那一个词(铸条目那一次与 {@link frame} 上同名键**同一次**读定;帧型该键顶注是全文)。同一个
|
|
554
|
+
* `GET /v1/approvals` 响应里 durable `pending[]` 行的 `origin` 也在顶层(park 行孪生座),两族行同一种读法。 */
|
|
555
|
+
origin?: string;
|
|
533
556
|
/** 发起者为委派子代(originTaskId 在场)。 */
|
|
534
557
|
fromSubagent?: true;
|
|
535
558
|
/** [ref] C2(additive):委派子代的 **canonical taskId**。值域精确成文(codex R1-F3 问询后按 core dist
|
|
@@ -695,6 +718,11 @@ export declare const PERSIST_RULE_BATCH_EXCLUSIVE_ERROR = "persistRule.batchOffe
|
|
|
695
718
|
*/
|
|
696
719
|
export declare const SETTLED_BY_MUST_BE_STRING = "settledBy must be a string \u2014 the settlement source is one word from the engine's closed set, and this layer judges only the SHAPE (which word it is, the engine decides)";
|
|
697
720
|
export declare const SETTLED_BY_DENY_ONLY = "settledBy is only meaningful with decision \"deny\" \u2014 only a person approves an action that EXECUTES, so a self-reported settlement source on an allow is a contradiction the engine refuses; the refusal would arrive too late to undo this door's own side effects (a session allow-all grant, a persisted rule, a DECIDED row that replays as a plain approval), so it is refused here, before the decision";
|
|
721
|
+
/** S-720(core 7.32.0 #1093)—— `readRootGrant` 只随 `decision:"allow"`(CC 卡的第三选项「Yes, and add directory」= 放行这一次
|
|
722
|
+
* + 加目录;`allow_session` 不是那一个选项,`deny` 上它没有意义)。裁决之前响亮拒,不静默丢。 */
|
|
723
|
+
export declare const READ_ROOT_GRANT_ALLOW_ONLY = "readRootGrant is only meaningful with decision \"allow\" \u2014 it is the card's \"Yes, and add directory\" answer (allow this call and add the card's readRootCandidate.dir to this run's read directories)";
|
|
724
|
+
/** S-720:`readRootGrant` 的形 —— 卡上 `readRootCandidate.dir` 的原串(非空,≤ core `READ_ROOT_CANDIDATE_DIR_MAX`;超限整只拒,不截:截过的目录是另一个目录)。 */
|
|
725
|
+
export declare const READ_ROOT_GRANT_SHAPE = "readRootGrant must be the card's readRootCandidate.dir sent back byte for byte \u2014 a non-empty string of at most 1024 characters";
|
|
698
726
|
export declare const PERSIST_RULE_SCOPE_REFUSED = "this endpoint does not accept a rule scope \u2014 the server mints it from where the approval happened (send neither `scope` nor `persistRule.scope`)";
|
|
699
727
|
/** Validate the respond body — the closed three-choice enum (rationale in the module header). */
|
|
700
728
|
export declare function parseToolApprovalResponse(body: unknown): {
|
|
@@ -704,6 +732,7 @@ export declare function parseToolApprovalResponse(body: unknown): {
|
|
|
704
732
|
persistRule?: ParsedPersistRule;
|
|
705
733
|
note?: string;
|
|
706
734
|
settledBy?: string;
|
|
735
|
+
readRootGrant?: string;
|
|
707
736
|
} | {
|
|
708
737
|
ok: false;
|
|
709
738
|
error: string;
|
package/dist/tool-approval.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { contentKindOf } from "./approval-content-kind.js";
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
-
import { uuidv7, MAX_RULE_TEXT_CHARS, parseRuleText, pathConfinableWriteToolNames } from "@sema-agent/core";
|
|
3
|
+
import { uuidv7, MAX_RULE_TEXT_CHARS, READ_ROOT_CANDIDATE_DIR_MAX, parseRuleText, pathConfinableWriteToolNames } from "@sema-agent/core";
|
|
4
4
|
import { precheckCardRuleText } from "./rules-consent.js";
|
|
5
5
|
import { redactDeep, redactSecrets, redactSecretsWithArms } from "./trace/redact.js";
|
|
6
6
|
import { hasBidiControls } from "./text-bidi.js";
|
|
@@ -8,7 +8,7 @@ import { approvalPayloadFingerprint } from "./plugins/approval-payload-fingerpri
|
|
|
8
8
|
import { createLogger } from "./observability/logger.js";
|
|
9
9
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
10
10
|
import { deriveAskId, deriveAskScopedBatchId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
|
|
11
|
-
import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readReadRootCandidate, readRuleEvidence, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
|
|
11
|
+
import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readReadRootCandidate, readRuleEvidence, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, readClosedWordSeat, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
|
|
12
12
|
import { POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
|
|
13
13
|
import { settleByPolicy } from "./approval-policy-settlement.js";
|
|
14
14
|
import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
|
|
@@ -111,6 +111,8 @@ function respondSettledBy(parsed) {
|
|
|
111
111
|
return parsed.settledBy;
|
|
112
112
|
}
|
|
113
113
|
export const SETTLED_BY_DENY_ONLY = 'settledBy is only meaningful with decision "deny" — only a person approves an action that EXECUTES, so a self-reported settlement source on an allow is a contradiction the engine refuses; the refusal would arrive too late to undo this door\'s own side effects (a session allow-all grant, a persisted rule, a DECIDED row that replays as a plain approval), so it is refused here, before the decision';
|
|
114
|
+
export const READ_ROOT_GRANT_ALLOW_ONLY = 'readRootGrant is only meaningful with decision "allow" — it is the card\'s "Yes, and add directory" answer (allow this call and add the card\'s readRootCandidate.dir to this run\'s read directories)';
|
|
115
|
+
export const READ_ROOT_GRANT_SHAPE = `readRootGrant must be the card's readRootCandidate.dir sent back byte for byte — a non-empty string of at most ${READ_ROOT_CANDIDATE_DIR_MAX} characters`;
|
|
114
116
|
export const PERSIST_RULE_SCOPE_REFUSED = "this endpoint does not accept a rule scope — the server mints it from where the approval happened (send neither `scope` nor `persistRule.scope`)";
|
|
115
117
|
export function parseToolApprovalResponse(body) {
|
|
116
118
|
if (body === null || typeof body !== "object" || Array.isArray(body))
|
|
@@ -161,6 +163,12 @@ export function parseToolApprovalResponse(body) {
|
|
|
161
163
|
if (rawSettledBy !== undefined && typeof rawSettledBy !== "string")
|
|
162
164
|
return { ok: false, error: SETTLED_BY_MUST_BE_STRING };
|
|
163
165
|
const settledBy = rawSettledBy;
|
|
166
|
+
const rawGrant = body.readRootGrant;
|
|
167
|
+
if (rawGrant !== undefined && d !== "allow")
|
|
168
|
+
return { ok: false, error: READ_ROOT_GRANT_ALLOW_ONLY };
|
|
169
|
+
if (rawGrant !== undefined && (typeof rawGrant !== "string" || rawGrant === "" || rawGrant.length > READ_ROOT_CANDIDATE_DIR_MAX))
|
|
170
|
+
return { ok: false, error: READ_ROOT_GRANT_SHAPE };
|
|
171
|
+
const readRootGrant = rawGrant;
|
|
164
172
|
return {
|
|
165
173
|
ok: true,
|
|
166
174
|
value: d,
|
|
@@ -168,6 +176,7 @@ export function parseToolApprovalResponse(body) {
|
|
|
168
176
|
...(persistRule !== undefined ? { persistRule } : {}),
|
|
169
177
|
...(note !== undefined ? { note } : {}),
|
|
170
178
|
...(rawSettledBy !== undefined ? { settledBy } : {}),
|
|
179
|
+
...(readRootGrant !== undefined ? { readRootGrant } : {}),
|
|
171
180
|
};
|
|
172
181
|
}
|
|
173
182
|
return { ok: false, error: 'decision must be one of "allow" | "allow_session" | "deny"' };
|
|
@@ -938,6 +947,8 @@ export class ToolApprovalCoordinator {
|
|
|
938
947
|
const bounded = boundArgs(req.args);
|
|
939
948
|
let effectiveGovernanceForced = governanceForced;
|
|
940
949
|
let ruleLaneMaterial = this.buildRuleLaneMaterial(req, origin.owner, governanceForced, origin.sessionId);
|
|
950
|
+
const mandateWord = readClosedWordSeat(req, "mandate");
|
|
951
|
+
const originWord = typeof req.origin === "string" && req.origin.length > 0 ? req.origin : undefined;
|
|
941
952
|
const frame = {
|
|
942
953
|
type: "tool_approval",
|
|
943
954
|
approvalId: id,
|
|
@@ -963,7 +974,8 @@ export class ToolApprovalCoordinator {
|
|
|
963
974
|
...(governanceForced ? { governanceForced: true } : {}),
|
|
964
975
|
...(req.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
965
976
|
...(req.mandated === true ? { mandated: true } : {}),
|
|
966
|
-
...(
|
|
977
|
+
...(mandateWord !== undefined ? { mandate: mandateWord } : {}),
|
|
978
|
+
...(originWord !== undefined ? { origin: originWord } : {}),
|
|
967
979
|
...(typeof req.persistedRuleShadowed === "string" && req.persistedRuleShadowed !== ""
|
|
968
980
|
? { persistedRuleShadowed: redactSecrets(req.persistedRuleShadowed).slice(0, MAX_RULE_TEXT_CHARS) }
|
|
969
981
|
: {}),
|
|
@@ -1170,11 +1182,17 @@ export class ToolApprovalCoordinator {
|
|
|
1170
1182
|
return unattended === "unavailable" ? unattended : shapeDeny(unattended);
|
|
1171
1183
|
}
|
|
1172
1184
|
if (settled.allowed) {
|
|
1173
|
-
|
|
1185
|
+
if (settled.updatedInput === undefined && settled.readRootGrant === undefined)
|
|
1186
|
+
return true;
|
|
1187
|
+
return {
|
|
1188
|
+
allow: true,
|
|
1189
|
+
...(settled.updatedInput !== undefined ? { updatedInput: settled.updatedInput } : {}),
|
|
1190
|
+
...(settled.readRootGrant !== undefined ? { readRootGrant: settled.readRootGrant } : {}),
|
|
1191
|
+
};
|
|
1174
1192
|
}
|
|
1175
1193
|
return shapeDeny(false);
|
|
1176
1194
|
};
|
|
1177
|
-
const settle = (allowed, outcome, updatedInput, hostSettledBy, denyReason) => {
|
|
1195
|
+
const settle = (allowed, outcome, updatedInput, hostSettledBy, denyReason, readRootGrant) => {
|
|
1178
1196
|
if (done)
|
|
1179
1197
|
return;
|
|
1180
1198
|
done = true;
|
|
@@ -1208,7 +1226,7 @@ export class ToolApprovalCoordinator {
|
|
|
1208
1226
|
...(hostSettledBy !== undefined ? { settledBy: hostSettledBy } : {}),
|
|
1209
1227
|
settledAtMs: Date.now(),
|
|
1210
1228
|
});
|
|
1211
|
-
resolveAllowed({ allowed, ...(allowed && updatedInput !== undefined ? { updatedInput } : {}) });
|
|
1229
|
+
resolveAllowed({ allowed, ...(allowed && updatedInput !== undefined ? { updatedInput } : {}), ...(allowed && readRootGrant !== undefined ? { readRootGrant } : {}) });
|
|
1212
1230
|
};
|
|
1213
1231
|
const broadcastClaimTruth = (read) => {
|
|
1214
1232
|
if (askId === undefined)
|
|
@@ -1483,6 +1501,8 @@ export class ToolApprovalCoordinator {
|
|
|
1483
1501
|
...(effectiveGovernanceForced ? { governanceForced: true } : {}),
|
|
1484
1502
|
...(req.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
1485
1503
|
...(req.mandated === true ? { mandated: true } : {}),
|
|
1504
|
+
...(mandateWord !== undefined ? { mandate: mandateWord } : {}),
|
|
1505
|
+
...(originWord !== undefined ? { origin: originWord } : {}),
|
|
1486
1506
|
...(askId !== undefined && batchId !== undefined ? { askId, batchId } : {}),
|
|
1487
1507
|
};
|
|
1488
1508
|
selfEntry = pendingEntry;
|
|
@@ -2051,7 +2071,7 @@ export class ToolApprovalCoordinator {
|
|
|
2051
2071
|
approvalPayloadFingerprint({ updatedInput: ambiguousConfirmedRow.updatedInput }) === approvalPayloadFingerprint({ updatedInput: parsed.updatedInput });
|
|
2052
2072
|
const result = this.finishRespond(id, entry, parsed, {
|
|
2053
2073
|
noteRecorded: noteLanded,
|
|
2054
|
-
...(winnerRow !== undefined ? { settleOutcome: outcomeOfDecidedRow(winnerRow) } : {}),
|
|
2074
|
+
...(winnerRow !== undefined ? { settleOutcome: outcomeOfDecidedRow(winnerRow), settleOutcomeIsThisRequest: ambiguousConfirmedRow === undefined } : {}),
|
|
2055
2075
|
...(ambiguousConfirmedRow !== undefined ? { updatedInputDelivered: payloadIsMine } : {}),
|
|
2056
2076
|
});
|
|
2057
2077
|
if (winnerRow !== undefined)
|
|
@@ -2077,7 +2097,8 @@ export class ToolApprovalCoordinator {
|
|
|
2077
2097
|
}
|
|
2078
2098
|
const allowed = parsed.value !== "deny";
|
|
2079
2099
|
const settleArgs = opts?.settleOutcome !== undefined ? settleArgsOfDecided(opts.settleOutcome) : { allowed, updatedInput: parsed.updatedInput, settledBy: respondSettledBy(parsed) };
|
|
2080
|
-
|
|
2100
|
+
const readRootGrant = opts?.settleOutcome === undefined || opts.settleOutcomeIsThisRequest === true ? parsed.readRootGrant : undefined;
|
|
2101
|
+
entry.settle(settleArgs.allowed, settleArgs.allowed ? "allowed" : "denied", settleArgs.updatedInput, settleArgs.settledBy, settleArgs.allowed ? undefined : parsed.note, readRootGrant);
|
|
2081
2102
|
const updatedInputDelivered = opts?.updatedInputDelivered ?? true;
|
|
2082
2103
|
const noteRecorded = opts?.noteRecorded ?? false;
|
|
2083
2104
|
return {
|
|
@@ -2107,6 +2128,8 @@ export class ToolApprovalCoordinator {
|
|
|
2107
2128
|
...(e.requiresRealApproval ? { requiresRealApproval: true } : {}),
|
|
2108
2129
|
...(e.governanceForced ? { governanceForced: true } : {}),
|
|
2109
2130
|
...(e.mandated ? { mandated: true } : {}),
|
|
2131
|
+
...(e.mandate !== undefined ? { mandate: e.mandate } : {}),
|
|
2132
|
+
...(e.origin !== undefined ? { origin: e.origin } : {}),
|
|
2110
2133
|
...(e.originTaskId !== undefined ? { fromSubagent: true, originTaskId: e.originTaskId } : {}),
|
|
2111
2134
|
...(contentKind !== undefined ? { contentKind } : {}),
|
|
2112
2135
|
frame: e.frame.approvalId === id ? e.frame : { ...e.frame, approvalId: id },
|
|
@@ -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" | "mandated";
|
|
46
|
+
type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "ruleStoreUnreadable" | "readRootCandidate" | "mandated" | "mandate";
|
|
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>>;
|
|
@@ -130,7 +130,7 @@ type _GuardPermissionTraceReverse = AssertAllKeysHandled<Exclude<PermissionTrace
|
|
|
130
130
|
/** `T` 非 `never` 时收敛到 `never`(空集 ⇒ 残留 `"EMPTY"` ⇒ tsc 红)。 */
|
|
131
131
|
type AssertNonEmpty<T> = [T] extends [never] ? "EMPTY" : never;
|
|
132
132
|
type _GuardPermissionTraceNonEmpty = AssertAllKeysHandled<AssertNonEmpty<PermissionTraceKind>>;
|
|
133
|
-
type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "ruleStoreUnreadable" | "mandated";
|
|
133
|
+
type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "ruleStoreUnreadable" | "mandated" | "mandate";
|
|
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>>;
|
|
@@ -138,7 +138,7 @@ type ParkArmKeys = keyof Extract<PendingAction, {
|
|
|
138
138
|
kind: "tool_approval";
|
|
139
139
|
}>;
|
|
140
140
|
type ParkArmQueueProjected = "toolName" | "toolCallId" | "boundInputHash" | "ruleOffers" | "hasBidiControls" | keyof import("../approval-card.js").ParkRowFacts;
|
|
141
|
-
type ParkArmQueueExcluded = "kind" | "args" | "batchToolCallIds" | "completedCallIds" | "capabilityId" | "contractId" | "shapeDigest" | "implementationRevision" | "rosterDigest" | "family" | "pathTarget" | "preview" | "previewWithheld" | "execCwd" | "denialLimitFallback" | "
|
|
141
|
+
type ParkArmQueueExcluded = "kind" | "args" | "batchToolCallIds" | "completedCallIds" | "capabilityId" | "contractId" | "shapeDigest" | "implementationRevision" | "rosterDigest" | "family" | "pathTarget" | "preview" | "previewWithheld" | "execCwd" | "denialLimitFallback" | "ruleStoreUnreadable";
|
|
142
142
|
type _GuardParkArmQueue = AssertAllKeysHandled<Exclude<ParkArmKeys, ParkArmQueueProjected | ParkArmQueueExcluded>>;
|
|
143
143
|
type _GuardParkArmQueueReverse = AssertAllKeysHandled<Exclude<ParkArmQueueProjected | ParkArmQueueExcluded, ParkArmKeys>>;
|
|
144
144
|
type _NoDoubleBookParkArmQueue = AssertAllKeysHandled<Extract<ParkArmQueueProjected, ParkArmQueueExcluded>>;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* 同文件 G1 e2e 的全码扇出夹具、以及契约文档附录 D.3 那张表(含**小节标题里的码数** —— 那道门本批
|
|
17
17
|
* 刚立,立完就在下一次加码时自己咬住了)。
|
|
18
18
|
*/
|
|
19
|
-
export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval", "config.durable_gate_unavailable", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "mcp.injection_dropped", "delegation.ask_unresolvable", "task.interrupt_unconsumed", "instructions.source_changed"];
|
|
19
|
+
export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval", "config.durable_gate_unavailable", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "mcp.injection_dropped", "delegation.ask_unresolvable", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "instructions.source_changed"];
|
|
20
20
|
export type EngineNoticeWireCode = (typeof ENGINE_NOTICE_WIRE_CODES)[number];
|
|
21
21
|
/**
|
|
22
22
|
* S-690 —— **本仓自铸**的通告码与它们的受众(码 / 铸点 / 受众三者的属主都是本仓)。
|
|
@@ -23,6 +23,8 @@ export const ENGINE_NOTICE_WIRE_CODES = [
|
|
|
23
23
|
"mcp.injection_dropped",
|
|
24
24
|
"delegation.ask_unresolvable",
|
|
25
25
|
"task.interrupt_unconsumed",
|
|
26
|
+
"approval.read_root_granted",
|
|
27
|
+
"approval.read_root_grant_rejected",
|
|
26
28
|
"instructions.source_changed",
|
|
27
29
|
];
|
|
28
30
|
export const SERVER_NOTICE_AUDIENCE = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.102.0",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@sema-agent/core": "7.
|
|
65
|
-
"@sema-agent/settings-schema": "6.
|
|
64
|
+
"@sema-agent/core": "7.32.1",
|
|
65
|
+
"@sema-agent/settings-schema": "6.1.0",
|
|
66
66
|
"e2b": "^2.28.0",
|
|
67
67
|
"libsodium-wrappers": "^0.8.4",
|
|
68
68
|
"mysql2": "^3.22.4",
|