@sema-agent/server 7.112.0 → 7.113.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.
Files changed (66) hide show
  1. package/MIGRATION.md +60 -0
  2. package/USAGE.md +3 -3
  3. package/dist/approval-ask-audit-store.d.ts +3 -4
  4. package/dist/approval-ask-audit-store.js +3 -3
  5. package/dist/approval-card.d.ts +4 -4
  6. package/dist/boot/coordinators.js +1 -1
  7. package/dist/boot/stores.js +4 -6
  8. package/dist/boot/workflow-orchestration.js +4 -4
  9. package/dist/config-types.d.ts +1 -1
  10. package/dist/config.js +2 -1
  11. package/dist/hooks/hook-runner.d.ts +2 -2
  12. package/dist/hooks/hook-runner.js +4 -4
  13. package/dist/host-decision.d.ts +5 -3
  14. package/dist/http/admission.js +6 -2
  15. package/dist/http/request-guards.js +2 -2
  16. package/dist/http/resume-legs.d.ts +9 -10
  17. package/dist/http/resume-legs.js +64 -18
  18. package/dist/http/route-ctx.d.ts +38 -8
  19. package/dist/http/route-ctx.js +4 -0
  20. package/dist/http/routes/background.js +2 -2
  21. package/dist/http/routes/mcp-reconnect.js +2 -2
  22. package/dist/http/routes/notify-wake.js +3 -3
  23. package/dist/http/routes/runs.js +19 -13
  24. package/dist/http/routes/session-sync.js +6 -6
  25. package/dist/http/routes/sessions.js +20 -20
  26. package/dist/http/routes/tasks.js +1 -0
  27. package/dist/http/send.d.ts +1 -1
  28. package/dist/http/send.js +1 -1
  29. package/dist/http/wire-types.d.ts +1 -1
  30. package/dist/local-data-root.d.ts +14 -0
  31. package/dist/local-data-root.js +5 -0
  32. package/dist/observability/corrupt-read-seat.d.ts +46 -66
  33. package/dist/observability/corrupt-read-seat.js +74 -59
  34. package/dist/observability/fail-open.d.ts +24 -0
  35. package/dist/observability/fail-open.js +24 -0
  36. package/dist/observability/run-terminal.js +39 -0
  37. package/dist/orchestration/workflow-agent-session-index.d.ts +1 -5
  38. package/dist/orchestration/workflow-agent-session-index.js +2 -2
  39. package/dist/orchestration/workflow-notify-journal.d.ts +1 -3
  40. package/dist/orchestration/workflow-notify-journal.js +2 -2
  41. package/dist/plugins/approval-ask-store-file.d.ts +1 -4
  42. package/dist/plugins/approval-ask-store-file.js +2 -2
  43. package/dist/plugins/file-resume-anchor-store.d.ts +1 -6
  44. package/dist/plugins/file-resume-anchor-store.js +2 -4
  45. package/dist/plugins/file-run-store.d.ts +0 -4
  46. package/dist/plugins/file-run-store.js +1 -3
  47. package/dist/plugins/send-file-ledger.d.ts +1 -5
  48. package/dist/plugins/send-file-ledger.js +2 -2
  49. package/dist/plugins/store-backend.js +8 -9
  50. package/dist/plugins/user-input-ledger-file.d.ts +1 -5
  51. package/dist/plugins/user-input-ledger-file.js +2 -2
  52. package/dist/run-local.d.ts +1 -1
  53. package/dist/run-local.js +2 -2
  54. package/dist/runs.js +1 -0
  55. package/dist/spec-fields.js +0 -1
  56. package/dist/tool-approval.d.ts +1 -1
  57. package/dist/trace/core-keyset-guard.d.ts +1 -1
  58. package/dist/trace/project.js +2 -3
  59. package/dist/trace/redact.d.ts +18 -0
  60. package/dist/trace/redact.js +4 -0
  61. package/dist/unattended-permission-mode.d.ts +9 -2
  62. package/dist/unattended-permission-mode.js +5 -1
  63. package/dist/user-input-ledger.d.ts +24 -5
  64. package/dist/user-input-ledger.js +46 -11
  65. package/package.json +3 -2
  66. package/scripts/retired-mode-seed-drain-local.mjs +317 -0
