@sema-agent/server 7.70.0 → 7.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +8 -4
- package/dist/approval-card.d.ts +16 -17
- package/dist/approval-card.js +0 -9
- package/dist/approval-content-kind.d.ts +13 -7
- package/dist/approval-content-kind.js +3 -1
- package/dist/approval.js +3 -2
- package/dist/bench/s1/live-deps.js +2 -0
- package/dist/boot/execution-env.js +12 -2
- package/dist/boot/memory-consolidation.d.ts +2 -1
- package/dist/boot/parked-revive-gate.d.ts +2 -0
- package/dist/boot/parked-revive-gate.js +5 -1
- package/dist/boot/resolve-spec.js +8 -3
- package/dist/boot/runner-deps.d.ts +48 -59
- package/dist/boot/runner-deps.js +10 -4
- package/dist/brain.d.ts +3 -1
- package/dist/brain.js +2 -8
- package/dist/budget.d.ts +12 -1
- package/dist/budget.js +1 -1
- package/dist/config-catalog.js +13 -11
- package/dist/config-center/apply-effective.js +2 -4
- package/dist/config-center/facade.d.ts +1 -1
- package/dist/config-center/restart-signal.js +2 -2
- package/dist/config-center/skills-mcp.js +15 -0
- package/dist/config-types.d.ts +86 -4
- package/dist/config-types.js +19 -0
- package/dist/config.d.ts +42 -0
- package/dist/config.js +71 -22
- package/dist/deployment-governance.d.ts +6 -3
- package/dist/deployment-governance.js +2 -1
- package/dist/execution-lane-caps.d.ts +51 -37
- package/dist/execution-lane-caps.js +23 -0
- package/dist/fleet/fleet-bus.d.ts +22 -1
- package/dist/fleet/fleet-bus.js +2 -1
- package/dist/http/durable-run-leg.d.ts +64 -0
- package/dist/http/durable-run-leg.js +63 -0
- package/dist/http/route-ctx.d.ts +14 -0
- package/dist/http/route-ctx.js +3 -0
- package/dist/http/routes/approvals-assistant.js +3 -3
- package/dist/http/routes/capabilities.js +2 -2
- package/dist/http/routes/runs.js +10 -58
- package/dist/http/routes/workflows.js +1 -0
- package/dist/http/server.js +72 -22
- package/dist/index.d.ts +1 -1
- package/dist/leader/wire.js +4 -3
- package/dist/main.js +8 -3
- package/dist/memory-operator-faces.d.ts +1 -1
- package/dist/memory-scope.d.ts +24 -0
- package/dist/memory-scope.js +12 -3
- package/dist/observability/fail-open.d.ts +1 -1
- package/dist/observability/fail-open.js +1 -1
- package/dist/observability/metrics.d.ts +4 -0
- package/dist/observability/metrics.js +3 -1
- package/dist/observability/run-terminal-log.d.ts +4 -3
- package/dist/observability/run-terminal-log.js +4 -15
- package/dist/observability/tool-trace.d.ts +18 -1
- package/dist/observability/tool-trace.js +4 -3
- package/dist/orchestration/workflow-agent-session-index.d.ts +85 -2
- package/dist/orchestration/workflow-agent-session-index.js +38 -5
- package/dist/parked-decide.d.ts +40 -1
- package/dist/parked-decide.js +22 -4
- package/dist/plugins/checkpoint-store-sql.js +5 -5
- package/dist/plugins/e2b-orphan-reclaim.d.ts +4 -4
- package/dist/plugins/e2b-orphan-reclaim.js +18 -20
- package/dist/plugins/local-checkpoint-store.js +3 -3
- package/dist/plugins/remote-env-adb.d.ts +11 -0
- package/dist/plugins/remote-env-adb.js +2 -0
- package/dist/plugins/remote-env-device.d.ts +9 -0
- package/dist/plugins/remote-env-device.js +3 -0
- package/dist/plugins/remote-env-e2b.d.ts +11 -0
- package/dist/plugins/remote-env-e2b.js +2 -0
- package/dist/plugins/remote-env-host.d.ts +11 -0
- package/dist/plugins/remote-env-host.js +2 -0
- package/dist/plugins/remote-env-k8s.d.ts +11 -0
- package/dist/plugins/remote-env-k8s.js +2 -0
- package/dist/plugins/remote-env-local-docker.d.ts +11 -0
- package/dist/plugins/remote-env-local-docker.js +2 -0
- package/dist/plugins/remote-env-ssh.d.ts +11 -0
- package/dist/plugins/remote-env-ssh.js +2 -0
- package/dist/plugins/store-backend.d.ts +34 -12
- package/dist/plugins/store-backend.js +12 -3
- package/dist/plugins/store-contracts.d.ts +81 -18
- package/dist/plugins/store-contracts.js +18 -30
- package/dist/plugins/workflow-run-store-sql.d.ts +31 -9
- package/dist/plugins/workflow-run-store-sql.js +28 -1
- package/dist/run-cancel-context.js +1 -1
- package/dist/run-local.js +7 -1
- package/dist/runs.d.ts +16 -2
- package/dist/runs.js +2 -1
- package/dist/runtime-caps-resolver.js +5 -3
- package/dist/task-a2a.js +2 -1
- package/dist/task-mcp.js +2 -1
- package/dist/task-settings.d.ts +21 -8
- package/dist/task-settings.js +2 -2
- package/dist/tool-approval.d.ts +26 -21
- package/dist/tool-approval.js +2 -2
- package/dist/trace/core-keyset-guard.d.ts +136 -10
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +1 -0
- package/dist/trace/ledger-events.d.ts +5 -1
- package/dist/trace/ledger-sink.js +8 -1
- package/dist/trace/project.d.ts +33 -4
- package/dist/trace/project.js +24 -6
- package/dist/trace/redact.js +10 -5
- package/dist/usage-analytics.d.ts +9 -1
- package/dist/usage-analytics.js +8 -0
- package/dist/wire-name-bound.d.ts +14 -0
- package/dist/wire-name-bound.js +13 -0
- package/package.json +3 -3
package/dist/http/routes/runs.js
CHANGED
|
@@ -6,13 +6,10 @@ import { isTerminalRunStatus, isParkedRunStatus } from "../../plugins/store-cont
|
|
|
6
6
|
import { LIVE_RUN_GATE_KINDS, TERMINAL_PARK_GATE_KINDS } from "../../plugins/checkpoint-store-sql.js";
|
|
7
7
|
import { redactSecrets } from "../../trace/redact.js";
|
|
8
8
|
import { HttpError, verifiedPrincipal, isUuidV7, encodeCheckpointScope } from "../../security.js";
|
|
9
|
-
import { runInBackground } from "../../runs.js";
|
|
10
9
|
import { redactSteerIn, STEER_IN_MAX_CHARS, STEER_IN_MAX_REQUEST_CHARS } from "../../orchestration/workflow-agent-steer.js";
|
|
11
10
|
import { defaultSubagentTailBus } from "../../fleet/subagent-tail-bus.js";
|
|
12
|
-
import { fleetRunPublisher, fleetRunLabels } from "../../fleet/fleet-bus.js";
|
|
13
11
|
import { composeSupervisorCost, infraCost, infraUsageFromEvents, hasInfraPricing } from "../../observability/cost-taxonomy.js";
|
|
14
12
|
import { recordFailOpen } from "../../observability/fail-open.js";
|
|
15
|
-
import { cascadeConfig, runMeta } from "../run-meta.js";
|
|
16
13
|
import { createHash } from "node:crypto";
|
|
17
14
|
import { scopedIdempotencyKey } from "../idempotency.js";
|
|
18
15
|
import { streamSseLog } from "../sse-log.js";
|
|
@@ -22,12 +19,12 @@ import { ASK_DECISION_CONSUMER_ABSENT, resolveStreamApprovalGate } from "../../t
|
|
|
22
19
|
import { MAX_DECISION_NOTE_CHARS } from "../../approval-ask-machine.js";
|
|
23
20
|
import { normalizeRunEventType } from "../../trace/project.js";
|
|
24
21
|
import { sendJson, sendError, httpErrorCode, sseHeaders } from "../send.js";
|
|
25
|
-
import {
|
|
26
|
-
import { clearTurnActivity, readTurnActivityMs } from "../../turn-activity.js";
|
|
22
|
+
import { readTurnActivityMs } from "../../turn-activity.js";
|
|
27
23
|
import { mintCancelledResult } from "../../run-cancel-context.js";
|
|
28
|
-
import { buildCrossSliceUsage, readResourceWindow
|
|
24
|
+
import { buildCrossSliceUsage, readResourceWindow } from "../../resource-window.js";
|
|
29
25
|
import { headerStr, gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
30
26
|
import { ActorAssertionWire } from "@sema-agent/settings-schema";
|
|
27
|
+
import { mintAndDriveDurableRun } from "../durable-run-leg.js";
|
|
31
28
|
import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage } from "../route-ctx.js";
|
|
32
29
|
import { withPrincipal } from "../../observability/principal-context.js";
|
|
33
30
|
export const RUN_ID_RE = /^\/v1\/runs\/([^/]+)(\/events)?$/;
|
|
@@ -1559,58 +1556,13 @@ export async function createDurableRun(req, res, ctx, prepared, idemKey) {
|
|
|
1559
1556
|
}
|
|
1560
1557
|
if ((await leaseDenied(req, res)) || (await usageWindowDenied(req, res)))
|
|
1561
1558
|
return null;
|
|
1562
|
-
const resp = await idemCache.run(idemKey, async () => {
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
recordResourceWindow(taskId, prepared.spec.resourceSuspend);
|
|
1570
|
-
if (created.ok)
|
|
1571
|
-
deps.sessionTitler?.maybeTitle(sessionId, prepared.spec.objective, typeof prepared.spec.model === "string" ? prepared.spec.model : prepared.spec.model?.id);
|
|
1572
|
-
if (!created.ok) {
|
|
1573
|
-
if (clientTaskId && created.activeTaskId === clientTaskId) {
|
|
1574
|
-
return { status: 202, body: { taskId: clientTaskId, sessionId, status: "running" } };
|
|
1575
|
-
}
|
|
1576
|
-
return { status: 409, body: await buildActiveRunConflict({ runStore, checkpointStore: deps.checkpointStore, governance: deps.config, runStaleSec: deps.config.runStaleSec, turnActivity: readTurnActivityMs }, sessionId, created.activeTaskId) };
|
|
1577
|
-
}
|
|
1578
|
-
if (deps.checkpointStore) {
|
|
1579
|
-
await deps.checkpointStore.putCtx(sessionId, { body: prepared.body, memoryScope: prepared.auth?.memoryScope });
|
|
1580
|
-
}
|
|
1581
|
-
const anchorOwner = prepared.auth?.principal ?? null;
|
|
1582
|
-
const getLeafId = deps.sessionStorage?.getLeafId?.bind(deps.sessionStorage);
|
|
1583
|
-
const captureTurnAnchor = deps.resumeAnchorStore && getLeafId
|
|
1584
|
-
? async (eventId) => {
|
|
1585
|
-
const leaf = await getLeafId(sessionId);
|
|
1586
|
-
if (leaf)
|
|
1587
|
-
await deps.resumeAnchorStore.put(sessionId, eventId, leaf, anchorOwner);
|
|
1588
|
-
}
|
|
1589
|
-
: undefined;
|
|
1590
|
-
const captureUserMessageAnchor = deps.resumeAnchorStore
|
|
1591
|
-
? async (entryId) => { await deps.resumeAnchorStore.put(sessionId, taskId, entryId, anchorOwner); }
|
|
1592
|
-
: undefined;
|
|
1593
|
-
const fleetPub = fleetRunPublisher(deps.fleetBus, {
|
|
1594
|
-
runId: taskId,
|
|
1595
|
-
scope: gatedPrincipal(req, deps.config) ?? "default",
|
|
1596
|
-
rootTaskId: taskId,
|
|
1597
|
-
...fleetRunLabels(prepared.spec.objective),
|
|
1598
|
-
});
|
|
1599
|
-
void runInBackground(prepared.runner, { ...prepared.spec, sessionId }, runStore, taskId, deps.metrics, prepared.auth?.principal, prepared.verify, prepared.cascade ? cascadeConfig(deps.config.cascadeLadder, prepared.spec.limits?.maxCostUsd) : undefined, deps.instrumentDegenerate, deps.planCacheProbe, deps.config.traceThinking, inflightRuns, preemptableRuns, steerableRuns, deps.modelUsage, deps.elicitation, gatedPrincipal(req, deps.config) ?? null, captureTurnAnchor, fleetPub, captureUserMessageAnchor, deps.question, deps.workflowCompletionInbox, deps.subagentSteerRegistry, (m, x) => deps.logger?.info?.(m, x), deps.sendUserFile, deps.promptManifests, deps.toolApproval
|
|
1600
|
-
? {
|
|
1601
|
-
coordinator: deps.toolApproval,
|
|
1602
|
-
streamApprovalOn: resolveStreamApprovalGate({
|
|
1603
|
-
toolApprovalEnabled: true,
|
|
1604
|
-
streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
|
|
1605
|
-
backend: deps.backend,
|
|
1606
|
-
parkFacility: deps.checkpointStore !== undefined,
|
|
1607
|
-
}).active,
|
|
1608
|
-
windowMs: deps.config.streamApproval?.windowMs,
|
|
1609
|
-
windowMarginMs: deps.config.streamAskWindowMarginMs,
|
|
1610
|
-
}
|
|
1611
|
-
: undefined, deps.checkpointStore, prepared.engineNotices);
|
|
1612
|
-
return { status: 202, body: { taskId, sessionId, status: "running" } };
|
|
1613
|
-
}, (r) => r.status === 202);
|
|
1559
|
+
const resp = await idemCache.run(idemKey, async () => await mintAndDriveDurableRun({ deps, registry: ctx.registry, helpers: ctx.helpers, local: ctx.local, legs: ctx.legs }, runStore, {
|
|
1560
|
+
prepared,
|
|
1561
|
+
...(clientTaskId !== undefined ? { clientTaskId } : {}),
|
|
1562
|
+
fleetScope: gatedPrincipal(req, deps.config) ?? "default",
|
|
1563
|
+
elicitOwner: gatedPrincipal(req, deps.config) ?? null,
|
|
1564
|
+
source,
|
|
1565
|
+
}), (r) => r.status === 202);
|
|
1614
1566
|
return resp;
|
|
1615
1567
|
}
|
|
1616
1568
|
//# sourceMappingURL=runs.js.map
|
|
@@ -338,6 +338,7 @@ function summarizeWorkflowDetail(run) {
|
|
|
338
338
|
durationMs: durationOf(run),
|
|
339
339
|
rev: run.rev,
|
|
340
340
|
...(run.error !== undefined ? { error: redactErrorMessage(run.error) ?? "" } : {}),
|
|
341
|
+
...(run.errorCode !== undefined ? { errorCode: run.errorCode } : {}),
|
|
341
342
|
...(run.result !== undefined ? { result: redactSecrets(run.result) } : {}),
|
|
342
343
|
phases,
|
|
343
344
|
...(unphasedView ? { unphased: unphasedView } : {}),
|
package/dist/http/server.js
CHANGED
|
@@ -3,7 +3,7 @@ import { redactErrorMessage, redactHead } from "../observability/run-terminal-lo
|
|
|
3
3
|
import { once } from "node:events";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { uuidv7, isThinkingLevel, expandTiers, resumeWithVerification, CheckpointError, defaultTaskRegistry, validatePendingSteer, subscribeWorkflow, GLOBAL_USAGE_KEY, usageRetryAfterMs, terminalProjection, PAUSE_REGISTRY } from "@sema-agent/core";
|
|
6
|
-
import { decideParkedAgent, findParkOrigin, planWorkflowParkedDecide } from "../parked-decide.js";
|
|
6
|
+
import { decideParkedAgent, denyWorkflowParkIdentityLost, findParkOrigin, planWorkflowParkedDecide } from "../parked-decide.js";
|
|
7
7
|
import { isModelAllowlisted, matchCatalogModel } from "../model-select.js";
|
|
8
8
|
import {} from "../config-center/facade.js";
|
|
9
9
|
import { HttpError, principalFrom, verifiedPrincipal, setSsoPrincipal, ssoVerifiedPrincipal, setSsoScope, isUuidV7, isDestructiveSessionWrite, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../security.js";
|
|
@@ -33,7 +33,7 @@ import { TERMINAL_PARK_GATE_KINDS } from "../plugins/checkpoint-store-sql.js";
|
|
|
33
33
|
import { createWallClockJumpGuard } from "../wall-clock-jump-guard.js";
|
|
34
34
|
import {} from "../observability/rate-limit.js";
|
|
35
35
|
import { withPrincipal } from "../observability/principal-context.js";
|
|
36
|
-
import { turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
|
|
36
|
+
import { turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
|
|
37
37
|
import { redactSecrets } from "../trace/redact.js";
|
|
38
38
|
import { createLedgerSink, createSerialLedgerAppend } from "../trace/ledger-sink.js";
|
|
39
39
|
import { registerEngineNoticeLeg } from "../trace/engine-notice-wire.js";
|
|
@@ -43,6 +43,8 @@ import { IdempotencyCache, scopedIdempotencyKey } from "./idempotency.js";
|
|
|
43
43
|
export { scopedIdempotencyKey };
|
|
44
44
|
import { streamSseLog } from "./sse-log.js";
|
|
45
45
|
import { verifyRoundsFromBody } from "./verify-rounds.js";
|
|
46
|
+
import { acceptedResumingReceipt } from "./route-ctx.js";
|
|
47
|
+
import { mintAndDriveDurableRun } from "./durable-run-leg.js";
|
|
46
48
|
import { RUN_NOT_FOUND_MESSAGE } from "./route-ctx.js";
|
|
47
49
|
import { handleCapabilities } from "./routes/capabilities.js";
|
|
48
50
|
import { handleObservability } from "./routes/observability.js";
|
|
@@ -85,6 +87,7 @@ export { coarseStatusForState, errorCodeForExit };
|
|
|
85
87
|
import { sendJson, sendError, httpErrorCode, msg, isSseResponse } from "./send.js";
|
|
86
88
|
import { authorized, systemFor, gatedPrincipal, explicitOperatorOk, isOperator } from "./principal-gate.js";
|
|
87
89
|
import { buildActiveRunConflict, resumeEntryForGate } from "./active-run-conflict.js";
|
|
90
|
+
import { ASK_USER_QUESTION_TOOL_NAME } from "../approval-content-kind.js";
|
|
88
91
|
export { explicitOperatorOk, isOperator };
|
|
89
92
|
export function flattenServiceDeps(deps) {
|
|
90
93
|
const { stores, coordinators, seams, observability, governance, deployment, knobs, ...flat } = deps;
|
|
@@ -1143,7 +1146,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1143
1146
|
};
|
|
1144
1147
|
}
|
|
1145
1148
|
const paForGrant = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction : undefined;
|
|
1146
|
-
const loadedGrantTool = paForGrant !== undefined && paForGrant.toolName && paForGrant.toolName !==
|
|
1149
|
+
const loadedGrantTool = paForGrant !== undefined && paForGrant.toolName && paForGrant.toolName !== ASK_USER_QUESTION_TOOL_NAME
|
|
1147
1150
|
? paForGrant.toolName
|
|
1148
1151
|
: null;
|
|
1149
1152
|
const boundGrant = onResumeCommitted
|
|
@@ -1208,27 +1211,23 @@ export function createHttpServer(rawDeps) {
|
|
|
1208
1211
|
noteLateSettleIfAccepted(parked);
|
|
1209
1212
|
return parked;
|
|
1210
1213
|
}
|
|
1214
|
+
if (origin?.kind === "workflow_identity_lost") {
|
|
1215
|
+
void ctxPromise.catch(() => { });
|
|
1216
|
+
return denyWorkflowParkIdentityLost(origin);
|
|
1217
|
+
}
|
|
1211
1218
|
if (origin?.kind === "workflow") {
|
|
1212
1219
|
void ctxPromise.catch(() => { });
|
|
1213
1220
|
const plan = planWorkflowParkedDecide(origin, parkedReq);
|
|
1214
1221
|
if (!plan.ok)
|
|
1215
1222
|
return plan.deny;
|
|
1223
|
+
const parkedResume = { workflowParkedResume: [{ runId: plan.runId, token, outcome: plan.outcome }] };
|
|
1216
1224
|
const hostToken = await cs
|
|
1217
1225
|
.findPendingTokenBySession(plan.hostSessionId, undefined, { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" })
|
|
1218
1226
|
.catch(() => undefined);
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
error: "the workflow's originating session is not parked awaiting this run — the deployment can only deliver the decision on the host's next turn; retry once the host session parks, or resume the workflow run yourself",
|
|
1224
|
-
errorCode: "decide.workflow_host_not_parked",
|
|
1225
|
-
runId: plan.runId,
|
|
1226
|
-
},
|
|
1227
|
-
};
|
|
1228
|
-
}
|
|
1229
|
-
return await resumeWake(plan.hostSessionId, plan.message, decider?.principal, req, acceptEarly, [
|
|
1230
|
-
{ runId: plan.runId, token, outcome: plan.outcome },
|
|
1231
|
-
]);
|
|
1227
|
+
const admittedPayer = decodeCheckpointScope(cp.scope);
|
|
1228
|
+
return hostToken
|
|
1229
|
+
? await resumeWake(plan.hostSessionId, plan.message, decider?.principal, req, acceptEarly, parkedResume.workflowParkedResume, admittedPayer)
|
|
1230
|
+
: await startFreshHostTurn(plan.hostSessionId, plan.message, admittedPayer, req, parkedResume, plan.runId);
|
|
1232
1231
|
}
|
|
1233
1232
|
}
|
|
1234
1233
|
let ctx;
|
|
@@ -1256,10 +1255,10 @@ export function createHttpServer(rawDeps) {
|
|
|
1256
1255
|
throw e;
|
|
1257
1256
|
}
|
|
1258
1257
|
const pendingTool = cp.pendingAction.toolName;
|
|
1259
|
-
if (decision === "approve" && pendingTool ===
|
|
1258
|
+
if (decision === "approve" && pendingTool === ASK_USER_QUESTION_TOOL_NAME && !answer) {
|
|
1260
1259
|
return { status: 400, body: { error: "pending action is AskUserQuestion — approve must carry body.answer ({ answers: [{ header, selected: string[], note? }] })", errorCode: "request.field_conflict" } };
|
|
1261
1260
|
}
|
|
1262
|
-
if (answer && (decision !== "approve" || pendingTool !==
|
|
1261
|
+
if (answer && (decision !== "approve" || pendingTool !== ASK_USER_QUESTION_TOOL_NAME)) {
|
|
1263
1262
|
return { status: 400, body: { error: `body.answer is only valid when approving a pending AskUserQuestion (decision: "${decision}", pending: "${pendingTool ?? "unknown"}")`, errorCode: "request.field_conflict" } };
|
|
1264
1263
|
}
|
|
1265
1264
|
const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
|
|
@@ -1281,9 +1280,11 @@ export function createHttpServer(rawDeps) {
|
|
|
1281
1280
|
noteLateSettleIfAccepted(driven);
|
|
1282
1281
|
return driven;
|
|
1283
1282
|
}
|
|
1283
|
+
const ONE_SHOT_HISTORY_KEYS = ["resumeAt", "restoreFiles", "acceptPartialRestore", "rewindFilesTo", "rewindFiles"];
|
|
1284
|
+
const ONE_SHOT_HISTORY_STRIP = Object.fromEntries(ONE_SHOT_HISTORY_KEYS.map((k) => [k, undefined]));
|
|
1284
1285
|
const resolveResumeSpec = async (body, req, auth) => {
|
|
1285
1286
|
const engineNotices = [];
|
|
1286
|
-
const spec = await deps.resolveSpec({ ...body,
|
|
1287
|
+
const spec = await deps.resolveSpec({ ...body, ...ONE_SHOT_HISTORY_STRIP }, req, auth, { leg: "resume", onNotice: (n) => engineNotices.push(n) });
|
|
1287
1288
|
return { spec, engineNotices };
|
|
1288
1289
|
};
|
|
1289
1290
|
async function driveResumeIntoRunLog(args) {
|
|
@@ -1633,7 +1634,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1633
1634
|
steerableRuns.set(taskId, stream);
|
|
1634
1635
|
if (!taskId || !rs)
|
|
1635
1636
|
return await stream.result();
|
|
1636
|
-
onAccepted?.(
|
|
1637
|
+
onAccepted?.(acceptedResumingReceipt(taskId, sessionId));
|
|
1637
1638
|
for await (const ev of stream) {
|
|
1638
1639
|
recordTurnActivity(taskId);
|
|
1639
1640
|
fleetPub?.onEvent(ev);
|
|
@@ -1691,6 +1692,13 @@ export function createHttpServer(rawDeps) {
|
|
|
1691
1692
|
await flush();
|
|
1692
1693
|
await append("wiring_manifest", wiringManifestEventData(ev));
|
|
1693
1694
|
break;
|
|
1695
|
+
case "tool_roster_delta": {
|
|
1696
|
+
await flush();
|
|
1697
|
+
const rosterDelta = toolRosterDeltaEventData(ev);
|
|
1698
|
+
if (rosterDelta !== undefined)
|
|
1699
|
+
await append("tool_roster_delta", rosterDelta);
|
|
1700
|
+
break;
|
|
1701
|
+
}
|
|
1694
1702
|
case "human_input":
|
|
1695
1703
|
await flush();
|
|
1696
1704
|
await append("human_input", humanInputEventData(ev));
|
|
@@ -1820,7 +1828,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1820
1828
|
(e.code === "checkpoint.unsupported_version" && checkpointRowRedeemableElsewhere(e.detail?.reason));
|
|
1821
1829
|
if (claimedRow && taskId && deps.runStore) {
|
|
1822
1830
|
if (retriable && freshRunClaimed) {
|
|
1823
|
-
const c = { taskId, sessionId, terminal: { kind: "failed", code: e.code, message: e.message }, stats: { turns: 0, tokens: 0 } };
|
|
1831
|
+
const c = { taskId, sessionId, terminal: { kind: "failed", code: e.code, message: e.message }, stats: { turns: 0, tokens: 0, usageMissing: true } };
|
|
1824
1832
|
await deps.runStore.setTerminal(taskId, "failed", c, e.message).catch((err) => {
|
|
1825
1833
|
deps.logger?.warn?.("wake_synthetic_run_release_failed", { taskId, sessionId, errorCode: e.code, error: err instanceof Error ? err.message : String(err), note: "the synthetic run row for this refused fresh-run resume could not be settled — its task_active claim is still held and the next attempt on this session will 409 conflict.session_active_run until reapStale collects it (runStaleSec)" });
|
|
1826
1834
|
});
|
|
@@ -1975,7 +1983,48 @@ export function createHttpServer(rawDeps) {
|
|
|
1975
1983
|
const verifyRounds = verifyRoundsFromBody(ctx.body);
|
|
1976
1984
|
return driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome: { gate: "resource_limit", decision: "continue" }, verifyRounds, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
|
|
1977
1985
|
}
|
|
1978
|
-
async function
|
|
1986
|
+
async function startFreshHostTurn(sessionId, objective, principal, req, trustedInternals, runId) {
|
|
1987
|
+
const cs = deps.checkpointStore;
|
|
1988
|
+
if (!deps.runStore) {
|
|
1989
|
+
return { status: 501, body: { error: "delivering a workflow decision on the host's next turn requires a durable run store (DB_BACKEND=mysql|pg|local)", errorCode: "capability.run_store_required" } };
|
|
1990
|
+
}
|
|
1991
|
+
const ctx = await cs.getCtx(sessionId).catch(() => null);
|
|
1992
|
+
if (!ctx) {
|
|
1993
|
+
return {
|
|
1994
|
+
status: 409,
|
|
1995
|
+
body: {
|
|
1996
|
+
error: "the workflow's originating session no longer has the rebuild inputs for a new turn: a session's resume context is garbage-collected once it is stale AND that session itself has no pending checkpoint and no active run (REAP_RUN_STALE_SEC, default 120s) — a workflow CHILD's park does not hold the HOST session's row open. Raise REAP_RUN_STALE_SEC to cover how long approvals may sit before someone decides them, or resume the workflow run yourself with the runId below",
|
|
1997
|
+
errorCode: "conflict.resume_context_unavailable",
|
|
1998
|
+
runId,
|
|
1999
|
+
},
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
const auth = { sessionId, principal, memoryScope: ctx.memoryScope };
|
|
2003
|
+
let spec;
|
|
2004
|
+
let engineNotices = [];
|
|
2005
|
+
try {
|
|
2006
|
+
({ spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth));
|
|
2007
|
+
}
|
|
2008
|
+
catch (e) {
|
|
2009
|
+
if (e instanceof HttpError) {
|
|
2010
|
+
return { status: 409, body: { error: `resume blocked by an admission policy change: ${e.message}`, errorCode: "resume_blocked_by_policy", ...(e.code ? { blockedBy: e.code } : {}), ...(e.extra ?? {}) } };
|
|
2011
|
+
}
|
|
2012
|
+
throw e;
|
|
2013
|
+
}
|
|
2014
|
+
const hostSpec = { ...spec, objective, sessionId };
|
|
2015
|
+
const out = await mintAndDriveDurableRun(routeCtxBase, deps.runStore, {
|
|
2016
|
+
prepared: { spec: hostSpec, runner: runnerFor(hostSpec), auth, body: ctx.body, ...(engineNotices.length > 0 ? { engineNotices } : {}) },
|
|
2017
|
+
fleetScope: resumeFleetScope(req, auth),
|
|
2018
|
+
elicitOwner: principal ?? null,
|
|
2019
|
+
source: "workflow_parked_decide",
|
|
2020
|
+
trustedInternals,
|
|
2021
|
+
});
|
|
2022
|
+
if (out.status !== 202)
|
|
2023
|
+
return { status: out.status, body: out.body };
|
|
2024
|
+
const minted = out.body;
|
|
2025
|
+
return acceptedResumingReceipt(minted.taskId, minted.sessionId);
|
|
2026
|
+
}
|
|
2027
|
+
async function resumeWake(sessionId, message, caller, req, acceptEarly, workflowParkedResume, ownerLeaseAdmittedFor) {
|
|
1979
2028
|
const cs = deps.checkpointStore;
|
|
1980
2029
|
const wakeBinding = { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" };
|
|
1981
2030
|
const token = (await cs.findPendingTokenBySession(sessionId, undefined, wakeBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
|
|
@@ -2046,6 +2095,7 @@ export function createHttpServer(rawDeps) {
|
|
|
2046
2095
|
...(acceptEarly === true ? { acceptEarly: true } : {}),
|
|
2047
2096
|
...(req !== undefined ? { callerInitiated: true } : {}),
|
|
2048
2097
|
...(workflowParkedResume !== undefined ? { workflowParkedResume } : {}),
|
|
2098
|
+
...(ownerLeaseAdmittedFor !== undefined && auth.principal === ownerLeaseAdmittedFor ? { ownerLeaseAdmitted: true } : {}),
|
|
2049
2099
|
});
|
|
2050
2100
|
}
|
|
2051
2101
|
async function resumePlanReview(sessionId, decision, editedPlan, reason, req, log, acceptEarly, decider) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { loadConfig, type ServiceConfig } from "./config.js";
|
|
2
2
|
export { createBrain } from "./brain.js";
|
|
3
3
|
export { createSessionStore } from "./plugins/session-store.js";
|
|
4
|
-
export { createStoreBackend, type StoreBackend } from "./plugins/store-backend.js";
|
|
4
|
+
export { createStoreBackend, type StoreBackend, type SessionCaptureRecordSource } from "./plugins/store-backend.js";
|
|
5
5
|
export { CachingSessionStore, type CachingOptions } from "./plugins/caching-session-store.js";
|
|
6
6
|
export type { RunRecord, RunEvent } from "./plugins/store-contracts.js";
|
|
7
7
|
export { runInBackground } from "./runs.js";
|
package/dist/leader/wire.js
CHANGED
|
@@ -6,6 +6,7 @@ import { join } from "node:path";
|
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { Runner, combinePolicies, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, delimitUntrusted, hasDestroy, } from "@sema-agent/core";
|
|
8
8
|
import { RemoteContainerExecutionEnv } from "../plugins/remote-env-e2b.js";
|
|
9
|
+
import { E2B_SANDBOX_HOME } from "../execution-lane-caps.js";
|
|
9
10
|
import { createDurableAskPolicy } from "../approval.js";
|
|
10
11
|
import { presignS3Url } from "../plugins/s3-presign.js";
|
|
11
12
|
import { buildUploadScript, uploadStepSuffix, fetchUploadedDiff, withStaging } from "./diffup.js";
|
|
@@ -376,7 +377,7 @@ export function createLeaderRunner(cfg) {
|
|
|
376
377
|
}
|
|
377
378
|
if (!cfg.e2bApiKey)
|
|
378
379
|
throw new Error("leader wire: no envFactory+s3 (factory mode) and no e2bApiKey (static mode)");
|
|
379
|
-
const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs });
|
|
380
|
+
const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs, homeDir: E2B_SANDBOX_HOME });
|
|
380
381
|
if (!(await env.connect()).ok)
|
|
381
382
|
throw new Error(`connect failed for ${sub.workerId}`);
|
|
382
383
|
await sh(env)(`${body.seedCmd}; cd ${repo}; git checkout -q -b ${sub.branch}`);
|
|
@@ -410,7 +411,7 @@ export function createLeaderRunner(cfg) {
|
|
|
410
411
|
}
|
|
411
412
|
if (!cfg.e2bApiKey)
|
|
412
413
|
throw new Error("leader wire: no envFactory and no e2bApiKey");
|
|
413
|
-
const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs });
|
|
414
|
+
const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs, homeDir: E2B_SANDBOX_HOME });
|
|
414
415
|
if (!(await env.connect()).ok) {
|
|
415
416
|
await env.destroy().catch(() => { });
|
|
416
417
|
throw new Error("integ connect failed");
|
|
@@ -449,7 +450,7 @@ export function createLeaderRunner(cfg) {
|
|
|
449
450
|
else {
|
|
450
451
|
if (!cfg.e2bApiKey)
|
|
451
452
|
throw new Error("leader wire: repair-loop grader needs an envFactory or e2bApiKey");
|
|
452
|
-
const e2b = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs });
|
|
453
|
+
const e2b = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs, homeDir: E2B_SANDBOX_HOME });
|
|
453
454
|
if (!(await e2b.connect()).ok) {
|
|
454
455
|
await e2b.destroy().catch(() => { });
|
|
455
456
|
throw new Error("repair-loop grader connect failed");
|
package/dist/main.js
CHANGED
|
@@ -7,7 +7,7 @@ import { posIntEnv } from "./session-watch.js";
|
|
|
7
7
|
import { selectEnvironmentTool } from "./capabilities/select-environment-tool.js";
|
|
8
8
|
import { subagentSendUserFileExtraTools } from "./capabilities/send-user-file-tool.js";
|
|
9
9
|
import { brainSummary } from "./brain.js";
|
|
10
|
-
import { loadConfig, logConfigDiagnostics, resolveBindHost, splitLocalRoots, parseCapEnv, parseNumOrFailNonNegative } from "./config.js";
|
|
10
|
+
import { buildUnpricedCeilingNotice, loadConfig, logConfigDiagnostics, resolveBindHost, splitLocalRoots, parseCapEnv, parseNumOrFailNonNegative } from "./config.js";
|
|
11
11
|
import { buildWriteProtectionPosture } from "./write-protection.js";
|
|
12
12
|
import { cardRuleScopeRoot } from "./task-cwd.js";
|
|
13
13
|
import { isPricingConfigured } from "./budget.js";
|
|
@@ -129,9 +129,13 @@ async function main() {
|
|
|
129
129
|
}
|
|
130
130
|
const configCenter = await createConfigCenterRuntime({ config, logger, metrics, localRoot });
|
|
131
131
|
await configCenter.applyLocalRemoteExec();
|
|
132
|
+
const unpricedCeiling = buildUnpricedCeilingNotice(config.models, config);
|
|
133
|
+
if (unpricedCeiling)
|
|
134
|
+
logger.warn(unpricedCeiling.event, unpricedCeiling.fields);
|
|
132
135
|
const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, usageWindowStore, memoryPosture, taskListLane, sqlEngineFacts, } = await openStores({ config, logger, metrics, localRoot });
|
|
133
136
|
const instanceId = uuidv7();
|
|
134
|
-
const
|
|
137
|
+
const sessionCaptureRecords = backend?.sessionCaptureRecords();
|
|
138
|
+
const sessionCaptureRecordStore = sessionCaptureRecords?.();
|
|
135
139
|
const memoryBundleFaces = memoryEngine ? createMemoryBundleFaces(memoryEngine, { ...(sessionCaptureRecordStore !== undefined ? { captureRecordStore: sessionCaptureRecordStore } : {}) }) : undefined;
|
|
136
140
|
const memoryComplianceFaces = memoryEngine
|
|
137
141
|
? createMemoryComplianceFaces(memoryEngine, {
|
|
@@ -271,7 +275,7 @@ async function main() {
|
|
|
271
275
|
sessionShellGateExplicitlyOff: sessionShellGateRegistry.explicitlyOff,
|
|
272
276
|
config, logger, metrics, localRoot, promptSource: configCenter.promptSource, rosterStore, backgroundAgentStore, mailboxStore, usageWindowStore, brain,
|
|
273
277
|
pricing, tracer, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine,
|
|
274
|
-
|
|
278
|
+
sessionCaptureRecords,
|
|
275
279
|
memorySyncRunner, toolResultStore: runnerOffloadStore, checkpointStore, sessionPolicyStore, runtimeCapsResolver, fileHistoryStore,
|
|
276
280
|
legacyRewindBoundaryProbe: backend?.legacyRewindBoundaryProbe ? backend.legacyRewindBoundaryProbe.bind(backend) : undefined,
|
|
277
281
|
permissionRuleStore: permissionRuleProvider,
|
|
@@ -330,6 +334,7 @@ async function main() {
|
|
|
330
334
|
const subRunnerSessions = new ForkRoutingSessionStore(sessionStore, new TtlSessionStore({ defaultTtlDays: 1 / 24 }));
|
|
331
335
|
const subRunnerDeps = {
|
|
332
336
|
...createSharedRunnerDeps({
|
|
337
|
+
metrics,
|
|
333
338
|
config,
|
|
334
339
|
logger,
|
|
335
340
|
hands: commitHands,
|
|
@@ -227,7 +227,7 @@ export interface SessionMemoryStatusFace {
|
|
|
227
227
|
*
|
|
228
228
|
* `captureRecordStore` = [ref]② 的 SQL 载体(main.ts 单实例,与 Runner/其余引擎同源 —— 全站同批注入律,
|
|
229
229
|
* 机器钉 capture-optout-lane ②):`captureOptedOut` / `optOutSource` 两键经它读,多副本部署上答的才是
|
|
230
|
-
*
|
|
230
|
+
* 跨副本的真记录。S-215 起 local 车道在引擎接线时也供(core 文件三腿,同一只控制面,单机字节不变);缺席 ⇒ 记忆面暗。
|
|
231
231
|
*
|
|
232
232
|
* 面**不抛**(core 契约:sessionMemoryStatus never throws —— 每个事实源各自 catch 折缺席键);
|
|
233
233
|
* 不接 onIncident:这条只读路径上今天零 sink 站点,而且缺席键本身就是它的披露形。
|
package/dist/memory-scope.d.ts
CHANGED
|
@@ -104,6 +104,30 @@ export declare function memoryEngineRootFor(config: Pick<ServiceConfig, "memoryE
|
|
|
104
104
|
* 所以它的根必须走这条链而不是 `memoryEngineRootFor` 的分派(codex 修复验证轮 [medium]:分派会把 run-local 的
|
|
105
105
|
* 记忆搬到 `<localDataRoot>/memory-work`,存量记忆与控制面升级后隐形)。 */
|
|
106
106
|
export declare function fileMemoryEngineRoot(config: Pick<ServiceConfig, "memoryEngineDir">, fallbackRoot?: string): string;
|
|
107
|
+
/** 本部署 file 记忆引擎的**平面三坐标**(根 / 记忆目录 / 控制面目录)—— {@link fileMemoryPlaneFor} 一次算齐。 */
|
|
108
|
+
export interface FileMemoryPlane {
|
|
109
|
+
readonly root: string;
|
|
110
|
+
readonly memoryDir: string;
|
|
111
|
+
readonly controlDir: string;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* S-215(B-085)—— **file 记忆引擎平面的单铸点**,兼这条腿的**接线门**。
|
|
115
|
+
*
|
|
116
|
+
* 为什么是一只函数而不是两处各自推导:平面坐标有**两个**消费者 —— ① {@link memoryEngineBackendFor}
|
|
117
|
+
* 钉给 `FileMemoryEngineBackend` 的 `memoryDir` + `controlDir`;② local 车道的 capture opt-out **记录店**
|
|
118
|
+
* (`StoreBackend.sessionCaptureRecords()` 喂 core 的 `fileSessionCaptureRecordStore(controlDir)`)。
|
|
119
|
+
* 两者必须是**同一只目录**,否则声明写进 A、引擎读 B ⇒ 一条被「受理」的隐私声明在引擎面根本不存在,
|
|
120
|
+
* 那比一句诚实的 409 更坏。两处各写一份推导链 = 哪天根链改了只改一处的必然漂移,所以推导(根 → 记忆目录 →
|
|
121
|
+
* 控制面目录,三步都在这里、只写一遍)与门合并在此(合并复审 R37:此前 `join(root, "memory")` 在本函数与
|
|
122
|
+
* `memoryEngineBackendFor` 各写一份 —— 记忆目录规则有了第二个写者,现已收回一处)。
|
|
123
|
+
*
|
|
124
|
+
* 返回 `undefined` ⇔ 本部署上 file 记忆引擎**不接线**(多租户 / `MEMORY_ENGINE=off` / 平面分裂车道未 allow)。
|
|
125
|
+
* 门与 `memoryEngineBackendFor` 逐字同源(它就读本函数),所以「有引擎 ⇔ 有控制面 ⇔ 供记录店」三者
|
|
126
|
+
* 在构造上不可能分家。
|
|
127
|
+
*/
|
|
128
|
+
export declare function fileMemoryPlaneFor(config: ServiceConfig, fallbackRoot?: string): FileMemoryPlane | undefined;
|
|
129
|
+
/** 控制面目录那一坐标(local 车道 capture 记录店的依托)—— 只是 {@link fileMemoryPlaneFor} 的投影,不是第二条推导。 */
|
|
130
|
+
export declare function fileMemoryControlDirFor(config: ServiceConfig, fallbackRoot?: string): string | undefined;
|
|
107
131
|
export declare function memoryEngineBackendFor(config: ServiceConfig, fallbackRoot?: string): {
|
|
108
132
|
backend: FileMemoryEngineBackend;
|
|
109
133
|
root: string;
|
package/dist/memory-scope.js
CHANGED
|
@@ -68,14 +68,23 @@ export function memoryEngineRootFor(config, fallbackRoot) {
|
|
|
68
68
|
export function fileMemoryEngineRoot(config, fallbackRoot) {
|
|
69
69
|
return resolveMemoryEngineRoot(config.memoryEngineDir ?? fallbackRoot);
|
|
70
70
|
}
|
|
71
|
-
export function
|
|
71
|
+
export function fileMemoryPlaneFor(config, fallbackRoot) {
|
|
72
72
|
if (config.requirePrincipal === true || !config.memoryEngineEnabled)
|
|
73
73
|
return undefined;
|
|
74
74
|
if (memoryEngineRemoteLanePosture(config)?.posture === "dark")
|
|
75
75
|
return undefined;
|
|
76
76
|
const root = fileMemoryEngineRoot(config, fallbackRoot);
|
|
77
|
-
const
|
|
78
|
-
return {
|
|
77
|
+
const memoryDir = join(root, "memory");
|
|
78
|
+
return { root, memoryDir, controlDir: deriveControlPlaneDir(root, memoryDir) };
|
|
79
|
+
}
|
|
80
|
+
export function fileMemoryControlDirFor(config, fallbackRoot) {
|
|
81
|
+
return fileMemoryPlaneFor(config, fallbackRoot)?.controlDir;
|
|
82
|
+
}
|
|
83
|
+
export function memoryEngineBackendFor(config, fallbackRoot) {
|
|
84
|
+
const plane = fileMemoryPlaneFor(config, fallbackRoot);
|
|
85
|
+
if (plane === undefined)
|
|
86
|
+
return undefined;
|
|
87
|
+
return { backend: new FileMemoryEngineBackend(plane.memoryDir, { controlDir: plane.controlDir }), root: plane.root };
|
|
79
88
|
}
|
|
80
89
|
export function memorySpecForRequest(scope, memoryWrite, defaultScopes, originPolicy, memoryCapture) {
|
|
81
90
|
if (!scope)
|
|
@@ -19,7 +19,7 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
19
19
|
};
|
|
20
20
|
readonly "server.trace.gate-outcome-defective": {
|
|
21
21
|
readonly cls: "F";
|
|
22
|
-
readonly note: "S-136(core 7.6.0 design/390 S6-A,`trace/project.ts` 的 `screenedGate
|
|
22
|
+
readonly note: "S-136(core 7.6.0 design/390 S6-A,`trace/project.ts` 的 `screenedGate`:写腿 `toolEndEventData` 直用;两条耐久读腿(账本回放 `toolResultFieldsOf` / `/decide` 幂等回放 `executionOutcome`)经 `screenedStoredGate` 认过退役词旧拼法后同走这一只筛):一条 `tool_end` / 账本行携带的 `GateOutcome` 过不了 core 的 `screenGateOutcome`(I1–I4 不变量,或某一格词出闭集)⇒ **整条 gate 记录不上帧**(report + withhold,core 契约逐字给出的两种响亮形之一;绝不「静默修补」成一条自洽的记录)。放行的最坏后果=这一帧的消费端渲染不出「谁拒的 / 那次等待怎么结束的」,退回到 `isError` + 文案;它**不改变任何裁决**(门早已在 core 里判完,这里只是观测面)。反方向(把一条自相矛盾的记录照发)更坏:消费端按 I2/I3 读出来的结论会是假的。计数让「某个世代的产生者一直在发坏记录」显形。";
|
|
23
23
|
};
|
|
24
24
|
readonly "server.trace.tool-roster-malformed": {
|
|
25
25
|
readonly cls: "F";
|
|
@@ -10,7 +10,7 @@ export const FAIL_OPEN_TAGS = {
|
|
|
10
10
|
},
|
|
11
11
|
"server.trace.gate-outcome-defective": {
|
|
12
12
|
cls: "F",
|
|
13
|
-
note: "S-136(core 7.6.0 design/390 S6-A,`trace/project.ts` 的 `screenedGate
|
|
13
|
+
note: "S-136(core 7.6.0 design/390 S6-A,`trace/project.ts` 的 `screenedGate`:写腿 `toolEndEventData` 直用;两条耐久读腿(账本回放 `toolResultFieldsOf` / `/decide` 幂等回放 `executionOutcome`)经 `screenedStoredGate` 认过退役词旧拼法后同走这一只筛):一条 `tool_end` / 账本行携带的 `GateOutcome` 过不了 core 的 `screenGateOutcome`(I1–I4 不变量,或某一格词出闭集)⇒ **整条 gate 记录不上帧**(report + withhold,core 契约逐字给出的两种响亮形之一;绝不「静默修补」成一条自洽的记录)。放行的最坏后果=这一帧的消费端渲染不出「谁拒的 / 那次等待怎么结束的」,退回到 `isError` + 文案;它**不改变任何裁决**(门早已在 core 里判完,这里只是观测面)。反方向(把一条自相矛盾的记录照发)更坏:消费端按 I2/I3 读出来的结论会是假的。计数让「某个世代的产生者一直在发坏记录」显形。",
|
|
14
14
|
},
|
|
15
15
|
"server.trace.tool-roster-malformed": {
|
|
16
16
|
cls: "F",
|
|
@@ -45,6 +45,10 @@ export interface MetricsSummary {
|
|
|
45
45
|
rateLimited: number;
|
|
46
46
|
costQuotaRejected: number;
|
|
47
47
|
budgetExceeded: Record<string, number>;
|
|
48
|
+
/** 答后撞顶(walltime/turns)的计数。⚠️ 与 {@link budgetExceeded} **口径不同**:本枚在 deps 级通告席上铸,
|
|
49
|
+
* 覆盖**每一条**引擎 run(后台子代 / 委派嵌套腿 / leader 车道);`budgetExceeded` 只由 HTTP 提交腿的
|
|
50
|
+
* `finalizeTaskResult` 递增(= 顶层 HTTP 提交的 run)。两枚相加只在后者那个较窄的总体里成立。 */
|
|
51
|
+
limitCeilingAfterAnswer: Record<string, number>;
|
|
48
52
|
degraded: Record<string, number>;
|
|
49
53
|
degenerate: Record<string, number>;
|
|
50
54
|
memoryConsolidationOps: Record<string, number>;
|
|
@@ -174,6 +174,7 @@ export class Metrics {
|
|
|
174
174
|
rateLimited: ctotal("rate_limited_total"),
|
|
175
175
|
costQuotaRejected: ctotal("cost_quota_rejected_total"),
|
|
176
176
|
budgetExceeded: cby("budget_exceeded_total", "code"),
|
|
177
|
+
limitCeilingAfterAnswer: cby("limit_ceiling_after_answer_total", "origin"),
|
|
177
178
|
degraded: cby("degraded_total", "reason"),
|
|
178
179
|
degenerate: cby("degenerate_total", "class"),
|
|
179
180
|
memoryConsolidationOps: cby("memory_consolidation_ops_total", "op"),
|
|
@@ -215,6 +216,7 @@ export function createMetrics() {
|
|
|
215
216
|
]);
|
|
216
217
|
m.counter("tool_calls_total", "Tool invocations by name and outcome");
|
|
217
218
|
m.counter("budget_exceeded_total", "Tasks failed by a budget/limit ceiling, by errorCode");
|
|
219
|
+
m.counter("limit_ceiling_after_answer_total", "Runs where a walltime/turns ceiling fired AFTER the run had already answered (engine 7.13.0: these end completed with NO limits.* code). Dual of budget_exceeded_total, but NOT over the same population: this one is minted on the deps-level notice sink so it sees EVERY engine run (background children, delegated/nested legs, leader lanes); budget_exceeded_total is incremented only by the HTTP submit legs finalizeTaskResult, i.e. top-level HTTP-submitted runs. Sum them only within that narrower population; see the forwarder comment, by origin");
|
|
218
220
|
m.counter("cost_quota_rejected_total", "Tasks refused because the principal is over its cumulative cost quota");
|
|
219
221
|
m.counter("degraded_total", "Tasks degraded to a cheaper model (1.40), by reason (budget/rate_limit/breaker_open)");
|
|
220
222
|
m.counter("task_model_not_allowlisted_resume_total", "Resumed legs whose model/compactionModel is outside the @-mention allowlist (allowed to keep running by design — the allowlist gates fresh picks only)");
|
|
@@ -261,7 +263,7 @@ export function createMetrics() {
|
|
|
261
263
|
m.counter("stale_cache_conflict_total", "Warm session-cache entries evicted by a cross-instance write while cached (S25)");
|
|
262
264
|
m.counter("breaker_writethrough_failed_total", "Circuit-breaker cross-replica write-through failures (LOW), by backend");
|
|
263
265
|
m.counter("web_search_bad_payload_total", "Web-search provider responses whose results field was not an array (LOW), by provider");
|
|
264
|
-
m.counter("
|
|
266
|
+
m.counter("auto_mode_classifier_deny_cause_total", "Auto-mode gate denials where the classifier did NOT rule, by the FORM of the deny (core CLASSIFIER_DENY_CAUSES on GateDisposition.cause: 'unavailable' = the classifier could not run at all (threw/rejected/ran past the cap — retryable), 'parse_error' = it ran but answered outside its verdict contract (blocked for safety); 'other' = this build cannot name the word). 🔴 BREAKING in server 7.72.0: this counter was named auto_mode_classifier_unavailable_total through 7.71.0 and is RENAMED with NO alias (a counter that also counts parse_error is not an 'unavailable' counter); its label vocabulary was 'error'/'timeout' (WHY it was unavailable) — those two collapsed into 'unavailable' (the WHY now rides the deny sentence and the auto_mode.classified trace frame), and 'parse_error' is a bucket the old counter could not see, so by-cause series do NOT splice across the upgrade. Distinct from permission_denied_total{deniedBy=\"classifier\"}, which also counts genuine safety blocks (the classifier's own <block> ruling carries NO cause) — a spike here means the classifier itself is not working (gateway/credential fault, too slow, or answering off-contract), not that the model kept asking for dangerous things");
|
|
265
267
|
m.counter("permission_denied_total", "Tool-gate denials by the LAYER whose verdict is the deny (core's DeniedBy: policy/hook/org/classifier/plan_mode/compliance/write_protection/ask_resolution/persisted_rule; 'other' = this build cannot name the layer; label is `deniedBy` since server 7.64.0 — it was `source` over core's retired PermissionDeniedSource; goal B4 always-on deny meter)");
|
|
266
268
|
m.counter("permission_rule_events_total", buildPermissionRuleEventsHelp());
|
|
267
269
|
m.counter("compaction_events_total", "Compaction lifecycle events by outcome (started/completed/failed/skipped) and trigger");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type RunRecord } from "../plugins/store-contracts.js";
|
|
2
2
|
/** 一次终局写口交给观测面的原料(store 侧装配,**未脱敏** —— 脱敏在本模块内做,见 {@link notifyRunTerminal})。 */
|
|
3
3
|
export interface RunTerminalRecord {
|
|
4
4
|
/** 这条 run 的账本 id = `GET /v1/runs/:id` 的 id(本仓 run 行主键名为 taskId,同一个值)。 */
|
|
@@ -20,8 +20,9 @@ export type RunTerminalObserver = (level: "error" | "info", fields: Record<strin
|
|
|
20
20
|
/**
|
|
21
21
|
* 终局行的级别判据。`undefined` = 不打行。
|
|
22
22
|
*
|
|
23
|
-
* `blocked`
|
|
24
|
-
* `
|
|
23
|
+
* 一句话:**非成功终局才出声**(`failed` / `blocked`),用户主动取消降 `info`。词的分类不在本模块做 ——
|
|
24
|
+
* 它的唯一属主是 `plugins/store-contracts.ts` 的 `RUN_STATUS_CLASS` 表(S-213③:本模块此前手抄了一张
|
|
25
|
+
* 穷举 switch,并在 `blocked` 那一格给出与 fleet 读面互斥的答案)。表的编译期双向钉在属主处。
|
|
25
26
|
*/
|
|
26
27
|
export declare function runTerminalLogLevel(status: RunRecord["status"], errorCode: string | undefined): "error" | "info" | undefined;
|
|
27
28
|
/** 装配点(`main.ts` 唯一)。传 `undefined` 卸载(测试隔离)。 */
|
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { redactSecrets } from "../trace/redact.js";
|
|
3
3
|
import { isParkLedgerEventType } from "../trace/ledger-events.js";
|
|
4
|
+
import { isUnsuccessfulRunStatus } from "../plugins/store-contracts.js";
|
|
4
5
|
import { errorText } from "./err-text.js";
|
|
5
6
|
import { recordFailOpen } from "./fail-open.js";
|
|
6
7
|
const MESSAGE_CAP = 1024;
|
|
7
8
|
export function runTerminalLogLevel(status, errorCode) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
case "blocked":
|
|
12
|
-
case "completed":
|
|
13
|
-
case "running":
|
|
14
|
-
case "suspended":
|
|
15
|
-
case "needs_review":
|
|
16
|
-
return undefined;
|
|
17
|
-
default: {
|
|
18
|
-
const unhandled = status;
|
|
19
|
-
void unhandled;
|
|
20
|
-
return undefined;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
9
|
+
if (!isUnsuccessfulRunStatus(status))
|
|
10
|
+
return undefined;
|
|
11
|
+
return errorCode === "cancelled" ? "info" : "error";
|
|
23
12
|
}
|
|
24
13
|
let observer;
|
|
25
14
|
export function setRunTerminalObserver(fn) {
|
|
@@ -15,7 +15,8 @@ import type { Metrics } from "./metrics.js";
|
|
|
15
15
|
* (`policy/hook/safety/shellGate/planMode/classifier/org`)换成八词
|
|
16
16
|
* (`policy/hook/org/classifier/plan_mode/compliance/write_protection/ask_resolution`)。仪表盘/告警规则要改。
|
|
17
17
|
*
|
|
18
|
-
* 🔴 **第二个计数器 `
|
|
18
|
+
* 🔴 **第二个计数器 `auto_mode_classifier_deny_cause_total{cause}`(server 7.70.0 生为
|
|
19
|
+
* `auto_mode_classifier_unavailable_total`,core 7.12.0 [ref];**7.72.0 / core 7.14.0 [ref] 改名、换座、换词**,见本段末)**
|
|
19
20
|
* —— 分类器**停摆**的常开信号。7.12.0 之前这件事有两条可观测路:熔断跳闸时的
|
|
20
21
|
* `auto_mode_breaker_open_total`,以及「`unavailable` 的 ask 会走到人面前」(审批 inbox 里看得见)。
|
|
21
22
|
* 该版把熔断族整族退役、又把 `unavailable` 改成**当场 deny** ⇒ 两条路同时没了,而
|
|
@@ -26,6 +27,22 @@ import type { Metrics } from "./metrics.js";
|
|
|
26
27
|
* 两个计数刻意**不合并成一个标签**:它们答两个问题(「谁拒的」vs「这次拒是不是因为分类器停摆、哪一类」),
|
|
27
28
|
* 而运维对停摆要立刻动手(改配置 / 等重试 / 看后端),对安全拦截不要。
|
|
28
29
|
* ⛔ 纯计量:不改任何裁决、不新开观察面、事实缺席时**不铸**(缺席 ≠ 「分类器好着呢」,只是这次拒不是它)。
|
|
30
|
+
*
|
|
31
|
+
* 🔴 **7.72.0(core 7.14.0 [ref] C-a)—— 同一只计数器换了名、换了座、也换了词表,运维面 BREAKING**:
|
|
32
|
+
* · **名**(合并复审 R2):`auto_mode_classifier_unavailable_total` ⇒ `auto_mode_classifier_deny_cause_total`,
|
|
33
|
+
* **无别名** —— 一只也数 `parse_error`(分类器跑了、答得出格)的计数器再叫 unavailable 就是名不副实;
|
|
34
|
+
* 与下面两条是同一次运维 BREAKING,只痛一次。
|
|
35
|
+
* · **座**:`payload.classifierUnavailable.cause`(整族退役)⇒ `payload.gate.disposition.cause`。同一条
|
|
36
|
+
* 门记录的三张脸(`tool_end.gate` / 本观察者的 `payload.gate` / 行的 `resolvedOutcome`)core 一次铸、
|
|
37
|
+
* 三处投,所以这一位与 `deniedBy` 现在**读自同一只对象**——两个标签不可能再各说各话。
|
|
38
|
+
* · **词表**:`AUTO_MODE_UNAVAILABLE_CAUSES`(`error` / `timeout` = 分类器**为什么**没跑成)
|
|
39
|
+
* ⇒ `CLASSIFIER_DENY_CAUSES`(`unavailable` / `parse_error` = **这次 deny 是什么形**)。
|
|
40
|
+
* ⚠️ 两代不是同一个问题的两组词:`unavailable` 覆盖了旧的 error+timeout 两格(「为什么」降级成散文,
|
|
41
|
+
* 只在 deny 句与 `auto_mode.classified` 诊断帧上),而 `parse_error`(分类器跑了、但答得出格 ⇒ 为安全
|
|
42
|
+
* 起见拦下)是**旧计数器根本数不到**的一格。看板/告警的 `cause=` 过滤要改,按旧词 by-cause 分组的
|
|
43
|
+
* 历史序列与新序列**不可直接拼**。
|
|
44
|
+
* · **计数总量方向**:新座只在「分类器那一轮没有裁决」时在场(`<block>yes` 的真裁决恒缺席),与旧座的
|
|
45
|
+
* 「分类器停摆」同向;多出来的那一格是 `parse_error`,它也确实是「分类器没正常工作」的一种。
|
|
29
46
|
*/
|
|
30
47
|
export declare function createPermissionDeniedMeter(metrics: Pick<Metrics, "inc">): Hooks;
|
|
31
48
|
/**
|