@sema-agent/server 7.115.0 → 7.116.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +167 -4
- package/USAGE.md +6 -3
- package/dist/approval-ask-audit-store.d.ts +25 -6
- package/dist/approval-ask-audit-store.js +6 -1
- package/dist/approval-card.d.ts +8 -4
- package/dist/approval-card.js +2 -0
- package/dist/approval-crash-convergence.d.ts +60 -0
- package/dist/approval-crash-convergence.js +75 -0
- package/dist/approval-deny-reasons.d.ts +13 -0
- package/dist/approval-deny-reasons.js +2 -0
- package/dist/approval.js +2 -1
- package/dist/boot/coordinators.js +0 -12
- package/dist/boot/execution-env.js +2 -1
- package/dist/boot/resolve-spec.js +1 -0
- package/dist/boot/stage-07-capability-layer.js +21 -15
- package/dist/boot/stores.js +4 -4
- package/dist/capabilities/hands-lane.d.ts +0 -23
- package/dist/capabilities/hands-lane.js +3 -2
- package/dist/capabilities/send-user-file-tool.d.ts +10 -22
- package/dist/capabilities/send-user-file-tool.js +1 -8
- package/dist/capability-facts.d.ts +296 -0
- package/dist/capability-facts.js +354 -0
- package/dist/config-center/apply-effective.js +1 -1
- package/dist/config.d.ts +16 -13
- package/dist/config.js +12 -12
- package/dist/http/admission.d.ts +6 -0
- package/dist/http/admission.js +22 -5
- package/dist/http/principal-gate.d.ts +3 -0
- package/dist/http/principal-gate.js +3 -0
- package/dist/http/resume-legs.js +10 -1
- package/dist/http/retired-skill-manifest.d.ts +23 -0
- package/dist/http/retired-skill-manifest.js +22 -0
- package/dist/http/route-table.d.ts +3 -2
- package/dist/http/route-table.js +5 -5
- package/dist/http/routes/a2a-serve.js +14 -21
- package/dist/http/routes/admin-config-refresh.js +7 -2
- package/dist/http/routes/admin-drain.js +7 -2
- package/dist/http/routes/admin-memory-optout.js +20 -6
- package/dist/http/routes/adoption.js +19 -5
- package/dist/http/routes/agents-roster.js +4 -2
- package/dist/http/routes/approvals-assistant.js +41 -18
- package/dist/http/routes/background.d.ts +0 -2
- package/dist/http/routes/background.js +4 -5
- package/dist/http/routes/capabilities.d.ts +2 -1
- package/dist/http/routes/capabilities.js +62 -65
- package/dist/http/routes/config-catalog.d.ts +4 -5
- package/dist/http/routes/config-catalog.js +16 -2
- package/dist/http/routes/devices.js +17 -11
- package/dist/http/routes/diagnostics.js +6 -1
- package/dist/http/routes/fleet.js +4 -2
- package/dist/http/routes/images.js +30 -3
- package/dist/http/routes/leader.js +7 -1
- package/dist/http/routes/mcp-reconnect.js +17 -10
- package/dist/http/routes/memory-bundle.js +20 -6
- package/dist/http/routes/memory-compliance.js +19 -5
- package/dist/http/routes/memory-consolidation.d.ts +9 -12
- package/dist/http/routes/memory-consolidation.js +17 -3
- package/dist/http/routes/memory-origin.js +19 -5
- package/dist/http/routes/notify-wake.js +5 -3
- package/dist/http/routes/observability.js +19 -5
- package/dist/http/routes/retention-ops.js +6 -1
- package/dist/http/routes/rules.js +49 -21
- package/dist/http/routes/run-memory-capture.js +5 -2
- package/dist/http/routes/runs.js +90 -60
- package/dist/http/routes/session-sync.js +64 -53
- package/dist/http/routes/sessions-list.js +8 -5
- package/dist/http/routes/sessions.js +39 -24
- package/dist/http/routes/shared-memory.js +5 -3
- package/dist/http/routes/tasks.js +2 -1
- package/dist/http/routes/trace-usage.js +18 -11
- package/dist/http/routes/workflows.js +36 -13
- package/dist/http/server.js +4 -1
- package/dist/http/wire-types.d.ts +6 -0
- package/dist/namespaced-rule-name.d.ts +54 -0
- package/dist/namespaced-rule-name.js +33 -0
- package/dist/observability/corrupt-read-seat.d.ts +4 -3
- package/dist/observability/corrupt-read-seat.js +4 -3
- package/dist/observability/fail-open.d.ts +5 -1
- package/dist/observability/fail-open.js +5 -1
- package/dist/observability/permission-rule-events.d.ts +3 -0
- package/dist/observability/permission-rule-events.js +4 -0
- package/dist/plugins/file-run-store.d.ts +7 -1
- package/dist/plugins/file-run-store.js +20 -7
- package/dist/plugins/memory-run-store.js +2 -1
- package/dist/plugins/one-time-migrations.d.ts +4 -2
- package/dist/plugins/one-time-migrations.js +26 -3
- package/dist/plugins/pg-session-storage.js +1 -1
- package/dist/plugins/run-store-sql.js +4 -4
- package/dist/plugins/session-policy-store-sql.js +4 -2
- package/dist/plugins/store-backend.d.ts +6 -1
- package/dist/plugins/store-backend.js +2 -1
- package/dist/plugins/store-contracts.d.ts +27 -8
- package/dist/plugins/store-contracts.js +12 -3
- package/dist/plugins/tidb-session-store.js +1 -1
- package/dist/request-key-closure.js +1 -1
- package/dist/run-local.d.ts +37 -3
- package/dist/run-local.js +48 -5
- package/dist/runs.d.ts +0 -58
- package/dist/runs.js +15 -20
- package/dist/session-policy-wire.d.ts +40 -60
- package/dist/session-policy-wire.js +60 -32
- package/dist/spec-fields.d.ts +32 -0
- package/dist/spec-fields.js +31 -16
- package/dist/standing-answer-grant.d.ts +8 -0
- package/dist/standing-answer-grant.js +5 -0
- package/dist/task-settings.d.ts +13 -3
- package/dist/tool-approval.d.ts +16 -0
- package/dist/tool-approval.js +29 -7
- package/dist/trace/core-keyset-guard.d.ts +4 -4
- package/dist/trace/self-minted-wire.d.ts +42 -0
- package/dist/trace/self-minted-wire.js +13 -0
- package/dist/user-input-ledger.d.ts +8 -5
- package/package.json +5 -3
- package/scripts/drain-check-local.mjs +115 -25
- package/scripts/drain-check.mysql.sql +67 -0
- package/scripts/drain-check.pg.sql +71 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { APPROVAL_ASK_AUDIT_DIR, APPROVAL_ASK_AUDIT_LEDGER, FileApprovalAskAuditStore } from "./approval-ask-audit-store.js";
|
|
4
|
+
import { ApprovalCardEnvelopeSchema } from "./approval-card.js";
|
|
5
|
+
import { VOID_REASONS } from "./approval-deny-reasons.js";
|
|
6
|
+
import { writerGoneEvidenceFor } from "./user-input-ledger.js";
|
|
7
|
+
import { recordFailOpen } from "./observability/fail-open.js";
|
|
8
|
+
const CONVERGE_PAGE = 500;
|
|
9
|
+
export function totalAsksVoided(report) {
|
|
10
|
+
return Object.values(report.asksVoided).reduce((sum, n) => sum + n, 0);
|
|
11
|
+
}
|
|
12
|
+
export function openAuditEvidenceForConvergence(live, localDataRoot) {
|
|
13
|
+
if (live !== undefined)
|
|
14
|
+
return { audit: live, release: () => { } };
|
|
15
|
+
if (localDataRoot === undefined || !existsSync(join(localDataRoot, APPROVAL_ASK_AUDIT_DIR, APPROVAL_ASK_AUDIT_LEDGER)))
|
|
16
|
+
return { audit: undefined, release: () => { } };
|
|
17
|
+
const opened = new FileApprovalAskAuditStore(localDataRoot);
|
|
18
|
+
return { audit: opened, release: () => opened.close() };
|
|
19
|
+
}
|
|
20
|
+
export async function convergeCrashedAsksAtBoot(input) {
|
|
21
|
+
const now = input.now ?? Date.now;
|
|
22
|
+
const audited = input.audit?.convergeCrashedAsks() ?? { pending: 0, decided: 0, inconsistent: 0, corruptLines: 0 };
|
|
23
|
+
const asksVoided = { [VOID_REASONS.HOLDER_CRASHED]: 0, [VOID_REASONS.HOLDER_SHUTDOWN]: 0 };
|
|
24
|
+
let causeUnreadable = 0;
|
|
25
|
+
const store = input.askStore;
|
|
26
|
+
if (store !== undefined) {
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
for (;;) {
|
|
29
|
+
const page = (await store.listByState("STREAM_PENDING", CONVERGE_PAGE)).filter((r) => !seen.has(r.askId));
|
|
30
|
+
if (page.length === 0)
|
|
31
|
+
break;
|
|
32
|
+
for (const row of page) {
|
|
33
|
+
seen.add(row.askId);
|
|
34
|
+
const envelope = ApprovalCardEnvelopeSchema.safeParse(row.cardJson);
|
|
35
|
+
if (!envelope.success) {
|
|
36
|
+
causeUnreadable += 1;
|
|
37
|
+
recordFailOpen("server.approval-crash-convergence.cause-unreadable", `ask ${row.askId} (task ${row.taskId}): card envelope unreadable — cause defaulted to ${VOID_REASONS.HOLDER_CRASHED}`);
|
|
38
|
+
}
|
|
39
|
+
const reason = envelope.success && input.audit !== undefined ? input.audit.causeFor(envelope.data.approvalId) : VOID_REASONS.HOLDER_CRASHED;
|
|
40
|
+
let won;
|
|
41
|
+
try {
|
|
42
|
+
won = await store.transitionAsk(row.askId, "STREAM_PENDING", "VOID", { deniedReason: reason, updatedAtMs: now() });
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
throw new Error(`approval crash convergence: could not close the previous process's pending approval ask ${row.askId} (task ${row.taskId}): ` +
|
|
46
|
+
`${err instanceof Error ? err.message : String(err)} — refusing to start, because serving now would leave a card that nothing waits for ` +
|
|
47
|
+
"decidable with a 200. Fix the data root (disk full / read-only?) and start again.");
|
|
48
|
+
}
|
|
49
|
+
if (won)
|
|
50
|
+
asksVoided[reason] += 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return { ...audited, asksVoided, causeUnreadable };
|
|
55
|
+
}
|
|
56
|
+
export async function runBootCrashConvergence(ctx) {
|
|
57
|
+
const { backend, runStore, logger } = ctx;
|
|
58
|
+
if (backend === undefined || runStore === undefined || writerGoneEvidenceFor(backend.kind, runStore).kind !== "exclusive_data_root")
|
|
59
|
+
return undefined;
|
|
60
|
+
const evidence = openAuditEvidenceForConvergence(ctx.liveAudit, ctx.localDataRoot);
|
|
61
|
+
const converged = await convergeCrashedAsksAtBoot({ askStore: backend.approvalAsk(), audit: evidence.audit }).finally(evidence.release);
|
|
62
|
+
if (converged.pending > 0 || converged.decided > 0 || converged.inconsistent > 0 || converged.corruptLines > 0 || totalAsksVoided(converged) > 0) {
|
|
63
|
+
logger.warn("approval_ask_audit_crash_converged", {
|
|
64
|
+
pending: converged.pending,
|
|
65
|
+
decided: converged.decided,
|
|
66
|
+
inconsistent: converged.inconsistent,
|
|
67
|
+
corruptLines: converged.corruptLines,
|
|
68
|
+
asksVoided: converged.asksVoided,
|
|
69
|
+
causeUnreadable: converged.causeUnreadable,
|
|
70
|
+
note: "approval asks left pending by the previous process were converged: each such ask is now VOID in the ask ledger (the decision endpoint answers 410 gone.ask with this cause as deniedReason) and is listed under GET /v1/approvals crashConverged when the audit ledger saw it (cause crashed_before_park = the process crashed; cause shutdown_before_park = an orderly shutdown cut the leg off before it closed or before its park committed)",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return converged;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=approval-crash-convergence.js.map
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* 值域不相交这条纪律的用处(`ALL_TERMINAL_REASONS` 的机器判据钉住不相交性)。
|
|
17
17
|
*
|
|
18
18
|
* 写点纪律(grep 钉):全仓 `DENIED` 只有**一个**写点 = 收敛器判据 2 的非取消臂(`approval-reconciler.ts`)。
|
|
19
|
+
* 启动收敛(进程没了)落的是 VOID 不是 DENIED:状态机上 `STREAM_PENDING` 没有到 DENIED 的边(DENIED 只在「已路由去 park」之后),
|
|
20
|
+
* 而承载它的进程没了 = 这条腿死了 = abort 语义。
|
|
19
21
|
*/
|
|
20
22
|
/** `DENIED` 终态的归因闭集(v1 单员)。 */
|
|
21
23
|
export declare const DENY_REASONS: {
|
|
@@ -49,8 +51,19 @@ export declare const VOID_REASONS: {
|
|
|
49
51
|
/** 判据 5:任何在 `STREAM_APPROVAL_ORPHAN_TTL_MS` 内仍未收敛的 `PARKING` 行(量 immutable 的
|
|
50
52
|
* `createdAtMs`,§9 C5)—— 约束①(遗孤最终可判)的最后兜底,带一条 warn。 */
|
|
51
53
|
readonly ORPHAN_TTL_EXCEEDED: "orphan_ttl_exceeded";
|
|
54
|
+
/**
|
|
55
|
+
* 启动收敛(`approval-crash-convergence.ts`,local 车道):**承载这一问的进程没了** —— 前世进程崩溃 / 被杀,留下的
|
|
56
|
+
* `STREAM_PENDING` 行在下一次启动、任何卡能开始之前收成 VOID。证据是数据根 `LOCK`(前世写者全死),不是「等够久了」。
|
|
57
|
+
* 🔴 值与 `GET /v1/approvals` `crashConverged[].cause` 是**同一个词**(审计店的 `CrashConvergedCause` 从本表取型):
|
|
58
|
+
* 同一问在两个读面上只有一个成因词。
|
|
59
|
+
*/
|
|
60
|
+
readonly HOLDER_CRASHED: "crashed_before_park";
|
|
61
|
+
/** 同上,但承载它的腿是被**有序关停**截断的(审计账本上有本腿的关停记号;关停收尾没能在宽限窗内把这张卡结清)。 */
|
|
62
|
+
readonly HOLDER_SHUTDOWN: "shutdown_before_park";
|
|
52
63
|
};
|
|
53
64
|
export type VoidReason = (typeof VOID_REASONS)[keyof typeof VOID_REASONS];
|
|
65
|
+
/** 「承载这一问的进程没了」的两个成因词(启动收敛写进问账行 `deniedReason`,审计收敛行 `cause` 读同一型)。 */
|
|
66
|
+
export type HolderGoneReason = typeof VOID_REASONS.HOLDER_CRASHED | typeof VOID_REASONS.HOLDER_SHUTDOWN;
|
|
54
67
|
/** 两组的并集(机器判据用:值域不相交 + 列宽 ≤ 64 的钉子在 test/approval-reconciler.test.ts)。 */
|
|
55
68
|
export declare const ALL_TERMINAL_REASONS: readonly string[];
|
|
56
69
|
//# sourceMappingURL=approval-deny-reasons.d.ts.map
|
|
@@ -7,6 +7,8 @@ export const VOID_REASONS = {
|
|
|
7
7
|
BATCH_BOUND_ELSEWHERE: "batch_bound_elsewhere",
|
|
8
8
|
ADHOC_LEG_NO_DURABLE_DOMAIN: "adhoc_leg_no_durable_domain",
|
|
9
9
|
ORPHAN_TTL_EXCEEDED: "orphan_ttl_exceeded",
|
|
10
|
+
HOLDER_CRASHED: "crashed_before_park",
|
|
11
|
+
HOLDER_SHUTDOWN: "shutdown_before_park",
|
|
10
12
|
};
|
|
11
13
|
export const ALL_TERMINAL_REASONS = [...Object.values(DENY_REASONS), ...Object.values(VOID_REASONS)];
|
|
12
14
|
//# sourceMappingURL=approval-deny-reasons.js.map
|
package/dist/approval.js
CHANGED
|
@@ -4,6 +4,7 @@ import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
|
|
|
4
4
|
import { isExplicitPosture } from "./posture-source.js";
|
|
5
5
|
import { handsOnWorkerFilePlane } from "./execution-lane-caps.js";
|
|
6
6
|
import { heldRuleName } from "./tool-rule-names.js";
|
|
7
|
+
import { answerGrantsBeyondCall } from "./standing-answer-grant.js";
|
|
7
8
|
export function hasApprovalListIntent(config) {
|
|
8
9
|
return (config.approvalRequire.length > 0 ||
|
|
9
10
|
config.approvalDeny.length > 0 ||
|
|
@@ -97,7 +98,7 @@ export function createDurableAskPolicy(opts) {
|
|
|
97
98
|
if (remEarly !== undefined && remEarly <= 0) {
|
|
98
99
|
return operatorAlwaysHumanAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
|
|
99
100
|
}
|
|
100
|
-
if (opts.exempt && heldRuleName(req, isQuestionTool) === undefined) {
|
|
101
|
+
if (opts.exempt && heldRuleName(req, isQuestionTool) === undefined && !answerGrantsBeyondCall(req)) {
|
|
101
102
|
return (async () => {
|
|
102
103
|
let exempted = false;
|
|
103
104
|
try {
|
|
@@ -43,18 +43,6 @@ export function createLiveCoordinators(ctx) {
|
|
|
43
43
|
const approvalAskAudit = config.toolApprovalEnabled && backend?.kind === "local" && config.localDataRoot !== undefined
|
|
44
44
|
? new FileApprovalAskAuditStore(config.localDataRoot)
|
|
45
45
|
: undefined;
|
|
46
|
-
if (approvalAskAudit) {
|
|
47
|
-
const converged = approvalAskAudit.convergeCrashedAsks();
|
|
48
|
-
if (converged.pending > 0 || converged.decided > 0 || converged.inconsistent > 0 || converged.corruptLines > 0) {
|
|
49
|
-
logger.warn("approval_ask_audit_crash_converged", {
|
|
50
|
-
pending: converged.pending,
|
|
51
|
-
decided: converged.decided,
|
|
52
|
-
inconsistent: converged.inconsistent,
|
|
53
|
-
corruptLines: converged.corruptLines,
|
|
54
|
-
note: "orphan approval asks from the previous process were converged (cause crashed_before_park = it crashed; cause shutdown_before_park = an orderly shutdown cut the leg off before it closed or before its park committed); see GET /v1/approvals crashConverged",
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
46
|
const approvalExemptionStore = backend ? backend.approvalExemption() : undefined;
|
|
59
47
|
const toolApproval = config.toolApprovalEnabled
|
|
60
48
|
? new ToolApprovalCoordinator({
|
|
@@ -15,6 +15,7 @@ import { localDockerExecutionEnvFactory } from "../plugins/remote-env-local-dock
|
|
|
15
15
|
import { sshExecutionEnvFactory } from "../plugins/remote-env-ssh.js";
|
|
16
16
|
import { isRemoteScratchpadLane, withRemoteScratchpad } from "../plugins/remote-scratchpad.js";
|
|
17
17
|
import { FileSchedulerBackend, defaultSchedulerStorePath } from "../plugins/scheduler-support.js";
|
|
18
|
+
import * as facts from "../capability-facts.js";
|
|
18
19
|
import { materializeAttachmentsInto } from "../plugins/task-attachment-store.js";
|
|
19
20
|
import { reapOrphanWorktrees, withWorktreeIsolation } from "../plugins/worktree-isolation.js";
|
|
20
21
|
import { customPkgSourceFromEnv, derivePkgSourceEnv } from "../sandbox-pkg-source.js";
|
|
@@ -204,7 +205,7 @@ export function createExecutionEnv(ctx) {
|
|
|
204
205
|
}
|
|
205
206
|
else if (config.remoteExec?.provider === "host") {
|
|
206
207
|
{
|
|
207
|
-
const hostScheduler =
|
|
208
|
+
const hostScheduler = facts.hostScheduler(config).available
|
|
208
209
|
? new FileSchedulerBackend({
|
|
209
210
|
...(config.schedulerStorePath ? { storePath: config.schedulerStorePath } : {}),
|
|
210
211
|
...(config.schedulerSessionWakeup === false || config.schedulerSessionLifetime === false
|
|
@@ -442,6 +442,7 @@ export function createResolveSpec(ctx) {
|
|
|
442
442
|
return merged && config.toolDeferLongtail ? applyLongtailDefer(merged, true) : merged;
|
|
443
443
|
})(cap.tools),
|
|
444
444
|
skills: body.disableSkills === true ? [] : mergeUserSkills(cap.skills, body.skills, logger),
|
|
445
|
+
...(() => { const v = toolNameListFromBody(body.userInvokedSkills); return v ? { userInvokedSkills: v } : {}; })(),
|
|
445
446
|
...(parsedSettings.settings?.skillListingBudgetFraction !== undefined ? { skillListingBudgetFraction: parsedSettings.settings.skillListingBudgetFraction } : {}),
|
|
446
447
|
...(parsedSettings.settings?.skillListingMaxDescChars !== undefined ? { skillListingMaxDescChars: parsedSettings.settings.skillListingMaxDescChars } : {}),
|
|
447
448
|
mcp: resolveRequestMcp(mcpForScenario(config.mcpServers, scenarioName), body.mcpServers, config, logger, opts?.onNotice !== undefined ? { sessionId: auth?.sessionId, emit: opts.onNotice } : undefined),
|
|
@@ -16,6 +16,7 @@ import { explicitOperatorOk } from "../http/server.js";
|
|
|
16
16
|
import { selfOrchestrationDenial } from "../task-workflow.js";
|
|
17
17
|
import { centerEntitlementSourceWired } from "../runtime-caps-resolver.js";
|
|
18
18
|
import { reclaimOrphansAtBoot } from "../boot-reclaim.js";
|
|
19
|
+
import { runBootCrashConvergence } from "../approval-crash-convergence.js";
|
|
19
20
|
import { createUserInputLedgerAtBoot, writerGoneEvidenceFor } from "../user-input-ledger.js";
|
|
20
21
|
import { createParkedReviveInheritedGate } from "./parked-revive-gate.js";
|
|
21
22
|
import { createParkedReviveSurface } from "../parked-revive-surface.js";
|
|
@@ -23,8 +24,9 @@ import { createWebFetchSummarizeLane } from "./webfetch-summarize-lane.js";
|
|
|
23
24
|
import { createRunnerDepsOnAsk } from "./runner-deps.js";
|
|
24
25
|
import { bindRateLimiter, bindRunStore } from "./late-binds.js";
|
|
25
26
|
import { buildDisplaySafeUrl } from "../trace/redact.js";
|
|
27
|
+
import * as facts from "../capability-facts.js";
|
|
26
28
|
export async function runStage07(ctx) {
|
|
27
|
-
const { approvalExemptionStore, backend, backgroundAgentStore, brain, checkpointStore, config, configCenter, counterDegradeHook, durableEnabled, handslessSubRunner, instanceId, localRoot, logger, memoryEngine, metrics, question, runner, runnerDeps, sendUserFileToolSpec, sessionEnvSelection, sessionStore, subRunner, subRunnerSessions, taskListLane, toolApproval } = ctx;
|
|
29
|
+
const { approvalAskAudit, approvalExemptionStore, backend, backgroundAgentStore, brain, checkpointStore, config, configCenter, counterDegradeHook, durableEnabled, handslessSubRunner, instanceId, localRoot, logger, memoryEngine, metrics, question, runner, runnerDeps, sendUserFileToolSpec, sessionEnvSelection, sessionStore, subRunner, subRunnerSessions, taskListLane, toolApproval } = ctx;
|
|
28
30
|
const authorize = createAuthorizer(config, sessionStore);
|
|
29
31
|
const runStore = backend ? backend.run() : undefined;
|
|
30
32
|
bindRunStore(runStore);
|
|
@@ -51,6 +53,7 @@ export async function runStage07(ctx) {
|
|
|
51
53
|
if (gateRefusal !== undefined)
|
|
52
54
|
throw new Error(gateRefusal);
|
|
53
55
|
await reclaimOrphansAtBoot(runStore, checkpointStore, logger);
|
|
56
|
+
await runBootCrashConvergence({ backend, runStore, liveAudit: approvalAskAudit, localDataRoot: config.localDataRoot, logger });
|
|
54
57
|
const userInputLedgerStore = backend && runStore ? backend.userInputLedger() : undefined;
|
|
55
58
|
const userInputLedger = backend && runStore && userInputLedgerStore
|
|
56
59
|
? await (async () => {
|
|
@@ -79,14 +82,22 @@ export async function runStage07(ctx) {
|
|
|
79
82
|
if (durableEnabled)
|
|
80
83
|
logger.info("durable_approval_enabled", { toolApprovalGate: config.approvalRequire.length });
|
|
81
84
|
if (config.a2aServe) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
const peer = facts.a2aServe({ config, runStore });
|
|
86
|
+
if (peer.available) {
|
|
87
|
+
logger.info("a2a_serve_enabled", {
|
|
88
|
+
card: "/.well-known/agent-card.json",
|
|
89
|
+
rpc: "/v1/a2a",
|
|
90
|
+
advertisedUrl: buildDisplaySafeUrl(config.a2aServe.url),
|
|
91
|
+
skills: config.a2aServe.skills.length,
|
|
92
|
+
blockingWaitMs: config.a2aServe.blockingWaitMs,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
logger.warn("a2a_serve_not_served", {
|
|
97
|
+
reason: peer.why,
|
|
98
|
+
detail: "A2A_SERVE_ENABLED is on but this worker has no durable run store, so the agent card and POST /v1/a2a answer 404 (message/send and tasks/get both need it) — set DB_BACKEND=mysql|pg|local, or turn A2A_SERVE_ENABLED off",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
90
101
|
}
|
|
91
102
|
for (const w of buildNoResponderBootWarns({
|
|
92
103
|
lane: executionLaneOf(config.remoteExec?.provider),
|
|
@@ -148,12 +159,7 @@ export async function runStage07(ctx) {
|
|
|
148
159
|
ensureChildSessionDurable: ensureChildSessionDurable ? ensureChildSessionDurable : undefined,
|
|
149
160
|
taskListStoreFor: taskListLane.storeFor,
|
|
150
161
|
brandIdentity: config.configProvider === "local",
|
|
151
|
-
subagentExtraTools: sendUserFileToolSpec
|
|
152
|
-
? subagentSendUserFileExtraTools({
|
|
153
|
-
spec: sendUserFileToolSpec,
|
|
154
|
-
approvalGate: () => ({ deny: config.approvalDeny, require: config.approvalRequire, neverAuto: config.approvalNeverAuto }),
|
|
155
|
-
})
|
|
156
|
-
: undefined,
|
|
162
|
+
subagentExtraTools: sendUserFileToolSpec ? subagentSendUserFileExtraTools({ spec: sendUserFileToolSpec }) : undefined,
|
|
157
163
|
};
|
|
158
164
|
const scenarios = buildScenarios(scenarioDeps);
|
|
159
165
|
const builtinDefaultScenario = scenarios.default;
|
package/dist/boot/stores.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { FileBackgroundAgentStore, FileMailboxStore, FileRosterStore,
|
|
2
|
+
import { FileBackgroundAgentStore, FileMailboxStore, FileRosterStore, InMemoryUsageWindowStore, mailboxCrossProcessMountVerdict } from "@sema-agent/core";
|
|
3
3
|
import { createCorruptReadFailOpenSeat } from "../observability/corrupt-read-seat.js";
|
|
4
4
|
import { createMemorySyncRunner, createMemorySyncTransport } from "../memory-sync-client.js";
|
|
5
5
|
import { memoryEmbedderFor } from "../plugins/memory-embedder.js";
|
|
@@ -333,13 +333,13 @@ export async function openStores(ctx) {
|
|
|
333
333
|
await ensureTiDBUsageWindowSchema(mysqlPool);
|
|
334
334
|
usageWindowStore = new TiDBUsageWindowStore(mysqlPool);
|
|
335
335
|
}
|
|
336
|
-
else if (backend?.
|
|
337
|
-
usageWindowStore =
|
|
336
|
+
else if (backend?.fileUsageWindowStore !== undefined) {
|
|
337
|
+
usageWindowStore = backend.fileUsageWindowStore();
|
|
338
338
|
}
|
|
339
339
|
else {
|
|
340
340
|
usageWindowStore = new InMemoryUsageWindowStore();
|
|
341
341
|
}
|
|
342
|
-
logger.info("usage_window_store_enabled", { backend: pgPool ? "pg" : mysqlPool ? "tidb" : backend?.
|
|
342
|
+
logger.info("usage_window_store_enabled", { backend: pgPool ? "pg" : mysqlPool ? "tidb" : backend?.fileUsageWindowStore !== undefined ? "file" : "memory", windows: config.usageWindows.length });
|
|
343
343
|
}
|
|
344
344
|
const memoryExportBackend = config.memoryEngineBackend !== "file" ? memoryEngine?.backend : undefined;
|
|
345
345
|
const memoryLane = memoryEngineRemoteLanePosture(config);
|
|
@@ -33,29 +33,6 @@ export interface HandsRunnerPair {
|
|
|
33
33
|
}
|
|
34
34
|
/** 按场景表态取 Runner。**穷举 switch**:`ScenarioHands` 加词而此处不改 = 编译错(无 default 臂)。 */
|
|
35
35
|
export declare function pickHandsRunner(hands: ScenarioHands, pair: HandsRunnerPair): Runner;
|
|
36
|
-
/**
|
|
37
|
-
* core 手带 band 的工具名**全集** —— 「挂 executionEnvFactory 才出现、不挂就消失」的那一批。
|
|
38
|
-
*
|
|
39
|
-
* 两段来源,都不是拍脑袋:
|
|
40
|
-
* · `handBandEffects()`(core 7.8.0 B1 起的导出面;此前是手抄表常量 `HAND_TOOL_EFFECTS`)—— band 的
|
|
41
|
-
* effect 表,由**静态工具目录**按 `mountedBy.includes("hands")` 派生。名册单源之后这张表不再是一张
|
|
42
|
-
* 手抄名表,加/退工具由目录一处说了算。
|
|
43
|
-
* · 下方锚定补集 —— `createHandsToolkit` / 手带块还会铸但**不在** effect 表里的 5 名(实测差集,见
|
|
44
|
-
* test/scenario-hands-lane.test.ts 的对表格:它用真 Runner 跑「有手 roster ∖ 无手 roster」,与本常量逐名对账,
|
|
45
|
-
* core 加/改 band 成员时红,防漂移)。
|
|
46
|
-
*
|
|
47
|
-
* ⚠️ 本全集是**观测口径**(用于防漂移对账),**不是** deny 名单 —— deny 用下面的
|
|
48
|
-
* {@link HANDS_EXCLUSIVE_TOOL_NAMES},理由见那里。
|
|
49
|
-
*
|
|
50
|
-
* 🚨 [ref] P0(2026-08-09):派生时必须过滤 core 的 `RETIRED_TOOL_NAMES` —— core 5.21.0 的
|
|
51
|
-
* `HAND_TOOL_EFFECTS` 里残留已退役的 `MultiEdit`,而 core `prepare-task` 的策略名审计见退役名
|
|
52
|
-
* **无条件 throw**(`config.legacy_tool_name`);不过滤,deny 名单一进 `spec.toolPolicy`,
|
|
53
|
-
* `hands: none` 三场景(scan/code-review/team)在**所有部署**上零 turn 全灭。core 5.21.1 已删
|
|
54
|
-
* 那条死条目([ref]),这道 filter 是结构性防御:将来 core 再退役任何 band 名都不会重演
|
|
55
|
-
* (test/scenario-hands-lane.test.ts 格7/格8 双钉:真 Runner 过审计 + 零交集门)。
|
|
56
|
-
* filter 打在**整表**(effect 表 + 锚定补集)之后 —— caret floor 下装置会解析到更新的 core,
|
|
57
|
-
* 补集名哪天被退役,只滤前半就是同一次故障换个名字(codex 复审 finding,2026-08-09)。
|
|
58
|
-
*/
|
|
59
36
|
export declare const HANDS_BAND_TOOL_NAMES: readonly string[];
|
|
60
37
|
/**
|
|
61
38
|
* band 里**只有手才可能出现**的那批(= deny 名单)。
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { handBandEffects,
|
|
1
|
+
import { handBandEffects, judgeRuleToolName, combinePolicies, createAllowDenyPolicy } from "@sema-agent/core";
|
|
2
2
|
export function withoutExecutionEnv(deps) {
|
|
3
3
|
const { executionEnvFactory: _handsDropped, ...rest } = deps;
|
|
4
4
|
return rest;
|
|
@@ -11,6 +11,7 @@ export function pickHandsRunner(hands, pair) {
|
|
|
11
11
|
return pair.handsless;
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
const NO_DECLARED_TOOL_NAMES = new Set();
|
|
14
15
|
export const HANDS_BAND_TOOL_NAMES = Object.freeze([
|
|
15
16
|
...handBandEffects().keys(),
|
|
16
17
|
"SendMessage",
|
|
@@ -19,7 +20,7 @@ export const HANDS_BAND_TOOL_NAMES = Object.freeze([
|
|
|
19
20
|
"EnterWorktree",
|
|
20
21
|
"ExitWorktree",
|
|
21
22
|
"ListAgents",
|
|
22
|
-
].filter((n, i, all) =>
|
|
23
|
+
].filter((n, i, all) => judgeRuleToolName(n, NO_DECLARED_TOOL_NAMES).kind === "current" && all.indexOf(n) === i));
|
|
23
24
|
export const HANDS_DUAL_USE_TOOL_NAMES = Object.freeze(["TaskOutput", "TaskStop", "SendMessage", "AgentTranscript", "ListAgents"]);
|
|
24
25
|
export const HANDS_EXCLUSIVE_TOOL_NAMES = Object.freeze(HANDS_BAND_TOOL_NAMES.filter((n) => !HANDS_DUAL_USE_TOOL_NAMES.includes(n)));
|
|
25
26
|
export function handsDenyPolicy() {
|
|
@@ -44,32 +44,20 @@ export declare function sendUserFileTool(deps: {
|
|
|
44
44
|
}): ToolSpec;
|
|
45
45
|
export declare const SEND_USER_FILE_TOOL_NAME = "SendUserFile";
|
|
46
46
|
/**
|
|
47
|
-
* Build the `SubagentToolOptions.extraTools` factory that injects SendUserFile into delegated-child rosters
|
|
48
|
-
* WITH the two gates the bare `() => [spec]` wiring lacked:
|
|
47
|
+
* Build the `SubagentToolOptions.extraTools` factory that injects SendUserFile into delegated-child rosters.
|
|
49
48
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* PRE-CHECK: if the deployment's approval face gates SendUserFile at all (deny OR ask — approvalDeny /
|
|
55
|
-
* approvalRequire / approvalNeverAuto, matched in canonical space exactly like src/approval.ts), the factory
|
|
56
|
-
* injects NOTHING. deny obviously must not reach children; ask-gated is ALSO withheld because the child leg has
|
|
57
|
-
* no policy to route the ask (injecting would silently auto-allow — the exact bypass). The gate is read LIVE per
|
|
58
|
-
* spawn (a getter, not a boot snapshot) so center/env hot-applied governance is honored, mirroring how the
|
|
59
|
-
* per-request approval baseline reads live config. A deployment with no gate intent is unchanged.
|
|
49
|
+
* F2 (MED, codex; a product judgment of this server, kept): read-only builtin agents (Explore/Plan) must NOT get
|
|
50
|
+
* SendUserFile — a read-only agent that can read sensitive files AND mint public links off them breaks the read-only
|
|
51
|
+
* posture (their denyTools sets predate this tool, so core's resolveToolSubset would let it through). `ctx.agentType`
|
|
52
|
+
* carries the spawned definition's name (core subagent.js); membership in core's builtin catalog ⇒ inject nothing.
|
|
60
53
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
54
|
+
* 🔴 S-1058(7.116.0):修前这里还有一道 **F1 部署策略预判** —— 部署审批面(`APPROVAL_DENY` / `REQUIRE` / `NEVER_AUTO`)点名 SendUserFile 就一只都
|
|
55
|
+
* 不注入,前提是「subRunner 不带策略、core 的子代不继承父的 toolPolicy」(core 1.287 时的事实)。core 自 7.40.1 起顶层 run 解析后的调用方策略
|
|
56
|
+
* (`spec.toolPolicy ?? deps.toolPolicy`)与它冻结的审批席经继承链(`InheritedGate.parentConstraints`)判子代的注入工具:deny ⇒ 零执行;ask ⇒ 顶层
|
|
57
|
+
* 审批席被问一次;无审批席 ⇒ 拒(core 7.41.0 钉)。本仓在真实装配上亲跑十格(spec / deps 两槽 × deny / ask 允 / ask 拒 / 无审批席 / 耐久车道无活席,
|
|
58
|
+
* `test/subagent-extra-tools-ancestor-policy-s1058.test.ts`)全部成立 ⇒ 预判整段删:部署的判决在继承链上由策略本身做,本仓不再复刻一份「按名单猜」。
|
|
65
59
|
*/
|
|
66
60
|
export declare function subagentSendUserFileExtraTools(deps: {
|
|
67
61
|
spec: ToolSpec;
|
|
68
|
-
/** LIVE deployment approval face (config.approvalDeny/approvalRequire/approvalNeverAuto) — evaluated per spawn. */
|
|
69
|
-
approvalGate: () => {
|
|
70
|
-
deny?: string[];
|
|
71
|
-
require?: string[];
|
|
72
|
-
neverAuto?: string[];
|
|
73
|
-
};
|
|
74
62
|
}): (ctx: SubagentSpawnContext) => ToolSpec[];
|
|
75
63
|
//# sourceMappingURL=send-user-file-tool.d.ts.map
|
|
@@ -94,13 +94,6 @@ export function sendUserFileTool(deps) {
|
|
|
94
94
|
export const SEND_USER_FILE_TOOL_NAME = "SendUserFile";
|
|
95
95
|
const READONLY_BUILTIN_AGENT_TYPES = new Set(builtinAgentDefinitions().map((d) => d.name));
|
|
96
96
|
export function subagentSendUserFileExtraTools(deps) {
|
|
97
|
-
|
|
98
|
-
return (ctx) => {
|
|
99
|
-
if (ctx.agentType !== undefined && READONLY_BUILTIN_AGENT_TYPES.has(ctx.agentType))
|
|
100
|
-
return [];
|
|
101
|
-
const gate = deps.approvalGate();
|
|
102
|
-
const gated = [...(gate.deny ?? []), ...(gate.require ?? []), ...(gate.neverAuto ?? [])].some((t) => t === canonical);
|
|
103
|
-
return gated ? [] : [deps.spec];
|
|
104
|
-
};
|
|
97
|
+
return (ctx) => (ctx.agentType !== undefined && READONLY_BUILTIN_AGENT_TYPES.has(ctx.agentType) ? [] : [deps.spec]);
|
|
105
98
|
}
|
|
106
99
|
//# sourceMappingURL=send-user-file-tool.js.map
|