@@ -72,8 +72,10 @@ export interface RouteHelpers {
72
72
  usageWindowDenied(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
73
73
  safeDecode(seg: string): string | null;
74
74
  isFleetWide(req: IncomingMessage): boolean;
75
- /** 属主门。`notFoundForm` 选 not-yours 臂的 404 拼法:缺省 `"run"`(not_found.run 长文,[ref]);session
76
- * 资源面传 `"session"`(not_found.session + "session not found")与其 unknown 臂同码同串(G52 [ref])。 */
75
+ /** 属主门。`notFoundForm` 选 not-yours 臂的 404 拼法:缺省 `"run"`(not_found.run 长文,[ref]);**每一个 session
76
+ * 资源面都必须传 `"session"`**(not_found.session + {@link SESSION_NOT_FOUND_MESSAGE})与其 unknown 臂同码同串
77
+ * (G52 [ref];S-989 补上了 detail / head / events 三个读口 —— 它们此前吃缺省 "run",单端点内 unknown / not-yours
78
+ * 两臂码与串都不同)。缺省 `"run"` 的语义不变:run 面不传。 */
77
79
  runOwnerOk(req: IncomingMessage, res: ServerResponse, owner: string | null, notFoundForm?: "run" | "session"): boolean;
78
80
  sessionOwnerScope(req: IncomingMessage): {
79
81
  fleetWide: boolean;
@@ -408,10 +410,7 @@ export interface RouteLegs {
408
410
  * 顶注)。HTTP `/wake` 腿**不设**此位 —— 它是 `/decide` 第三条车道的专用席,而两条腿共用这一条驱动
409
411
  * 路径正是 `notify-wake.ts` 那条「wake 会烧模型,所以复用 resume 家族的同一条腿、不自建第二条驱动
410
412
  * 路径」的成文纪律。 */
411
- workflowParkedResume?: DriveResumeArgs["workflowParkedResume"]): Promise<{
412
- status: number;
413
- body: object;
414
- }>;
413
+ workflowParkedResume?: DriveResumeArgs["workflowParkedResume"]): Promise<HostLegOutcome>;
415
414
  /**
416
415
  * assistant plan_review 三态腿的**纯读段**(S-609,7.97.0):选卡 → 属主轻读 → 载入 → 行级 scope → 门类 → 这条决断
417
416
  * 唯一的一次 ctx 读。任何一步拒 ⇒ `{ok:false, answer}`(不改任何状态;端点在消费一次性 nonce **之前**把它答出去);
@@ -626,7 +625,7 @@ export type RouteDecl = RouteDispatchDecl | RouteGateDecl;
626
625
  * api-error-text-freeze 基线流程。
627
626
  */
628
627
  /**
629
- * resume 家族一条腿的结局:HTTP 应答(`status` / `body`)+ 一位**进程内**事实(不上 wire)。
628
+ * resume 家族**引擎腿**的结局:HTTP 应答(`status` / `body`)+ 一位**进程内**事实(不上 wire)。workflow 宿主腿的结局另型({@link HostLegOutcome}),并集 = {@link ResumeLegOutcome}。
630
629
  *
631
630
  * `decisionCommitted` —— **这条腿的终局是在引擎受理了这次决定之后才结出来的**([ref] X5,7.112.0):本腿走到了「引擎交回结果」那一步
632
631
  * (`resumeStream` / `resumeWithVerification` 解析 = core 的全部 pre-CAS 门 + checkpoint resolve CAS 都过了 —— 受理点的判据属主
@@ -640,11 +639,34 @@ export type RouteDecl = RouteDispatchDecl | RouteGateDecl;
640
639
  * 读它」(`plugins/store-contracts.ts` `RunCancelContext` 顶注)—— 这两道门正是判定;而且门读的是 `/decide` 的平面体、看不到结果对象。
641
640
  * 受理与否是腿自己的控制流事实,在腿上说,零 wire 变更。
642
641
  */
643
- export interface ResumeLegOutcome {
642
+ export interface EngineLegOutcome {
644
643
  status: number;
645
644
  body: object;
646
645
  decisionCommitted?: true;
647
646
  }
647
+ /**
648
+ * **workflow 宿主腿的结局(wake / fresh)—— 型上就没有 `decisionCommitted`**(S-992 ②,7.113.0;`never` 让「这条路径不铸」成为**编译事实**,不是靠人记)。
649
+ *
650
+ * 为什么这条路径不该有该位:`/decide` 的 workflow 第三条车道(`resumeCheckpoint` 的 parked 分流,`planWorkflowParkedDecide` 之后)与 `POST /v1/sessions/:id/wake`
651
+ * 把决定 / 消息交给**宿主会话的下一跑**(`resumeWake` / `startFreshHostTurn`)。它们的 200 只到「已交给宿主的下一跑」—— 子代 checkpoint 的 resolve CAS 发生在
652
+ * workflow 引擎自己驱动那一步,server 观察不到(`resume-legs.ts` 该分流处「刻意不落 `late_settle`」同源)。「这次决定被引擎受理了」这句话在这里**没有可说的对象**,
653
+ * 于是 `decisionCommitted` 在这条路径上语义含混(修前:`resumeWake` 的收尾是 `driveResumeIntoRunLog` 的 `ResumeLegOutcome`,非受理点早返形(无 `acceptEarly`)下它会把
654
+ * 引擎腿的 `decisionCommitted:true` 原样带出去 —— 型被声明返回型 `{status, body}` 擦掉了,运行期仍在)。读者核实:两道提交门(`noteLateSettleIfAccepted` 迟到结算留痕、
655
+ * `/decide` 的 remember 豁免落店)在这条路径上今天**零读取**(前者该分流直接 `return`、不调;后者的 `pendingGrant` 只由引擎腿的 `onResumeCommitted` 回调置位,宿主腿不调它,且 `planWorkflowParkedDecide` 对 workflow 车道的 `remember` 本就 fail-closed 拒 —— `decide.workflow_remember_unsupported`,`parked-decide.ts`)——
656
+ * 无读者 ⇒ 剥掉该位,而不是留一个「铸了也没人读、读了不知道什么意思」的位。出口经 {@link buildHostLegOutcome} 只带 `status` / `body`。
657
+ */
658
+ export interface HostLegOutcome {
659
+ status: number;
660
+ body: object;
661
+ decisionCommitted?: never;
662
+ }
663
+ /** resume 家族一条腿的结局:引擎腿(可带 `decisionCommitted`)∪ workflow 宿主腿(型上没有)。`out.decisionCommitted === true` 同时收窄到引擎腿。 */
664
+ export type ResumeLegOutcome = EngineLegOutcome | HostLegOutcome;
665
+ /** 把任意腿的结局折成宿主腿结局:**只带 `status` / `body`**(丢掉引擎腿的 `decisionCommitted` —— 见 {@link HostLegOutcome})。纯数据。 */
666
+ export declare function buildHostLegOutcome(out: {
667
+ status: number;
668
+ body: object;
669
+ }): HostLegOutcome;
648
670
  /**
649
671
  * **「决定/续跑已受理」那一枚 200 收据的单一铸点**(S-218)。
650
672
  *
@@ -660,6 +682,14 @@ export declare function acceptedResumingReceipt(taskId: string, sessionId: strin
660
682
  body: object;
661
683
  };
662
684
  export declare const RUN_NOT_FOUND_MESSAGE = "run not found \u2014 the id belongs to no run in this deployment's run store (a run from another server process, or an in-memory store that did not survive a restart, is not visible here)";
685
+ /**
686
+ * 会话资源面 404 的**唯一一句**(与 {@link RUN_NOT_FOUND_MESSAGE} 同一冻结纪律,常量锚在 `test/api-error-text-freeze.test.ts`)。
687
+ *
688
+ * 为什么是常量不是各点字面量:这句话要在**每一个**会话面的「不存在」臂与「非属主」臂上逐字节同串(G52 [ref] /
689
+ * S-989 —— 两臂差一个码或一个字就是单端点内的存在性 oracle)。十余个出站点各抄一份,漂开的那天没有任何东西会说话;
690
+ * 走一只常量 + `runOwnerOk(..., "session")` 的会话拼法,「改一处」就是改全域。
691
+ */
692
+ export declare const SESSION_NOT_FOUND_MESSAGE = "session not found";
663
693
  /** [ref] 前缀=域判别子既成契约:a\* 与 wa\* 形 id 给 TaskOutput/journal 指路;其余给 {@link RUN_NOT_FOUND_MESSAGE}。 */
664
694
  export declare function runNotFoundMessage(id: string): string;
665
695
  export {};
@@ -21,10 +21,14 @@ export function assertNever(x) {
21
21
  throw new Error(`unreachable route id: ${String(x)}`);
22
22
  }
23
23
  export const ROUTE_METHOD_ORDER = ["GET", "POST", "PUT", "DELETE"];
24
+ export function buildHostLegOutcome(out) {
25
+ return { status: out.status, body: out.body };
26
+ }
24
27
  export function acceptedResumingReceipt(taskId, sessionId) {
25
28
  return { status: 200, body: { taskId, sessionId, status: "resuming", bindingEnforced: true } };
26
29
  }
27
30
  export const RUN_NOT_FOUND_MESSAGE = "run not found — the id belongs to no run in this deployment's run store (a run from another server process, or an in-memory store that did not survive a restart, is not visible here)";
31
+ export const SESSION_NOT_FOUND_MESSAGE = "session not found";
28
32
  export function runNotFoundMessage(id) {
29
33
  const aStar = /^(?:a|wa)[0-9a-f]{4,}$/i.test(id);
30
34
  return aStar
@@ -5,7 +5,7 @@ import { redactSecrets } from "../../trace/redact.js";
5
5
  import { sendJson, sendError } from "../send.js";
6
6
  import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
7
7
  import {} from "../route-ctx.js";
8
- import { assertNever } from "../route-ctx.js";
8
+ import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
9
9
  const LIST_LIMIT = 500;
10
10
  export const DESCRIPTION_MAX = 500;
11
11
  const ROW_KEY_DISPOSITION = {
@@ -65,7 +65,7 @@ export async function handleBackground(req, res, match, ctx) {
65
65
  }
66
66
  const owner = await deps.sessionStorage.ownerOf(sessionId);
67
67
  if (owner === undefined) {
68
- sendError(res, 404, "not_found.session", "session not found");
68
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
69
69
  return;
70
70
  }
71
71
  const isOperator = explicitOperatorOk(principal, deps.config.operatorPrincipals);
@@ -4,7 +4,7 @@ import { redactSecrets } from "../../trace/redact.js";
4
4
  import { sendJson, sendError } from "../send.js";
5
5
  import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
6
6
  import {} from "../route-ctx.js";
7
- import { assertNever } from "../route-ctx.js";
7
+ import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
8
8
  const BodySchema = z.object({ server: z.string().min(1).max(190) }).strict();
9
9
  const NEUTRALIZED_TEXT_MAX = 200;
10
10
  const humanText = (s) => redactSecrets(s).slice(0, NEUTRALIZED_TEXT_MAX);
@@ -82,7 +82,7 @@ export async function handleMcpReconnect(req, res, match, ctx) {
82
82
  }
83
83
  const owner = await deps.sessionStorage.ownerOf(sessionId);
84
84
  if (owner === undefined) {
85
- sendError(res, 404, "not_found.session", "session not found");
85
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
86
86
  return;
87
87
  }
88
88
  const isOperator = explicitOperatorOk(principal, deps.config.operatorPrincipals);
@@ -2,7 +2,7 @@ import { taskNotificationInboxEntry } from "../../orchestration/workflow-complet
2
2
  import { sendJson, sendError } from "../send.js";
3
3
  import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
4
4
  import { sendResumeOutcome } from "../route-ctx.js";
5
- import { assertNever } from "../route-ctx.js";
5
+ import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
6
6
  export async function handleNotifyWake(req, res, match, ctx) {
7
7
  const { deps } = ctx;
8
8
  const { steerableRuns } = ctx.registry;
@@ -63,12 +63,12 @@ export async function handleNotifyWake(req, res, match, ctx) {
63
63
  }
64
64
  const notifyOwner = await deps.sessionStorage.ownerOf(notifySession);
65
65
  if (notifyOwner === undefined) {
66
- sendError(res, 404, "not_found.session", "session not found");
66
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
67
67
  return;
68
68
  }
69
69
  const notifyOperator = explicitOperatorOk(principal, deps.config.operatorPrincipals);
70
70
  if (!notifyOperator && notifyOwner !== null && notifyOwner !== principal) {
71
- sendError(res, 404, "not_found.session", "session not found");
71
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
72
72
  return;
73
73
  }
74
74
  const payload = {
@@ -146,6 +146,20 @@ async function acceptLiveUserInput(deps, res, at) {
146
146
  return "refused";
147
147
  }
148
148
  }
149
+ async function relayAcceptedInput(deps, acceptance, call) {
150
+ let out;
151
+ try {
152
+ out = await call();
153
+ }
154
+ catch (e) {
155
+ if (acceptance !== undefined)
156
+ await deps.userInputLedger.withdraw(acceptance);
157
+ throw e;
158
+ }
159
+ if (acceptance !== undefined)
160
+ await deps.userInputLedger.confirm(acceptance);
161
+ return out;
162
+ }
149
163
  export async function handleRuns(req, res, match, ctx) {
150
164
  const { deps } = ctx;
151
165
  const { idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints } = ctx.registry;
@@ -481,13 +495,11 @@ export async function handleRuns(req, res, match, ctx) {
481
495
  if (acceptance === "refused")
482
496
  return;
483
497
  try {
484
- await live.steer(text, { trusted, ...(steerInputId !== undefined ? { inputId: steerInputId } : {}) });
498
+ await relayAcceptedInput(deps, acceptance, () => live.steer(text, { trusted, inputId: messageId }));
485
499
  sendJson(res, 200, { taskId, status: "running", delivery: "applied", messageId, ...(priority ? { priority } : {}) });
486
500
  return;
487
501
  }
488
502
  catch (e) {
489
- if (acceptance !== undefined)
490
- await deps.userInputLedger.withdraw(acceptance);
491
503
  const code = e.code;
492
504
  if (code === "steering.invalid_content") {
493
505
  sendError(res, 422, "steering.invalid_content", e instanceof Error ? e.message : "invalid steering content");
@@ -794,11 +806,9 @@ export async function handleRuns(req, res, match, ctx) {
794
806
  if (acceptance === "refused")
795
807
  return;
796
808
  try {
797
- await liveStream.steer(text, { trusted, priority: "now", ...(interruptInputId !== undefined ? { inputId: interruptInputId } : {}) });
809
+ await relayAcceptedInput(deps, acceptance, () => liveStream.steer(text, { trusted, priority: "now", ...(interruptInputId !== undefined ? { inputId: interruptInputId } : {}) }));
798
810
  }
799
811
  catch (e) {
800
- if (acceptance !== undefined)
801
- await deps.userInputLedger.withdraw(acceptance);
802
812
  const code = e.code;
803
813
  if (code === "steering.invalid_content") {
804
814
  sendError(res, 422, "steering.invalid_content", e instanceof Error ? e.message : "invalid steering content");
@@ -1152,7 +1162,6 @@ export async function handleRunVerbs(req, res, match, ctx) {
1152
1162
  }
1153
1163
  const handle = resolution?.handle;
1154
1164
  if (handle) {
1155
- let acceptance;
1156
1165
  try {
1157
1166
  if (verb === "resume") {
1158
1167
  if (!handle.resume) {
@@ -1169,11 +1178,10 @@ export async function handleRunVerbs(req, res, match, ctx) {
1169
1178
  return;
1170
1179
  }
1171
1180
  const messageId = uuidv7();
1172
- const accepted = await acceptLiveUserInput(deps, res, { messageId, sessionId: run.sessionId, taskId: runId, leg: handle });
1173
- if (accepted === "refused")
1181
+ const acceptance = await acceptLiveUserInput(deps, res, { messageId, sessionId: run.sessionId, taskId: runId, leg: handle });
1182
+ if (acceptance === "refused")
1174
1183
  return;
1175
- acceptance = accepted;
1176
- const marker = await handle.steer(redacted);
1184
+ const marker = await relayAcceptedInput(deps, acceptance, () => handle.steer(redacted));
1177
1185
  if (acceptance !== undefined) {
1178
1186
  const ledger = deps.userInputLedger;
1179
1187
  const settle = () => ledger.settleLeg(handle);
@@ -1183,8 +1191,6 @@ export async function handleRunVerbs(req, res, match, ctx) {
1183
1191
  return;
1184
1192
  }
1185
1193
  catch (e) {
1186
- if (acceptance !== undefined)
1187
- await deps.userInputLedger.withdraw(acceptance);
1188
1194
  const code = e.code;
1189
1195
  if (code === "steering.not_running") {
1190
1196
  send409(code, "sub-agent just finished — no longer accepting steers");
@@ -12,7 +12,7 @@ const SYNC_BLOB_MAX_BYTES = 64 * 1024 * 1024;
12
12
  const SYNC_NDJSON_LINE_MAX_BYTES = 8 * 1024 * 1024;
13
13
  const SYNC_IMPORT_BATCH = 500;
14
14
  import { SHA256_HEX_RE } from "../../digest-form.js";
15
- import { assertNever } from "../route-ctx.js";
15
+ import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
16
16
  import { readSessionRulesRecords, describeSessionRulesRecordProblem, legacyToolNameProblemOf } from "../../session-policy-wire.js";
17
17
  export function createSessionSyncLocal() {
18
18
  const pendingStagings = new Map();
@@ -60,18 +60,18 @@ export async function handleSessionSync(req, res, match, ctx) {
60
60
  const ownsOrFleet = (o) => scope.fleetWide || o === scope.gateOwner;
61
61
  const pullGateOk = () => {
62
62
  if (owner === undefined) {
63
- sendError(res, 404, "not_found.session", "session not found");
63
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
64
64
  return false;
65
65
  }
66
66
  if (!ownsOrFleet(owner)) {
67
- sendError(res, 404, "not_found.session", "session not found");
67
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
68
68
  return false;
69
69
  }
70
70
  return true;
71
71
  };
72
72
  const pushGateOk = () => {
73
73
  if (owner !== undefined && !ownsOrFleet(owner)) {
74
- sendError(res, 404, "not_found.session", "session not found");
74
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
75
75
  return false;
76
76
  }
77
77
  return true;
@@ -89,7 +89,7 @@ export async function handleSessionSync(req, res, match, ctx) {
89
89
  throw e;
90
90
  }
91
91
  if (out === null) {
92
- sendError(res, 404, "not_found.session", "session not found");
92
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
93
93
  return;
94
94
  }
95
95
  sendJson(res, 200, { manifest: out.manifest });
@@ -115,7 +115,7 @@ export async function handleSessionSync(req, res, match, ctx) {
115
115
  }
116
116
  const it = await stream(sessionId, afterSeq === undefined ? undefined : { afterSeq });
117
117
  if (it === null) {
118
- sendError(res, 404, "not_found.session", "session not found");
118
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
119
119
  return;
120
120
  }
121
121
  res.writeHead(200, { "content-type": "application/x-ndjson", "transfer-encoding": "chunked" });
@@ -9,7 +9,7 @@ import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_PERMISSION_RULES, MAX_SET
9
9
  import { readWritableSessionPermissionRules, describeSessionPermissionRulesProblem } from "../../session-policy-wire.js";
10
10
  import { sendJson, sendError, sendSessionError, sendSessionPolicyError, sseHeaders } from "../send.js";
11
11
  import { gatedPrincipal, explicitOperatorOk, runOwnerReadable, sendPrincipalRefusal, sendUnauthorized } from "../principal-gate.js";
12
- import { assertNever } from "../route-ctx.js";
12
+ import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
13
13
  import { rosterAgentId } from "./agents-roster.js";
14
14
  const MCP_STATUS_TTL_MS = 5_000;
15
15
  const MCP_MATERIALIZE_TIMEOUT_MS = 10_000;
@@ -68,15 +68,15 @@ async function readLastLegMcp(deps, sessionId, principal) {
68
68
  export async function handleSessions(req, res, match, ctx) {
69
69
  const { deps } = ctx;
70
70
  const { readJson, safeDecode, runOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite } = ctx.helpers;
71
- const readOwnedSessionView = async (sessionAudit, sessionId, notFoundForm) => {
71
+ const readOwnedSessionView = async (sessionAudit, sessionId) => {
72
72
  const ownerOf = deps.sessionStorage?.ownerOf?.bind(deps.sessionStorage);
73
73
  if (ownerOf !== undefined) {
74
74
  const owner = await ownerOf(sessionId);
75
75
  if (owner === undefined) {
76
- sendError(res, 404, "not_found.session", "session not found");
76
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
77
77
  return undefined;
78
78
  }
79
- if (!runOwnerOk(req, res, owner, notFoundForm))
79
+ if (!runOwnerOk(req, res, owner, "session"))
80
80
  return undefined;
81
81
  }
82
82
  let audit;
@@ -89,10 +89,10 @@ export async function handleSessions(req, res, match, ctx) {
89
89
  throw new Error(`session view read failed before ownership could be established: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
90
90
  }
91
91
  if (!audit) {
92
- sendError(res, 404, "not_found.session", "session not found");
92
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
93
93
  return undefined;
94
94
  }
95
- if (!runOwnerOk(req, res, audit.owner, notFoundForm))
95
+ if (!runOwnerOk(req, res, audit.owner, "session"))
96
96
  return undefined;
97
97
  return audit;
98
98
  };
@@ -261,21 +261,21 @@ export async function handleSessions(req, res, match, ctx) {
261
261
  if (getHead) {
262
262
  const head = await getHead(headSession);
263
263
  if (head === undefined) {
264
- sendError(res, 404, "not_found.session", "session not found");
264
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
265
265
  return;
266
266
  }
267
267
  owner = head.owner;
268
- if (!runOwnerOk(req, res, owner ?? null))
268
+ if (!runOwnerOk(req, res, owner ?? null, "session"))
269
269
  return;
270
270
  leafId = head.leafId;
271
271
  }
272
272
  else {
273
273
  owner = await ownerOf(headSession);
274
274
  if (owner === undefined) {
275
- sendError(res, 404, "not_found.session", "session not found");
275
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
276
276
  return;
277
277
  }
278
- if (!runOwnerOk(req, res, owner ?? null))
278
+ if (!runOwnerOk(req, res, owner ?? null, "session"))
279
279
  return;
280
280
  leafId = await getLeafId(headSession);
281
281
  }
@@ -355,10 +355,10 @@ export async function handleSessions(req, res, match, ctx) {
355
355
  }
356
356
  if (owner === undefined) {
357
357
  cleanup();
358
- sendError(res, 404, "not_found.session", "session not found");
358
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
359
359
  return;
360
360
  }
361
- if (!runOwnerOk(req, res, owner ?? null)) {
361
+ if (!runOwnerOk(req, res, owner ?? null, "session")) {
362
362
  cleanup();
363
363
  return;
364
364
  }
@@ -498,7 +498,7 @@ export async function handleSessions(req, res, match, ctx) {
498
498
  sendError(res, 501, "capability.memory_engine_required", "memory provenance and erasure are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/tidb memory backends do not (their control plane would land on a replica's local disk)");
499
499
  return;
500
500
  }
501
- if ((await readOwnedSessionView(deps.sessionAudit, sessionId, "session")) === undefined)
501
+ if ((await readOwnedSessionView(deps.sessionAudit, sessionId)) === undefined)
502
502
  return;
503
503
  const faces = deps.memoryCompliance;
504
504
  if (faces === undefined) {
@@ -550,7 +550,7 @@ export async function handleSessions(req, res, match, ctx) {
550
550
  sendError(res, 501, "capability.memory_engine_required", "session memory status is not available on this deployment — it requires a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/tidb memory backends do not (their control plane would land on a replica's local disk)");
551
551
  return;
552
552
  }
553
- if ((await readOwnedSessionView(deps.sessionAudit, sessionId, "session")) === undefined)
553
+ if ((await readOwnedSessionView(deps.sessionAudit, sessionId)) === undefined)
554
554
  return;
555
555
  const face = deps.sessionMemoryStatus;
556
556
  if (face === undefined) {
@@ -594,7 +594,7 @@ export async function handleSessions(req, res, match, ctx) {
594
594
  sendError(res, 404, "not_found.route", "not found");
595
595
  return;
596
596
  }
597
- const audit = await readOwnedSessionView(deps.sessionAudit, match.params.sessionId, "run");
597
+ const audit = await readOwnedSessionView(deps.sessionAudit, match.params.sessionId);
598
598
  if (audit === undefined)
599
599
  return;
600
600
  const q = new URLSearchParams(match.search);
@@ -665,17 +665,17 @@ export async function handleSessions(req, res, match, ctx) {
665
665
  }
666
666
  const owner = await ownerOf(sourceId);
667
667
  if (owner === undefined) {
668
- sendError(res, 404, "not_found.session", "session not found");
668
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
669
669
  return;
670
670
  }
671
671
  if (!scope.fleetWide && owner !== scope.gateOwner && !legacyUnownedSessionOk(deps.config.requirePrincipal, scope.gateOwner, owner)) {
672
- sendError(res, 404, "not_found.session", "session not found");
672
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
673
673
  return;
674
674
  }
675
675
  const newOwner = scope.fleetWide ? (owner ?? null) : scope.gateOwner;
676
676
  const newId = await fork(sourceId, newOwner);
677
677
  if (newId === null) {
678
- sendError(res, 404, "not_found.session", "session not found");
678
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
679
679
  return;
680
680
  }
681
681
  sendJson(res, 200, { sessionId: newId });
@@ -747,7 +747,7 @@ export async function handleSessions(req, res, match, ctx) {
747
747
  }
748
748
  const owner = await ownerOf(sessionId);
749
749
  if (owner === undefined) {
750
- sendError(res, 404, "not_found.session", "session not found");
750
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
751
751
  return;
752
752
  }
753
753
  if (owner === null) {
@@ -756,7 +756,7 @@ export async function handleSessions(req, res, match, ctx) {
756
756
  }
757
757
  const operator = explicitOperatorOk(principal, deps.config.operatorPrincipals);
758
758
  if (!operator && owner !== (principal ?? null)) {
759
- sendError(res, 404, "not_found.session", "session not found");
759
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
760
760
  return;
761
761
  }
762
762
  const target = owner;
@@ -418,6 +418,7 @@ export async function handleTasks(req, res, match, ctx) {
418
418
  if (ev.type === "message_committed" && ev.role === "user")
419
419
  userMsgEntryId = ev.entryId;
420
420
  const causeAtDone = ev.type === "done" ? ac.cause() : undefined;
421
+ await deps.userInputLedger?.observeLegEvent(liveStream, ev);
421
422
  if (ev.type === "done") {
422
423
  finalResult = await mintSubmitExitResult(ev.result, deps.checkpointStore);
423
424
  if (durableTaskId && finalResult)
@@ -80,7 +80,7 @@ export declare function sendSessionPolicyError(res: ServerResponse, e: unknown):
80
80
  * 句子:属主给了固定句就发它(import 面 `invalid_session` 那一句),否则发 core 的原句过既有脱敏(`redactErrorMessage`:
81
81
  * 不抛、有上限)。core 的 `corrupt_session` 原句只点名会话 id 与跳过计数(「Session <id> is unreadable: its log has no
82
82
  * meta header (N of M records skipped as corrupt)」),SQL 两店的原句点名 `<表>.<列>` 与行号 —— 两者都不含盘上路径
83
- * (路径只进运维日志:`session_repo_corrupt_read` 行)。
83
+ * (路径只进运维面:`server.session-repo.corrupt-record-skipped` 的探针行,S-990;SQL 两店走各自的拒绝句)。
84
84
  */
85
85
  export declare function sendSessionError(res: ServerResponse, e: unknown, face: SessionErrorFace): boolean;
86
86
  export declare function msg(e: unknown): string;
package/dist/http/send.js CHANGED
@@ -43,7 +43,7 @@ export function sendSessionPolicyError(res, e) {
43
43
  if (!(e instanceof SessionPolicyError))
44
44
  return false;
45
45
  const h = sessionPolicyErrorHttp(e.code);
46
- sendError(res, h.status, h.errorCode, e.message);
46
+ sendError(res, h.status, h.errorCode, redactErrorMessage(e.message) ?? "session policy error");
47
47
  return true;
48
48
  }
49
49
  export function sendSessionError(res, e, face) {
@@ -233,13 +233,13 @@ export interface TaskRequestBody {
233
233
  * core 默认开列表注入的通道)与三个 deadline opt-out 在类型面失踪。agentListing/skillsListing 是
234
234
  * boolean 透传(core DEFAULT-ON,explicit false 才关);budgetUsd/mcpInstructions 只认 literal true;
235
235
  * limits 三个 opt-out 只认 literal false(normalizer 同判)。 */
236
+ /** core 7.39.0 #1254:`planModeReminder` 退役(硬破坏,零别名)—— 提交即 400 点名退役;plan 姿态由引擎从 `permissionMode:"plan"` 自己陈述。 */
236
237
  attachments?: {
237
238
  todoReminder?: boolean;
238
239
  todoReminderMode?: "baseline" | "off";
239
240
  changedFiles?: boolean | {
240
241
  maxFiles?: number;
241
242
  };
242
- planModeReminder?: boolean;
243
243
  budgetUsd?: true;
244
244
  backgroundTasks?: boolean;
245
245
  toolsDelta?: boolean;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * **本机数据根的唯一解析**(`DB_BACKEND=local` 的 file 店把全部落盘件放在这个目录下;`config.localDataRoot` 就是它的值)。
3
+ *
4
+ * 链(第一个非空者胜,空串视作缺席):`LOCAL_DATA_ROOT` → `CONFIG_LOCAL_DIR` → `AGENT_DATA_DIR` → `<home>/.ai-agent`。
5
+ *
6
+ * 为什么是一只**纯函数、env 与 home 都由调用方递入**:本函数有两个读者 —— 服务自己的 `loadConfig`(递 `process.env` / `os.homedir()`)与随包发的运维脚本
7
+ * `scripts/retired-mode-seed-drain-local.mjs`(经 `dist/local-data-root.js` 取同一只函数,缺省数据根不再自抄一份链:抄本只对了一半,
8
+ * 设了 `AGENT_DATA_DIR` 的部署不带参数跑脚本会读到另一个目录并报「干净」)。纯叶、零反向依赖 ⇒ 脚本引它不会把整个服务装载起来。
9
+ *
10
+ * ⚠️ 与**配置根**(`configLocalRoot`:`CONFIG_LOCAL_DIR` → `AGENT_DATA_DIR` → `~/.ai-agent`,不吃 `LOCAL_DATA_ROOT`)不是同一条链 —— 两根分家的那一形由
11
+ * boot 期的 `local_roots_split` 告警说出来,本函数不改那条解析。
12
+ */
13
+ export declare function resolveLocalDataRoot(env: Readonly<Record<string, string | undefined>>, home: string): string;
14
+ //# sourceMappingURL=local-data-root.d.ts.map
@@ -0,0 +1,5 @@
1
+ import { join } from "node:path";
2
+ export function resolveLocalDataRoot(env, home) {
3
+ return env["LOCAL_DATA_ROOT"] || env["CONFIG_LOCAL_DIR"] || env["AGENT_DATA_DIR"] || join(home, ".ai-agent");
4
+ }
5
+ //# sourceMappingURL=local-data-root.js.map