oasis_test 0.1.120 → 0.1.122

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 (2) hide show
  1. package/dist/index.js +1747 -380
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -2877,11 +2877,18 @@ var init_execution_continuity = __esm({
2877
2877
  });
2878
2878
 
2879
2879
  // ../contract/src/automation.ts
2880
- var AUTOMATION_EVENT_NAMES;
2880
+ function normalizeChatTarget(raw) {
2881
+ if (raw?.mode === "existing_session" && typeof raw.sessionId === "string" && raw.sessionId) {
2882
+ return { mode: "existing_session", sessionId: raw.sessionId };
2883
+ }
2884
+ return DEFAULT_CHAT_TARGET;
2885
+ }
2886
+ var AUTOMATION_EVENT_NAMES, DEFAULT_CHAT_TARGET;
2881
2887
  var init_automation = __esm({
2882
2888
  "../contract/src/automation.ts"() {
2883
2889
  "use strict";
2884
2890
  AUTOMATION_EVENT_NAMES = ["workorder.completed"];
2891
+ DEFAULT_CHAT_TARGET = { mode: "new_session" };
2885
2892
  }
2886
2893
  });
2887
2894
 
@@ -2899,6 +2906,19 @@ var init_chat = __esm({
2899
2906
  }
2900
2907
  });
2901
2908
 
2909
+ // ../contract/src/chat-turn.ts
2910
+ function automationTurnSourceRunId(automationRunId) {
2911
+ return `automation:${automationRunId}`;
2912
+ }
2913
+ function automationTurnMessageKey(automationRunId) {
2914
+ return `automation-message:${automationRunId}`;
2915
+ }
2916
+ var init_chat_turn = __esm({
2917
+ "../contract/src/chat-turn.ts"() {
2918
+ "use strict";
2919
+ }
2920
+ });
2921
+
2902
2922
  // ../contract/src/knowledge.ts
2903
2923
  var init_knowledge = __esm({
2904
2924
  "../contract/src/knowledge.ts"() {
@@ -2952,6 +2972,7 @@ var init_src2 = __esm({
2952
2972
  init_automation();
2953
2973
  init_playbook();
2954
2974
  init_chat();
2975
+ init_chat_turn();
2955
2976
  init_knowledge();
2956
2977
  init_http_api();
2957
2978
  }
@@ -18615,6 +18636,111 @@ var init_command_policy = __esm({
18615
18636
  }
18616
18637
  });
18617
18638
 
18639
+ // ../server/src/chat-turn-gate.ts
18640
+ function localTurnSourceRunId(source, turnId) {
18641
+ return `${source}:${turnId}`;
18642
+ }
18643
+ function localTurnMessageKey(source, turnId) {
18644
+ return `${source}-message:${turnId}`;
18645
+ }
18646
+ async function openChatTurn(args) {
18647
+ const turns = args.turns;
18648
+ if (!turns) return { ok: true, turn: null };
18649
+ const nowIso = args.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
18650
+ const turnId = (args.newId ?? import_node_crypto4.randomUUID)();
18651
+ const messageKey = localTurnMessageKey(args.source, turnId);
18652
+ const claim = await turns.claimTurn({
18653
+ turnId,
18654
+ chatSessionId: args.chatSessionId,
18655
+ source: args.source,
18656
+ sourceRunId: localTurnSourceRunId(args.source, turnId),
18657
+ messageKey,
18658
+ reservedAt: nowIso()
18659
+ });
18660
+ if (claim.outcome === "busy") return { ok: false, activeTurn: claim.activeTurn };
18661
+ const id = claim.turn.id;
18662
+ let settled = false;
18663
+ return {
18664
+ ok: true,
18665
+ turn: {
18666
+ id,
18667
+ messageKey: claim.turn.messageKey,
18668
+ async start(handles) {
18669
+ await turns.settleTurn(id, {
18670
+ status: "running",
18671
+ startedAt: nowIso(),
18672
+ ...handles.assistantRunId ? { assistantRunId: handles.assistantRunId } : {},
18673
+ ...handles.dispatchId ? { dispatchId: handles.dispatchId } : {}
18674
+ }).catch((e) => args.log?.(`[chat-turn] \u8F6E\u6B21\u8F6C running \u5931\u8D25 ${id}: ${String(e)}`));
18675
+ },
18676
+ async settle(patch) {
18677
+ if (settled) return;
18678
+ settled = true;
18679
+ await turns.settleTurn(id, patch).catch((e) => args.log?.(`[chat-turn] \u8F6E\u6B21\u6536\u53E3\u5931\u8D25 ${id}: ${String(e)}`));
18680
+ }
18681
+ }
18682
+ };
18683
+ }
18684
+ async function reconcileChatTurn(turn, deps) {
18685
+ if (deps.isOwned?.(turn)) return { settled: false, turnId: turn.id, reason: "owned" };
18686
+ const grace = deps.noRunGraceMs ?? 10 * 6e4;
18687
+ const age = deps.now - Date.parse(turn.reservedAt);
18688
+ const runId = turn.assistantRunId ?? turn.dispatchId;
18689
+ if (!runId) {
18690
+ if (age < grace) return { settled: false, turnId: turn.id, reason: "in_grace" };
18691
+ await deps.turns.settleTurn(turn.id, {
18692
+ status: "orphaned",
18693
+ completedAt: new Date(deps.now).toISOString(),
18694
+ lastError: "\u5360\u4F4D\u540E\u672A\u63D0\u4EA4 runtime\uFF08\u8FDB\u7A0B\u4E2D\u65AD\uFF09\uFF0C\u8D85\u5BBD\u9650\u5224\u5B64\u513F\u5E76\u91CA\u653E\u4F1A\u8BDD\u69FD"
18695
+ });
18696
+ return { settled: true, turnId: turn.id, status: "orphaned", reason: "\u5360\u4F4D\u540E\u672A\u63D0\u4EA4 runtime" };
18697
+ }
18698
+ const run = await deps.getRun(runId).catch(() => null);
18699
+ if (run && (run.status === "running" || run.status === "queued")) {
18700
+ return { settled: false, turnId: turn.id, reason: "still_running" };
18701
+ }
18702
+ if (!run) {
18703
+ if (age < grace) return { settled: false, turnId: turn.id, reason: "in_grace" };
18704
+ await deps.turns.settleTurn(turn.id, {
18705
+ status: "orphaned",
18706
+ completedAt: new Date(deps.now).toISOString(),
18707
+ lastError: `\u8D26\u672C\u67E5\u65E0 run ${runId}\uFF0C\u8D85\u5BBD\u9650\u5224\u5B64\u513F\u5E76\u91CA\u653E\u4F1A\u8BDD\u69FD`
18708
+ });
18709
+ return { settled: true, turnId: turn.id, status: "orphaned", reason: `\u8D26\u672C\u67E5\u65E0 run ${runId}` };
18710
+ }
18711
+ const status = run.status === "succeeded" ? "succeeded" : run.status === "cancelled" ? "cancelled" : "failed";
18712
+ await deps.turns.settleTurn(turn.id, {
18713
+ status,
18714
+ completedAt: run.endedAt ?? new Date(deps.now).toISOString(),
18715
+ ...status === "succeeded" ? {} : { lastError: `run ${runId} \u7EC8\u6001 ${run.status}\uFF08\u7531\u6062\u590D\u5668\u6536\u53E3\uFF09` }
18716
+ });
18717
+ return { settled: true, turnId: turn.id, status, reason: `run \u7EC8\u6001 ${run.status}` };
18718
+ }
18719
+ async function sweepStaleChatTurns(deps) {
18720
+ const before = new Date(deps.now - (deps.staleAfterMs ?? 2 * 6e4)).toISOString();
18721
+ const stale = await deps.turns.listStaleActiveTurns(before, deps.limit ?? 50);
18722
+ const settled = [];
18723
+ for (const turn of stale) {
18724
+ const outcome = await reconcileChatTurn(turn, deps).catch((e) => {
18725
+ deps.log?.(`[chat-turn-sweep] \u6838\u5B9E\u8F6E\u6B21 ${turn.id} \u5931\u8D25: ${String(e)}`);
18726
+ return null;
18727
+ });
18728
+ if (!outcome?.settled) continue;
18729
+ settled.push(outcome);
18730
+ deps.log?.(
18731
+ `[chat-turn-sweep] \u8F6E\u6B21 ${turn.id}\uFF08\u4F1A\u8BDD ${turn.chatSessionId}\uFF09\u6536\u53E3\u4E3A ${outcome.status}\uFF08${outcome.reason}\uFF09\uFF0C\u4F1A\u8BDD\u69FD\u5DF2\u91CA\u653E`
18732
+ );
18733
+ }
18734
+ return settled;
18735
+ }
18736
+ var import_node_crypto4;
18737
+ var init_chat_turn_gate = __esm({
18738
+ "../server/src/chat-turn-gate.ts"() {
18739
+ "use strict";
18740
+ import_node_crypto4 = require("node:crypto");
18741
+ }
18742
+ });
18743
+
18618
18744
  // ../channels/src/memory-store.ts
18619
18745
  var init_memory_store = __esm({
18620
18746
  "../channels/src/memory-store.ts"() {
@@ -18800,11 +18926,11 @@ var init_parse = __esm({
18800
18926
 
18801
18927
  // ../channels/src/feishu/crypto.ts
18802
18928
  function decryptFeishu(encrypt, encryptKey) {
18803
- const key = (0, import_node_crypto4.createHash)("sha256").update(encryptKey).digest();
18929
+ const key = (0, import_node_crypto5.createHash)("sha256").update(encryptKey).digest();
18804
18930
  const data = Buffer.from(encrypt, "base64");
18805
18931
  const iv = data.subarray(0, 16);
18806
18932
  const ciphertext = data.subarray(16);
18807
- const decipher = (0, import_node_crypto4.createDecipheriv)("aes-256-cbc", key, iv);
18933
+ const decipher = (0, import_node_crypto5.createDecipheriv)("aes-256-cbc", key, iv);
18808
18934
  decipher.setAutoPadding(true);
18809
18935
  return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
18810
18936
  }
@@ -18838,11 +18964,11 @@ function verifyToken(decoded, expected) {
18838
18964
  const token = headerToken ?? decoded["token"];
18839
18965
  return token === expected;
18840
18966
  }
18841
- var import_node_crypto4;
18967
+ var import_node_crypto5;
18842
18968
  var init_crypto = __esm({
18843
18969
  "../channels/src/feishu/crypto.ts"() {
18844
18970
  "use strict";
18845
- import_node_crypto4 = require("node:crypto");
18971
+ import_node_crypto5 = require("node:crypto");
18846
18972
  }
18847
18973
  });
18848
18974
 
@@ -18978,11 +19104,12 @@ var init_src6 = __esm({
18978
19104
  function errMsg(e) {
18979
19105
  return e instanceof Error ? e.message : String(e);
18980
19106
  }
18981
- var import_node_crypto5, FEISHU, ChannelService, CardStreamer;
19107
+ var import_node_crypto6, FEISHU, ChannelService, CardStreamer;
18982
19108
  var init_service = __esm({
18983
19109
  "../server/src/channels/service.ts"() {
18984
19110
  "use strict";
18985
- import_node_crypto5 = require("node:crypto");
19111
+ import_node_crypto6 = require("node:crypto");
19112
+ init_chat_turn_gate();
18986
19113
  init_src6();
18987
19114
  FEISHU = "feishu";
18988
19115
  ChannelService = class {
@@ -19062,7 +19189,7 @@ var init_service = __esm({
19062
19189
  return existing.chatSessionId;
19063
19190
  }
19064
19191
  }
19065
- const id = (0, import_node_crypto5.randomUUID)();
19192
+ const id = (0, import_node_crypto6.randomUUID)();
19066
19193
  const now = this.now();
19067
19194
  if (cs) {
19068
19195
  const rt = await this.deps.resolveRuntime?.(a.aiActorId).catch(() => null);
@@ -19098,8 +19225,20 @@ var init_service = __esm({
19098
19225
  if (cs) {
19099
19226
  const sess = await cs.getSession(intent.chatSessionId).catch(() => null);
19100
19227
  resumeRt = sess?.runtimeSessionId ?? void 0;
19101
- await cs.appendMessage({ id: (0, import_node_crypto5.randomUUID)(), sessionId: intent.chatSessionId, role: "user", content: intent.message, createdAt: this.now() }).catch(() => void 0);
19228
+ await cs.appendMessage({ id: (0, import_node_crypto6.randomUUID)(), sessionId: intent.chatSessionId, role: "user", content: intent.message, createdAt: this.now() }).catch(() => void 0);
19102
19229
  }
19230
+ const opened = await openChatTurn({
19231
+ turns: this.deps.chatTurns,
19232
+ chatSessionId: intent.chatSessionId,
19233
+ source: "channel",
19234
+ now: () => this.now(),
19235
+ log: (m2) => this.log(m2)
19236
+ });
19237
+ if (!opened.ok) {
19238
+ await client.sendText(intent.chatId, "\u23F3 \u4E0A\u4E00\u6761\u8FD8\u5728\u5904\u7406\u4E2D\uFF0C\u7B49\u5B83\u8DD1\u5B8C\u518D\u53D1\u8FD9\u6761\u5427\uFF08\u8FD9\u6B21\u6CA1\u6709\u6392\u961F\uFF09\u3002").catch(() => void 0);
19239
+ return;
19240
+ }
19241
+ const held = opened.turn;
19103
19242
  let session;
19104
19243
  try {
19105
19244
  session = await this.deps.dispatchChat({
@@ -19109,18 +19248,21 @@ var init_service = __esm({
19109
19248
  ...resumeRt ? { sessionId: resumeRt } : {}
19110
19249
  });
19111
19250
  } catch (err) {
19251
+ await held?.settle({ status: "failed", completedAt: this.now(), lastError: errMsg(err) });
19112
19252
  await client.sendText(intent.chatId, `\u26A0\uFE0F \u6682\u65F6\u65E0\u6CD5\u5904\u7406\uFF1A${errMsg(err)}`).catch(() => void 0);
19113
19253
  return;
19114
19254
  }
19255
+ await held?.start({ assistantRunId: session.runId ?? null, dispatchId: session.runId ?? null });
19115
19256
  const streamer = new CardStreamer(client, intent.chatId, this.cardIntervalMs, this.log);
19116
19257
  session.onOutput((chunk) => streamer.push(chunk));
19117
19258
  void session.done.then(async () => {
19259
+ await held?.settle({ status: "succeeded", completedAt: this.now() });
19118
19260
  await streamer.finalize();
19119
19261
  if (cs) {
19120
19262
  const reply = streamer.text();
19121
19263
  if (reply || session.runId) {
19122
19264
  await cs.appendMessage({
19123
- id: (0, import_node_crypto5.randomUUID)(),
19265
+ id: (0, import_node_crypto6.randomUUID)(),
19124
19266
  sessionId: intent.chatSessionId,
19125
19267
  role: "assistant",
19126
19268
  content: reply,
@@ -19137,6 +19279,7 @@ var init_service = __esm({
19137
19279
  }).catch(() => void 0);
19138
19280
  }
19139
19281
  }).catch(async (err) => {
19282
+ await held?.settle({ status: "failed", completedAt: this.now(), lastError: errMsg(err) });
19140
19283
  await streamer.error(errMsg(err));
19141
19284
  });
19142
19285
  }
@@ -40197,7 +40340,7 @@ var require_websocket = __commonJS({
40197
40340
  var http2 = require("http");
40198
40341
  var net = require("net");
40199
40342
  var tls = require("tls");
40200
- var { randomBytes: randomBytes9, createHash: createHash21 } = require("crypto");
40343
+ var { randomBytes: randomBytes9, createHash: createHash22 } = require("crypto");
40201
40344
  var { Duplex, Readable } = require("stream");
40202
40345
  var { URL: URL2 } = require("url");
40203
40346
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -40865,7 +41008,7 @@ var require_websocket = __commonJS({
40865
41008
  abortHandshake(websocket, socket, "Invalid Upgrade header");
40866
41009
  return;
40867
41010
  }
40868
- const digest = createHash21("sha1").update(key + GUID).digest("base64");
41011
+ const digest = createHash22("sha1").update(key + GUID).digest("base64");
40869
41012
  if (res.headers["sec-websocket-accept"] !== digest) {
40870
41013
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
40871
41014
  return;
@@ -41234,7 +41377,7 @@ var require_websocket_server = __commonJS({
41234
41377
  var EventEmitter = require("events");
41235
41378
  var http2 = require("http");
41236
41379
  var { Duplex } = require("stream");
41237
- var { createHash: createHash21 } = require("crypto");
41380
+ var { createHash: createHash22 } = require("crypto");
41238
41381
  var extension2 = require_extension();
41239
41382
  var PerMessageDeflate2 = require_permessage_deflate();
41240
41383
  var subprotocol2 = require_subprotocol();
@@ -41541,7 +41684,7 @@ var require_websocket_server = __commonJS({
41541
41684
  );
41542
41685
  }
41543
41686
  if (this._state > RUNNING) return abortHandshake(socket, 503);
41544
- const digest = createHash21("sha1").update(key + GUID).digest("base64");
41687
+ const digest = createHash22("sha1").update(key + GUID).digest("base64");
41545
41688
  const headers = [
41546
41689
  "HTTP/1.1 101 Switching Protocols",
41547
41690
  "Upgrade: websocket",
@@ -149144,7 +149287,7 @@ function slug4(name) {
149144
149287
  return s2 || "user";
149145
149288
  }
149146
149289
  function short6() {
149147
- return (0, import_node_crypto6.randomBytes)(4).toString("hex").slice(0, 6);
149290
+ return (0, import_node_crypto7.randomBytes)(4).toString("hex").slice(0, 6);
149148
149291
  }
149149
149292
  async function assertBindingWritePermission(caller, targetHumanId, deps) {
149150
149293
  if (caller === targetHumanId) return true;
@@ -149209,11 +149352,11 @@ async function resolveWorkorderPrincipals(caller, cliOverrides, deps) {
149209
149352
  }
149210
149353
  return { error: "UNBOUND_AGENT_NO_OWNER", message: `owner \u65E0\u6CD5\u89E3\u6790\uFF1A\u8C03\u7528\u65B9 ${caller} \u65E2\u975E\u5728\u5C97\u771F\u4EBA\u4E5F\u975E\u5DF2\u7ED1\u52A9\u7406` };
149211
149354
  }
149212
- var import_node_crypto6, DEFAULT_ASSISTANT_PROMPT, DEFAULT_ASSISTANT_SKILLS, DEFAULT_ASSISTANT_RUNTIME_KIND, ASSISTANT_ARCHIVED_CODE, AssistantLazyCreateError, AssistantsService;
149355
+ var import_node_crypto7, DEFAULT_ASSISTANT_PROMPT, DEFAULT_ASSISTANT_SKILLS, DEFAULT_ASSISTANT_RUNTIME_KIND, ASSISTANT_ARCHIVED_CODE, AssistantLazyCreateError, AssistantsService;
149213
149356
  var init_assistants = __esm({
149214
149357
  "../server/src/domains/actors/assistants.ts"() {
149215
149358
  "use strict";
149216
- import_node_crypto6 = require("node:crypto");
149359
+ import_node_crypto7 = require("node:crypto");
149217
149360
  init_src2();
149218
149361
  DEFAULT_ASSISTANT_PROMPT = "\u4F60\u662F\u8FD9\u540D\u771F\u4EBA\u5458\u5DE5\u7684\u4E13\u5C5E\u4E2A\u4EBA\u52A9\u7406\u3002\u4F60\u4EE5\u5176\u300C\u5DE5\u5355\u7BA1\u7406\u8005\u300D\u8EAB\u4EFD\u4EE3\u8DD1\uFF0C\u9075\u5FAA\u4E09\u6863\u6743\u9650\u6A21\u578B\uFF1A\u6863 A\uFF08\u52A0\u8FB9\u3001\u65B0\u5EFA\u8282\u70B9\u3001\u6539\u6807\u9898\u4E0E brief\u3001\u5E38\u89C4\u8BC4\u8BBA\u7B54\u7591\u3001\u63A8\u8FDB\u81EA\u5DF1\u63A5\u7684\u6D3B\uFF09\u53EF\u9006\u6539\u52A8\u76F4\u63A5\u751F\u6548\uFF1B\u6863 B\uFF08\u65AD\u8FB9\u3001\u5E9F\u8282\u70B9\u3001\u6539\u6D3E\u3001\u64A4\u90E8\u4EF6\uFF09\u9500\u6BC1\u6027\u6539\u52A8\u81EA\u52A8\u6253\u5305\u6210\u5F85\u786E\u8BA4\u63D0\u6848\uFF0C\u4EA4\u7531\u672C\u4EBA\u786E\u8BA4\u540E\u751F\u6548\uFF1B\u6863 C\uFF08force-conclude\u3001promote \u7B49\u6CBB\u7406\u547D\u4EE4\uFF09\u8D70\u4EBA\u5DE5\u6388\u6743\u5361\u3002\u4F60\u4E0D\u627F\u62C5\u4E1A\u52A1\u4E13\u4E1A\u5C97\u4F4D\u804C\u8D23\uFF0C\u53EA\u5728\u5176\u6388\u6743\u8303\u56F4\u5185\u534F\u52A9\u5EFA\u5355\u3001\u8DDF\u8FDB\u4E0E\u6C9F\u901A\u3002";
149219
149362
  DEFAULT_ASSISTANT_SKILLS = [];
@@ -151488,6 +151631,59 @@ var init_injected_context = __esm({
151488
151631
  }
151489
151632
  });
151490
151633
 
151634
+ // ../server/src/domains/chat-sessions/continuation.ts
151635
+ function chatNodeChanged(recorded, current) {
151636
+ const nodeChanged = Boolean(recorded?.nodeId && current?.nodeId && recorded.nodeId !== current.nodeId);
151637
+ const kindChanged = Boolean(recorded?.runtimeKind && current?.runtimeKind && recorded.runtimeKind !== current.runtimeKind);
151638
+ return nodeChanged || kindChanged;
151639
+ }
151640
+ function toHistory(messages, excludeMessageKey) {
151641
+ return messages.filter((m2) => m2.role !== "system" && !(m2.role === "assistant" && m2.status === "running")).filter((m2) => !excludeMessageKey || m2.messageKey !== excludeMessageKey).map((m2) => ({
151642
+ role: m2.role,
151643
+ content: m2.role === "user" ? stripInjectedChatContext(m2.content) : m2.content
151644
+ }));
151645
+ }
151646
+ async function planChatContinuation(input) {
151647
+ const { session } = input;
151648
+ const currentRuntimeId = input.currentRuntime?.nodeId ?? null;
151649
+ const currentRuntimeKind = input.currentRuntime?.runtimeKind ?? null;
151650
+ const runtimeChanged = chatNodeChanged(
151651
+ { nodeId: session.runtimeId, runtimeKind: session.runtimeKind },
151652
+ { nodeId: currentRuntimeId, runtimeKind: currentRuntimeKind }
151653
+ );
151654
+ const limit = input.historyLimit ?? 20;
151655
+ const effectiveSessionId = runtimeChanged ? void 0 : session.runtimeSessionId ?? void 0;
151656
+ const needsHistoryInjection = runtimeChanged || !session.runtimeSessionId;
151657
+ const readHistory = async () => toHistory(await input.listMessages(session.id, limit), input.excludeMessageKey);
151658
+ const history = needsHistoryInjection ? await readHistory() : [];
151659
+ const runtimeMessage = buildRuntimeChatPrompt({
151660
+ userMessage: input.userMessage,
151661
+ ...history.length ? { history } : {},
151662
+ ...input.draftWorkspace ? { draftWorkspace: input.draftWorkspace } : {}
151663
+ });
151664
+ const fallbackHistory = effectiveSessionId ? await readHistory() : [];
151665
+ const fallbackMessage = fallbackHistory.length ? buildRuntimeChatPrompt({
151666
+ userMessage: input.userMessage,
151667
+ history: fallbackHistory,
151668
+ ...input.draftWorkspace ? { draftWorkspace: input.draftWorkspace } : {}
151669
+ }) : void 0;
151670
+ return {
151671
+ runtimeMessage,
151672
+ ...fallbackMessage ? { fallbackMessage } : {},
151673
+ ...effectiveSessionId ? { effectiveSessionId } : {},
151674
+ runtimeChanged,
151675
+ currentRuntimeId,
151676
+ currentRuntimeKind,
151677
+ injectedHistoryCount: history.length
151678
+ };
151679
+ }
151680
+ var init_continuation = __esm({
151681
+ "../server/src/domains/chat-sessions/continuation.ts"() {
151682
+ "use strict";
151683
+ init_injected_context();
151684
+ }
151685
+ });
151686
+
151491
151687
  // ../server/src/domains/knowledge/config-store.ts
151492
151688
  function normalizedHttpUrl(value, field, allowEmpty = false) {
151493
151689
  if (allowEmpty && (value === void 0 || value === null || value === "")) return "";
@@ -151637,11 +151833,11 @@ var init_config_store = __esm({
151637
151833
  function clone2(value) {
151638
151834
  return structuredClone(value);
151639
151835
  }
151640
- var import_node_crypto7, import_promises2, import_node_path2, MemoryKnowledgeRunStore, FileKnowledgeRunStore;
151836
+ var import_node_crypto8, import_promises2, import_node_path2, MemoryKnowledgeRunStore, FileKnowledgeRunStore;
151641
151837
  var init_run_store = __esm({
151642
151838
  "../server/src/domains/knowledge/run-store.ts"() {
151643
151839
  "use strict";
151644
- import_node_crypto7 = require("node:crypto");
151840
+ import_node_crypto8 = require("node:crypto");
151645
151841
  import_promises2 = require("node:fs/promises");
151646
151842
  import_node_path2 = require("node:path");
151647
151843
  MemoryKnowledgeRunStore = class {
@@ -151758,7 +151954,7 @@ var init_run_store = __esm({
151758
151954
  }
151759
151955
  async persist(runs) {
151760
151956
  const shape = { version: 1, runs };
151761
- const temp = `${this.file}.${process.pid}.${(0, import_node_crypto7.randomUUID)()}.tmp`;
151957
+ const temp = `${this.file}.${process.pid}.${(0, import_node_crypto8.randomUUID)()}.tmp`;
151762
151958
  try {
151763
151959
  await (0, import_promises2.writeFile)(temp, `${JSON.stringify(shape, null, 2)}
151764
151960
  `, { mode: 384 });
@@ -152301,11 +152497,11 @@ function retryableKnowledgeRun(record8) {
152301
152497
  const latestAnswerAttempt = [...record8.attempts ?? []].reverse().find((attempt) => attempt.kind !== "fast");
152302
152498
  return record8.status === "succeeded" && record8.request.action === "ask" && record8.executionHealth === "degraded" && latestAnswerAttempt?.status === "failed";
152303
152499
  }
152304
- var import_node_crypto8, ACTION_LABEL, STATUS_MARKER, FINAL_ANSWER_MARKER, ACTIVE_STATUSES, RETRYABLE_STATUSES, HEARTBEAT_INTERVAL_MS, LEASE_MS, MAX_CONCURRENT_READS, ACTION_FLOW, KnowledgeService;
152500
+ var import_node_crypto9, ACTION_LABEL, STATUS_MARKER, FINAL_ANSWER_MARKER, ACTIVE_STATUSES, RETRYABLE_STATUSES, HEARTBEAT_INTERVAL_MS, LEASE_MS, MAX_CONCURRENT_READS, ACTION_FLOW, KnowledgeService;
152305
152501
  var init_service2 = __esm({
152306
152502
  "../server/src/domains/knowledge/service.ts"() {
152307
152503
  "use strict";
152308
- import_node_crypto8 = require("node:crypto");
152504
+ import_node_crypto9 = require("node:crypto");
152309
152505
  init_src5();
152310
152506
  init_config_store();
152311
152507
  init_run_store();
@@ -152347,7 +152543,7 @@ var init_service2 = __esm({
152347
152543
  constructor(options) {
152348
152544
  this.options = options;
152349
152545
  this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
152350
- this.id = options.id ?? import_node_crypto8.randomUUID;
152546
+ this.id = options.id ?? import_node_crypto9.randomUUID;
152351
152547
  this.runStore = options.runStore ?? new MemoryKnowledgeRunStore();
152352
152548
  this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS;
152353
152549
  this.leaseMs = options.leaseMs ?? LEASE_MS;
@@ -153612,7 +153808,7 @@ function trustedKnowledgeInvocationId(body, turn) {
153612
153808
  if (body.action !== "ask") return void 0;
153613
153809
  const scope = turn?.artifactId ?? turn?.dispatchId;
153614
153810
  if (!scope) return void 0;
153615
- const fingerprint = (0, import_node_crypto9.createHash)("sha256").update(JSON.stringify({
153811
+ const fingerprint = (0, import_node_crypto10.createHash)("sha256").update(JSON.stringify({
153616
153812
  action: body.action,
153617
153813
  message: body.message?.trim() ?? "",
153618
153814
  queryMode: body.queryMode === "deep" ? "deep" : "fast"
@@ -153711,11 +153907,11 @@ function knowledgeRoutes(service) {
153711
153907
  });
153712
153908
  };
153713
153909
  }
153714
- var import_node_crypto9;
153910
+ var import_node_crypto10;
153715
153911
  var init_routes = __esm({
153716
153912
  "../server/src/domains/knowledge/routes.ts"() {
153717
153913
  "use strict";
153718
- import_node_crypto9 = require("node:crypto");
153914
+ import_node_crypto10 = require("node:crypto");
153719
153915
  init_src5();
153720
153916
  init_router();
153721
153917
  }
@@ -154920,7 +155116,7 @@ function createAppJwt(appId, privateKeyPem, nowMs) {
154920
155116
  exp: nowSec + JWT_TTL_SEC,
154921
155117
  iss: appId
154922
155118
  }));
154923
- const signer = (0, import_node_crypto10.createSign)("RSA-SHA256");
155119
+ const signer = (0, import_node_crypto11.createSign)("RSA-SHA256");
154924
155120
  signer.update(`${header}.${payload}`);
154925
155121
  signer.end();
154926
155122
  return `${header}.${payload}.${b64url(signer.sign(privateKeyPem))}`;
@@ -155024,11 +155220,11 @@ async function exchangeManifestCode(code, deps = {}) {
155024
155220
  ...typeof body["webhook_secret"] === "string" ? { webhookSecret: body["webhook_secret"] } : {}
155025
155221
  };
155026
155222
  }
155027
- var import_node_crypto10, GITHUB_API, JWT_TTL_SEC, JWT_IAT_SKEW_SEC, defaultDeps2;
155223
+ var import_node_crypto11, GITHUB_API, JWT_TTL_SEC, JWT_IAT_SKEW_SEC, defaultDeps2;
155028
155224
  var init_app_auth = __esm({
155029
155225
  "../connectors/src/github/app-auth.ts"() {
155030
155226
  "use strict";
155031
- import_node_crypto10 = require("node:crypto");
155227
+ import_node_crypto11 = require("node:crypto");
155032
155228
  GITHUB_API = "https://api.github.com";
155033
155229
  JWT_TTL_SEC = 9 * 60;
155034
155230
  JWT_IAT_SKEW_SEC = 60;
@@ -157288,7 +157484,7 @@ var init_readiness = __esm({
157288
157484
  function deriveBoardKey(artifactId) {
157289
157485
  const tail = artifactId.split(":").pop() ?? artifactId;
157290
157486
  const ascii = tail.replace(/[^a-zA-Z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 24);
157291
- const sha8 = (0, import_node_crypto11.createHash)("sha256").update(artifactId).digest("hex").slice(0, 8);
157487
+ const sha8 = (0, import_node_crypto12.createHash)("sha256").update(artifactId).digest("hex").slice(0, 8);
157292
157488
  return ascii ? `od-${ascii}-${sha8}` : `od-${sha8}`;
157293
157489
  }
157294
157490
  async function odApi(base, fetchImpl, method, p2, body) {
@@ -157331,7 +157527,7 @@ function materializeContext(resolvedDir, files) {
157331
157527
  }
157332
157528
  }
157333
157529
  async function putVisibleUserTurn(base, fetchImpl, args) {
157334
- const messageId = (0, import_node_crypto11.randomUUID)();
157530
+ const messageId = (0, import_node_crypto12.randomUUID)();
157335
157531
  await odApi(
157336
157532
  base,
157337
157533
  fetchImpl,
@@ -157346,8 +157542,8 @@ async function startDesignRun(base, fetchImpl, args) {
157346
157542
  agentId: args.agentId,
157347
157543
  projectId: args.boardKey,
157348
157544
  conversationId: args.conversationId,
157349
- assistantMessageId: (0, import_node_crypto11.randomUUID)(),
157350
- clientRequestId: (0, import_node_crypto11.randomUUID)(),
157545
+ assistantMessageId: (0, import_node_crypto12.randomUUID)(),
157546
+ clientRequestId: (0, import_node_crypto12.randomUUID)(),
157351
157547
  message: args.message,
157352
157548
  // 提案 design-conversational-driving 坑②:systemPrompt 从写死改为可选——缺省用引擎侧默认,
157353
157549
  // 让设计意图由驱动方每轮的话(message)表达,而非焊死一句。
@@ -157528,11 +157724,11 @@ async function runDesignChatTurn(opts) {
157528
157724
  const { artifacts, fileCount } = await fetchResultPackage(opts.base, fetchImpl, runId);
157529
157725
  return { boardKey, conversationId, runId, userMessageId, artifacts, fileCount };
157530
157726
  }
157531
- var import_node_crypto11, fs4, os, path3;
157727
+ var import_node_crypto12, fs4, os, path3;
157532
157728
  var init_driver = __esm({
157533
157729
  "../adapters/src/open-design/driver.ts"() {
157534
157730
  "use strict";
157535
- import_node_crypto11 = require("node:crypto");
157731
+ import_node_crypto12 = require("node:crypto");
157536
157732
  fs4 = __toESM(require("node:fs"), 1);
157537
157733
  os = __toESM(require("node:os"), 1);
157538
157734
  path3 = __toESM(require("node:path"), 1);
@@ -157625,7 +157821,7 @@ function resolveWorkRoots(workRoot) {
157625
157821
  }
157626
157822
  function workdirSlug(workdirKey) {
157627
157823
  const safe = workdirKey.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 48);
157628
- const h = (0, import_node_crypto12.createHash)("sha256").update(workdirKey).digest("hex").slice(0, 8);
157824
+ const h = (0, import_node_crypto13.createHash)("sha256").update(workdirKey).digest("hex").slice(0, 8);
157629
157825
  return `${safe}-${h}`;
157630
157826
  }
157631
157827
  function sessionDirFor(workRoot, runtimeKind, workdirKey) {
@@ -157757,11 +157953,11 @@ function prepareWorkdir(args) {
157757
157953
  function legacyChatSessionDir(workRoot, rtId) {
157758
157954
  return import_node_path13.default.join(resolveLegacyWorkRoot(workRoot), "oasis-chat-sessions", rtId.replace(/[^a-zA-Z0-9_-]+/g, "_"));
157759
157955
  }
157760
- var import_node_crypto12, import_node_fs9, import_node_os6, import_node_path13, META_FILE, LOCK_FILE, NEW_WORK_ROOT, slug5, LEGACY_ROOTS, LEGACY_ONESHOT_PREFIX;
157956
+ var import_node_crypto13, import_node_fs9, import_node_os6, import_node_path13, META_FILE, LOCK_FILE, NEW_WORK_ROOT, slug5, LEGACY_ROOTS, LEGACY_ONESHOT_PREFIX;
157761
157957
  var init_session_paths = __esm({
157762
157958
  "../adapters/src/_core/session-paths.ts"() {
157763
157959
  "use strict";
157764
- import_node_crypto12 = require("node:crypto");
157960
+ import_node_crypto13 = require("node:crypto");
157765
157961
  import_node_fs9 = __toESM(require("node:fs"), 1);
157766
157962
  import_node_os6 = __toESM(require("node:os"), 1);
157767
157963
  import_node_path13 = __toESM(require("node:path"), 1);
@@ -157789,7 +157985,7 @@ function executionEnvHash(p2) {
157789
157985
  binPkg: p2.binPackageDir ?? null,
157790
157986
  extraRo: [...p2.extraRoBindDirs ?? []].sort()
157791
157987
  });
157792
- return (0, import_node_crypto13.createHash)("sha256").update(material).digest("hex").slice(0, 16);
157988
+ return (0, import_node_crypto14.createHash)("sha256").update(material).digest("hex").slice(0, 16);
157793
157989
  }
157794
157990
  function persistContainerName(workdirKey) {
157795
157991
  return `oasis-wd-${workdirSlug(workdirKey)}`;
@@ -157909,11 +158105,11 @@ function wrapForContainer(p2) {
157909
158105
  function killContainerArgs(containerName) {
157910
158106
  return ["kill", containerName];
157911
158107
  }
157912
- var import_node_crypto13, path6, CONTAINER_DEFAULT_PATH, CONTAINER_HOME, EE_HASH_LABEL;
158108
+ var import_node_crypto14, path6, CONTAINER_DEFAULT_PATH, CONTAINER_HOME, EE_HASH_LABEL;
157913
158109
  var init_container_wrap = __esm({
157914
158110
  "../adapters/src/_core/container-wrap.ts"() {
157915
158111
  "use strict";
157916
- import_node_crypto13 = require("node:crypto");
158112
+ import_node_crypto14 = require("node:crypto");
157917
158113
  path6 = __toESM(require("node:path"), 1);
157918
158114
  init_session_paths();
157919
158115
  CONTAINER_DEFAULT_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
@@ -158380,12 +158576,12 @@ function classifyExit(result) {
158380
158576
  if (status === 429 || status === 529) return "rate-limit";
158381
158577
  return "error";
158382
158578
  }
158383
- var import_node_child_process7, import_node_crypto14, fs9, path8, readline, CLAUDE_BLOCKED_FLAGS, ONE_SHOT_UNSAFE_TOOLS, ClaudeCodeAdapter;
158579
+ var import_node_child_process7, import_node_crypto15, fs9, path8, readline, CLAUDE_BLOCKED_FLAGS, ONE_SHOT_UNSAFE_TOOLS, ClaudeCodeAdapter;
158384
158580
  var init_claude_code = __esm({
158385
158581
  "../adapters/src/claude-code/index.ts"() {
158386
158582
  "use strict";
158387
158583
  import_node_child_process7 = require("node:child_process");
158388
- import_node_crypto14 = require("node:crypto");
158584
+ import_node_crypto15 = require("node:crypto");
158389
158585
  fs9 = __toESM(require("node:fs"), 1);
158390
158586
  path8 = __toESM(require("node:path"), 1);
158391
158587
  readline = __toESM(require("node:readline"), 1);
@@ -158455,7 +158651,7 @@ var init_claude_code = __esm({
158455
158651
  }
158456
158652
  async spawn(job) {
158457
158653
  const slug6 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
158458
- const id = `claude-${slug6}-${(0, import_node_crypto14.randomUUID)().slice(0, 8)}`;
158654
+ const id = `claude-${slug6}-${(0, import_node_crypto15.randomUUID)().slice(0, 8)}`;
158459
158655
  const dir = job.workdirKey ? prepareWorkdir({
158460
158656
  workRoot: this.opts.workRoot,
158461
158657
  runtimeKind: "claude",
@@ -159934,13 +160130,13 @@ function normalizeCodexConsoleLine(line, state = createCodexNormalizeState()) {
159934
160130
  tool.output.push(line);
159935
160131
  return [];
159936
160132
  }
159937
- var import_node_child_process9, import_node_crypto15, fs10, os5, path10, readline2, CODEX_BLOCKED_FLAGS, CODEX_ARGV_PROMPT_MAX_BYTES, CodexAdapter;
160133
+ var import_node_child_process9, import_node_crypto16, fs10, os5, path10, readline2, CODEX_BLOCKED_FLAGS, CODEX_ARGV_PROMPT_MAX_BYTES, CodexAdapter;
159938
160134
  var init_codex = __esm({
159939
160135
  "../adapters/src/codex/index.ts"() {
159940
160136
  "use strict";
159941
160137
  import_node_child_process9 = require("node:child_process");
159942
160138
  init_container_runtime();
159943
- import_node_crypto15 = require("node:crypto");
160139
+ import_node_crypto16 = require("node:crypto");
159944
160140
  fs10 = __toESM(require("node:fs"), 1);
159945
160141
  os5 = __toESM(require("node:os"), 1);
159946
160142
  path10 = __toESM(require("node:path"), 1);
@@ -159986,7 +160182,7 @@ var init_codex = __esm({
159986
160182
  capabilities = { appendInput: false };
159987
160183
  async spawn(job) {
159988
160184
  const slug6 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
159989
- const id = `codex-${slug6}-${(0, import_node_crypto15.randomUUID)().slice(0, 8)}`;
160185
+ const id = `codex-${slug6}-${(0, import_node_crypto16.randomUUID)().slice(0, 8)}`;
159990
160186
  const startedAtMs = Date.now();
159991
160187
  const dir = job.workdirKey ? prepareWorkdir({
159992
160188
  workRoot: this.opts.workRoot,
@@ -160775,7 +160971,7 @@ async function runACPSession(job, cfg) {
160775
160971
  }
160776
160972
  const slug6 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
160777
160973
  const binTag = path11.basename(cfg.bin).replace(/[^a-zA-Z0-9]/g, "").slice(0, 12) || "acp";
160778
- const id = `${binTag}-${slug6}-${(0, import_node_crypto16.randomUUID)().slice(0, 8)}`;
160974
+ const id = `${binTag}-${slug6}-${(0, import_node_crypto17.randomUUID)().slice(0, 8)}`;
160779
160975
  const dir = job.workdirKey ? prepareWorkdir({
160780
160976
  workRoot: cfg.workRoot,
160781
160977
  runtimeKind: cfg.runtimeKind ?? "subprocess",
@@ -161085,12 +161281,12 @@ ${task}` : task;
161085
161281
  }
161086
161282
  };
161087
161283
  }
161088
- var import_node_child_process10, import_node_crypto16, fs11, path11, readline3, ACPClient, LEGACY_APPROVE_OPTION_ID;
161284
+ var import_node_child_process10, import_node_crypto17, fs11, path11, readline3, ACPClient, LEGACY_APPROVE_OPTION_ID;
161089
161285
  var init_acp = __esm({
161090
161286
  "../adapters/src/_core/acp.ts"() {
161091
161287
  "use strict";
161092
161288
  import_node_child_process10 = require("node:child_process");
161093
- import_node_crypto16 = require("node:crypto");
161289
+ import_node_crypto17 = require("node:crypto");
161094
161290
  fs11 = __toESM(require("node:fs"), 1);
161095
161291
  path11 = __toESM(require("node:path"), 1);
161096
161292
  readline3 = __toESM(require("node:readline"), 1);
@@ -161588,7 +161784,7 @@ var init_reasonix = __esm({
161588
161784
  // ../adapters/src/_core/subprocess.ts
161589
161785
  async function materialize(job, cfg) {
161590
161786
  const slug6 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
161591
- const id = `${slug6}-${(0, import_node_crypto17.randomUUID)().slice(0, 8)}`;
161787
+ const id = `${slug6}-${(0, import_node_crypto18.randomUUID)().slice(0, 8)}`;
161592
161788
  const dir = job.workdirKey ? prepareWorkdir({
161593
161789
  workRoot: cfg.workRoot,
161594
161790
  runtimeKind: cfg.runtimeKind ?? "subprocess",
@@ -161860,12 +162056,12 @@ async function runOneShotText(job, cfg) {
161860
162056
  }
161861
162057
  });
161862
162058
  }
161863
- var import_node_child_process11, import_node_crypto17, fs12, path12, readline4;
162059
+ var import_node_child_process11, import_node_crypto18, fs12, path12, readline4;
161864
162060
  var init_subprocess = __esm({
161865
162061
  "../adapters/src/_core/subprocess.ts"() {
161866
162062
  "use strict";
161867
162063
  import_node_child_process11 = require("node:child_process");
161868
- import_node_crypto17 = require("node:crypto");
162064
+ import_node_crypto18 = require("node:crypto");
161869
162065
  fs12 = __toESM(require("node:fs"), 1);
161870
162066
  path12 = __toESM(require("node:path"), 1);
161871
162067
  readline4 = __toESM(require("node:readline"), 1);
@@ -162312,11 +162508,11 @@ function normalizeOpenClaw(line) {
162312
162508
  }
162313
162509
  return [];
162314
162510
  }
162315
- var import_node_crypto18, OpenClawAdapter;
162511
+ var import_node_crypto19, OpenClawAdapter;
162316
162512
  var init_openclaw = __esm({
162317
162513
  "../adapters/src/openclaw/index.ts"() {
162318
162514
  "use strict";
162319
- import_node_crypto18 = require("node:crypto");
162515
+ import_node_crypto19 = require("node:crypto");
162320
162516
  init_subprocess();
162321
162517
  OpenClawAdapter = class {
162322
162518
  constructor(opts = {}) {
@@ -162334,7 +162530,7 @@ var init_openclaw = __esm({
162334
162530
  ---
162335
162531
 
162336
162532
  ${task}` : task;
162337
- const sessionId = job2.runtimeSessionId ?? `oasis-${(0, import_node_crypto18.randomUUID)().slice(0, 8)}`;
162533
+ const sessionId = job2.runtimeSessionId ?? `oasis-${(0, import_node_crypto19.randomUUID)().slice(0, 8)}`;
162338
162534
  return [
162339
162535
  "agent",
162340
162536
  ...opts.mode !== "gateway" ? ["--local"] : [],
@@ -162818,11 +163014,6 @@ function resolveCallbackBase(supplied, requestHost) {
162818
163014
  }
162819
163015
  return `${parsed.origin}${parsed.pathname.replace(/\/+$/, "")}`;
162820
163016
  }
162821
- function chatNodeChanged(recorded, current) {
162822
- const nodeChanged = Boolean(recorded?.nodeId && current?.nodeId && recorded.nodeId !== current.nodeId);
162823
- const kindChanged = Boolean(recorded?.runtimeKind && current?.runtimeKind && recorded.runtimeKind !== current.runtimeKind);
162824
- return nodeChanged || kindChanged;
162825
- }
162826
163017
  function wantsChatParts(req) {
162827
163018
  const accept = req.headers.accept;
162828
163019
  return typeof accept === "string" && accept.includes("application/x-ndjson");
@@ -164266,12 +164457,12 @@ async function startOasisServer(opts) {
164266
164457
  if (!store || !dispatch) return;
164267
164458
  const session = await store.getSession(origin).catch(() => null);
164268
164459
  if (!session) return;
164269
- const { randomUUID: randomUUID30 } = await import("node:crypto");
164460
+ const { randomUUID: randomUUID31 } = await import("node:crypto");
164270
164461
  const approved = resolution.status === "approved";
164271
164462
  const by = ctx.authorizedBy ?? "\u7BA1\u7406\u8005";
164272
164463
  const label = ctx.effectLabel || ctx.command || "";
164273
164464
  const statusText = approved ? `\u547D\u4EE4\u5DF2\u7531 ${by} \u6279\u51C6\u5E76\u4EE5\u5176\u540D\u4E49\u6267\u884C${label ? `\uFF1A${label}` : ""}\u3002` : `\u547D\u4EE4\u5DF2\u88AB ${by} \u9A73\u56DE${ctx.reason ? `\uFF1A${ctx.reason}` : ""}\u3002`;
164274
- await store.appendMessage({ id: randomUUID30(), sessionId: origin, role: "system", content: statusText, createdAt: (/* @__PURE__ */ new Date()).toISOString() }).catch(() => void 0);
164465
+ await store.appendMessage({ id: randomUUID31(), sessionId: origin, role: "system", content: statusText, createdAt: (/* @__PURE__ */ new Date()).toISOString() }).catch(() => void 0);
164275
164466
  if (liveChat.isRunning(origin)) return;
164276
164467
  const prompt = approved ? `\u4E0A\u4E00\u6761\u5F85\u6388\u6743\u547D\u4EE4\u5DF2\u88AB ${by} \u6279\u51C6\u5E76\u4EE5\u5176\u540D\u4E49\u6267\u884C\uFF08\u547D\u4EE4\uFF1A${label}\uFF09\u3002\u8BF7\u7EE7\u7EED\u539F\u4EFB\u52A1\u3002` : `\u4E0A\u4E00\u6761\u5F85\u6388\u6743\u547D\u4EE4\u5DF2\u88AB ${by} \u9A73\u56DE${ctx.reason ? `\uFF08\u7406\u7531\uFF1A${ctx.reason}\uFF09` : ""}\u3002\u522B\u91CD\u8BD5\u540C\u4E00\u6761\u2014\u2014\u8BF7\u6539\u65B9\u6848\u6216\u5148\u5411\u53D1\u8D77\u4EBA\u95EE\u6E05\u695A\u3002`;
164277
164468
  let dispatched;
@@ -164292,7 +164483,7 @@ async function startOasisServer(opts) {
164292
164483
  void dispatched.done.then(async () => {
164293
164484
  if (text || dispatched.runId) {
164294
164485
  await store.appendMessage({
164295
- id: randomUUID30(),
164486
+ id: randomUUID31(),
164296
164487
  sessionId: origin,
164297
164488
  role: "assistant",
164298
164489
  content: text,
@@ -164307,6 +164498,7 @@ async function startOasisServer(opts) {
164307
164498
  store: opts.channels.store,
164308
164499
  dispatchChat: (req) => opts.dispatchChat(req),
164309
164500
  chatSession: opts.chatSession,
164501
+ chatTurns: opts.chatTurns,
164310
164502
  revealVar: (key, actorId) => opts.actors.service.revealResolvedVariable(key, actorId),
164311
164503
  // 决策 0080 第一期:探活成功时把 bot 的 app_name 落到连接器记录(account)供显示——
164312
164504
  // 此前名称在 botInfo 后被丢弃,前端身份位只能显示 App ID(cli_xxx)。
@@ -165097,11 +165289,11 @@ async function startOasisServer(opts) {
165097
165289
  const itemKey = `${baseItemKey}:${ctx.ownerId}`;
165098
165290
  let chatSessionId;
165099
165291
  if (chatStore) {
165100
- const { randomUUID: randomUUID30 } = await import("node:crypto");
165292
+ const { randomUUID: randomUUID31 } = await import("node:crypto");
165101
165293
  const known = discussSessions.get(itemKey);
165102
165294
  if (known && await chatStore.getSession(known).catch(() => null)) chatSessionId = known;
165103
165295
  if (!chatSessionId) {
165104
- chatSessionId = randomUUID30();
165296
+ chatSessionId = randomUUID31();
165105
165297
  const now = (/* @__PURE__ */ new Date()).toISOString();
165106
165298
  const title = `\u89E3\u51B3\uFF1A${ctx.seed.slice(ctx.seed.indexOf("\uFF1A") + 1, ctx.seed.indexOf("\uFF1A") + 25) || "\u5DE5\u5355\u5361\u70B9"}`;
165107
165299
  const reg0 = (await resolveEngine(currentCompanyId).catch(() => null))?.registry ?? opts.registry;
@@ -165555,38 +165747,62 @@ async function startOasisServer(opts) {
165555
165747
  }
165556
165748
  const registry2 = engine.registry ?? opts.registry;
165557
165749
  const currentBinding = persistTarget && registry2 ? await registry2.getBinding(persistTarget.aiActorId).catch(() => null) : null;
165558
- const currentRuntimeId = currentBinding?.status === "active" ? currentBinding.nodeId : null;
165559
- const currentRuntimeKind = currentBinding?.status === "active" ? currentBinding.runtimeKind : null;
165560
- const runtimeChanged = chatNodeChanged(
165561
- { nodeId: persistTarget?.runtimeId, runtimeKind: persistTarget?.runtimeKind },
165562
- { nodeId: currentRuntimeId, runtimeKind: currentRuntimeKind }
165563
- );
165564
165750
  const persistedUserMessage = persistTarget ? stripInjectedChatContext(body.message) : body.message;
165565
- const needsHistoryInjection = Boolean(persistTarget && (runtimeChanged || !persistTarget.runtimeSessionId));
165566
- const history = needsHistoryInjection && chatStore && persistTarget ? (await chatStore.listMessages(persistTarget.id, 20)).filter((message) => message.role !== "system" && !(message.role === "assistant" && message.status === "running")).map((message) => ({
165567
- role: message.role,
165568
- content: message.role === "user" ? stripInjectedChatContext(message.content) : message.content
165569
- })) : [];
165570
165751
  const draft = persistTarget && opts.workorderDrafts ? await opts.workorderDrafts.forSession(persistTarget.id) : void 0;
165571
165752
  const draftWorkspace = draft && ![...engine.kernel.model.artifacts.values()].some((artifact) => artifact.workspace === draft.workspace) ? draft.workspace : void 0;
165572
- const runtimeMessage = buildRuntimeChatPrompt({
165753
+ const plan = persistTarget && chatStore ? await planChatContinuation({
165754
+ session: persistTarget,
165755
+ userMessage: persistedUserMessage,
165756
+ listMessages: (sid, limit) => chatStore.listMessages(sid, limit),
165757
+ currentRuntime: currentBinding?.status === "active" ? { nodeId: currentBinding.nodeId, runtimeKind: currentBinding.runtimeKind } : null,
165758
+ ...draftWorkspace ? { draftWorkspace } : {}
165759
+ }) : null;
165760
+ const currentRuntimeId = plan?.currentRuntimeId ?? null;
165761
+ const currentRuntimeKind = plan?.currentRuntimeKind ?? null;
165762
+ const runtimeMessage = plan ? plan.runtimeMessage : buildRuntimeChatPrompt({
165573
165763
  userMessage: persistedUserMessage,
165574
- ...history.length ? { history } : {},
165575
165764
  ...draftWorkspace ? { draftWorkspace } : {}
165576
165765
  });
165577
- const effectiveSessionId = persistTarget ? runtimeChanged ? void 0 : persistTarget.runtimeSessionId ?? void 0 : body.sessionId || void 0;
165578
- const resumeFallbackHistory = effectiveSessionId && chatStore && persistTarget ? (await chatStore.listMessages(persistTarget.id, 20)).filter((m2) => m2.role !== "system" && !(m2.role === "assistant" && m2.status === "running")).map((m2) => ({ role: m2.role, content: m2.role === "user" ? stripInjectedChatContext(m2.content) : m2.content })) : [];
165579
- const fallbackMessage = resumeFallbackHistory.length ? buildRuntimeChatPrompt({ userMessage: persistedUserMessage, history: resumeFallbackHistory, ...draftWorkspace ? { draftWorkspace } : {} }) : void 0;
165580
- const session = await opts.dispatchChat({
165581
- actorId: body.actorId,
165582
- message: runtimeMessage,
165583
- ...effectiveSessionId ? { sessionId: effectiveSessionId } : {},
165584
- ...fallbackMessage ? { fallbackMessage } : {},
165585
- ...body.chatSessionId ? { chatSessionId: body.chatSessionId } : {},
165586
- ...body.workspace ? { workspace: body.workspace } : {},
165587
- ...currentCompanyId !== void 0 ? { companyId: currentCompanyId } : {},
165588
- ...attachments.length ? { attachments } : {}
165589
- });
165766
+ const effectiveSessionId = plan ? plan.effectiveSessionId : body.sessionId || void 0;
165767
+ const fallbackMessage = plan?.fallbackMessage;
165768
+ let heldTurn = null;
165769
+ if (persistTarget) {
165770
+ const opened = await openChatTurn({
165771
+ turns: opts.chatTurns,
165772
+ chatSessionId: persistTarget.id,
165773
+ source: "human",
165774
+ log: (m2) => console.warn(m2)
165775
+ });
165776
+ if (!opened.ok) {
165777
+ throw new ApiError(
165778
+ 409,
165779
+ "CHAT_SESSION_BUSY",
165780
+ `\u8FD9\u4E2A\u4F1A\u8BDD\u6B63\u5728\u8DD1\u4E00\u8F6E\uFF0C\u8FD9\u53E5\u8BDD\u6CA1\u6709\u53D1\u51FA\u2014\u2014\u4E0D\u6392\u961F\u3001\u4E5F\u4E0D\u63D2\u8BDD\u3002\u7B49\u8FD9\u4E00\u8F6E\u8DD1\u5B8C\u518D\u53D1\uFF0C\u6216\u8005\u7528\u300C\u63D2\u5165\u300D\u628A\u5B83\u9001\u8FDB\u6B63\u5728\u8DD1\u7684\u90A3\u4E00\u8F6E\u3002\uFF08\u5360\u7740\u7684\u8F6E\u6B21 ${opened.activeTurn.id}\uFF0C\u6765\u6E90 ${opened.activeTurn.source}\uFF09`
165781
+ );
165782
+ }
165783
+ heldTurn = opened.turn;
165784
+ }
165785
+ let session;
165786
+ try {
165787
+ session = await opts.dispatchChat({
165788
+ actorId: body.actorId,
165789
+ message: runtimeMessage,
165790
+ ...effectiveSessionId ? { sessionId: effectiveSessionId } : {},
165791
+ ...fallbackMessage ? { fallbackMessage } : {},
165792
+ ...body.chatSessionId ? { chatSessionId: body.chatSessionId } : {},
165793
+ ...body.workspace ? { workspace: body.workspace } : {},
165794
+ ...currentCompanyId !== void 0 ? { companyId: currentCompanyId } : {},
165795
+ ...attachments.length ? { attachments } : {}
165796
+ });
165797
+ } catch (err) {
165798
+ await heldTurn?.settle({
165799
+ status: "failed",
165800
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
165801
+ lastError: err instanceof Error ? err.message : String(err)
165802
+ });
165803
+ throw err;
165804
+ }
165805
+ await heldTurn?.start({ assistantRunId: session.runId ?? null, dispatchId: session.runId ?? null });
165590
165806
  if (chatStore && persistTarget) {
165591
165807
  if (currentRuntimeId) {
165592
165808
  await chatStore.updateSession(persistTarget.id, {
@@ -165596,14 +165812,14 @@ async function startOasisServer(opts) {
165596
165812
  runtimeSessionId: session.nativeSessionId ?? session.id
165597
165813
  }).catch(() => void 0);
165598
165814
  }
165599
- const { randomUUID: randomUUID31 } = await import("node:crypto");
165815
+ const { randomUUID: randomUUID32 } = await import("node:crypto");
165600
165816
  const persistAttachments = attachments.map((a) => ({
165601
165817
  name: a.name,
165602
165818
  ...a.blobRef !== void 0 ? { blobRef: a.blobRef } : {},
165603
165819
  ...a.contentType !== void 0 ? { contentType: a.contentType } : {}
165604
165820
  }));
165605
165821
  await chatStore.appendMessage({
165606
- id: randomUUID31(),
165822
+ id: randomUUID32(),
165607
165823
  sessionId: persistTarget.id,
165608
165824
  role: "user",
165609
165825
  content: persistedUserMessage,
@@ -165613,8 +165829,8 @@ async function startOasisServer(opts) {
165613
165829
  }
165614
165830
  let assistantMsgId;
165615
165831
  if (chatStore && persistTarget) {
165616
- const { randomUUID: randomUUID31 } = await import("node:crypto");
165617
- const id = randomUUID31();
165832
+ const { randomUUID: randomUUID32 } = await import("node:crypto");
165833
+ const id = randomUUID32();
165618
165834
  await chatStore.appendMessage({
165619
165835
  id,
165620
165836
  sessionId: persistTarget.id,
@@ -165650,9 +165866,9 @@ async function startOasisServer(opts) {
165650
165866
  }).catch(() => void 0);
165651
165867
  } else {
165652
165868
  if (!assistantText && !session.runId && parts.length === 0) return;
165653
- const { randomUUID: randomUUID31 } = await import("node:crypto");
165869
+ const { randomUUID: randomUUID32 } = await import("node:crypto");
165654
165870
  await chatStore.appendMessage({
165655
- id: randomUUID31(),
165871
+ id: randomUUID32(),
165656
165872
  sessionId: persistTarget.id,
165657
165873
  role: "assistant",
165658
165874
  content: assistantText,
@@ -165663,6 +165879,12 @@ async function startOasisServer(opts) {
165663
165879
  ...parts.length ? { parts } : {}
165664
165880
  }).catch(() => void 0);
165665
165881
  }
165882
+ await heldTurn?.settle({
165883
+ status: status === "done" ? "succeeded" : "failed",
165884
+ completedAt: finishedAt,
165885
+ ...session.runId ? { assistantRunId: session.runId, dispatchId: session.runId } : {},
165886
+ ...status === "done" ? {} : { lastError: "\u8FD9\u4E00\u8F6E\u4EE5 error \u6536\u5C3E" }
165887
+ });
165666
165888
  await chatStore.updateSession(persistTarget.id, {
165667
165889
  ...currentRuntimeId ? { runtimeId: currentRuntimeId } : {},
165668
165890
  ...currentRuntimeKind ? { runtimeKind: currentRuntimeKind } : {},
@@ -165885,8 +166107,8 @@ async function startOasisServer(opts) {
165885
166107
  }
165886
166108
  }
165887
166109
  const { spawn: spawn9 } = await import("node:child_process");
165888
- const { randomUUID: randomUUID30 } = await import("node:crypto");
165889
- const sessionId = body.sessionId ?? randomUUID30();
166110
+ const { randomUUID: randomUUID31 } = await import("node:crypto");
166111
+ const sessionId = body.sessionId ?? randomUUID31();
165890
166112
  const args = [
165891
166113
  "-p",
165892
166114
  body.message,
@@ -166111,8 +166333,8 @@ async function startOasisServer(opts) {
166111
166333
  res.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify({ error: `\u56DE\u8C03\u5730\u5740\u4E0D\u53EF\u7528\uFF0C\u5DF2\u4E2D\u6B62\uFF08\u907F\u514D\u5728 GitHub \u4E0A\u7559\u4E0B\u5B64\u513F App\uFF09\uFF1A${e.message}` }));
166112
166334
  return;
166113
166335
  }
166114
- const { randomUUID: randomUUID30 } = await import("node:crypto");
166115
- const state = randomUUID30();
166336
+ const { randomUUID: randomUUID31 } = await import("node:crypto");
166337
+ const state = randomUUID31();
166116
166338
  const nameSlug = (actorName || actorId?.split(":").pop() || owner?.login || "bot").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "").slice(0, 24) || "bot";
166117
166339
  const manifest = buildAppManifest({
166118
166340
  employeeSlug: nameSlug,
@@ -166563,8 +166785,11 @@ var init_server3 = __esm({
166563
166785
  init_planner();
166564
166786
  init_chat_parts();
166565
166787
  init_injected_context();
166788
+ init_continuation();
166789
+ init_chat_turn_gate();
166566
166790
  init_knowledge2();
166567
166791
  init_src8();
166792
+ init_continuation();
166568
166793
  githubAppPending = new PendingAppCreations();
166569
166794
  defaultResolveActor = (token) => token.startsWith("token:") ? token.slice("token:".length) : null;
166570
166795
  enc = (s2) => new TextEncoder().encode(s2);
@@ -166662,7 +166887,7 @@ function createNodeTokenStore(file) {
166662
166887
  return {
166663
166888
  issue(nodeId) {
166664
166889
  const table = read();
166665
- const token = `ont_${(0, import_node_crypto19.randomBytes)(24).toString("base64url")}`;
166890
+ const token = `ont_${(0, import_node_crypto20.randomBytes)(24).toString("base64url")}`;
166666
166891
  table[token] = nodeId;
166667
166892
  write(table);
166668
166893
  return token;
@@ -166704,7 +166929,7 @@ function createEnrollTokenStore(defaultTtlMs = 30 * 60 * 1e3, file) {
166704
166929
  return {
166705
166930
  issue(name, ttlMs = defaultTtlMs) {
166706
166931
  const m2 = prune();
166707
- const token = `ent_${(0, import_node_crypto19.randomBytes)(24).toString("base64url")}`;
166932
+ const token = `ent_${(0, import_node_crypto20.randomBytes)(24).toString("base64url")}`;
166708
166933
  const expiresAt2 = Date.now() + ttlMs;
166709
166934
  m2.set(token, { name, expiresAt: expiresAt2 });
166710
166935
  write(m2);
@@ -166734,33 +166959,33 @@ function createEnrollTokenStore(defaultTtlMs = 30 * 60 * 1e3, file) {
166734
166959
  }
166735
166960
  };
166736
166961
  }
166737
- var import_node_crypto19, fs15, path15, SESSION_TOKEN_PREFIX, b64url2, fromB64url, readOrCreateSecret, sign, signatureMatches, isTokenClaims;
166962
+ var import_node_crypto20, fs15, path15, SESSION_TOKEN_PREFIX, b64url2, fromB64url, readOrCreateSecret, sign, signatureMatches, isTokenClaims;
166738
166963
  var init_tokens = __esm({
166739
166964
  "../server/src/tokens.ts"() {
166740
166965
  "use strict";
166741
- import_node_crypto19 = require("node:crypto");
166966
+ import_node_crypto20 = require("node:crypto");
166742
166967
  fs15 = __toESM(require("node:fs"), 1);
166743
166968
  path15 = __toESM(require("node:path"), 1);
166744
166969
  SESSION_TOKEN_PREFIX = "oat_v2_";
166745
166970
  b64url2 = (value) => Buffer.from(value).toString("base64url");
166746
166971
  fromB64url = (value) => Buffer.from(value, "base64url").toString("utf8");
166747
166972
  readOrCreateSecret = (file) => {
166748
- if (!file) return (0, import_node_crypto19.randomBytes)(32);
166973
+ if (!file) return (0, import_node_crypto20.randomBytes)(32);
166749
166974
  try {
166750
166975
  const raw = fs15.readFileSync(file, "utf8").trim();
166751
166976
  if (raw) return Buffer.from(raw, "base64url");
166752
166977
  } catch {
166753
166978
  }
166754
- const secret = (0, import_node_crypto19.randomBytes)(32);
166979
+ const secret = (0, import_node_crypto20.randomBytes)(32);
166755
166980
  fs15.mkdirSync(path15.dirname(file), { recursive: true });
166756
166981
  fs15.writeFileSync(file, secret.toString("base64url"), { mode: 384 });
166757
166982
  return secret;
166758
166983
  };
166759
- sign = (secret, payload) => (0, import_node_crypto19.createHmac)("sha256", secret).update(payload).digest("base64url");
166984
+ sign = (secret, payload) => (0, import_node_crypto20.createHmac)("sha256", secret).update(payload).digest("base64url");
166760
166985
  signatureMatches = (actual, expected) => {
166761
166986
  const a = Buffer.from(actual);
166762
166987
  const b2 = Buffer.from(expected);
166763
- return a.length === b2.length && (0, import_node_crypto19.timingSafeEqual)(a, b2);
166988
+ return a.length === b2.length && (0, import_node_crypto20.timingSafeEqual)(a, b2);
166764
166989
  };
166765
166990
  isTokenClaims = (value) => {
166766
166991
  if (value === null || typeof value !== "object") return false;
@@ -166807,31 +167032,31 @@ function createDataplaneTokenIssuer(opts = {}) {
166807
167032
  }
166808
167033
  };
166809
167034
  }
166810
- var import_node_crypto20, fs16, path16, PREFIX, b64url3, fromB64url2, sign2, sigMatches, readOrCreateSecret2, isClaims;
167035
+ var import_node_crypto21, fs16, path16, PREFIX, b64url3, fromB64url2, sign2, sigMatches, readOrCreateSecret2, isClaims;
166811
167036
  var init_session_dataplane_token = __esm({
166812
167037
  "../server/src/session-dataplane-token.ts"() {
166813
167038
  "use strict";
166814
- import_node_crypto20 = require("node:crypto");
167039
+ import_node_crypto21 = require("node:crypto");
166815
167040
  fs16 = __toESM(require("node:fs"), 1);
166816
167041
  path16 = __toESM(require("node:path"), 1);
166817
167042
  init_src2();
166818
167043
  PREFIX = "osd_";
166819
167044
  b64url3 = (v2) => Buffer.from(v2, "utf8").toString("base64url");
166820
167045
  fromB64url2 = (v2) => Buffer.from(v2, "base64url").toString("utf8");
166821
- sign2 = (secret, payload) => (0, import_node_crypto20.createHmac)("sha256", secret).update(payload).digest("base64url");
167046
+ sign2 = (secret, payload) => (0, import_node_crypto21.createHmac)("sha256", secret).update(payload).digest("base64url");
166822
167047
  sigMatches = (actual, expected) => {
166823
167048
  const a = Buffer.from(actual);
166824
167049
  const b2 = Buffer.from(expected);
166825
- return a.length === b2.length && (0, import_node_crypto20.timingSafeEqual)(a, b2);
167050
+ return a.length === b2.length && (0, import_node_crypto21.timingSafeEqual)(a, b2);
166826
167051
  };
166827
167052
  readOrCreateSecret2 = (file) => {
166828
- if (!file) return (0, import_node_crypto20.randomBytes)(32);
167053
+ if (!file) return (0, import_node_crypto21.randomBytes)(32);
166829
167054
  try {
166830
167055
  const raw = fs16.readFileSync(file, "utf8").trim();
166831
167056
  if (raw) return Buffer.from(raw, "base64url");
166832
167057
  } catch {
166833
167058
  }
166834
- const secret = (0, import_node_crypto20.randomBytes)(32);
167059
+ const secret = (0, import_node_crypto21.randomBytes)(32);
166835
167060
  fs16.mkdirSync(path16.dirname(file), { recursive: true });
166836
167061
  fs16.writeFileSync(file, secret.toString("base64url"), { mode: 384 });
166837
167062
  return secret;
@@ -168195,7 +168420,9 @@ var init_memory_automation_store = __esm({
168195
168420
  collaborators: [],
168196
168421
  createdBy: input.createdBy ?? null,
168197
168422
  createdAt: now,
168198
- updatedAt: now
168423
+ updatedAt: now,
168424
+ chatTarget: input.chatTarget ?? null,
168425
+ ownerHumanActorId: input.ownerHumanActorId ?? null
168199
168426
  };
168200
168427
  this.automations.set(id, structuredClone(a));
168201
168428
  if (input.triggers?.length) await this.replaceTriggers(id, input.triggers);
@@ -168678,11 +168905,11 @@ function onlyHits(pool, q) {
168678
168905
  if (!q.requireMatch || q.keywords.length === 0) return [...pool];
168679
168906
  return pool.filter((r) => memoryMatchScore(r, q.keywords) > 0);
168680
168907
  }
168681
- var import_node_crypto21, MemoryActorMemoryStore, toIndexEntry;
168908
+ var import_node_crypto22, MemoryActorMemoryStore, toIndexEntry;
168682
168909
  var init_memory_actor_memory_store = __esm({
168683
168910
  "../testkit/src/memory-actor-memory-store.ts"() {
168684
168911
  "use strict";
168685
- import_node_crypto21 = require("node:crypto");
168912
+ import_node_crypto22 = require("node:crypto");
168686
168913
  init_src2();
168687
168914
  MemoryActorMemoryStore = class {
168688
168915
  rows = /* @__PURE__ */ new Map();
@@ -168736,7 +168963,7 @@ var init_memory_actor_memory_store = __esm({
168736
168963
  async write(input, now) {
168737
168964
  if (input.memId === void 0) {
168738
168965
  const rec = {
168739
- memId: `mem:${(0, import_node_crypto21.randomUUID)()}`,
168966
+ memId: `mem:${(0, import_node_crypto22.randomUUID)()}`,
168740
168967
  actorId: input.actorId,
168741
168968
  projectId: input.projectId,
168742
168969
  keywords: [...input.keywords],
@@ -170628,11 +170855,11 @@ var init_service3 = __esm({
170628
170855
  });
170629
170856
 
170630
170857
  // ../server/src/dev-store.ts
170631
- var import_node_crypto22, fs17, path17, NdjsonOplogStore, DirBlobStore, MUTATORS, FileTypeRegistryStore, FileRoleRegistryStore, FileRegistryStore, FileAssistantBindStore, FileHumanPrefsStore, FileProjectStateStore, FileProjectDocumentStore, FileArtifactStateStore, FileTraceStore, MemoryChatSessionStore, FileChatSessionStore, MemoryReadMarkerStore, FileReadMarkerStore;
170858
+ var import_node_crypto23, fs17, path17, NdjsonOplogStore, DirBlobStore, MUTATORS, FileTypeRegistryStore, FileRoleRegistryStore, FileRegistryStore, FileAssistantBindStore, FileHumanPrefsStore, FileProjectStateStore, FileProjectDocumentStore, FileArtifactStateStore, FileTraceStore, MemoryChatSessionStore, FileChatSessionStore, MemoryChatTurnStore, FileChatTurnStore, MemoryReadMarkerStore, FileReadMarkerStore;
170632
170859
  var init_dev_store = __esm({
170633
170860
  "../server/src/dev-store.ts"() {
170634
170861
  "use strict";
170635
- import_node_crypto22 = require("node:crypto");
170862
+ import_node_crypto23 = require("node:crypto");
170636
170863
  fs17 = __toESM(require("node:fs"), 1);
170637
170864
  path17 = __toESM(require("node:path"), 1);
170638
170865
  init_src2();
@@ -170686,7 +170913,7 @@ var init_dev_store = __esm({
170686
170913
  return path17.join(this.dir, hash);
170687
170914
  }
170688
170915
  async put(bytes) {
170689
- const hash = (0, import_node_crypto22.createHash)("sha256").update(bytes).digest("hex");
170916
+ const hash = (0, import_node_crypto23.createHash)("sha256").update(bytes).digest("hex");
170690
170917
  const file = this.fileOf(hash);
170691
170918
  if (!fs17.existsSync(file)) fs17.writeFileSync(file, bytes);
170692
170919
  return hash;
@@ -171186,6 +171413,10 @@ var init_dev_store = __esm({
171186
171413
  this.sessionWorkOrders.delete(id);
171187
171414
  }
171188
171415
  async appendMessage(m2) {
171416
+ if (m2.messageKey) {
171417
+ const existing = await this.getMessageByKey(m2.messageKey);
171418
+ if (existing) return existing;
171419
+ }
171189
171420
  const list = this.messages.get(m2.sessionId) ?? [];
171190
171421
  const seq = (list.length ? Math.max(...list.map((x2) => x2.seq)) : 0) + 1;
171191
171422
  const record8 = { ...m2, seq };
@@ -171193,6 +171424,26 @@ var init_dev_store = __esm({
171193
171424
  this.messages.set(m2.sessionId, list);
171194
171425
  return record8;
171195
171426
  }
171427
+ async getMessageByKey(messageKey) {
171428
+ for (const list of this.messages.values()) {
171429
+ const hit = list.find((x2) => x2.messageKey === messageKey);
171430
+ if (hit) return hit;
171431
+ }
171432
+ return null;
171433
+ }
171434
+ async countMessages(sessionId) {
171435
+ return (this.messages.get(sessionId) ?? []).length;
171436
+ }
171437
+ /** 选择器搜索(ADR 会话投递 D8):与 PG 版同口径——三元组精确 + 标题/ID 前缀 + touchedAt 倒序。 */
171438
+ async searchSessionsForTarget(q) {
171439
+ const text = q.text?.trim().toLowerCase();
171440
+ const all = [...this.sessions.values()].filter((s2) => (s2.companyId ?? null) === q.companyId && s2.humanActorId === q.humanActorId && s2.aiActorId === q.aiActorId && !s2.analyzedRunId && (!text || (s2.title ?? "").toLowerCase().includes(text) || s2.id.toLowerCase().startsWith(text))).sort((a, b2) => b2.touchedAt.localeCompare(a.touchedAt) || b2.id.localeCompare(a.id));
171441
+ const page = all.slice(q.offset, q.offset + q.limit);
171442
+ return {
171443
+ items: page.map((s2) => ({ ...s2, messageCount: (this.messages.get(s2.id) ?? []).length })),
171444
+ hasMore: q.offset + q.limit < all.length
171445
+ };
171446
+ }
171196
171447
  async updateMessage(id, patch) {
171197
171448
  for (const list of this.messages.values()) {
171198
171449
  const item = list.find((x2) => x2.id === id);
@@ -171277,6 +171528,94 @@ var init_dev_store = __esm({
171277
171528
  fs17.writeFileSync(this.file, JSON.stringify(snap, null, 2));
171278
171529
  }
171279
171530
  };
171531
+ MemoryChatTurnStore = class {
171532
+ turns = /* @__PURE__ */ new Map();
171533
+ /** 同步临界区:故意不 await,保证 claim 是原子的。 */
171534
+ claimSync(input) {
171535
+ for (const t of this.turns.values()) {
171536
+ if (t.source === input.source && t.sourceRunId === input.sourceRunId) return { outcome: "resumed", turn: t };
171537
+ }
171538
+ for (const t of this.turns.values()) {
171539
+ if (t.chatSessionId === input.chatSessionId && (t.status === "reserved" || t.status === "running")) {
171540
+ return { outcome: "busy", activeTurn: t };
171541
+ }
171542
+ }
171543
+ const turn = {
171544
+ id: input.turnId,
171545
+ chatSessionId: input.chatSessionId,
171546
+ source: input.source,
171547
+ sourceRunId: input.sourceRunId,
171548
+ messageKey: input.messageKey,
171549
+ status: "reserved",
171550
+ dispatchId: null,
171551
+ assistantRunId: null,
171552
+ reservedAt: input.reservedAt,
171553
+ startedAt: null,
171554
+ completedAt: null,
171555
+ lastError: null
171556
+ };
171557
+ this.turns.set(turn.id, turn);
171558
+ return { outcome: "claimed", turn };
171559
+ }
171560
+ async claimTurn(input) {
171561
+ const claim = this.claimSync(input);
171562
+ if (claim.outcome === "claimed") this.persist();
171563
+ return claim;
171564
+ }
171565
+ async getTurn(turnId) {
171566
+ return this.turns.get(turnId) ?? null;
171567
+ }
171568
+ async getTurnBySourceRun(source, sourceRunId) {
171569
+ for (const t of this.turns.values()) if (t.source === source && t.sourceRunId === sourceRunId) return t;
171570
+ return null;
171571
+ }
171572
+ async activeTurn(chatSessionId) {
171573
+ for (const t of this.turns.values()) {
171574
+ if (t.chatSessionId === chatSessionId && (t.status === "reserved" || t.status === "running")) return t;
171575
+ }
171576
+ return null;
171577
+ }
171578
+ async settleTurn(turnId, patch) {
171579
+ const t = this.turns.get(turnId);
171580
+ if (!t) return;
171581
+ this.turns.set(turnId, {
171582
+ ...t,
171583
+ status: patch.status,
171584
+ ...patch.dispatchId !== void 0 ? { dispatchId: patch.dispatchId } : {},
171585
+ ...patch.assistantRunId !== void 0 ? { assistantRunId: patch.assistantRunId } : {},
171586
+ ...patch.startedAt !== void 0 ? { startedAt: patch.startedAt } : {},
171587
+ ...patch.completedAt !== void 0 ? { completedAt: patch.completedAt } : {},
171588
+ ...patch.lastError !== void 0 ? { lastError: patch.lastError } : {}
171589
+ });
171590
+ this.persist();
171591
+ }
171592
+ async listStaleActiveTurns(reservedBefore, limit = 50) {
171593
+ return [...this.turns.values()].filter((t) => (t.status === "reserved" || t.status === "running") && t.reservedAt < reservedBefore).sort((a, b2) => a.reservedAt.localeCompare(b2.reservedAt)).slice(0, limit);
171594
+ }
171595
+ /** 文件版覆写。内存版不落盘。 */
171596
+ persist() {
171597
+ }
171598
+ };
171599
+ FileChatTurnStore = class _FileChatTurnStore extends MemoryChatTurnStore {
171600
+ constructor(file) {
171601
+ super();
171602
+ this.file = file;
171603
+ }
171604
+ static async open(file) {
171605
+ const store = new _FileChatTurnStore(file);
171606
+ if (fs17.existsSync(file)) {
171607
+ const snap = JSON.parse(fs17.readFileSync(file, "utf8"));
171608
+ for (const t of snap.turns ?? []) store.turns.set(t.id, t);
171609
+ } else {
171610
+ fs17.mkdirSync(path17.dirname(file), { recursive: true });
171611
+ fs17.writeFileSync(file, JSON.stringify({ turns: [] }, null, 2));
171612
+ }
171613
+ return store;
171614
+ }
171615
+ persist() {
171616
+ fs17.writeFileSync(this.file, JSON.stringify({ turns: [...this.turns.values()] }, null, 2));
171617
+ }
171618
+ };
171280
171619
  MemoryReadMarkerStore = class _MemoryReadMarkerStore {
171281
171620
  markers = /* @__PURE__ */ new Map();
171282
171621
  static key(actorId, scope) {
@@ -171461,11 +171800,11 @@ function typeDefError(def, mode) {
171461
171800
  }
171462
171801
  return null;
171463
171802
  }
171464
- var import_node_crypto23, RESERVED_FIELD_NAMES, ARTIFACT_TYPE_ACCENTS, TYPE_DEF_SCHEMA, TypesAdminService;
171803
+ var import_node_crypto24, RESERVED_FIELD_NAMES, ARTIFACT_TYPE_ACCENTS, TYPE_DEF_SCHEMA, TypesAdminService;
171465
171804
  var init_types_admin = __esm({
171466
171805
  "../server/src/types-admin.ts"() {
171467
171806
  "use strict";
171468
- import_node_crypto23 = require("node:crypto");
171807
+ import_node_crypto24 = require("node:crypto");
171469
171808
  init_zod();
171470
171809
  init_src5();
171471
171810
  init_config_audit();
@@ -171592,7 +171931,7 @@ var init_types_admin = __esm({
171592
171931
  async trace(change, target, by, detail) {
171593
171932
  try {
171594
171933
  await this.audit?.({
171595
- id: `reg_${(0, import_node_crypto23.randomUUID)()}`,
171934
+ id: `reg_${(0, import_node_crypto24.randomUUID)()}`,
171596
171935
  actor: by ?? SYSTEM_CONFIG_ACTOR,
171597
171936
  kind: "registry_change",
171598
171937
  target,
@@ -171740,11 +172079,11 @@ function roleDefError(def, mode) {
171740
172079
  }
171741
172080
  return null;
171742
172081
  }
171743
- var import_node_crypto24, ACCENTS, ROLE_DEF_SCHEMA, RolesAdminService;
172082
+ var import_node_crypto25, ACCENTS, ROLE_DEF_SCHEMA, RolesAdminService;
171744
172083
  var init_roles_admin = __esm({
171745
172084
  "../server/src/roles-admin.ts"() {
171746
172085
  "use strict";
171747
- import_node_crypto24 = require("node:crypto");
172086
+ import_node_crypto25 = require("node:crypto");
171748
172087
  init_zod();
171749
172088
  init_src5();
171750
172089
  init_config_audit();
@@ -171769,7 +172108,7 @@ var init_roles_admin = __esm({
171769
172108
  async trace(change, target, by, detail) {
171770
172109
  try {
171771
172110
  await this.deps.audit?.({
171772
- id: `reg_${(0, import_node_crypto24.randomUUID)()}`,
172111
+ id: `reg_${(0, import_node_crypto25.randomUUID)()}`,
171773
172112
  actor: by ?? SYSTEM_CONFIG_ACTOR,
171774
172113
  kind: "registry_change",
171775
172114
  target,
@@ -173747,7 +174086,7 @@ function wireRecoveredChatTurn(deps) {
173747
174086
  const segText = assistantText;
173748
174087
  const segParts = collectedParts();
173749
174088
  const closingId = currentMsgId;
173750
- const nextId = (0, import_node_crypto25.randomUUID)();
174089
+ const nextId = (0, import_node_crypto26.randomUUID)();
173751
174090
  assistantText = "";
173752
174091
  currentMsgId = nextId;
173753
174092
  segmentBaseSeq = live.bufferedParts().reduce((max, p2) => p2.seq > max ? p2.seq : max, 0);
@@ -173760,7 +174099,7 @@ function wireRecoveredChatTurn(deps) {
173760
174099
  ...segParts.length ? { parts: segParts } : {}
173761
174100
  }).catch(() => void 0);
173762
174101
  await store.appendMessage({
173763
- id: (0, import_node_crypto25.randomUUID)(),
174102
+ id: (0, import_node_crypto26.randomUUID)(),
173764
174103
  sessionId: plan.chatSessionId,
173765
174104
  role: "user",
173766
174105
  content: text,
@@ -173939,11 +174278,11 @@ async function reconcileChatExitFrame(deps) {
173939
174278
  }
173940
174279
  return false;
173941
174280
  }
173942
- var import_node_crypto25, asObj3, partsOf, maxPartSeq, exitFailed, exitErrorText, runTerminalPatch;
174281
+ var import_node_crypto26, asObj3, partsOf, maxPartSeq, exitFailed, exitErrorText, runTerminalPatch;
173943
174282
  var init_chat_recovery = __esm({
173944
174283
  "../server/src/chat-recovery.ts"() {
173945
174284
  "use strict";
173946
- import_node_crypto25 = require("node:crypto");
174285
+ import_node_crypto26 = require("node:crypto");
173947
174286
  init_chat_parts();
173948
174287
  init_sink();
173949
174288
  init_resilient_appender();
@@ -173970,8 +174309,8 @@ var init_chat_recovery = __esm({
173970
174309
 
173971
174310
  // ../server/src/auth/crypto.ts
173972
174311
  function hashPassword(password) {
173973
- const salt = (0, import_node_crypto26.randomBytes)(16);
173974
- const dk = (0, import_node_crypto26.scryptSync)(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, maxmem: 64 * 1024 * 1024 });
174312
+ const salt = (0, import_node_crypto27.randomBytes)(16);
174313
+ const dk = (0, import_node_crypto27.scryptSync)(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, maxmem: 64 * 1024 * 1024 });
173975
174314
  return `scrypt$${SCRYPT_N}$${salt.toString("base64url")}$${dk.toString("base64url")}`;
173976
174315
  }
173977
174316
  function verifyPassword(password, stored) {
@@ -173988,8 +174327,8 @@ function verifyPassword(password, stored) {
173988
174327
  return false;
173989
174328
  }
173990
174329
  if (expected.length === 0) return false;
173991
- const dk = (0, import_node_crypto26.scryptSync)(password, salt, expected.length, { N, maxmem: 64 * 1024 * 1024 });
173992
- return dk.length === expected.length && (0, import_node_crypto26.timingSafeEqual)(dk, expected);
174330
+ const dk = (0, import_node_crypto27.scryptSync)(password, salt, expected.length, { N, maxmem: 64 * 1024 * 1024 });
174331
+ return dk.length === expected.length && (0, import_node_crypto27.timingSafeEqual)(dk, expected);
173993
174332
  }
173994
174333
  function b64urlJson(value) {
173995
174334
  return Buffer.from(JSON.stringify(value)).toString("base64url");
@@ -173997,17 +174336,17 @@ function b64urlJson(value) {
173997
174336
  function signSession(claims, secret) {
173998
174337
  const head = b64urlJson({ alg: "HS256", typ: "JWT" });
173999
174338
  const body = b64urlJson(claims);
174000
- const sig = (0, import_node_crypto26.createHmac)("sha256", secret).update(`${head}.${body}`).digest("base64url");
174339
+ const sig = (0, import_node_crypto27.createHmac)("sha256", secret).update(`${head}.${body}`).digest("base64url");
174001
174340
  return `${head}.${body}.${sig}`;
174002
174341
  }
174003
174342
  function verifySession(token, secret, nowMs) {
174004
174343
  const parts = token.split(".");
174005
174344
  if (parts.length !== 3) return null;
174006
174345
  const [head, body, sig] = parts;
174007
- const expected = (0, import_node_crypto26.createHmac)("sha256", secret).update(`${head}.${body}`).digest("base64url");
174346
+ const expected = (0, import_node_crypto27.createHmac)("sha256", secret).update(`${head}.${body}`).digest("base64url");
174008
174347
  const got = Buffer.from(sig);
174009
174348
  const exp = Buffer.from(expected);
174010
- if (got.length !== exp.length || !(0, import_node_crypto26.timingSafeEqual)(got, exp)) return null;
174349
+ if (got.length !== exp.length || !(0, import_node_crypto27.timingSafeEqual)(got, exp)) return null;
174011
174350
  let claims;
174012
174351
  try {
174013
174352
  claims = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
@@ -174019,21 +174358,21 @@ function verifySession(token, secret, nowMs) {
174019
174358
  return claims;
174020
174359
  }
174021
174360
  function generateCode() {
174022
- return String((0, import_node_crypto26.randomInt)(0, 1e6)).padStart(6, "0");
174361
+ return String((0, import_node_crypto27.randomInt)(0, 1e6)).padStart(6, "0");
174023
174362
  }
174024
174363
  function hashCode(code, secret) {
174025
- return (0, import_node_crypto26.createHmac)("sha256", secret).update(`code:${code}`).digest("base64url");
174364
+ return (0, import_node_crypto27.createHmac)("sha256", secret).update(`code:${code}`).digest("base64url");
174026
174365
  }
174027
174366
  function safeEqualHash(a, b2) {
174028
174367
  const ba = Buffer.from(a);
174029
174368
  const bb = Buffer.from(b2);
174030
- return ba.length === bb.length && (0, import_node_crypto26.timingSafeEqual)(ba, bb);
174369
+ return ba.length === bb.length && (0, import_node_crypto27.timingSafeEqual)(ba, bb);
174031
174370
  }
174032
- var import_node_crypto26, SCRYPT_N, SCRYPT_KEYLEN;
174371
+ var import_node_crypto27, SCRYPT_N, SCRYPT_KEYLEN;
174033
174372
  var init_crypto2 = __esm({
174034
174373
  "../server/src/auth/crypto.ts"() {
174035
174374
  "use strict";
174036
- import_node_crypto26 = require("node:crypto");
174375
+ import_node_crypto27 = require("node:crypto");
174037
174376
  SCRYPT_N = 16384;
174038
174377
  SCRYPT_KEYLEN = 32;
174039
174378
  }
@@ -174165,6 +174504,13 @@ var init_daemon_hub = __esm({
174165
174504
  sessionMeta = /* @__PURE__ */ new Map();
174166
174505
  sessionConnectListeners = [];
174167
174506
  sessionDisconnectListeners = [];
174507
+ /**
174508
+ * 「这次派发归谁管、现在什么状态」的认领口。**必须由 hub 汇总,不能各 adapter 自己判**——
174509
+ * serve 里同时有两个 DaemonHubAdapter(chat 一个、dispatch 一个),一次派发只属于其中一个。
174510
+ * 让 adapter 自己判「我不认识就 evict」,另一个 adapter 会把别人的活会话一枪打死
174511
+ * (2026-08-25 真机实测:chat 会话刚连上就收到 evict(unknown),用户看到「对话已中断」)。
174512
+ */
174513
+ sessionClaimCheckers = [];
174168
174514
  resolveDataplane;
174169
174515
  /** 每个 daemon 最近一次收到任何帧的时间戳(含它自发的心跳 pong)——liveness 判定的依据。 */
174170
174516
  lastSeen = /* @__PURE__ */ new Map();
@@ -174374,6 +174720,7 @@ var init_daemon_hub = __esm({
174374
174720
  this.sessionSockets.set(dispatchId, ws);
174375
174721
  this.sessionMeta.set(dispatchId, { nodeId, sessionId: msg.sessionId, connectedAt: Date.now() });
174376
174722
  for (const l of this.sessionConnectListeners) l({ dispatchId, sessionId: msg.sessionId, nodeId });
174723
+ this.judgeSession(dispatchId);
174377
174724
  }
174378
174725
  return;
174379
174726
  }
@@ -174395,6 +174742,28 @@ var init_daemon_hub = __esm({
174395
174742
  });
174396
174743
  ws.on("error", (err) => console.error(`[hub] session ${dispatchId} error:`, err.message));
174397
174744
  }
174745
+ /**
174746
+ * 注册一个认领判断。返回:
174747
+ * live —— 这次派发在我这儿还活着(别动它)
174748
+ * settled —— 我这儿已经收尾/收割了(该 evict,让旧进程自尽)
174749
+ * unknown —— 我不认识(可能归别人管,**不构成 evict 理由**)
174750
+ */
174751
+ registerSessionClaimCheck(fn) {
174752
+ this.sessionClaimCheckers.push(fn);
174753
+ }
174754
+ /** 会话连上来时问一圈:没人说 live、且有人说 settled,才 evict。全 unknown = 没人认领,也 evict。 */
174755
+ judgeSession(dispatchId) {
174756
+ if (this.sessionClaimCheckers.length === 0) return;
174757
+ const verdicts = this.sessionClaimCheckers.map((f2) => {
174758
+ try {
174759
+ return f2(dispatchId);
174760
+ } catch {
174761
+ return "unknown";
174762
+ }
174763
+ });
174764
+ if (verdicts.includes("live")) return;
174765
+ this.evictSession(dispatchId, verdicts.includes("settled") ? "reaped" : "unknown");
174766
+ }
174398
174767
  /** 会话连接建立(`session_hello` 通过校验)时触发——2b 的收割判据挂在这上面。 */
174399
174768
  addSessionConnectListener(l) {
174400
174769
  this.sessionConnectListeners.push(l);
@@ -174931,13 +175300,13 @@ function variableKeyFromEnv(env = process.env) {
174931
175300
  return buf;
174932
175301
  }
174933
175302
  if (env.NODE_ENV === "production") throw new Error("OASIS_VAR_KEY is required in production");
174934
- return (0, import_node_crypto27.createHash)("sha256").update("oasis-dev-only-variable-key").digest();
175303
+ return (0, import_node_crypto28.createHash)("sha256").update("oasis-dev-only-variable-key").digest();
174935
175304
  }
174936
- var import_node_crypto27, REDACTED_MARKER, ActorsService, mask, changedKeys;
175305
+ var import_node_crypto28, REDACTED_MARKER, ActorsService, mask, changedKeys;
174937
175306
  var init_service4 = __esm({
174938
175307
  "../server/src/domains/actors/service.ts"() {
174939
175308
  "use strict";
174940
- import_node_crypto27 = require("node:crypto");
175309
+ import_node_crypto28 = require("node:crypto");
174941
175310
  init_skill_fetcher();
174942
175311
  init_identity();
174943
175312
  init_migrate();
@@ -175425,7 +175794,7 @@ ${input.description}
175425
175794
  */
175426
175795
  buildSkillFile(skillId, path37, content, now) {
175427
175796
  const bytes = new TextEncoder().encode(content);
175428
- const blobHash = (0, import_node_crypto27.createHash)("sha256").update(bytes).digest("hex");
175797
+ const blobHash = (0, import_node_crypto28.createHash)("sha256").update(bytes).digest("hex");
175429
175798
  return { skillId, path: path37, content, blobHash, size: bytes.length, updatedAt: now };
175430
175799
  }
175431
175800
  /**
@@ -175896,15 +176265,15 @@ ${input.description}
175896
176265
  return hit ? { value: hit.value, ...hit.origin ? { origin: hit.origin } : {} } : null;
175897
176266
  }
175898
176267
  encrypt(plain) {
175899
- const iv = (0, import_node_crypto27.randomBytes)(12);
175900
- const cipher = (0, import_node_crypto27.createCipheriv)("aes-256-gcm", this.opts.variableKey, iv);
176268
+ const iv = (0, import_node_crypto28.randomBytes)(12);
176269
+ const cipher = (0, import_node_crypto28.createCipheriv)("aes-256-gcm", this.opts.variableKey, iv);
175901
176270
  const enc4 = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
175902
176271
  return [iv.toString("base64"), cipher.getAuthTag().toString("base64"), enc4.toString("base64")].join(".");
175903
176272
  }
175904
176273
  decrypt(packed) {
175905
176274
  const [iv, tag, data] = packed.split(".");
175906
176275
  if (!iv || !tag || typeof data !== "string") throw new Error("malformed ciphertext");
175907
- const decipher = (0, import_node_crypto27.createDecipheriv)("aes-256-gcm", this.opts.variableKey, Buffer.from(iv, "base64"));
176276
+ const decipher = (0, import_node_crypto28.createDecipheriv)("aes-256-gcm", this.opts.variableKey, Buffer.from(iv, "base64"));
175908
176277
  decipher.setAuthTag(Buffer.from(tag, "base64"));
175909
176278
  return Buffer.concat([decipher.update(Buffer.from(data, "base64")), decipher.final()]).toString("utf8");
175910
176279
  }
@@ -176868,7 +177237,7 @@ async function listSkillMetadataForActor(args) {
176868
177237
  usedDirs.add(dir);
176869
177238
  const sorted = [...metas].sort((a, b2) => a.path.localeCompare(b2.path));
176870
177239
  let latest = "1970-01-01T00:00:00.000Z";
176871
- const hasher = (0, import_node_crypto28.createHash)("sha256");
177240
+ const hasher = (0, import_node_crypto29.createHash)("sha256");
176872
177241
  for (const m2 of sorted) {
176873
177242
  if (m2.updatedAt > latest) latest = m2.updatedAt;
176874
177243
  hasher.update(m2.path);
@@ -176893,11 +177262,11 @@ async function listSkillMetadataForActor(args) {
176893
177262
  const dir = sanitizeSkillDir(c.name, c.slug);
176894
177263
  if (usedDirs.has(dir)) continue;
176895
177264
  usedDirs.add(dir);
176896
- const hasher = (0, import_node_crypto28.createHash)("sha256");
177265
+ const hasher = (0, import_node_crypto29.createHash)("sha256");
176897
177266
  for (const p2 of paths) {
176898
177267
  hasher.update(p2);
176899
177268
  hasher.update("\0");
176900
- hasher.update((0, import_node_crypto28.createHash)("sha256").update(c.files[p2]).digest("hex"));
177269
+ hasher.update((0, import_node_crypto29.createHash)("sha256").update(c.files[p2]).digest("hex"));
176901
177270
  hasher.update("\0");
176902
177271
  }
176903
177272
  out.push({
@@ -176918,11 +177287,11 @@ async function listSkillMetadataForActor(args) {
176918
177287
  const dir = sanitizeSkillDir(b2.name, b2.id);
176919
177288
  if (usedDirs.has(dir)) continue;
176920
177289
  usedDirs.add(dir);
176921
- const hasher = (0, import_node_crypto28.createHash)("sha256");
177290
+ const hasher = (0, import_node_crypto29.createHash)("sha256");
176922
177291
  for (const p2 of paths) {
176923
177292
  hasher.update(p2);
176924
177293
  hasher.update("\0");
176925
- hasher.update((0, import_node_crypto28.createHash)("sha256").update(b2.files[p2]).digest("hex"));
177294
+ hasher.update((0, import_node_crypto29.createHash)("sha256").update(b2.files[p2]).digest("hex"));
176926
177295
  hasher.update("\0");
176927
177296
  }
176928
177297
  out.push({
@@ -176942,7 +177311,7 @@ function builtinSkillToFiles(b2) {
176942
177311
  skillId: `builtin:${b2.id}`,
176943
177312
  path: path37,
176944
177313
  content,
176945
- blobHash: (0, import_node_crypto28.createHash)("sha256").update(content).digest("hex"),
177314
+ blobHash: (0, import_node_crypto29.createHash)("sha256").update(content).digest("hex"),
176946
177315
  size: Buffer.byteLength(content, "utf8"),
176947
177316
  updatedAt: "1970-01-01T00:00:00.000Z"
176948
177317
  }));
@@ -176966,7 +177335,7 @@ function sanitizeSkillDir(name, fallbackId) {
176966
177335
  if (byName) return byName;
176967
177336
  const byId = fallbackId ? fold2(fallbackId) : "";
176968
177337
  if (byId) return byId;
176969
- return `skill-${(0, import_node_crypto28.createHash)("sha256").update(name).digest("hex").slice(0, 8)}`;
177338
+ return `skill-${(0, import_node_crypto29.createHash)("sha256").update(name).digest("hex").slice(0, 8)}`;
176970
177339
  }
176971
177340
  async function materializeSkillFiles(args) {
176972
177341
  const skills = await args.service.listInstalledSkillsForActor(args.actorId);
@@ -176999,7 +177368,7 @@ function connectorSkillToFiles(c) {
176999
177368
  skillId: c.id,
177000
177369
  path: path37,
177001
177370
  content,
177002
- blobHash: (0, import_node_crypto28.createHash)("sha256").update(content).digest("hex"),
177371
+ blobHash: (0, import_node_crypto29.createHash)("sha256").update(content).digest("hex"),
177003
177372
  size: Buffer.byteLength(content, "utf8"),
177004
177373
  updatedAt: "1970-01-01T00:00:00.000Z"
177005
177374
  }));
@@ -177016,11 +177385,11 @@ function connectorSkillDisplayRows(connectorSkills, enabledConnectors) {
177016
177385
  ...s2.version !== void 0 ? { version: s2.version } : {}
177017
177386
  }));
177018
177387
  }
177019
- var import_node_crypto28;
177388
+ var import_node_crypto29;
177020
177389
  var init_skill_materializer = __esm({
177021
177390
  "../server/src/domains/actors/skill-materializer.ts"() {
177022
177391
  "use strict";
177023
- import_node_crypto28 = require("node:crypto");
177392
+ import_node_crypto29 = require("node:crypto");
177024
177393
  }
177025
177394
  });
177026
177395
 
@@ -178248,7 +178617,7 @@ function createActorsDomain(opts) {
178248
178617
  ...kernel !== void 0 ? { onRolesChanged: (a, r) => kernel.setActorRoles(a, r) } : {},
178249
178618
  audit: async (entry) => {
178250
178619
  await opts.audit?.({
178251
- id: `reg_${(0, import_node_crypto29.randomUUID)()}`,
178620
+ id: `reg_${(0, import_node_crypto30.randomUUID)()}`,
178252
178621
  actor: entry.by,
178253
178622
  kind: "registry_change",
178254
178623
  target: entry.actorId,
@@ -178290,11 +178659,11 @@ function createActorsDomain(opts) {
178290
178659
  register: actorsDomain({ resolveCtx, ...opts.trace ? { trace: opts.trace } : {}, ...opts.listBuiltinSkills ? { listBuiltinSkills: opts.listBuiltinSkills } : {}, ...opts.getBuiltinSkills ? { getBuiltinSkills: opts.getBuiltinSkills } : {}, ...opts.getConnectorSkills ? { getConnectorSkills: opts.getConnectorSkills } : {}, ...opts.refreshConnectorSkills ? { refreshConnectorSkills: opts.refreshConnectorSkills } : {}, ...opts.memory ? { memory: opts.memory } : {}, ...opts.resolveDispatchScope ? { resolveDispatchScope: opts.resolveDispatchScope } : {}, ...opts.projectExists ? { projectExists: opts.projectExists } : {}, ...opts.credentialAudit ? { credentialAudit: opts.credentialAudit } : {}, ...opts.readCredentialAudit ? { readCredentialAudit: opts.readCredentialAudit } : {}, ...opts.credentialRevealLimits ? { credentialRevealLimits: opts.credentialRevealLimits } : {} })
178291
178660
  };
178292
178661
  }
178293
- var import_node_crypto29;
178662
+ var import_node_crypto30;
178294
178663
  var init_actors = __esm({
178295
178664
  "../server/src/domains/actors/index.ts"() {
178296
178665
  "use strict";
178297
- import_node_crypto29 = require("node:crypto");
178666
+ import_node_crypto30 = require("node:crypto");
178298
178667
  init_service4();
178299
178668
  init_routes2();
178300
178669
  init_service4();
@@ -179402,11 +179771,11 @@ var init_projects = __esm({
179402
179771
  });
179403
179772
 
179404
179773
  // ../server/src/domains/companies/service.ts
179405
- var import_node_crypto30, ROLES, INVITABLE_ROLES, INVITATION_TTL_MS, SLUG_RE, CompanyError, CompaniesService;
179774
+ var import_node_crypto31, ROLES, INVITABLE_ROLES, INVITATION_TTL_MS, SLUG_RE, CompanyError, CompaniesService;
179406
179775
  var init_service5 = __esm({
179407
179776
  "../server/src/domains/companies/service.ts"() {
179408
179777
  "use strict";
179409
- import_node_crypto30 = require("node:crypto");
179778
+ import_node_crypto31 = require("node:crypto");
179410
179779
  ROLES = ["owner", "admin", "member"];
179411
179780
  INVITABLE_ROLES = ["admin", "member"];
179412
179781
  INVITATION_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
@@ -179479,7 +179848,7 @@ var init_service5 = __esm({
179479
179848
  const existing = await this.store.getAccountByEmail(normalized4);
179480
179849
  if (existing) return existing;
179481
179850
  const account = {
179482
- id: `actor:human:${(0, import_node_crypto30.randomUUID)()}`,
179851
+ id: `actor:human:${(0, import_node_crypto31.randomUUID)()}`,
179483
179852
  email: normalized4,
179484
179853
  name: name ?? normalized4,
179485
179854
  status: "active"
@@ -179643,7 +180012,7 @@ var init_service5 = __esm({
179643
180012
  }
179644
180013
  const nowMs = Date.parse(this.now());
179645
180014
  const invitation = {
179646
- id: `invitation:${(0, import_node_crypto30.randomUUID)()}`,
180015
+ id: `invitation:${(0, import_node_crypto31.randomUUID)()}`,
179647
180016
  companyId,
179648
180017
  email: mail,
179649
180018
  role,
@@ -180914,7 +181283,7 @@ function nodesDomain(deps) {
180914
181283
  nodeId = incomingNodeId;
180915
181284
  deps.enrollTokens.setIssuedNodeId(enrollToken, nodeId);
180916
181285
  } else {
180917
- nodeId = `node-${(0, import_node_crypto31.randomUUID)().slice(0, 8)}`;
181286
+ nodeId = `node-${(0, import_node_crypto32.randomUUID)().slice(0, 8)}`;
180918
181287
  deps.enrollTokens.setIssuedNodeId(enrollToken, nodeId);
180919
181288
  }
180920
181289
  const existingNode = await deps.nodeStore.getNode(nodeId);
@@ -181050,11 +181419,11 @@ function nodesDomain(deps) {
181050
181419
  });
181051
181420
  };
181052
181421
  }
181053
- var import_node_crypto31, import_node_fs14, import_node_url6, import_node_path18;
181422
+ var import_node_crypto32, import_node_fs14, import_node_url6, import_node_path18;
181054
181423
  var init_routes6 = __esm({
181055
181424
  "../server/src/domains/nodes/routes.ts"() {
181056
181425
  "use strict";
181057
- import_node_crypto31 = require("node:crypto");
181426
+ import_node_crypto32 = require("node:crypto");
181058
181427
  import_node_fs14 = require("node:fs");
181059
181428
  import_node_url6 = require("node:url");
181060
181429
  import_node_path18 = require("node:path");
@@ -182670,13 +183039,13 @@ function nameKey(name) {
182670
183039
  function cleanName(name) {
182671
183040
  return name.trim().replace(/\s+/g, " ");
182672
183041
  }
182673
- var import_node_fs15, import_node_path19, import_node_crypto32, SEP, keyOf, prefixOf, MemoryWorkorderTagStore, FileWorkorderTagStore;
183042
+ var import_node_fs15, import_node_path19, import_node_crypto33, SEP, keyOf, prefixOf, MemoryWorkorderTagStore, FileWorkorderTagStore;
182674
183043
  var init_tags = __esm({
182675
183044
  "../server/src/domains/collab/tags.ts"() {
182676
183045
  "use strict";
182677
183046
  import_node_fs15 = __toESM(require("node:fs"), 1);
182678
183047
  import_node_path19 = __toESM(require("node:path"), 1);
182679
- import_node_crypto32 = __toESM(require("node:crypto"), 1);
183048
+ import_node_crypto33 = __toESM(require("node:crypto"), 1);
182680
183049
  SEP = "::";
182681
183050
  keyOf = (companyId, id) => `${companyId}${SEP}${id}`;
182682
183051
  prefixOf = (companyId) => `${companyId}${SEP}`;
@@ -182694,7 +183063,7 @@ var init_tags = __esm({
182694
183063
  if (!name) return null;
182695
183064
  if (this.entries(companyId).some((t) => nameKey(t.name) === nameKey(name))) return null;
182696
183065
  const tag = {
182697
- id: `tag-${import_node_crypto32.default.randomBytes(6).toString("hex")}`,
183066
+ id: `tag-${import_node_crypto33.default.randomBytes(6).toString("hex")}`,
182698
183067
  name,
182699
183068
  ...input.color?.trim() ? { color: input.color.trim() } : {},
182700
183069
  ...input.description?.trim() ? { description: input.description.trim() } : {}
@@ -183551,7 +183920,7 @@ function createBundle(root, target, input) {
183551
183920
  }
183552
183921
  }
183553
183922
  const bytes = fs23.statSync(absBundle).size;
183554
- const digest = (0, import_node_crypto33.createHash)("sha256").update(fs23.readFileSync(absBundle)).digest("hex");
183923
+ const digest = (0, import_node_crypto34.createHash)("sha256").update(fs23.readFileSync(absBundle)).digest("hex");
183555
183924
  const relBase = `inputs/git-bundles/${safeRepo}-${commit.slice(0, 12)}`;
183556
183925
  return {
183557
183926
  absoluteBundlePath: absBundle,
@@ -183647,12 +184016,12 @@ function classifyGitError(fallback, err, where) {
183647
184016
  function oneLine(msg) {
183648
184017
  return msg.split("\n").map((s2) => s2.trim()).filter(Boolean).slice(0, 3).join(" ");
183649
184018
  }
183650
- var import_node_child_process14, import_node_crypto33, fs23, os6, path21, GitReadonlyBundleError, HEX40, FETCH_TIMEOUT_MS2;
184019
+ var import_node_child_process14, import_node_crypto34, fs23, os6, path21, GitReadonlyBundleError, HEX40, FETCH_TIMEOUT_MS2;
183651
184020
  var init_readonly_bundle = __esm({
183652
184021
  "../server/src/git/readonly-bundle.ts"() {
183653
184022
  "use strict";
183654
184023
  import_node_child_process14 = require("node:child_process");
183655
- import_node_crypto33 = require("node:crypto");
184024
+ import_node_crypto34 = require("node:crypto");
183656
184025
  fs23 = __toESM(require("node:fs"), 1);
183657
184026
  os6 = __toESM(require("node:os"), 1);
183658
184027
  path21 = __toESM(require("node:path"), 1);
@@ -183672,7 +184041,7 @@ var init_readonly_bundle = __esm({
183672
184041
 
183673
184042
  // ../server/src/domains/collab/create-seeded-workorder.ts
183674
184043
  function makeSeededWorkorderCreator(deps) {
183675
- const genWorkspace = deps.genWorkspace ?? (() => `ws:wo-${(0, import_node_crypto34.randomUUID)().slice(0, 8)}`);
184044
+ const genWorkspace = deps.genWorkspace ?? (() => `ws:wo-${(0, import_node_crypto35.randomUUID)().slice(0, 8)}`);
183676
184045
  return async (input) => {
183677
184046
  const workspace = input.idempotencyKey ? workspaceForKey(input.idempotencyKey) : genWorkspace();
183678
184047
  if (input.idempotencyKey) {
@@ -183758,7 +184127,7 @@ function makeSeededWorkorderCreator(deps) {
183758
184127
  };
183759
184128
  }
183760
184129
  function workspaceForKey(key) {
183761
- return `ws:wo-${(0, import_node_crypto34.createHash)("sha256").update(key).digest("hex").slice(0, 8)}`;
184130
+ return `ws:wo-${(0, import_node_crypto35.createHash)("sha256").update(key).digest("hex").slice(0, 8)}`;
183762
184131
  }
183763
184132
  function existingWorkorder(kernel, workspace) {
183764
184133
  const arts = [...kernel.model.artifacts.values()].filter((a) => a.workspace === workspace);
@@ -183771,11 +184140,11 @@ function existingWorkorder(kernel, workspace) {
183771
184140
  spawned: arts.map((a) => ({ id: a.id, type: a.type, owner: a.owner }))
183772
184141
  };
183773
184142
  }
183774
- var import_node_crypto34, enc2;
184143
+ var import_node_crypto35, enc2;
183775
184144
  var init_create_seeded_workorder = __esm({
183776
184145
  "../server/src/domains/collab/create-seeded-workorder.ts"() {
183777
184146
  "use strict";
183778
- import_node_crypto34 = require("node:crypto");
184147
+ import_node_crypto35 = require("node:crypto");
183779
184148
  init_ephemeral_project();
183780
184149
  init_planner();
183781
184150
  enc2 = (s2) => new TextEncoder().encode(s2);
@@ -184966,12 +185335,12 @@ function todoAction(node) {
184966
185335
  if (node.progress.recoverability === "partial") return `\u8282\u70B9 ${node.nodeState}\uFF0Ccheckpoint \u90E8\u5206\u53EF\u6062\u590D\uFF0C\u9700\u5173\u6CE8\u5DF2\u6709\u4EA7\u51FA`;
184967
185336
  return `\u8282\u70B9 ${node.nodeState}\uFF0Ccheckpoint \u4E0D\u53EF\u6062\u590D`;
184968
185337
  }
184969
- var import_node_crypto35, DONE_STATES, BLOCKING_STATES, HANDOFF_OUTCOMES2, RESUME_EXTERNAL_EFFECT_LIMIT, RESUME_SNAPSHOT_PREFIX, RESUME_SNAPSHOT_SUFFIX, TODO_RANK, ExecutionContinuityService;
185338
+ var import_node_crypto36, DONE_STATES, BLOCKING_STATES, HANDOFF_OUTCOMES2, RESUME_EXTERNAL_EFFECT_LIMIT, RESUME_SNAPSHOT_PREFIX, RESUME_SNAPSHOT_SUFFIX, TODO_RANK, ExecutionContinuityService;
184970
185339
  var init_service7 = __esm({
184971
185340
  "../server/src/domains/execution-continuity/service.ts"() {
184972
185341
  "use strict";
184973
185342
  init_src2();
184974
- import_node_crypto35 = require("node:crypto");
185343
+ import_node_crypto36 = require("node:crypto");
184975
185344
  init_src5();
184976
185345
  DONE_STATES = /* @__PURE__ */ new Set(["succeeded", "no_change"]);
184977
185346
  BLOCKING_STATES = /* @__PURE__ */ new Set(["stalled", "failed", "blocked"]);
@@ -185201,7 +185570,7 @@ var init_service7 = __esm({
185201
185570
  if (typeof expectedHash !== "string" || !this.readBundleFile) return null;
185202
185571
  const injectedResume = this.readBundleFile(attempt.id, "execution/RESUME.md");
185203
185572
  if (!injectedResume) return null;
185204
- const actualHash = (0, import_node_crypto35.createHash)("sha256").update(injectedResume).digest("hex");
185573
+ const actualHash = (0, import_node_crypto36.createHash)("sha256").update(injectedResume).digest("hex");
185205
185574
  if (actualHash !== expectedHash) return null;
185206
185575
  const recoverySnapshot = parseResumePackSnapshot(injectedResume);
185207
185576
  if (!recoverySnapshot || recoverySnapshot.scenario !== "redispatch" || !recoverySnapshot.previousAttempt || recoverySnapshot.node.nodeId !== attempt.nodeId || recoverySnapshot.jobKey !== attempt.jobKey || recoverySnapshot.part !== attempt.part) return null;
@@ -185702,7 +186071,7 @@ function createChatSessionsDomain(opts) {
185702
186071
  if (!text.trim()) throw new ApiError(400, "BAD_REQUEST", "text required");
185703
186072
  const now = Date.now();
185704
186073
  sweepDrafts(now);
185705
- const id = `cd_${(0, import_node_crypto36.randomUUID)()}`;
186074
+ const id = `cd_${(0, import_node_crypto37.randomUUID)()}`;
185706
186075
  drafts.set(id, { text, actor: req.auth.actor, expiresAt: now + DRAFT_TTL_MS });
185707
186076
  return { status: 201, body: { id, expiresInMs: DRAFT_TTL_MS } };
185708
186077
  });
@@ -185735,7 +186104,7 @@ function createChatSessionsDomain(opts) {
185735
186104
  const runtimeId = await currentRuntimeId(body.aiActorId) ?? "unknown";
185736
186105
  const now = (/* @__PURE__ */ new Date()).toISOString();
185737
186106
  const session = {
185738
- id: (0, import_node_crypto36.randomUUID)(),
186107
+ id: (0, import_node_crypto37.randomUUID)(),
185739
186108
  humanActorId: req.auth.actor,
185740
186109
  aiActorId: body.aiActorId,
185741
186110
  runtimeId,
@@ -185808,7 +186177,7 @@ function createChatSessionsDomain(opts) {
185808
186177
  if (body.role !== "user" && body.role !== "assistant") throw new ApiError(400, "BAD_REQUEST", "role must be user or assistant");
185809
186178
  const role = body.role;
185810
186179
  const msg = await store.appendMessage({
185811
- id: (0, import_node_crypto36.randomUUID)(),
186180
+ id: (0, import_node_crypto37.randomUUID)(),
185812
186181
  sessionId: req.params.id,
185813
186182
  role,
185814
186183
  content: role === "user" ? stripInjectedChatContext(body.content) : body.content,
@@ -185818,6 +186187,47 @@ function createChatSessionsDomain(opts) {
185818
186187
  await store.updateSession(req.params.id, { touchedAt: (/* @__PURE__ */ new Date()).toISOString() });
185819
186188
  return { status: 201, body: msg };
185820
186189
  });
186190
+ router.get("/api/chat-sessions/:id/turns/active", async (req) => {
186191
+ if (!opts.chatTurns) throw new ApiError(404, "NOT_FOUND", "\u672C\u5B9E\u4F8B\u672A\u63A5\u4F1A\u8BDD\u8F6E\u6B21\u8D26\u672C");
186192
+ const session = await store.getSession(req.params.id);
186193
+ if (!session || session.humanActorId !== req.auth.actor) throw new ApiError(404, "NOT_FOUND", "session not found");
186194
+ const turn = await opts.chatTurns.activeTurn(req.params.id);
186195
+ return { status: 200, body: { turn } };
186196
+ });
186197
+ router.post("/api/chat-sessions/:id/turns/:turnId/settle", async (req) => {
186198
+ if (!opts.chatTurns) throw new ApiError(404, "NOT_FOUND", "\u672C\u5B9E\u4F8B\u672A\u63A5\u4F1A\u8BDD\u8F6E\u6B21\u8D26\u672C");
186199
+ const session = await store.getSession(req.params.id);
186200
+ if (!session || session.humanActorId !== req.auth.actor) throw new ApiError(404, "NOT_FOUND", "session not found");
186201
+ const turn = await opts.chatTurns.getTurn(req.params.turnId);
186202
+ if (!turn || turn.chatSessionId !== req.params.id) throw new ApiError(404, "NOT_FOUND", "turn not found");
186203
+ const body = req.body ?? {};
186204
+ if (turn.status !== "reserved" && turn.status !== "running") {
186205
+ return { status: 200, body: { settled: false, reason: "already_terminal", turn } };
186206
+ }
186207
+ if (body.force) {
186208
+ await opts.chatTurns.settleTurn(turn.id, {
186209
+ status: "cancelled",
186210
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
186211
+ lastError: `\u8FD0\u7EF4\u5F3A\u5236\u7F6E\u7EC8\u6001\uFF08${req.auth.actor}\uFF09\uFF1A${body.reason ?? "\u672A\u586B\u539F\u56E0"}`
186212
+ });
186213
+ return { status: 200, body: { settled: true, status: "cancelled", turn: await opts.chatTurns.getTurn(turn.id) } };
186214
+ }
186215
+ const outcome = await reconcileChatTurn(turn, {
186216
+ turns: opts.chatTurns,
186217
+ getRun: opts.getRun ?? (async () => null),
186218
+ now: Date.now(),
186219
+ // 运维入口不吃「宽限」——人是在明确要求现在就核实一次。
186220
+ noRunGraceMs: 0
186221
+ });
186222
+ if (!outcome.settled) {
186223
+ throw new ApiError(
186224
+ 409,
186225
+ "CHAT_TURN_STILL_RUNNING",
186226
+ `\u8FD9\u4E00\u8F6E\u5728\u8D26\u672C\u91CC\u4ECD\u662F\u6D3B\u7684\uFF08${outcome.reason}\uFF09\uFF0C\u4E0D\u80FD\u7F6E\u7EC8\u6001\u3002\u786E\u8BA4\u6267\u884C\u4EBA\u5DF2\u7ECF\u6B7B\u4E86\u518D\u52A0 force=true\u3002`
186227
+ );
186228
+ }
186229
+ return { status: 200, body: { settled: true, status: outcome.status, reason: outcome.reason, turn: await opts.chatTurns.getTurn(turn.id) } };
186230
+ });
185821
186231
  router.post("/api/chat-sessions/:id/work-orders", async (req) => {
185822
186232
  const session = await store.getSession(req.params.id);
185823
186233
  if (!session || session.humanActorId !== req.auth.actor) throw new ApiError(404, "NOT_FOUND", "session not found");
@@ -185913,16 +186323,17 @@ function createChatSessionsDomain(opts) {
185913
186323
  });
185914
186324
  };
185915
186325
  }
185916
- var import_node_crypto36, WORKDIR_READ_MAX_BYTES;
186326
+ var import_node_crypto37, WORKDIR_READ_MAX_BYTES;
185917
186327
  var init_chat_sessions = __esm({
185918
186328
  "../server/src/domains/chat-sessions/index.ts"() {
185919
186329
  "use strict";
185920
- import_node_crypto36 = require("node:crypto");
186330
+ import_node_crypto37 = require("node:crypto");
185921
186331
  init_chat_session();
185922
186332
  init_router();
185923
186333
  init_workorders();
185924
186334
  init_chat_parts();
185925
186335
  init_injected_context();
186336
+ init_chat_turn_gate();
185926
186337
  init_project_bundle();
185927
186338
  WORKDIR_READ_MAX_BYTES = 2 * 1024 * 1024;
185928
186339
  }
@@ -186451,7 +186862,9 @@ var init_registry3 = __esm({
186451
186862
 
186452
186863
  // ../server/src/domains/automations/attribution.ts
186453
186864
  function automationSubstantiveChange(before, after) {
186454
- return before.executionMode !== after.executionMode || (before.agentId ?? null) !== (after.agentId ?? null) || (before.playbookRef ?? null) !== (after.playbookRef ?? null) || !jsonEq(before.payload, after.payload) || (before.titleTemplate ?? null) !== (after.titleTemplate ?? null) || before.dispatch !== after.dispatch || before.enabled !== after.enabled;
186865
+ return before.executionMode !== after.executionMode || (before.agentId ?? null) !== (after.agentId ?? null) || (before.playbookRef ?? null) !== (after.playbookRef ?? null) || !jsonEq(before.payload, after.payload) || (before.titleTemplate ?? null) !== (after.titleTemplate ?? null) || before.dispatch !== after.dispatch || before.enabled !== after.enabled || // 目标会话策略(ADR 会话投递 D1):改「发到哪儿」= 改怎么跑,问责随之转移。
186866
+ // 归一后比较,`null` 与 `{mode:"new_session"}` 是同一件事,不该被判成变更。
186867
+ !jsonEq(chatTargetKey(before), chatTargetKey(after)) || (before.ownerHumanActorId ?? null) !== (after.ownerHumanActorId ?? null);
186455
186868
  }
186456
186869
  function triggerSubstantiveChange(before, after) {
186457
186870
  if (before.enabled !== after.enabled) return true;
@@ -186470,13 +186883,20 @@ function configSummaryOf(a) {
186470
186883
  payload: a.payload ?? null,
186471
186884
  titleTemplate: a.titleTemplate ?? null,
186472
186885
  dispatch: a.dispatch,
186473
- enabled: a.enabled
186886
+ enabled: a.enabled,
186887
+ chatTarget: chatTargetKey(a),
186888
+ ownerHumanActorId: a.ownerHumanActorId ?? null
186474
186889
  };
186475
186890
  }
186891
+ function chatTargetKey(a) {
186892
+ const t = normalizeChatTarget(a.chatTarget);
186893
+ return t.mode === "existing_session" ? { mode: t.mode, sessionId: t.sessionId } : { mode: "new_session" };
186894
+ }
186476
186895
  var jsonEq;
186477
186896
  var init_attribution = __esm({
186478
186897
  "../server/src/domains/automations/attribution.ts"() {
186479
186898
  "use strict";
186899
+ init_src2();
186480
186900
  jsonEq = (a, b2) => JSON.stringify(a ?? null) === JSON.stringify(b2 ?? null);
186481
186901
  }
186482
186902
  });
@@ -187292,8 +187712,11 @@ var init_scheduler = __esm({
187292
187712
  });
187293
187713
 
187294
187714
  // ../server/src/domains/automations/service.ts
187715
+ function busyError(chatSessionId, blockedBy) {
187716
+ return `\u76EE\u6807\u4F1A\u8BDD ${chatSessionId} \u6B63\u6709\u4E00\u8F6E\u5728\u8DD1${blockedBy ? `\uFF08${blockedBy}\uFF09` : ""}\u2014\u2014\u672C\u6B21\u89E6\u53D1\u5DF2\u4E22\u5F03\uFF0C\u4E0D\u6392\u961F\u3001\u4E0D\u8865\u8DD1`;
187717
+ }
187295
187718
  function genWebhookToken() {
187296
- return `owt_${(0, import_node_crypto37.randomBytes)(32).toString("base64url")}`;
187719
+ return `owt_${(0, import_node_crypto38.randomBytes)(32).toString("base64url")}`;
187297
187720
  }
187298
187721
  function validateTitleTemplate(tpl) {
187299
187722
  const residue = tpl.replace(DATE_TOKEN, "");
@@ -187349,11 +187772,12 @@ function appendSourceNote(brief, automation, source, atIso) {
187349
187772
  ---
187350
187773
  \u672C\u5DE5\u5355\u7531\u81EA\u52A8\u5316\u300C${automation.name}\u300D\u4E8E ${atIso} \u89E6\u53D1\uFF08\u6765\u6E90\uFF1A${source}\uFF09\u3002\u5F00\u5DE5\u540E\u8BF7\u628A\u5DE5\u5355\u6807\u9898\u6539\u6210\u51C6\u786E\u53CD\u6620\u5B9E\u9645\u5DE5\u4F5C\u7684\u63CF\u8FF0\u3002`;
187351
187774
  }
187352
- var import_node_crypto37, DATE_TOKEN, AutomationsService, EVENT_RETRY_BASE_MS, EVENT_RETRY_MAX_MS, EVENT_RETRY_MAX_ATTEMPTS, eventAttempts, eventLastAttemptAt, eventLastError, CHAT_REPLY_MAX;
187775
+ var import_node_crypto38, DATE_TOKEN, AutomationsService, EVENT_RETRY_BASE_MS, EVENT_RETRY_MAX_MS, EVENT_RETRY_MAX_ATTEMPTS, eventAttempts, eventLastAttemptAt, eventLastError, CHAT_REPLY_MAX;
187353
187776
  var init_service8 = __esm({
187354
187777
  "../server/src/domains/automations/service.ts"() {
187355
187778
  "use strict";
187356
- import_node_crypto37 = require("node:crypto");
187779
+ import_node_crypto38 = require("node:crypto");
187780
+ init_src2();
187357
187781
  init_registry3();
187358
187782
  init_attribution();
187359
187783
  init_scheduler();
@@ -187361,12 +187785,14 @@ var init_service8 = __esm({
187361
187785
  AutomationsService = class {
187362
187786
  store;
187363
187787
  deps;
187788
+ targets;
187364
187789
  genId;
187365
187790
  now;
187366
187791
  log;
187367
187792
  constructor(opts) {
187368
187793
  this.store = opts.store;
187369
187794
  this.deps = opts.fire;
187795
+ if (opts.chatTargets) this.targets = opts.chatTargets;
187370
187796
  this.genId = opts.genId ?? ((p2) => `${p2}_${Math.random().toString(36).slice(2, 12)}`);
187371
187797
  this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
187372
187798
  if (opts.log) this.log = opts.log;
@@ -187409,6 +187835,8 @@ var init_service8 = __esm({
187409
187835
  if (input.enabled !== before.enabled) patch.pauseReason = null;
187410
187836
  }
187411
187837
  if (input.subscribers !== void 0) patch.subscribers = input.subscribers;
187838
+ if (input.chatTarget !== void 0) patch.chatTarget = input.chatTarget;
187839
+ if (input.ownerHumanActorId !== void 0) patch.ownerHumanActorId = input.ownerHumanActorId;
187412
187840
  const updated = await this.store.updateAutomation(id, patch);
187413
187841
  let substantive = automationSubstantiveChange(before, updated);
187414
187842
  if (input.triggers !== void 0) {
@@ -187505,6 +187933,83 @@ var init_service8 = __esm({
187505
187933
  createdAt: this.now().toISOString()
187506
187934
  });
187507
187935
  }
187936
+ /* ---------- 固定目标会话(ADR 会话投递 D1/D3/D8) ---------- */
187937
+ /** 固定会话功能是否可用(接缝 + 轮次账本都在才算)。前端读能力位,不自己按部署形态猜。 */
187938
+ chatTargetSupported() {
187939
+ return Boolean(this.targets && this.deps.chatTurns);
187940
+ }
187941
+ /** 鉴权 actor → 人类归属者(D3)。 */
187942
+ resolveOwnerHuman(actor) {
187943
+ return this.targets ? this.targets.resolveOwnerHuman(actor) : Promise.resolve(null);
187944
+ }
187945
+ /**
187946
+ * 保存期校验(D3):目标必须当场可投递。返回 null = 通过,否则给出具体 issue。
187947
+ * **不做任何自动修正**——不改绑会话的 AI 身份、不替换归属人、不回退成新会话。
187948
+ */
187949
+ async validateChatTarget(a, sessionId) {
187950
+ if (!this.targets) return "session_missing";
187951
+ const r = await this.targets.inspect({
187952
+ companyId: a.companyId,
187953
+ agentId: a.agentId,
187954
+ ownerHumanActorId: a.ownerHumanActorId,
187955
+ sessionId
187956
+ });
187957
+ return r.ok ? null : r.issue;
187958
+ }
187959
+ /** 详情/列表用的目标只读投影(含成本观测)。非固定会话返回 null。 */
187960
+ async chatTargetView(a) {
187961
+ const target = normalizeChatTarget(a.chatTarget);
187962
+ if (target.mode !== "existing_session") return null;
187963
+ if (!this.targets) {
187964
+ return { sessionId: target.sessionId, available: false, issue: "session_missing", title: null, touchedAt: null, messageCount: null };
187965
+ }
187966
+ const r = await this.targets.inspect({
187967
+ companyId: a.companyId,
187968
+ agentId: a.agentId,
187969
+ ownerHumanActorId: a.ownerHumanActorId,
187970
+ sessionId: target.sessionId
187971
+ });
187972
+ if (r.ok) {
187973
+ return { sessionId: target.sessionId, available: true, title: r.title, touchedAt: r.touchedAt, messageCount: r.messageCount };
187974
+ }
187975
+ return {
187976
+ sessionId: target.sessionId,
187977
+ available: false,
187978
+ issue: r.issue,
187979
+ title: r.title ?? null,
187980
+ touchedAt: r.touchedAt ?? null,
187981
+ messageCount: null
187982
+ };
187983
+ }
187984
+ /** 选择器搜索(D8)。接缝缺省时给空集合而不是报错——前端展示「不可用」即可。 */
187985
+ async searchChatTargets(args) {
187986
+ if (!this.targets) return { items: [], hasMore: false };
187987
+ return this.targets.search(args);
187988
+ }
187989
+ /**
187990
+ * 「为这条自动化新建长期目标会话」的**服务端复合入口**(D1):建会话 + 回填 chatTarget。
187991
+ *
187992
+ * 之所以不让前端拼「建会话 → 更新自动化」:后者中途失败会留下一个没有来源、谁也不认领的
187993
+ * 孤儿会话。这里的补偿方向刻意选成「回填失败就把刚建的会话删掉」——宁可这次白建,
187994
+ * 也不让一个自动化指着半个配置。
187995
+ */
187996
+ async createChatTargetSession(automation, editor) {
187997
+ if (!this.targets) throw new Error("\u672C\u5B9E\u4F8B\u672A\u63A5\u4F1A\u8BDD\u76EE\u6807\u63A5\u7F1D");
187998
+ if (automation.executionMode !== "chat" || !automation.agentId) throw new Error("\u53EA\u6709 chat \u6A21\u5F0F\u4E14\u5DF2\u8BBE\u6267\u884C\u4EBA\u7684\u81EA\u52A8\u5316\u624D\u80FD\u5EFA\u957F\u671F\u76EE\u6807\u4F1A\u8BDD");
187999
+ if (!automation.ownerHumanActorId) throw new Error("\u8FD9\u6761\u81EA\u52A8\u5316\u6CA1\u6709\u53EF\u7528\u7684\u4EBA\u7C7B\u5F52\u5C5E\u8005\uFF0C\u65E0\u6CD5\u5EFA\u957F\u671F\u76EE\u6807\u4F1A\u8BDD");
188000
+ const { sessionId } = await this.targets.createTargetSession({
188001
+ companyId: automation.companyId,
188002
+ agentId: automation.agentId,
188003
+ ownerHumanActorId: automation.ownerHumanActorId,
188004
+ title: `\u81EA\u52A8\u5316 \xB7 ${automation.name}`.slice(0, 80)
188005
+ });
188006
+ try {
188007
+ return await this.updateAutomation(automation.id, { chatTarget: { mode: "existing_session", sessionId } }, editor);
188008
+ } catch (e) {
188009
+ await this.targets.deleteSession(sessionId).catch(() => void 0);
188010
+ throw e;
188011
+ }
188012
+ }
187508
188013
  /* ---------- firing(ADR 0156 D1/D3) ---------- */
187509
188014
  /**
187510
188015
  * 触发一次协作。链路:幂等落 run(pending) → 准入检查 → 真剧本解析 → SeededWorkorderCreator 建单
@@ -187546,7 +188051,7 @@ var init_service8 = __esm({
187546
188051
  */
187547
188052
  async dispatchRun(automation, trigger, run, source, opts = {}) {
187548
188053
  const skip = await this.admissionSkip(automation, source);
187549
- if (skip) return this.settleSkip(run, skip.reasonCode, skip.error);
188054
+ if (skip) return this.settleSkip(run, skip.reasonCode, skip.error, skip.result);
187550
188055
  if (automation.executionMode === "chat") {
187551
188056
  return this.fireChat(automation, trigger, run, opts);
187552
188057
  }
@@ -187689,7 +188194,10 @@ var init_service8 = __esm({
187689
188194
  if (!automation.enabled) return { reasonCode: "automation_paused", error: "\u81EA\u52A8\u5316\u5DF2\u6682\u505C" };
187690
188195
  if (automation.executionMode === "chat") {
187691
188196
  if (!automation.agentId) return { reasonCode: "config_error", error: "chat \u6A21\u5F0F\u7F3A\u5C11\u6267\u884C\u4EBA\uFF08agentId\uFF09" };
187692
- if (!this.deps.runChat) return { reasonCode: "config_error", error: "\u672C\u5B9E\u4F8B\u672A\u63A5 chat \u6267\u884C" };
188197
+ if (!this.deps.chat) return { reasonCode: "config_error", error: "\u672C\u5B9E\u4F8B\u672A\u63A5 chat \u6267\u884C" };
188198
+ if (!this.deps.chatTurns) {
188199
+ return { reasonCode: "config_error", error: "\u672C\u5B9E\u4F8B\u672A\u63A5\u4F1A\u8BDD\u8F6E\u6B21\u8D26\u672C\uFF08\u65E0\u6CD5\u5224\u5B9A\u76EE\u6807\u4F1A\u8BDD\u662F\u5426\u5728\u8DD1\uFF09" };
188200
+ }
187693
188201
  if (source === "event" && !this.deps.chatSupportsIdempotency) {
187694
188202
  return { reasonCode: "config_error", error: "\u5BF9\u8BDD\u6267\u884C\u6682\u4E0D\u652F\u6301\u8BA2\u9605\u89E6\u53D1\uFF08chat \u9002\u914D\u5668\u7F3A\u6301\u4E45\u5E42\u7B49\u952E\uFF09" };
187695
188203
  }
@@ -187706,29 +188214,67 @@ var init_service8 = __esm({
187706
188214
  return { reasonCode: "config_error", error: `\u5267\u672C ${automation.playbookRef} \u4E0D\u652F\u6301\u5EFA\u5355` };
187707
188215
  }
187708
188216
  if (automation.projectId && this.deps.projectExists && !await this.deps.projectExists(automation.projectId)) {
187709
- return { reasonCode: "target_unavailable", error: `\u76EE\u6807\u9879\u76EE\u4E0D\u5B58\u5728\uFF1A${automation.projectId}` };
188217
+ return {
188218
+ reasonCode: "target_unavailable",
188219
+ error: `\u76EE\u6807\u9879\u76EE\u4E0D\u5B58\u5728\uFF1A${automation.projectId}`,
188220
+ result: { targetKind: "project", projectId: automation.projectId }
188221
+ };
187710
188222
  }
187711
188223
  return null;
187712
188224
  }
187713
188225
  /**
187714
- * chat 模式执行(ADR 0159):run running(会话在途)→ 会话结束回调落 succeeded(+reply)/failed。
187715
- * 进程重启会丢回调——syncRuns 对超时 running 兜底回收(chat_lost)。
188226
+ * chat 模式执行(ADR 0159 + 会话投递 D2)。三步各自可单测、职责不重叠:
188227
+ *
188228
+ * 1. **解析目标**(不写消息、不 dispatch):`new_session` 由 run id 确定性映射出一个会话,
188229
+ * `existing_session` 读固定会话并复查 D3 五条。
188230
+ * 2. **持久占位**:拿到 active slot 才有资格产生副作用;拿不到 = 别人在跑 → 本次触发丢弃。
188231
+ * 3. **消费占位**:写确定性消息、真 dispatch、收流收口。
188232
+ *
188233
+ * 进程重启会丢 onDone 回调——syncRuns 对超时 running 兜底回收(chat_lost)。
187716
188234
  */
187717
188235
  async fireChat(automation, trigger, run, opts) {
187718
188236
  const now = this.now();
187719
188237
  const tz = trigger?.schedule?.tz;
187720
188238
  const title = automation.titleTemplate ? automation.titleTemplate.replace(DATE_TOKEN, renderDate(now, tz)) : `${automation.name} ${renderDate(now, tz)}`;
187721
188239
  const prompt = buildChatPrompt(automation, title, opts.envelope);
187722
- const human = automation.createdBy?.startsWith("actor:human:") ? automation.createdBy : null;
188240
+ let target;
188241
+ try {
188242
+ target = await this.deps.chat.resolveTarget({ automation, runId: run.id, title });
188243
+ } catch (e) {
188244
+ return this.failChatRun(run, e instanceof Error ? e.message : String(e));
188245
+ }
188246
+ if (!target.ok) return this.settleSkip(run, target.reasonCode, target.error, target.result);
188247
+ const claim = await this.claimChatTurn(run, target.chatSessionId).catch((e) => e);
188248
+ if (claim instanceof Error) return this.failChatRun(run, claim.message);
188249
+ if (claim.outcome === "busy") {
188250
+ await this.store.settleRun(run.id, {
188251
+ status: "skipped",
188252
+ reasonCode: "session_busy",
188253
+ error: busyError(target.chatSessionId, claim.activeTurn.sourceRunId),
188254
+ result: { chatSessionId: target.chatSessionId, blockedByTurnId: claim.activeTurn.id },
188255
+ completedAt: this.now().toISOString()
188256
+ });
188257
+ this.log?.(`[automations] run ${run.id} \u8DF3\u8FC7\uFF1A\u76EE\u6807\u4F1A\u8BDD ${target.chatSessionId} \u6B63\u5FD9\uFF08active turn ${claim.activeTurn.id}\uFF09`);
188258
+ return await this.store.getRun(run.id);
188259
+ }
188260
+ const turn = claim.turn;
188261
+ const chatSessionId = target.chatSessionId;
187723
188262
  try {
187724
- const { chatSessionId } = await this.deps.runChat(
187725
- { agentId: automation.agentId, humanActorId: human, title, prompt },
188263
+ await this.deps.chat.dispatchTurn(
188264
+ {
188265
+ automation,
188266
+ chatSessionId,
188267
+ turnId: turn.id,
188268
+ messageKey: turn.messageKey,
188269
+ prompt,
188270
+ runId: run.id
188271
+ },
187726
188272
  async (out) => {
187727
188273
  try {
187728
188274
  if (out.ok) {
187729
188275
  await this.store.settleRun(run.id, {
187730
188276
  status: "succeeded",
187731
- result: { chatSessionId, ...out.reply ? { reply: clipReply(out.reply) } : {} },
188277
+ result: { chatSessionId, turnId: turn.id, ...out.reply ? { reply: clipReply(out.reply) } : {} },
187732
188278
  completedAt: this.now().toISOString()
187733
188279
  });
187734
188280
  } else {
@@ -187736,7 +188282,7 @@ var init_service8 = __esm({
187736
188282
  status: "failed",
187737
188283
  error: out.error ?? "\u4F1A\u8BDD\u6267\u884C\u5931\u8D25",
187738
188284
  reasonCode: "spawn_failed",
187739
- result: { chatSessionId },
188285
+ result: { chatSessionId, turnId: turn.id },
187740
188286
  completedAt: this.now().toISOString()
187741
188287
  });
187742
188288
  }
@@ -187747,26 +188293,56 @@ var init_service8 = __esm({
187747
188293
  );
187748
188294
  await this.store.settleRun(run.id, {
187749
188295
  status: "running",
187750
- result: { chatSessionId },
188296
+ result: { chatSessionId, turnId: turn.id },
187751
188297
  completedAt: null
187752
188298
  });
187753
188299
  return await this.store.getRun(run.id);
187754
188300
  } catch (e) {
187755
188301
  const msg = e instanceof Error ? e.message : String(e);
187756
- await this.store.settleRun(run.id, {
187757
- status: "failed",
187758
- error: msg,
187759
- reasonCode: "spawn_failed",
187760
- completedAt: this.now().toISOString()
188302
+ await this.deps.chatTurns.settleTurn(turn.id, { status: "failed", completedAt: this.now().toISOString(), lastError: msg }).catch((err) => this.log?.(`[automations] \u8F6E\u6B21\u6536\u53E3\u5931\u8D25 ${turn.id}: ${String(err)}`));
188303
+ return this.failChatRun(run, msg, { chatSessionId, turnId: turn.id });
188304
+ }
188305
+ }
188306
+ /**
188307
+ * 占位(ADR 会话投递 D5)。存储层给了复合操作就走复合(claim 与 busy 落账同一事务);
188308
+ * 没给则退回两步——单进程内存实现语义等价,多实例部署一律有复合实现。
188309
+ */
188310
+ async claimChatTurn(run, chatSessionId) {
188311
+ const input = {
188312
+ turnId: this.genId("cturn"),
188313
+ chatSessionId,
188314
+ source: "automation",
188315
+ sourceRunId: automationTurnSourceRunId(run.id),
188316
+ messageKey: automationTurnMessageKey(run.id),
188317
+ reservedAt: this.now().toISOString()
188318
+ };
188319
+ if (this.store.claimChatTurnForRun) {
188320
+ return this.store.claimChatTurnForRun({
188321
+ ...input,
188322
+ automationRunId: run.id,
188323
+ busyError: busyError(chatSessionId, ""),
188324
+ now: this.now().toISOString()
187761
188325
  });
187762
- return await this.store.getRun(run.id);
187763
188326
  }
188327
+ return this.deps.chatTurns.claimTurn(input);
187764
188328
  }
187765
- async settleSkip(run, reasonCode, error2) {
188329
+ /** chat 路径的失败收口(基础设施故障,非 event 源,ADR 会话投递 D7)。 */
188330
+ async failChatRun(run, error2, result) {
188331
+ await this.store.settleRun(run.id, {
188332
+ status: "failed",
188333
+ error: error2,
188334
+ reasonCode: "spawn_failed",
188335
+ ...result !== void 0 ? { result } : {},
188336
+ completedAt: this.now().toISOString()
188337
+ });
188338
+ return await this.store.getRun(run.id);
188339
+ }
188340
+ async settleSkip(run, reasonCode, error2, result) {
187766
188341
  await this.store.settleRun(run.id, {
187767
188342
  status: "skipped",
187768
188343
  reasonCode,
187769
188344
  error: error2,
188345
+ ...result !== void 0 ? { result } : {},
187770
188346
  completedAt: this.now().toISOString()
187771
188347
  });
187772
188348
  return await this.store.getRun(run.id);
@@ -188005,11 +188581,11 @@ function verifySignature(rawBody, headers, secret) {
188005
188581
  if (!secret) return "not_required";
188006
188582
  const given = header1(headers, "x-hub-signature-256");
188007
188583
  if (!given) return "missing";
188008
- const expected = `sha256=${(0, import_node_crypto38.createHmac)("sha256", secret).update(rawBody).digest("hex")}`;
188584
+ const expected = `sha256=${(0, import_node_crypto39.createHmac)("sha256", secret).update(rawBody).digest("hex")}`;
188009
188585
  const a = Buffer.from(given);
188010
188586
  const b2 = Buffer.from(expected);
188011
188587
  if (a.length !== b2.length) return "invalid";
188012
- return (0, import_node_crypto38.timingSafeEqual)(a, b2) ? "valid" : "invalid";
188588
+ return (0, import_node_crypto39.timingSafeEqual)(a, b2) ? "valid" : "invalid";
188013
188589
  }
188014
188590
  function eventAllowed(filters, envelope) {
188015
188591
  if (!filters || filters.length === 0) return true;
@@ -188039,11 +188615,11 @@ function splitEvent(envelope) {
188039
188615
  }
188040
188616
  return { eventName, actionCandidates: candidates };
188041
188617
  }
188042
- var import_node_crypto38, MAX_BODY_BYTES, AutomationWebhookService, header1, PROVIDER_PREFIXES;
188618
+ var import_node_crypto39, MAX_BODY_BYTES, AutomationWebhookService, header1, PROVIDER_PREFIXES;
188043
188619
  var init_webhook = __esm({
188044
188620
  "../server/src/domains/automations/webhook.ts"() {
188045
188621
  "use strict";
188046
- import_node_crypto38 = require("node:crypto");
188622
+ import_node_crypto39 = require("node:crypto");
188047
188623
  MAX_BODY_BYTES = 256 * 1024;
188048
188624
  AutomationWebhookService = class {
188049
188625
  store;
@@ -188330,7 +188906,9 @@ function buildAutomationSummary(automation, triggers, recent, canWrite) {
188330
188906
  nextRunAt,
188331
188907
  recent: ordered.slice(0, RECENT_LIMIT).map(stripRunPayload),
188332
188908
  canWrite,
188333
- capabilities: { eventTrigger: { supported: true } }
188909
+ capabilities: { eventTrigger: { supported: true } },
188910
+ // 固定目标会话的只读投影要查会话表,纯派生函数够不着——路由层用 service.chatTargetView 覆盖。
188911
+ chatTargetView: null
188334
188912
  };
188335
188913
  }
188336
188914
  function deriveNextRunAt(triggers) {
@@ -188444,6 +189022,23 @@ function assertModeConfig(mode, agentId, playbookRef) {
188444
189022
  if (!playbookRef?.trim()) throw new ApiError(400, "BAD_REQUEST", "workorder \u6A21\u5F0F\u7F3A\u5C11 playbookRef");
188445
189023
  assertPlaybook(playbookRef);
188446
189024
  }
189025
+ function validateChatTarget(raw, mode) {
189026
+ if (raw === void 0) return void 0;
189027
+ if (raw === null) return null;
189028
+ if (mode !== "chat") throw new ApiError(400, "BAD_REQUEST", "chatTarget \u53EA\u5728\u5BF9\u8BDD\u6267\u884C\uFF08chat\uFF09\u6A21\u5F0F\u4E0B\u6709\u610F\u4E49");
189029
+ const t = raw;
189030
+ if (t?.mode === "new_session") {
189031
+ if (t.sessionId !== void 0) throw new ApiError(400, "BAD_REQUEST", "chatTarget.mode=new_session \u65F6\u4E0D\u80FD\u5E26 sessionId");
189032
+ return { mode: "new_session" };
189033
+ }
189034
+ if (t?.mode === "existing_session") {
189035
+ if (typeof t.sessionId !== "string" || !t.sessionId.trim()) {
189036
+ throw new ApiError(400, "BAD_REQUEST", "chatTarget.mode=existing_session \u9700\u8981 sessionId");
189037
+ }
189038
+ return { mode: "existing_session", sessionId: t.sessionId.trim() };
189039
+ }
189040
+ throw new ApiError(400, "BAD_REQUEST", "chatTarget.mode \u53EA\u80FD\u662F new_session \u6216 existing_session");
189041
+ }
188447
189042
  function assertTitleTemplate(tpl) {
188448
189043
  if (!tpl) return;
188449
189044
  const unknown2 = validateTitleTemplate(tpl);
@@ -188518,7 +189113,9 @@ function automationsDomain(opts) {
188518
189113
  return {
188519
189114
  ...buildAutomationSummary(automation, decorated, recent, canWrite),
188520
189115
  // 能力位服务端算(ADR 0163 §10):前端只消费不推断,否则前后端两套规则必漂移。
188521
- capabilities: { eventTrigger: service.eventTriggerCapability(automation) }
189116
+ capabilities: { eventTrigger: service.eventTriggerCapability(automation) },
189117
+ // 固定目标会话的可用性同理:D3 五条只在服务端有真值,前端复制一份必然漂移。
189118
+ chatTargetView: await service.chatTargetView(automation)
188522
189119
  };
188523
189120
  };
188524
189121
  const assertEventTriggerWritable = async (mode, cfg, existing, req) => {
@@ -188548,6 +189145,15 @@ function automationsDomain(opts) {
188548
189145
  throw new ApiError(400, "EVENT_TRIGGER_UNSUPPORTED", "\u8FD9\u6761\u81EA\u52A8\u5316\u6709\u8BA2\u9605\u89E6\u53D1\u5668\uFF0C\u6539\u6210\u5BF9\u8BDD\u6267\u884C\u4F1A\u5931\u53BB\u6301\u4E45\u5E42\u7B49\u4FDD\u8BC1\u2014\u2014\u8BF7\u5148\u5220\u9664\u8BA2\u9605\u89E6\u53D1\u5668");
188549
189146
  }
188550
189147
  };
189148
+ const assertChatTargetUsable = async (a, target) => {
189149
+ if (target?.mode !== "existing_session") return;
189150
+ if (!service.chatTargetSupported()) {
189151
+ throw new ApiError(400, "CHAT_TARGET_UNSUPPORTED", "\u672C\u5B9E\u4F8B\u672A\u63A5\u4F1A\u8BDD\u8F6E\u6B21\u8D26\u672C\uFF0C\u6682\u4E0D\u652F\u6301\u56FA\u5B9A\u76EE\u6807\u4F1A\u8BDD");
189152
+ }
189153
+ const issue2 = await service.validateChatTarget(a, target.sessionId);
189154
+ if (issue2) throw new ApiError(400, "CHAT_TARGET_UNAVAILABLE", TARGET_ISSUE_MESSAGE[issue2]);
189155
+ };
189156
+ const resolveOwner2 = async (existing, req) => existing ?? await service.resolveOwnerHuman(req.auth.actor);
188551
189157
  return (router) => {
188552
189158
  router.get("/api/automations/templates", async () => {
188553
189159
  const body = { items: AUTOMATION_TEMPLATES };
@@ -188561,6 +189167,32 @@ function automationsDomain(opts) {
188561
189167
  const body = { next: cronNextN(expr, tz, 3) };
188562
189168
  return { status: 200, body };
188563
189169
  });
189170
+ router.get("/api/automations/chat-session-options", async (req) => {
189171
+ const agentId = req.query?.get("agentId") ?? "";
189172
+ if (!agentId.startsWith("actor:agent:")) throw new ApiError(400, "BAD_REQUEST", "\u9700\u8981 agentId\uFF08actor:agent:*\uFF09");
189173
+ if (!service.chatTargetSupported()) {
189174
+ throw new ApiError(400, "CHAT_TARGET_UNSUPPORTED", "\u672C\u5B9E\u4F8B\u672A\u63A5\u4F1A\u8BDD\u8F6E\u6B21\u8D26\u672C\uFF0C\u6682\u4E0D\u652F\u6301\u56FA\u5B9A\u76EE\u6807\u4F1A\u8BDD");
189175
+ }
189176
+ const owner = await service.resolveOwnerHuman(req.auth.actor);
189177
+ if (!owner) {
189178
+ const empty = { items: [], hasMore: false };
189179
+ return { status: 200, body: empty };
189180
+ }
189181
+ const limitRaw = Number(req.query?.get("limit"));
189182
+ const offsetRaw = Number(req.query?.get("offset"));
189183
+ const limit = Number.isInteger(limitRaw) && limitRaw > 0 ? Math.min(limitRaw, 50) : 20;
189184
+ const offset = Number.isInteger(offsetRaw) && offsetRaw > 0 ? offsetRaw : 0;
189185
+ const text = req.query?.get("q")?.trim();
189186
+ const body = await service.searchChatTargets({
189187
+ companyId: companyOf(req),
189188
+ agentId,
189189
+ ownerHumanActorId: owner,
189190
+ ...text ? { text } : {},
189191
+ limit,
189192
+ offset
189193
+ });
189194
+ return { status: 200, body };
189195
+ });
188564
189196
  router.get("/api/automations", async (req) => {
188565
189197
  const includeArchived = req.query.get("includeArchived") === "1";
188566
189198
  const automations = await service.listAutomations(companyOf(req), { includeArchived });
@@ -188614,12 +189246,30 @@ function automationsDomain(opts) {
188614
189246
  const subscribers = assertActorIds(b2.subscribers, "subscribers");
188615
189247
  const triggers = validateTriggers(b2.triggers);
188616
189248
  await assertEventTriggerInputs(mode, triggers, [], req);
189249
+ const chatTarget = validateChatTarget(b2.chatTarget, mode);
189250
+ const createTargetSession = Boolean(b2.createChatTargetSession);
189251
+ if (createTargetSession) {
189252
+ if (mode !== "chat") throw new ApiError(400, "BAD_REQUEST", "createChatTargetSession \u53EA\u5728\u5BF9\u8BDD\u6267\u884C\uFF08chat\uFF09\u6A21\u5F0F\u4E0B\u6709\u610F\u4E49");
189253
+ if (chatTarget?.mode === "existing_session") {
189254
+ throw new ApiError(400, "BAD_REQUEST", "createChatTargetSession \u4E0E chatTarget.existing_session \u4E92\u65A5");
189255
+ }
189256
+ if (!service.chatTargetSupported()) {
189257
+ throw new ApiError(400, "CHAT_TARGET_UNSUPPORTED", "\u672C\u5B9E\u4F8B\u672A\u63A5\u4F1A\u8BDD\u8F6E\u6B21\u8D26\u672C\uFF0C\u6682\u4E0D\u652F\u6301\u56FA\u5B9A\u76EE\u6807\u4F1A\u8BDD");
189258
+ }
189259
+ }
189260
+ const ownerHumanActorId = mode === "chat" ? await service.resolveOwnerHuman(req.auth.actor) : null;
189261
+ if (mode === "chat" && !ownerHumanActorId && (chatTarget?.mode === "existing_session" || createTargetSession)) {
189262
+ throw new ApiError(400, "CHAT_TARGET_NO_OWNER", "\u89E3\u6790\u4E0D\u5230\u4EBA\u7C7B\u5F52\u5C5E\u8005\uFF0C\u65E0\u6CD5\u914D\u7F6E\u56FA\u5B9A\u76EE\u6807\u4F1A\u8BDD");
189263
+ }
189264
+ await assertChatTargetUsable({ companyId: companyOf(req), agentId: b2.agentId ?? null, ownerHumanActorId }, chatTarget);
188617
189265
  const created = await service.createAutomation({
188618
189266
  companyId: companyOf(req),
188619
189267
  name: b2.name,
188620
189268
  ...b2.description !== void 0 ? { description: b2.description } : {},
188621
189269
  executionMode: mode,
188622
189270
  ...b2.agentId !== void 0 ? { agentId: b2.agentId } : {},
189271
+ ...chatTarget !== void 0 ? { chatTarget } : {},
189272
+ ...ownerHumanActorId ? { ownerHumanActorId } : {},
188623
189273
  ...b2.projectId !== void 0 ? { projectId: b2.projectId } : {},
188624
189274
  ...b2.playbookRef !== void 0 ? { playbookRef: b2.playbookRef } : {},
188625
189275
  ...b2.payload !== void 0 ? { payload: b2.payload } : {},
@@ -188631,7 +189281,8 @@ function automationsDomain(opts) {
188631
189281
  // 一律取鉴权身份,不收请求体(归属不可伪造)
188632
189282
  ...triggers !== void 0 ? { triggers } : {}
188633
189283
  });
188634
- return { status: 201, body: await summarize3(created, req) };
189284
+ const withTarget = createTargetSession ? await service.createChatTargetSession(created, req.auth.actor) : created;
189285
+ return { status: 201, body: await summarize3(withTarget, req) };
188635
189286
  });
188636
189287
  router.patch("/api/automations/:id", async (req) => {
188637
189288
  const automation = await mustGet(req);
@@ -188650,6 +189301,14 @@ function automationsDomain(opts) {
188650
189301
  const kept = triggers !== void 0 ? [] : (await service.listTriggers(automation.id)).filter((t) => t.kind === "event" && t.event).map((t) => t.event);
188651
189302
  await assertEventTriggerInputs(effMode, triggers, kept, req);
188652
189303
  if (triggers === void 0) await assertModeKeepsEventTriggers(automation, effMode);
189304
+ const chatTarget = validateChatTarget(b2.chatTarget, effMode);
189305
+ const effTarget = chatTarget !== void 0 ? chatTarget : automation.chatTarget;
189306
+ const nextTarget = effMode === "workorder" ? null : effTarget;
189307
+ const ownerHumanActorId = effMode === "chat" ? await resolveOwner2(automation.ownerHumanActorId, req) : null;
189308
+ if (effMode === "chat" && nextTarget?.mode === "existing_session" && !ownerHumanActorId) {
189309
+ throw new ApiError(400, "CHAT_TARGET_NO_OWNER", "\u89E3\u6790\u4E0D\u5230\u4EBA\u7C7B\u5F52\u5C5E\u8005\uFF0C\u65E0\u6CD5\u914D\u7F6E\u56FA\u5B9A\u76EE\u6807\u4F1A\u8BDD");
189310
+ }
189311
+ await assertChatTargetUsable({ companyId: automation.companyId, agentId: effAgent ?? null, ownerHumanActorId }, nextTarget);
188653
189312
  const updated = await service.updateAutomation(
188654
189313
  automation.id,
188655
189314
  {
@@ -188664,12 +189323,28 @@ function automationsDomain(opts) {
188664
189323
  ...b2.dispatch !== void 0 ? { dispatch: Boolean(b2.dispatch) } : {},
188665
189324
  ...b2.enabled !== void 0 ? { enabled: b2.enabled } : {},
188666
189325
  ...subscribers !== void 0 ? { subscribers } : {},
188667
- ...triggers !== void 0 ? { triggers } : {}
189326
+ ...triggers !== void 0 ? { triggers } : {},
189327
+ ...nextTarget !== automation.chatTarget ? { chatTarget: nextTarget } : {},
189328
+ ...ownerHumanActorId !== automation.ownerHumanActorId ? { ownerHumanActorId } : {}
188668
189329
  },
188669
189330
  req.auth.actor
188670
189331
  );
188671
189332
  return { status: 200, body: await summarize3(updated, req) };
188672
189333
  });
189334
+ router.post("/api/automations/:id/chat-target-session", async (req) => {
189335
+ const automation = await mustGet(req);
189336
+ await requireWrite(automation, req);
189337
+ if (automation.executionMode !== "chat") throw new ApiError(400, "BAD_REQUEST", "\u53EA\u6709\u5BF9\u8BDD\u6267\u884C\uFF08chat\uFF09\u6A21\u5F0F\u624D\u6709\u76EE\u6807\u4F1A\u8BDD");
189338
+ if (!automation.agentId) throw new ApiError(400, "BAD_REQUEST", "\u5148\u7ED9\u8FD9\u6761\u81EA\u52A8\u5316\u8BBE\u7F6E\u6267\u884C\u4EBA");
189339
+ if (!service.chatTargetSupported()) {
189340
+ throw new ApiError(400, "CHAT_TARGET_UNSUPPORTED", "\u672C\u5B9E\u4F8B\u672A\u63A5\u4F1A\u8BDD\u8F6E\u6B21\u8D26\u672C\uFF0C\u6682\u4E0D\u652F\u6301\u56FA\u5B9A\u76EE\u6807\u4F1A\u8BDD");
189341
+ }
189342
+ const owner = await resolveOwner2(automation.ownerHumanActorId, req);
189343
+ if (!owner) throw new ApiError(400, "CHAT_TARGET_NO_OWNER", "\u89E3\u6790\u4E0D\u5230\u4EBA\u7C7B\u5F52\u5C5E\u8005\uFF0C\u65E0\u6CD5\u5EFA\u957F\u671F\u76EE\u6807\u4F1A\u8BDD");
189344
+ const withOwner = automation.ownerHumanActorId ? automation : await service.updateAutomation(automation.id, { ownerHumanActorId: owner }, req.auth.actor);
189345
+ const updated = await service.createChatTargetSession(withOwner, req.auth.actor);
189346
+ return { status: 201, body: await summarize3(updated, req) };
189347
+ });
188673
189348
  router.post("/api/automations/:id/enabled", async (req) => {
188674
189349
  const automation = await mustGet(req);
188675
189350
  await requireWrite(automation, req);
@@ -188817,7 +189492,7 @@ function automationsDomain(opts) {
188817
189492
  });
188818
189493
  };
188819
189494
  }
188820
- var RECENT_LIMIT2;
189495
+ var RECENT_LIMIT2, TARGET_ISSUE_MESSAGE;
188821
189496
  var init_routes9 = __esm({
188822
189497
  "../server/src/domains/automations/routes.ts"() {
188823
189498
  "use strict";
@@ -188830,6 +189505,13 @@ var init_routes9 = __esm({
188830
189505
  init_registry3();
188831
189506
  init_migrate();
188832
189507
  RECENT_LIMIT2 = 5;
189508
+ TARGET_ISSUE_MESSAGE = {
189509
+ session_missing: "\u76EE\u6807\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u5DF2\u88AB\u5220\u9664",
189510
+ cross_company: "\u76EE\u6807\u4F1A\u8BDD\u5C5E\u4E8E\u5176\u5B83\u516C\u53F8\uFF0C\u4E0D\u80FD\u8DE8\u516C\u53F8\u6295\u9012",
189511
+ company_unknown: "\u8FD9\u6761\u5386\u53F2\u4F1A\u8BDD\u6CA1\u6709\u516C\u53F8\u5F52\u5C5E\uFF0C\u4E0D\u80FD\u4F5C\u4E3A\u56FA\u5B9A\u76EE\u6807\u2014\u2014\u8BF7\u65B0\u5EFA\u4E00\u4E2A\u957F\u671F\u4F1A\u8BDD",
189512
+ owner_mismatch: "\u76EE\u6807\u4F1A\u8BDD\u4E0D\u5C5E\u4E8E\u8FD9\u6761\u81EA\u52A8\u5316\u7684\u5F52\u5C5E\u4EBA",
189513
+ agent_mismatch: "\u76EE\u6807\u4F1A\u8BDD\u7684 AI \u8EAB\u4EFD\u4E0E\u6267\u884C\u4EBA\u4E0D\u4E00\u81F4\u2014\u2014\u8BF7\u6362\u4E00\u4E2A\u4F1A\u8BDD\u6216\u6539\u6267\u884C\u4EBA\uFF08\u4E0D\u4F1A\u81EA\u52A8\u6539\u7ED1\uFF09"
189514
+ };
188833
189515
  }
188834
189516
  });
188835
189517
 
@@ -188902,6 +189584,7 @@ function createAutomationsDomain(opts) {
188902
189584
  const service = new AutomationsService({
188903
189585
  store: opts.store,
188904
189586
  fire: opts.fire,
189587
+ ...opts.chatTargets ? { chatTargets: opts.chatTargets } : {},
188905
189588
  ...opts.genId ? { genId: opts.genId } : {},
188906
189589
  ...opts.now ? { now: opts.now } : {},
188907
189590
  ...opts.log ? { log: opts.log } : {}
@@ -189086,7 +189769,7 @@ function playbooksDomain(opts) {
189086
189769
  opts.overrides.set(companyOf(req), ref2, nodeKey, nextOverride);
189087
189770
  try {
189088
189771
  await opts.audit?.({
189089
- id: `reg_${(0, import_node_crypto39.randomUUID)()}`,
189772
+ id: `reg_${(0, import_node_crypto40.randomUUID)()}`,
189090
189773
  actor: req.auth.actor,
189091
189774
  kind: "registry_change",
189092
189775
  target: `playbook:${ref2}#${nodeKey}`,
@@ -189103,11 +189786,11 @@ function playbooksDomain(opts) {
189103
189786
  });
189104
189787
  };
189105
189788
  }
189106
- var import_node_crypto39;
189789
+ var import_node_crypto40;
189107
189790
  var init_routes10 = __esm({
189108
189791
  "../server/src/domains/playbooks/routes.ts"() {
189109
189792
  "use strict";
189110
- import_node_crypto39 = require("node:crypto");
189793
+ import_node_crypto40 = require("node:crypto");
189111
189794
  init_router();
189112
189795
  init_registry3();
189113
189796
  init_planner();
@@ -189219,17 +189902,17 @@ function completionFingerprint(workspace, members) {
189219
189902
  workspace,
189220
189903
  members: [...members].sort((a, b2) => a.artifactId < b2.artifactId ? -1 : a.artifactId > b2.artifactId ? 1 : 0).map((m2) => ({ artifactId: m2.artifactId, currentRevisionId: m2.currentRevisionId ?? null }))
189221
189904
  });
189222
- return (0, import_node_crypto40.createHash)("sha256").update(canonical).digest("hex");
189905
+ return (0, import_node_crypto41.createHash)("sha256").update(canonical).digest("hex");
189223
189906
  }
189224
189907
  function eventConsumptionKey(args) {
189225
189908
  const raw = `project-event:${EVENT_KEY_VERSION}:${args.triggerId}:${args.event}:${args.workspace}:${args.fingerprint}`;
189226
- return raw.length <= 200 ? raw : `project-event:${EVENT_KEY_VERSION}:sha256:${(0, import_node_crypto40.createHash)("sha256").update(raw).digest("hex")}`;
189909
+ return raw.length <= 200 ? raw : `project-event:${EVENT_KEY_VERSION}:sha256:${(0, import_node_crypto41.createHash)("sha256").update(raw).digest("hex")}`;
189227
189910
  }
189228
- var import_node_crypto40, EVENT_KEY_VERSION, EMPTY_METRICS, AUTOMATION_EVENT_HANDLERS, ProjectEventPoller;
189911
+ var import_node_crypto41, EVENT_KEY_VERSION, EMPTY_METRICS, AUTOMATION_EVENT_HANDLERS, ProjectEventPoller;
189229
189912
  var init_project_event_poller = __esm({
189230
189913
  "../server/src/automations/project-event-poller.ts"() {
189231
189914
  "use strict";
189232
- import_node_crypto40 = require("node:crypto");
189915
+ import_node_crypto41 = require("node:crypto");
189233
189916
  EVENT_KEY_VERSION = "v1";
189234
189917
  EMPTY_METRICS = () => ({
189235
189918
  scanDurationMs: 0,
@@ -189448,11 +190131,11 @@ function remoteAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs) {
189448
190131
  entry.appendWaiters.push({ resolve: resolve10, timer });
189449
190132
  });
189450
190133
  }
189451
- var import_node_crypto41, STASH_TTL_MS, STASH_MAX_FRAMES_PER_ID, STASH_MAX_IDS, DaemonHubAdapter, BindingRouterAdapter, WorkdirBridge;
190134
+ var import_node_crypto42, STASH_TTL_MS, STASH_MAX_FRAMES_PER_ID, STASH_MAX_IDS, DaemonHubAdapter, BindingRouterAdapter, WorkdirBridge;
189452
190135
  var init_daemon_adapter = __esm({
189453
190136
  "../server/src/daemon-adapter.ts"() {
189454
190137
  "use strict";
189455
- import_node_crypto41 = require("node:crypto");
190138
+ import_node_crypto42 = require("node:crypto");
189456
190139
  init_src2();
189457
190140
  STASH_TTL_MS = 5 * 6e4;
189458
190141
  STASH_MAX_FRAMES_PER_ID = 500;
@@ -189468,10 +190151,16 @@ var init_daemon_adapter = __esm({
189468
190151
  this.log = opts.log ?? ((m2) => console.warn(m2));
189469
190152
  this.health = opts.health;
189470
190153
  this.stashEnabled = opts.stashUnknownFrames ?? false;
190154
+ this.onSettleWithoutFrame = opts.onSettleWithoutFrame;
189471
190155
  hub.addMessageListener((_daemonId, msg) => this.onDaemonMessage(msg));
189472
190156
  hub.addDisconnectListener((daemonId) => this.onNodeDown(daemonId));
189473
190157
  hub.addConnectListener((daemon) => this.onNodeUp(daemon));
189474
190158
  hub.addSessionConnectListener?.((i) => this.onSessionUp(i.dispatchId));
190159
+ hub.registerSessionClaimCheck?.((dispatchId) => {
190160
+ const e = this.pending.get(dispatchId);
190161
+ if (e) return e.exited ? "settled" : "live";
190162
+ return this.settledDispatchIds.has(dispatchId) ? "settled" : "unknown";
190163
+ });
189475
190164
  hub.addSessionDisconnectListener?.((i) => this.onSessionDown(i.dispatchId));
189476
190165
  }
189477
190166
  pending = /* @__PURE__ */ new Map();
@@ -189482,6 +190171,8 @@ var init_daemon_adapter = __esm({
189482
190171
  log;
189483
190172
  health;
189484
190173
  stashEnabled;
190174
+ /** 服务端自判死时的回调(见 DaemonHubAdapterOptions.onSettleWithoutFrame)。 */
190175
+ onSettleWithoutFrame;
189485
190176
  /** 未知会话帧暂存(stashUnknownFrames 开启时):dispatchId → 断链/重启窗口补发上来的帧。 */
189486
190177
  stash = /* @__PURE__ */ new Map();
189487
190178
  /** 断联节点 → 收割定时器。重连取消;到期收割。 */
@@ -189489,6 +190180,9 @@ var init_daemon_adapter = __esm({
189489
190180
  /** 会话粒度的失联倒计时(键=dispatchId)。与上面按 nodeId 的那张表并存,判据不同、互不替代。 */
189490
190181
  sessionReapTimers = /* @__PURE__ */ new Map();
189491
190182
  sessionReapGraceMs;
190183
+ /** 近期已收尾的 dispatchId(有界)。用于回答「这次派发我收过了」——否则收尾后 pending 里就没了,
190184
+ * 旧会话重连时会被判成 unknown,分不清「我收割过它」和「压根不归我管」。 */
190185
+ settledDispatchIds = /* @__PURE__ */ new Set();
189492
190186
  sessionAuth;
189493
190187
  /** 取走(并清除)某会话的暂存帧;无暂存返回空数组对。 */
189494
190188
  drainStash(dispatchId) {
@@ -189520,7 +190214,15 @@ var init_daemon_adapter = __esm({
189520
190214
  }
189521
190215
  }
189522
190216
  /** 统一结算一次会话退出:清 ack 定时器、置 exited、触发回调、从 pending 摘除。幂等(已退出即跳过)。 */
189523
- settle(dispatchId, info) {
190217
+ /**
190218
+ * 收尾一次派发。
190219
+ *
190220
+ * `fromFrame` = 这次收尾是不是**节点报上来的真退出帧**触发的。false(默认)意味着服务端
190221
+ * 自己判的死——存在性快照 / 会话失联收割 / socket 非 OPEN。那三条走不到 serve 挂在 hub
190222
+ * message listener 上的 work.response 桥,引擎那条 work 会一直挂 running 直到 30 分钟超时,
190223
+ * 所以这里要显式喊一声 `onSettleWithoutFrame`。**新增判死路径时默认值就是对的**,别顺手传 true。
190224
+ */
190225
+ settle(dispatchId, info, fromFrame = false) {
189524
190226
  const entry = this.pending.get(dispatchId);
189525
190227
  if (!entry || entry.exited) return;
189526
190228
  if (entry.ackTimer) {
@@ -189536,6 +190238,22 @@ var init_daemon_adapter = __esm({
189536
190238
  else this.health?.recordReachable(entry.nodeId);
189537
190239
  for (const cb of entry.exitCbs.splice(0)) cb(info);
189538
190240
  this.pending.delete(dispatchId);
190241
+ this.settledDispatchIds.add(dispatchId);
190242
+ if (this.settledDispatchIds.size > 2e3) {
190243
+ const it = this.settledDispatchIds.values();
190244
+ for (let i = 0; i < 500; i++) {
190245
+ const v2 = it.next();
190246
+ if (v2.done) break;
190247
+ this.settledDispatchIds.delete(v2.value);
190248
+ }
190249
+ }
190250
+ if (!fromFrame) {
190251
+ try {
190252
+ this.onSettleWithoutFrame?.(dispatchId, info);
190253
+ } catch (e) {
190254
+ this.log(`[dispatch-delivery] onSettleWithoutFrame \u629B\u9519\uFF08\u4E0D\u5F71\u54CD\u6536\u5C3E\uFF09\uFF1A${String(e)}`);
190255
+ }
190256
+ }
189539
190257
  }
189540
190258
  /** 当前在途(未退出)会话数;传 runtimeKind 时收窄到单个 runtime 实例。 */
189541
190259
  activeRunCount(nodeId, runtimeKind) {
@@ -189628,11 +190346,6 @@ var init_daemon_adapter = __esm({
189628
190346
  clearTimeout(timer);
189629
190347
  this.sessionReapTimers.delete(dispatchId);
189630
190348
  }
189631
- const entry = this.pending.get(dispatchId);
189632
- if (!entry || entry.exited) {
189633
- this.log(`[dispatch-delivery] ${dispatchId}\uFF1A\u4F1A\u8BDD\u8FDE\u4E0A\u6765\u4E86\uFF0C\u4F46\u670D\u52A1\u7AEF\u5DF2\u4E0D\u8BA4\u5B83\uFF08\u5DF2\u6536\u5272/\u5DF2\u91CD\u6D3E\uFF09\u2192 evict`);
189634
- this.hub.evictSession?.(dispatchId, entry ? "reaped" : "unknown");
189635
- }
189636
190349
  }
189637
190350
  /**
189638
190351
  * 会话连接断开(D2′.4):**断联即该会话失联**,不再经由节点代为证明。
@@ -189678,7 +190391,7 @@ var init_daemon_adapter = __esm({
189678
190391
  if (entry) this.health?.recordReachable(entry.nodeId);
189679
190392
  } else if (msg.type === "session_exited") {
189680
190393
  const info = msg.info.reason || msg.info.code !== null ? msg.info : { ...msg.info, reason: "error" };
189681
- if (this.pending.has(msg.dispatchId)) this.settle(msg.dispatchId, info);
190394
+ if (this.pending.has(msg.dispatchId)) this.settle(msg.dispatchId, info, true);
189682
190395
  else if (this.stashEnabled) this.stashFrame(msg.dispatchId, { type: "exit", info });
189683
190396
  } else if (msg.type === "session_event") {
189684
190397
  this.confirmDelivery(msg.dispatchId, "started");
@@ -189702,7 +190415,7 @@ var init_daemon_adapter = __esm({
189702
190415
  async spawn(job) {
189703
190416
  const nodeId = job.binding?.nodeId;
189704
190417
  if (!nodeId) throw new Error("DaemonHubAdapter \u9700\u8981 job.binding.nodeId\uFF08\u8DEF\u7531\u9519\u8BEF\uFF09");
189705
- const dispatchId = job.dispatchId ?? `dispatch:${(0, import_node_crypto41.randomUUID)()}`;
190418
+ const dispatchId = job.dispatchId ?? `dispatch:${(0, import_node_crypto42.randomUUID)()}`;
189706
190419
  const entry = {
189707
190420
  nodeId,
189708
190421
  ...job.binding?.runtimeKind ? { runtimeKind: job.binding.runtimeKind } : {},
@@ -189859,7 +190572,7 @@ var init_daemon_adapter = __esm({
189859
190572
  }));
189860
190573
  }
189861
190574
  request(nodeId, buildFrame) {
189862
- const requestId = (0, import_node_crypto41.randomUUID)();
190575
+ const requestId = (0, import_node_crypto42.randomUUID)();
189863
190576
  const sent = this.hub.dispatch(nodeId, buildFrame(requestId));
189864
190577
  if (!sent) return Promise.resolve({ ok: false, code: "NODE_OFFLINE" });
189865
190578
  return new Promise((resolve10) => {
@@ -189967,13 +190680,13 @@ function classifyPage(page, lastPushedHash, serviceAccount) {
189967
190680
  if (sha(body) === lastPushedHash) return { kind: "echo" };
189968
190681
  return { kind: "human-edit", content: body, updatedBy: page.updatedBy };
189969
190682
  }
189970
- var import_node_crypto42, sha, enc3, dec, MirrorEngine, MapMirrorIdentities;
190683
+ var import_node_crypto43, sha, enc3, dec, MirrorEngine, MapMirrorIdentities;
189971
190684
  var init_engine = __esm({
189972
190685
  "../server/src/mirror/engine.ts"() {
189973
190686
  "use strict";
189974
- import_node_crypto42 = require("node:crypto");
190687
+ import_node_crypto43 = require("node:crypto");
189975
190688
  init_src2();
189976
- sha = (s2) => (0, import_node_crypto42.createHash)("sha256").update(s2, "utf8").digest("hex");
190689
+ sha = (s2) => (0, import_node_crypto43.createHash)("sha256").update(s2, "utf8").digest("hex");
189977
190690
  enc3 = (s2) => new TextEncoder().encode(s2);
189978
190691
  dec = (b2) => new TextDecoder().decode(b2);
189979
190692
  MirrorEngine = class {
@@ -190180,11 +190893,11 @@ function humanExitCause(exit) {
190180
190893
  const base = (exit.reason ? label[exit.reason] : void 0) ?? `\u4F1A\u8BDD\u5F02\u5E38\u7ED3\u675F\uFF08${exit.reason ?? "\u65E0\u9000\u51FA\u4FE1\u606F"}\uFF09`;
190181
190894
  return exit.errorMessage ? `${base}\uFF1A${exit.errorMessage.slice(0, 200)}` : base;
190182
190895
  }
190183
- var import_node_crypto43, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, AUTONOMOUS_RECOVERY_TOOLS, HIGH_RISK_COMMANDS, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_RECOVERY_TOOLS, CONVERSATIONAL_ESCALATION_TOOLS, CONVERSATIONAL_MANAGER_BODY, MACHINE_EXIT_CODES, CoordinatorWorker;
190896
+ var import_node_crypto44, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, AUTONOMOUS_RECOVERY_TOOLS, HIGH_RISK_COMMANDS, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_RECOVERY_TOOLS, CONVERSATIONAL_ESCALATION_TOOLS, CONVERSATIONAL_MANAGER_BODY, MACHINE_EXIT_CODES, CoordinatorWorker;
190184
190897
  var init_worker = __esm({
190185
190898
  "../server/src/coordinator/worker.ts"() {
190186
190899
  "use strict";
190187
- import_node_crypto43 = require("node:crypto");
190900
+ import_node_crypto44 = require("node:crypto");
190188
190901
  init_src5();
190189
190902
  init_identity();
190190
190903
  AUTONOMOUS_ETHOS = `\u4F60\u662F\u8FD9\u4E2A\u5DE5\u5355\u7684**\u7BA1\u7406\u8005**\u2014\u2014\u804C\u8D23\u662F\u8BA9\u5B83\u987A\u7545\u8DD1\u5B8C\u3002\u7CFB\u7EDF\u5728\u67D0\u4E2A\u8282\u70B9\u5361\u4F4F\u3001\u673A\u68B0\u5206\u8BCA\u786E\u8BA4"\u9700\u8981\u4F60"\u65F6\u5524\u8D77\u4F60\uFF08\u65E0\u4EBA\u5728\u573A\uFF0C\u4F60\u8FD9\u4E00\u8F6E\u628A\u80FD\u505A\u7684\u505A\u6389\uFF09\u3002\u4F60\u7684\u624B\u6BB5\u5F88\u5BBD\uFF1A
@@ -190431,7 +191144,7 @@ ${HIGH_RISK_COMMANDS}`;
190431
191144
  return this.deps.kernel.model.lastSeq.get(artifactId) ?? 0;
190432
191145
  }
190433
191146
  evaluationKey(kind, artifactId, evidence) {
190434
- const fingerprint = (0, import_node_crypto43.createHash)("sha256").update(JSON.stringify(evidence)).digest("hex");
191147
+ const fingerprint = (0, import_node_crypto44.createHash)("sha256").update(JSON.stringify(evidence)).digest("hex");
190435
191148
  return `${kind}:${artifactId}:${fingerprint}`;
190436
191149
  }
190437
191150
  artifactEvidence(id) {
@@ -190808,8 +191521,8 @@ ${ctx.nodeFault}
190808
191521
  this.deps.log?.(`[coordinator] ${workspace} \u5168\u5C40\u4E0A\u4E0B\u6587\u8BFB\u53D6\u5931\u8D25\uFF0C\u7EE7\u7EED\u6CBF\u7528\u539F\u534F\u8C03\u8005\u4EFB\u52A1\uFF1A${String(error2)}`);
190809
191522
  }
190810
191523
  }
190811
- const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto43.randomUUID)();
190812
- const coordinatorRunId = `coordinate:${(0, import_node_crypto43.randomUUID)()}`;
191524
+ const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto44.randomUUID)();
191525
+ const coordinatorRunId = `coordinate:${(0, import_node_crypto44.randomUUID)()}`;
190813
191526
  const taskWithContext = coordinatorContext ? [
190814
191527
  task,
190815
191528
  ``,
@@ -190850,7 +191563,7 @@ ${ctx.nodeFault}
190850
191563
  const startedAtMs = Date.now();
190851
191564
  let trajectoryStarted = false;
190852
191565
  if (this.deps.trajectory) {
190853
- const bundleManifest = Object.fromEntries(Object.entries(job.bundle.files).map(([name, content]) => [name, (0, import_node_crypto43.createHash)("sha256").update(content).digest("hex")]));
191566
+ const bundleManifest = Object.fromEntries(Object.entries(job.bundle.files).map(([name, content]) => [name, (0, import_node_crypto44.createHash)("sha256").update(content).digest("hex")]));
190854
191567
  const session = {
190855
191568
  runId: coordinatorRunId,
190856
191569
  runtimeSessionId,
@@ -191216,6 +191929,7 @@ var init_src10 = __esm({
191216
191929
  init_recovery_plan();
191217
191930
  init_live_chat();
191218
191931
  init_chat_recovery();
191932
+ init_chat_turn_gate();
191219
191933
  init_crypto2();
191220
191934
  init_new_engine();
191221
191935
  init_daemon_hub();
@@ -191246,6 +191960,7 @@ var init_src10 = __esm({
191246
191960
  init_execution_continuity2();
191247
191961
  init_chat_sessions();
191248
191962
  init_dev_store();
191963
+ init_continuation();
191249
191964
  init_knowledge2();
191250
191965
  init_automations();
191251
191966
  init_automations();
@@ -191273,11 +191988,11 @@ var init_src10 = __esm({
191273
191988
  });
191274
191989
 
191275
191990
  // ../storage/src/postgres.ts
191276
- var import_node_crypto44, ident3, isUniqueViolation, PostgresOplogStore, PostgresBlobStore;
191991
+ var import_node_crypto45, ident3, isUniqueViolation, PostgresOplogStore, PostgresBlobStore;
191277
191992
  var init_postgres = __esm({
191278
191993
  "../storage/src/postgres.ts"() {
191279
191994
  "use strict";
191280
- import_node_crypto44 = require("node:crypto");
191995
+ import_node_crypto45 = require("node:crypto");
191281
191996
  init_esm();
191282
191997
  init_src2();
191283
191998
  ident3 = (s2) => {
@@ -191456,7 +192171,7 @@ var init_postgres = __esm({
191456
192171
  return new _PostgresBlobStore(pool, schema);
191457
192172
  }
191458
192173
  async put(bytes) {
191459
- const hash = (0, import_node_crypto44.createHash)("sha256").update(bytes).digest("hex");
192174
+ const hash = (0, import_node_crypto45.createHash)("sha256").update(bytes).digest("hex");
191460
192175
  await this.pool.query(
191461
192176
  `INSERT INTO ${this.t} (hash, bytes, size, content_type) VALUES ($1, $2, $3, $4) ON CONFLICT (hash) DO NOTHING`,
191462
192177
  [hash, Buffer.from(bytes), bytes.byteLength, sniffContentType(bytes) ?? null]
@@ -195724,6 +196439,144 @@ var init_postgres_trace = __esm({
195724
196439
  }
195725
196440
  });
195726
196441
 
196442
+ // ../storage/src/postgres-chat-turns.ts
196443
+ async function ensureChatTurnsTable(pool, schema = "public") {
196444
+ const s2 = ident10(schema);
196445
+ await pool.query(`CREATE SCHEMA IF NOT EXISTS "${s2}"`);
196446
+ await pool.query(`
196447
+ CREATE TABLE IF NOT EXISTS "${s2}".chat_session_turns (
196448
+ id text PRIMARY KEY,
196449
+ chat_session_id text NOT NULL,
196450
+ source text NOT NULL,
196451
+ source_run_id text NOT NULL,
196452
+ message_key text NOT NULL,
196453
+ status text NOT NULL,
196454
+ dispatch_id text,
196455
+ assistant_run_id text,
196456
+ reserved_at timestamptz NOT NULL,
196457
+ started_at timestamptz,
196458
+ completed_at timestamptz,
196459
+ last_error text
196460
+ )`);
196461
+ await pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS chat_session_turns_source_uq ON "${s2}".chat_session_turns (source, source_run_id)`);
196462
+ await pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS chat_session_turns_msgkey_uq ON "${s2}".chat_session_turns (message_key)`);
196463
+ await pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS chat_session_turns_active_uq ON "${s2}".chat_session_turns (chat_session_id) WHERE status IN ('reserved','running')`);
196464
+ await pool.query(`CREATE INDEX IF NOT EXISTS chat_session_turns_stale_idx ON "${s2}".chat_session_turns (reserved_at) WHERE status IN ('reserved','running')`);
196465
+ }
196466
+ var ident10, PostgresChatTurnStore, isUniqueViolation2, iso3, rowToTurn;
196467
+ var init_postgres_chat_turns = __esm({
196468
+ "../storage/src/postgres-chat-turns.ts"() {
196469
+ "use strict";
196470
+ init_esm();
196471
+ ident10 = (s2) => {
196472
+ if (!/^[a-z_][a-z0-9_]*$/.test(s2)) throw new Error(`invalid schema name: ${s2}`);
196473
+ return s2;
196474
+ };
196475
+ PostgresChatTurnStore = class _PostgresChatTurnStore {
196476
+ constructor(pool, schema) {
196477
+ this.pool = pool;
196478
+ this.s = `"${ident10(schema)}"`;
196479
+ }
196480
+ s;
196481
+ static async open(pool, schema = "public") {
196482
+ await ensureChatTurnsTable(pool, schema);
196483
+ return new _PostgresChatTurnStore(pool, schema);
196484
+ }
196485
+ async claimTurn(input) {
196486
+ try {
196487
+ const r = await this.pool.query(
196488
+ `INSERT INTO ${this.s}.chat_session_turns
196489
+ (id, chat_session_id, source, source_run_id, message_key, status, reserved_at)
196490
+ VALUES ($1,$2,$3,$4,$5,'reserved',$6)
196491
+ RETURNING *`,
196492
+ [input.turnId, input.chatSessionId, input.source, input.sourceRunId, input.messageKey, input.reservedAt]
196493
+ );
196494
+ return { outcome: "claimed", turn: rowToTurn(r.rows[0]) };
196495
+ } catch (err) {
196496
+ if (!isUniqueViolation2(err)) throw err;
196497
+ const mine = await this.getTurnBySourceRun(input.source, input.sourceRunId);
196498
+ if (mine) return { outcome: "resumed", turn: mine };
196499
+ const active = await this.activeTurn(input.chatSessionId);
196500
+ if (active) return { outcome: "busy", activeTurn: active };
196501
+ throw err;
196502
+ }
196503
+ }
196504
+ async getTurn(turnId) {
196505
+ const r = await this.pool.query(`SELECT * FROM ${this.s}.chat_session_turns WHERE id = $1`, [turnId]);
196506
+ return r.rows[0] ? rowToTurn(r.rows[0]) : null;
196507
+ }
196508
+ async getTurnBySourceRun(source, sourceRunId) {
196509
+ const r = await this.pool.query(
196510
+ `SELECT * FROM ${this.s}.chat_session_turns WHERE source = $1 AND source_run_id = $2`,
196511
+ [source, sourceRunId]
196512
+ );
196513
+ return r.rows[0] ? rowToTurn(r.rows[0]) : null;
196514
+ }
196515
+ async activeTurn(chatSessionId) {
196516
+ const r = await this.pool.query(
196517
+ `SELECT * FROM ${this.s}.chat_session_turns
196518
+ WHERE chat_session_id = $1 AND status IN ('reserved','running') LIMIT 1`,
196519
+ [chatSessionId]
196520
+ );
196521
+ return r.rows[0] ? rowToTurn(r.rows[0]) : null;
196522
+ }
196523
+ async settleTurn(turnId, patch) {
196524
+ const sets = [];
196525
+ const args = [];
196526
+ args.push(patch.status);
196527
+ sets.push(`status = $${args.length}`);
196528
+ if (patch.dispatchId !== void 0) {
196529
+ args.push(patch.dispatchId);
196530
+ sets.push(`dispatch_id = $${args.length}`);
196531
+ }
196532
+ if (patch.assistantRunId !== void 0) {
196533
+ args.push(patch.assistantRunId);
196534
+ sets.push(`assistant_run_id = $${args.length}`);
196535
+ }
196536
+ if (patch.startedAt !== void 0) {
196537
+ args.push(patch.startedAt);
196538
+ sets.push(`started_at = $${args.length}`);
196539
+ }
196540
+ if (patch.completedAt !== void 0) {
196541
+ args.push(patch.completedAt);
196542
+ sets.push(`completed_at = $${args.length}`);
196543
+ }
196544
+ if (patch.lastError !== void 0) {
196545
+ args.push(patch.lastError);
196546
+ sets.push(`last_error = $${args.length}`);
196547
+ }
196548
+ args.push(turnId);
196549
+ await this.pool.query(`UPDATE ${this.s}.chat_session_turns SET ${sets.join(", ")} WHERE id = $${args.length}`, args);
196550
+ }
196551
+ async listStaleActiveTurns(reservedBefore, limit = 50) {
196552
+ const r = await this.pool.query(
196553
+ `SELECT * FROM ${this.s}.chat_session_turns
196554
+ WHERE status IN ('reserved','running') AND reserved_at < $1
196555
+ ORDER BY reserved_at ASC LIMIT $2`,
196556
+ [reservedBefore, limit]
196557
+ );
196558
+ return r.rows.map(rowToTurn);
196559
+ }
196560
+ };
196561
+ isUniqueViolation2 = (err) => Boolean(err && typeof err === "object" && err.code === "23505");
196562
+ iso3 = (v2) => v2 != null ? new Date(v2).toISOString() : null;
196563
+ rowToTurn = (row) => ({
196564
+ id: row.id,
196565
+ chatSessionId: row.chat_session_id,
196566
+ source: row.source,
196567
+ sourceRunId: row.source_run_id,
196568
+ messageKey: row.message_key,
196569
+ status: row.status,
196570
+ dispatchId: row.dispatch_id ?? null,
196571
+ assistantRunId: row.assistant_run_id ?? null,
196572
+ reservedAt: iso3(row.reserved_at),
196573
+ startedAt: iso3(row.started_at),
196574
+ completedAt: iso3(row.completed_at),
196575
+ lastError: row.last_error ?? null
196576
+ });
196577
+ }
196578
+ });
196579
+
195727
196580
  // ../storage/src/postgres-automations.ts
195728
196581
  function storedEventConfig(t) {
195729
196582
  return {
@@ -195767,26 +196620,27 @@ function genWebhookToken2() {
195767
196620
  const b64 = btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
195768
196621
  return `owt_${b64}`;
195769
196622
  }
195770
- var ident10, genId, iso3, PostgresAutomationStore, rowToAutomation, rowToTrigger, rowToRun2, rowToDelivery;
196623
+ var ident11, genId, iso4, PostgresAutomationStore, chatTurnIso, rowToChatTurn, rowToAutomation, normalizeStoredChatTarget, rowToTrigger, rowToRun2, rowToDelivery;
195771
196624
  var init_postgres_automations = __esm({
195772
196625
  "../storage/src/postgres-automations.ts"() {
195773
196626
  "use strict";
195774
196627
  init_esm();
196628
+ init_postgres_chat_turns();
195775
196629
  init_src2();
195776
- ident10 = (s2) => {
196630
+ ident11 = (s2) => {
195777
196631
  if (!/^[a-z_][a-z0-9_]*$/.test(s2)) throw new Error(`invalid schema name: ${s2}`);
195778
196632
  return s2;
195779
196633
  };
195780
196634
  genId = (prefix) => `${prefix}_${Math.random().toString(36).slice(2, 12)}`;
195781
- iso3 = (v2) => v2 != null ? new Date(v2).toISOString() : null;
196635
+ iso4 = (v2) => v2 != null ? new Date(v2).toISOString() : null;
195782
196636
  PostgresAutomationStore = class _PostgresAutomationStore {
195783
196637
  constructor(pool, schema) {
195784
196638
  this.pool = pool;
195785
- this.s = `"${ident10(schema)}"`;
196639
+ this.s = `"${ident11(schema)}"`;
195786
196640
  }
195787
196641
  s;
195788
196642
  static async open(pool, schema = "public") {
195789
- const s2 = ident10(schema);
196643
+ const s2 = ident11(schema);
195790
196644
  await pool.query(`CREATE SCHEMA IF NOT EXISTS "${s2}"`);
195791
196645
  await pool.query(`
195792
196646
  CREATE TABLE IF NOT EXISTS "${s2}".automations (
@@ -195811,7 +196665,11 @@ var init_postgres_automations = __esm({
195811
196665
  `ADD COLUMN IF NOT EXISTS pause_reason text`,
195812
196666
  `ADD COLUMN IF NOT EXISTS archived_at text`,
195813
196667
  `ADD COLUMN IF NOT EXISTS subscribers jsonb NOT NULL DEFAULT '[]'::jsonb`,
195814
- `ADD COLUMN IF NOT EXISTS collaborators jsonb NOT NULL DEFAULT '[]'::jsonb`
196668
+ `ADD COLUMN IF NOT EXISTS collaborators jsonb NOT NULL DEFAULT '[]'::jsonb`,
196669
+ // ADR 会话投递 D1:chat 模式的目标会话策略。NULL = 每次新开会话(存量零变化)。
196670
+ `ADD COLUMN IF NOT EXISTS chat_target jsonb`,
196671
+ // ADR 会话投递 D3:固定会话的人类归属者(进规则快照,不随触发漂移)。
196672
+ `ADD COLUMN IF NOT EXISTS owner_human_actor_id text`
195815
196673
  ]) {
195816
196674
  await pool.query(`ALTER TABLE "${s2}".automations ${ddl}`);
195817
196675
  }
@@ -195902,6 +196760,7 @@ var init_postgres_automations = __esm({
195902
196760
  created_at text NOT NULL
195903
196761
  )`);
195904
196762
  await pool.query(`CREATE INDEX IF NOT EXISTS automation_rule_versions_idx ON "${s2}".automation_rule_versions (automation_id, created_at DESC)`);
196763
+ await ensureChatTurnsTable(pool, schema);
195905
196764
  return new _PostgresAutomationStore(pool, schema);
195906
196765
  }
195907
196766
  now() {
@@ -195930,13 +196789,15 @@ var init_postgres_automations = __esm({
195930
196789
  collaborators: [],
195931
196790
  createdBy: input.createdBy ?? null,
195932
196791
  createdAt: now,
195933
- updatedAt: now
196792
+ updatedAt: now,
196793
+ chatTarget: input.chatTarget ?? null,
196794
+ ownerHumanActorId: input.ownerHumanActorId ?? null
195934
196795
  };
195935
196796
  await this.pool.query(
195936
196797
  `INSERT INTO ${this.s}.automations
195937
196798
  (id, company_id, name, description, execution_mode, agent_id, project_id, playbook_ref, payload, title_template, dispatch,
195938
- enabled, pause_reason, archived_at, subscribers, collaborators, created_by, created_at, updated_at)
195939
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10,$11,$12,$13,$14,$15::jsonb,$16::jsonb,$17,$18,$19)`,
196799
+ enabled, pause_reason, archived_at, subscribers, collaborators, created_by, created_at, updated_at, chat_target, owner_human_actor_id)
196800
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10,$11,$12,$13,$14,$15::jsonb,$16::jsonb,$17,$18,$19,$20::jsonb,$21)`,
195940
196801
  [
195941
196802
  a.id,
195942
196803
  a.companyId,
@@ -195956,7 +196817,9 @@ var init_postgres_automations = __esm({
195956
196817
  JSON.stringify(a.collaborators),
195957
196818
  a.createdBy,
195958
196819
  a.createdAt,
195959
- a.updatedAt
196820
+ a.updatedAt,
196821
+ a.chatTarget !== null ? JSON.stringify(a.chatTarget) : null,
196822
+ a.ownerHumanActorId
195960
196823
  ]
195961
196824
  );
195962
196825
  if (input.triggers?.length) await this.replaceTriggers(id, input.triggers);
@@ -195989,7 +196852,9 @@ var init_postgres_automations = __esm({
195989
196852
  pauseReason: { col: "pause_reason" },
195990
196853
  archivedAt: { col: "archived_at" },
195991
196854
  subscribers: { col: "subscribers", json: true },
195992
- collaborators: { col: "collaborators", json: true }
196855
+ collaborators: { col: "collaborators", json: true },
196856
+ chatTarget: { col: "chat_target", json: true },
196857
+ ownerHumanActorId: { col: "owner_human_actor_id" }
195993
196858
  };
195994
196859
  async updateAutomation(id, patch) {
195995
196860
  const sets = [];
@@ -196377,7 +197242,7 @@ var init_postgres_automations = __esm({
196377
197242
  );
196378
197243
  return r.rows.map((row) => ({
196379
197244
  trigger: rowToTrigger(row),
196380
- plannedAt: iso3(row.planned_at_old)
197245
+ plannedAt: iso4(row.planned_at_old)
196381
197246
  }));
196382
197247
  }
196383
197248
  async rescheduleTrigger(triggerId, nextRunAt, lastFiredAt) {
@@ -196433,7 +197298,83 @@ var init_postgres_automations = __esm({
196433
197298
  );
196434
197299
  return r.rows[0] ? rowToRun2(r.rows[0]) : null;
196435
197300
  }
197301
+ /**
197302
+ * 复合占位(ADR 会话投递 D5 第 4 步)。一个事务、三种出口:
197303
+ * - INSERT 成功 → `claimed`,调用方才可以开始产生副作用;
197304
+ * - 撞 `(source, source_run_id)` → `resumed`,同一触发重来,取回同一轮(**不是** busy,也不另落 run);
197305
+ * - 撞每会话 active 唯一索引 → `busy`:**在同一事务里**把这条 automation run 落
197306
+ * `skipped/session_busy/completedAt`,然后提交。两件事一起成立或一起不成立,中间没有窗口。
197307
+ *
197308
+ * 触发幂等键(`planned_at` / `idempotency_key`)**照旧保留在行上不清空**——清了就等于允许
197309
+ * 下一次重扫补跑这次已经明确丢弃的触发。
197310
+ */
197311
+ async claimChatTurnForRun(input) {
197312
+ const client = await this.pool.connect();
197313
+ try {
197314
+ await client.query("BEGIN");
197315
+ try {
197316
+ const r = await client.query(
197317
+ `INSERT INTO ${this.s}.chat_session_turns
197318
+ (id, chat_session_id, source, source_run_id, message_key, status, reserved_at)
197319
+ VALUES ($1,$2,$3,$4,$5,'reserved',$6)
197320
+ RETURNING *`,
197321
+ [input.turnId, input.chatSessionId, input.source, input.sourceRunId, input.messageKey, input.reservedAt]
197322
+ );
197323
+ await client.query("COMMIT");
197324
+ return { outcome: "claimed", turn: rowToChatTurn(r.rows[0]) };
197325
+ } catch (err) {
197326
+ if (!isUniqueViolation2(err)) throw err;
197327
+ await client.query("ROLLBACK");
197328
+ await client.query("BEGIN");
197329
+ const mine = await client.query(
197330
+ `SELECT * FROM ${this.s}.chat_session_turns WHERE source = $1 AND source_run_id = $2`,
197331
+ [input.source, input.sourceRunId]
197332
+ );
197333
+ if (mine.rows[0]) {
197334
+ await client.query("COMMIT");
197335
+ return { outcome: "resumed", turn: rowToChatTurn(mine.rows[0]) };
197336
+ }
197337
+ const active = await client.query(
197338
+ `SELECT * FROM ${this.s}.chat_session_turns
197339
+ WHERE chat_session_id = $1 AND status IN ('reserved','running') LIMIT 1`,
197340
+ [input.chatSessionId]
197341
+ );
197342
+ if (!active.rows[0]) {
197343
+ await client.query("ROLLBACK");
197344
+ throw err;
197345
+ }
197346
+ await client.query(
197347
+ `UPDATE ${this.s}.automation_runs
197348
+ SET status = 'skipped', reason_code = 'session_busy', error = $2, completed_at = $3
197349
+ WHERE id = $1`,
197350
+ [input.automationRunId, input.busyError, input.now]
197351
+ );
197352
+ await client.query("COMMIT");
197353
+ return { outcome: "busy", activeTurn: rowToChatTurn(active.rows[0]) };
197354
+ }
197355
+ } catch (e) {
197356
+ await client.query("ROLLBACK").catch(() => void 0);
197357
+ throw e;
197358
+ } finally {
197359
+ client.release();
197360
+ }
197361
+ }
196436
197362
  };
197363
+ chatTurnIso = (v2) => v2 != null ? new Date(v2).toISOString() : null;
197364
+ rowToChatTurn = (row) => ({
197365
+ id: row.id,
197366
+ chatSessionId: row.chat_session_id,
197367
+ source: row.source,
197368
+ sourceRunId: row.source_run_id,
197369
+ messageKey: row.message_key,
197370
+ status: row.status,
197371
+ dispatchId: row.dispatch_id ?? null,
197372
+ assistantRunId: row.assistant_run_id ?? null,
197373
+ reservedAt: chatTurnIso(row.reserved_at),
197374
+ startedAt: chatTurnIso(row.started_at),
197375
+ completedAt: chatTurnIso(row.completed_at),
197376
+ lastError: row.last_error ?? null
197377
+ });
196437
197378
  rowToAutomation = (row) => ({
196438
197379
  id: row.id,
196439
197380
  companyId: row.company_id ?? "",
@@ -196453,8 +197394,19 @@ var init_postgres_automations = __esm({
196453
197394
  collaborators: row.collaborators ?? [],
196454
197395
  createdBy: row.created_by ?? null,
196455
197396
  createdAt: row.created_at,
196456
- updatedAt: row.updated_at
197397
+ updatedAt: row.updated_at,
197398
+ // NULL / 脏值一律读成「每次新开会话」——发布前后行为一致(ADR 会话投递 §6)。
197399
+ chatTarget: normalizeStoredChatTarget(row.chat_target),
197400
+ ownerHumanActorId: row.owner_human_actor_id ?? null
196457
197401
  });
197402
+ normalizeStoredChatTarget = (raw) => {
197403
+ const t = raw;
197404
+ if (t?.mode === "existing_session" && typeof t.sessionId === "string" && t.sessionId) {
197405
+ return { mode: "existing_session", sessionId: t.sessionId };
197406
+ }
197407
+ if (t?.mode === "new_session") return { mode: "new_session" };
197408
+ return null;
197409
+ };
196458
197410
  rowToTrigger = (row) => {
196459
197411
  const t = {
196460
197412
  id: row.id,
@@ -196468,9 +197420,9 @@ var init_postgres_automations = __esm({
196468
197420
  t.schedule = {
196469
197421
  cron: row.cron ?? "",
196470
197422
  tz: row.tz ?? "UTC",
196471
- nextRunAt: iso3(row.next_run_at),
197423
+ nextRunAt: iso4(row.next_run_at),
196472
197424
  lastFiredAt: row.last_fired_at ?? null,
196473
- claimedAt: iso3(row.claimed_at)
197425
+ claimedAt: iso4(row.claimed_at)
196474
197426
  };
196475
197427
  }
196476
197428
  if (t.kind === "webhook") {
@@ -196500,7 +197452,7 @@ var init_postgres_automations = __esm({
196500
197452
  status: row.status,
196501
197453
  spawnedWorkspaceId: row.spawned_workspace_id ?? null,
196502
197454
  spawnedArtifactId: row.spawned_artifact_id ?? null,
196503
- plannedAt: iso3(row.planned_at),
197455
+ plannedAt: iso4(row.planned_at),
196504
197456
  deliveryId: row.delivery_id ?? null,
196505
197457
  idempotencyKey: row.idempotency_key ?? null,
196506
197458
  ruleVersionId: row.rule_version_id ?? null,
@@ -196532,24 +197484,24 @@ var init_postgres_automations = __esm({
196532
197484
  });
196533
197485
 
196534
197486
  // ../storage/src/postgres-chat-sessions.ts
196535
- var ident11, PostgresChatSessionStore, rowToSession, rowToSessionWithQuality, rowToMessage;
197487
+ var ident12, PostgresChatSessionStore, rowToSession, rowToSessionWithQuality, rowToMessage;
196536
197488
  var init_postgres_chat_sessions = __esm({
196537
197489
  "../storage/src/postgres-chat-sessions.ts"() {
196538
197490
  "use strict";
196539
197491
  init_esm();
196540
197492
  init_chat_session();
196541
- ident11 = (s2) => {
197493
+ ident12 = (s2) => {
196542
197494
  if (!/^[a-z_][a-z0-9_]*$/.test(s2)) throw new Error(`invalid schema name: ${s2}`);
196543
197495
  return s2;
196544
197496
  };
196545
197497
  PostgresChatSessionStore = class _PostgresChatSessionStore {
196546
197498
  constructor(pool, schema) {
196547
197499
  this.pool = pool;
196548
- this.s = `"${ident11(schema)}"`;
197500
+ this.s = `"${ident12(schema)}"`;
196549
197501
  }
196550
197502
  s;
196551
197503
  static async open(pool, schema = "public") {
196552
- const s2 = ident11(schema);
197504
+ const s2 = ident12(schema);
196553
197505
  await pool.query(`CREATE SCHEMA IF NOT EXISTS "${s2}"`);
196554
197506
  await pool.query(`
196555
197507
  CREATE TABLE IF NOT EXISTS "${s2}".chat_sessions (
@@ -196592,6 +197544,10 @@ var init_postgres_chat_sessions = __esm({
196592
197544
  )
196593
197545
  UPDATE "${s2}".chat_messages m SET seq = ranked.rn FROM ranked WHERE m.id = ranked.id AND m.seq <> ranked.rn`);
196594
197546
  await pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS chat_messages_session_seq_uq ON "${s2}".chat_messages (session_id, seq)`);
197547
+ await pool.query(`ALTER TABLE "${s2}".chat_sessions ADD COLUMN IF NOT EXISTS company_id text`);
197548
+ await pool.query(`CREATE INDEX IF NOT EXISTS chat_sessions_target_idx ON "${s2}".chat_sessions (company_id, human_actor_id, ai_actor_id, touched_at DESC)`);
197549
+ await pool.query(`ALTER TABLE "${s2}".chat_messages ADD COLUMN IF NOT EXISTS message_key text`);
197550
+ await pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS chat_messages_key_uq ON "${s2}".chat_messages (message_key) WHERE message_key IS NOT NULL`);
196595
197551
  await pool.query(`
196596
197552
  CREATE TABLE IF NOT EXISTS "${s2}".chat_session_work_orders (
196597
197553
  session_id text NOT NULL,
@@ -196603,9 +197559,9 @@ var init_postgres_chat_sessions = __esm({
196603
197559
  }
196604
197560
  async createSession(s2) {
196605
197561
  await this.pool.query(
196606
- `INSERT INTO ${this.s}.chat_sessions (id, human_actor_id, ai_actor_id, runtime_id, runtime_kind, runtime_session_id, title, touched_at, created_at, workspace, analyzed_run_id, project_id, escalation_id)
196607
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)`,
196608
- [s2.id, s2.humanActorId, s2.aiActorId, s2.runtimeId, s2.runtimeKind ?? null, s2.runtimeSessionId ?? null, s2.title ?? null, s2.touchedAt, s2.createdAt, s2.workspace ?? null, s2.analyzedRunId ?? null, s2.projectId ?? null, s2.escalationId ?? null]
197562
+ `INSERT INTO ${this.s}.chat_sessions (id, human_actor_id, ai_actor_id, runtime_id, runtime_kind, runtime_session_id, title, touched_at, created_at, workspace, analyzed_run_id, project_id, escalation_id, company_id)
197563
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)`,
197564
+ [s2.id, s2.humanActorId, s2.aiActorId, s2.runtimeId, s2.runtimeKind ?? null, s2.runtimeSessionId ?? null, s2.title ?? null, s2.touchedAt, s2.createdAt, s2.workspace ?? null, s2.analyzedRunId ?? null, s2.projectId ?? null, s2.escalationId ?? null, s2.companyId ?? null]
196609
197565
  );
196610
197566
  }
196611
197567
  async listSessions(humanActorId) {
@@ -196718,11 +197674,11 @@ var init_postgres_chat_sessions = __esm({
196718
197674
  async appendMessage(m2) {
196719
197675
  const insert = async () => {
196720
197676
  const r = await this.pool.query(
196721
- `INSERT INTO ${this.s}.chat_messages (id, session_id, role, content, seq, created_at, run_id, parts, attachments, status, completed_at)
196722
- SELECT $1,$2,$3,$4, COALESCE(MAX(seq),0)+1, $5,$6,$7,$8,$9,$10
197677
+ `INSERT INTO ${this.s}.chat_messages (id, session_id, role, content, seq, created_at, run_id, parts, attachments, status, completed_at, message_key)
197678
+ SELECT $1,$2,$3,$4, COALESCE(MAX(seq),0)+1, $5,$6,$7,$8,$9,$10,$11
196723
197679
  FROM ${this.s}.chat_messages WHERE session_id = $2
196724
197680
  RETURNING seq`,
196725
- [m2.id, m2.sessionId, m2.role, m2.content, m2.createdAt, m2.runId ?? null, m2.parts !== void 0 ? JSON.stringify(m2.parts) : null, m2.attachments !== void 0 ? JSON.stringify(m2.attachments) : null, m2.status ?? null, m2.completedAt ?? null]
197681
+ [m2.id, m2.sessionId, m2.role, m2.content, m2.createdAt, m2.runId ?? null, m2.parts !== void 0 ? JSON.stringify(m2.parts) : null, m2.attachments !== void 0 ? JSON.stringify(m2.attachments) : null, m2.status ?? null, m2.completedAt ?? null, m2.messageKey ?? null]
196726
197682
  );
196727
197683
  return Number(r.rows[0].seq);
196728
197684
  };
@@ -196730,12 +197686,87 @@ var init_postgres_chat_sessions = __esm({
196730
197686
  try {
196731
197687
  return { ...m2, seq: await insert() };
196732
197688
  } catch (err) {
196733
- const isUniqueViolation2 = err && typeof err === "object" && err.code === "23505";
196734
- if (isUniqueViolation2 && attempt < 10) continue;
197689
+ const isUniqueViolation3 = err && typeof err === "object" && err.code === "23505";
197690
+ if (isUniqueViolation3 && m2.messageKey && String(err.constraint ?? "").includes("chat_messages_key_uq")) {
197691
+ const existing = await this.getMessageByKey(m2.messageKey);
197692
+ if (existing) return existing;
197693
+ }
197694
+ if (isUniqueViolation3 && attempt < 10) continue;
196735
197695
  throw err;
196736
197696
  }
196737
197697
  }
196738
197698
  }
197699
+ /** 按幂等键取回消息(崩溃重放路径)。 */
197700
+ async getMessageByKey(messageKey) {
197701
+ const r = await this.pool.query(`SELECT * FROM ${this.s}.chat_messages WHERE message_key = $1`, [messageKey]);
197702
+ return r.rows[0] ? rowToMessage(r.rows[0]) : null;
197703
+ }
197704
+ async countMessages(sessionId) {
197705
+ const r = await this.pool.query(`SELECT count(*)::int AS n FROM ${this.s}.chat_messages WHERE session_id = $1`, [sessionId]);
197706
+ return Number(r.rows[0]?.n ?? 0);
197707
+ }
197708
+ /**
197709
+ * 选择器搜索(ADR 会话投递 D8):`(company, human, ai)` 三元组精确圈定,标题 ILIKE **或** id 前缀命中。
197710
+ * 分析会话排除在外(它们不进主聊天列表,更不该当自动化的长期目标)。
197711
+ * 多取一条判 `hasMore`,避免再跑一次 count。
197712
+ */
197713
+ async searchSessionsForTarget(q) {
197714
+ const args = [q.companyId, q.humanActorId, q.aiActorId];
197715
+ let where = `s.company_id = $1 AND s.human_actor_id = $2 AND s.ai_actor_id = $3 AND s.analyzed_run_id IS NULL`;
197716
+ const text = q.text?.trim();
197717
+ if (text) {
197718
+ const literal2 = text.replace(/[%_\\]/g, (c) => `\\${c}`);
197719
+ args.push(`%${literal2}%`);
197720
+ args.push(`${literal2}%`);
197721
+ where += ` AND (s.title ILIKE $${args.length - 1} ESCAPE '\\' OR s.id LIKE $${args.length} ESCAPE '\\')`;
197722
+ }
197723
+ args.push(q.limit + 1, q.offset);
197724
+ const r = await this.pool.query(
197725
+ `SELECT s.*, (SELECT count(*)::int FROM ${this.s}.chat_messages m WHERE m.session_id = s.id) AS message_count
197726
+ FROM ${this.s}.chat_sessions s
197727
+ WHERE ${where}
197728
+ ORDER BY s.touched_at DESC, s.id DESC
197729
+ LIMIT $${args.length - 1} OFFSET $${args.length}`,
197730
+ args
197731
+ );
197732
+ const hasMore = r.rows.length > q.limit;
197733
+ const rows = hasMore ? r.rows.slice(0, q.limit) : r.rows;
197734
+ return {
197735
+ items: rows.map((row) => ({ ...rowToSession(row), messageCount: Number(row.message_count ?? 0) })),
197736
+ hasMore
197737
+ };
197738
+ }
197739
+ /**
197740
+ * 存量会话的公司回填(一次性、幂等、**只填能唯一判定的行**)。
197741
+ *
197742
+ * 判据只认一种:会话归属人在 `company_members` 里**只出现在一家公司**(agent 不是账号、
197743
+ * 不在这张表里,所以实际生效的是归属人这一条)。命中多家或一家都命不中 → 保持 NULL。
197744
+ *
197745
+ * 这不是保守过头——用「现在的成员关系」追认「过去的创建事实」,一个换过公司的人就能把旧会话
197746
+ * 认到新公司名下,而那是跨公司泄漏。判不出来的代价只是「不能被自动化选作固定目标」,
197747
+ * 用户新建一个长期会话即可。
197748
+ *
197749
+ * 返回真正回填的行数(稳态为 0)。表不存在(无控制面)时直接跳过。
197750
+ */
197751
+ async backfillCompanyIds() {
197752
+ const exists = await this.pool.query(
197753
+ `SELECT to_regclass($1) IS NOT NULL AS ok`,
197754
+ [`${this.s}.company_members`.replace(/"/g, "")]
197755
+ );
197756
+ if (!exists.rows[0]?.ok) return 0;
197757
+ const r = await this.pool.query(`
197758
+ WITH candidate AS (
197759
+ SELECT s.id, min(m.company_id) AS company_id, count(DISTINCT m.company_id) AS n
197760
+ FROM ${this.s}.chat_sessions s
197761
+ JOIN ${this.s}.company_members m ON m.actor_id IN (s.human_actor_id, s.ai_actor_id)
197762
+ WHERE s.company_id IS NULL
197763
+ GROUP BY s.id
197764
+ HAVING count(DISTINCT m.company_id) = 1
197765
+ )
197766
+ UPDATE ${this.s}.chat_sessions s SET company_id = c.company_id
197767
+ FROM candidate c WHERE s.id = c.id AND s.company_id IS NULL`);
197768
+ return r.rowCount ?? 0;
197769
+ }
196739
197770
  async updateMessage(id, patch) {
196740
197771
  const sets = [];
196741
197772
  const args = [];
@@ -196816,6 +197847,7 @@ var init_postgres_chat_sessions = __esm({
196816
197847
  };
196817
197848
  rowToSession = (row) => ({
196818
197849
  id: row.id,
197850
+ companyId: row.company_id ?? null,
196819
197851
  humanActorId: row.human_actor_id,
196820
197852
  aiActorId: row.ai_actor_id,
196821
197853
  runtimeId: row.runtime_id,
@@ -196847,30 +197879,33 @@ var init_postgres_chat_sessions = __esm({
196847
197879
  ...row.parts != null ? { parts: row.parts } : {},
196848
197880
  ...row.attachments != null ? { attachments: row.attachments } : {},
196849
197881
  ...row.status != null ? { status: row.status } : {},
196850
- ...row.completed_at != null ? { completedAt: new Date(row.completed_at).toISOString() } : {}
197882
+ ...row.completed_at != null ? { completedAt: new Date(row.completed_at).toISOString() } : {},
197883
+ // 幂等键必须**读得回来**:写进去却映射不出来,等于这一列只对唯一索引有效,对代码不可见。
197884
+ // 固定会话的续跑历史要按它把「本轮那条 user 消息」排掉,漏了它那条消息会既当历史又当正文。
197885
+ ...row.message_key != null ? { messageKey: row.message_key } : {}
196851
197886
  });
196852
197887
  }
196853
197888
  });
196854
197889
 
196855
197890
  // ../storage/src/postgres-nodes.ts
196856
- var import_node_crypto45, ident12, PostgresNodeStore, PostgresNodeTokenStore, rowToNode, rowToRuntime;
197891
+ var import_node_crypto46, ident13, PostgresNodeStore, PostgresNodeTokenStore, rowToNode, rowToRuntime;
196857
197892
  var init_postgres_nodes = __esm({
196858
197893
  "../storage/src/postgres-nodes.ts"() {
196859
197894
  "use strict";
196860
- import_node_crypto45 = require("node:crypto");
197895
+ import_node_crypto46 = require("node:crypto");
196861
197896
  init_esm();
196862
- ident12 = (s2) => {
197897
+ ident13 = (s2) => {
196863
197898
  if (!/^[a-z_][a-z0-9_]*$/.test(s2)) throw new Error(`invalid schema name: ${s2}`);
196864
197899
  return s2;
196865
197900
  };
196866
197901
  PostgresNodeStore = class _PostgresNodeStore {
196867
197902
  constructor(pool, schema) {
196868
197903
  this.pool = pool;
196869
- this.s = `"${ident12(schema)}"`;
197904
+ this.s = `"${ident13(schema)}"`;
196870
197905
  }
196871
197906
  s;
196872
197907
  static async open(pool, schema = "public") {
196873
- const s2 = ident12(schema);
197908
+ const s2 = ident13(schema);
196874
197909
  await pool.query(`CREATE SCHEMA IF NOT EXISTS "${s2}"`);
196875
197910
  await pool.query(`
196876
197911
  CREATE TABLE IF NOT EXISTS "${s2}".nodes (
@@ -196992,12 +198027,12 @@ var init_postgres_nodes = __esm({
196992
198027
  PostgresNodeTokenStore = class _PostgresNodeTokenStore {
196993
198028
  constructor(pool, schema) {
196994
198029
  this.pool = pool;
196995
- this.s = `"${ident12(schema)}"`;
198030
+ this.s = `"${ident13(schema)}"`;
196996
198031
  }
196997
198032
  s;
196998
198033
  cache = /* @__PURE__ */ new Map();
196999
198034
  static async open(pool, schema = "public") {
197000
- const s2 = ident12(schema);
198035
+ const s2 = ident13(schema);
197001
198036
  await pool.query(`CREATE SCHEMA IF NOT EXISTS "${s2}"`);
197002
198037
  await pool.query(`CREATE TABLE IF NOT EXISTS "${s2}".node_tokens (token text PRIMARY KEY, node_id text NOT NULL)`);
197003
198038
  const store = new _PostgresNodeTokenStore(pool, schema);
@@ -197006,7 +198041,7 @@ var init_postgres_nodes = __esm({
197006
198041
  return store;
197007
198042
  }
197008
198043
  issue(nodeId) {
197009
- const token = `ont_${(0, import_node_crypto45.randomBytes)(24).toString("base64url")}`;
198044
+ const token = `ont_${(0, import_node_crypto46.randomBytes)(24).toString("base64url")}`;
197010
198045
  this.cache.set(token, nodeId);
197011
198046
  void this.pool.query(`INSERT INTO ${this.s}.node_tokens (token,node_id) VALUES ($1,$2)`, [token, nodeId]);
197012
198047
  return token;
@@ -197058,23 +198093,23 @@ function rowToPrice(row) {
197058
198093
  updatedAt: new Date(row["updated_at"]).toISOString()
197059
198094
  };
197060
198095
  }
197061
- var ident13, PostgresModelPriceStore;
198096
+ var ident14, PostgresModelPriceStore;
197062
198097
  var init_postgres_model_prices = __esm({
197063
198098
  "../storage/src/postgres-model-prices.ts"() {
197064
198099
  "use strict";
197065
198100
  init_esm();
197066
- ident13 = (s2) => {
198101
+ ident14 = (s2) => {
197067
198102
  if (!/^[a-z_][a-z0-9_]*$/.test(s2)) throw new Error(`invalid schema name: ${s2}`);
197068
198103
  return s2;
197069
198104
  };
197070
198105
  PostgresModelPriceStore = class _PostgresModelPriceStore {
197071
198106
  constructor(pool, schema) {
197072
198107
  this.pool = pool;
197073
- this.s = `"${ident13(schema)}"`;
198108
+ this.s = `"${ident14(schema)}"`;
197074
198109
  }
197075
198110
  s;
197076
198111
  static async open(pool, schema = "public") {
197077
- const s2 = ident13(schema);
198112
+ const s2 = ident14(schema);
197078
198113
  await pool.query(`CREATE SCHEMA IF NOT EXISTS "${s2}"`);
197079
198114
  await pool.query(`
197080
198115
  CREATE TABLE IF NOT EXISTS "${s2}".model_prices (
@@ -197171,24 +198206,24 @@ async function backfillTypeRegistryFromFile(store, file) {
197171
198206
  console.log(`[type-registry-backfill] \u51B3\u7B56 0062 \u56DE\u586B\uFF1A${defs.length} \u4E2A\u4EA7\u7269\u7C7B\u578B\u5DF2\u8FC1\u5165 Postgres`);
197172
198207
  return { filled: defs.length };
197173
198208
  }
197174
- var import_node_fs18, ident14, PostgresTypeRegistryStore;
198209
+ var import_node_fs18, ident15, PostgresTypeRegistryStore;
197175
198210
  var init_postgres_type_registry = __esm({
197176
198211
  "../storage/src/postgres-type-registry.ts"() {
197177
198212
  "use strict";
197178
198213
  import_node_fs18 = __toESM(require("node:fs"), 1);
197179
198214
  init_esm();
197180
- ident14 = (s2) => {
198215
+ ident15 = (s2) => {
197181
198216
  if (!/^[a-z_][a-z0-9_]*$/.test(s2)) throw new Error(`invalid schema name: ${s2}`);
197182
198217
  return s2;
197183
198218
  };
197184
198219
  PostgresTypeRegistryStore = class _PostgresTypeRegistryStore {
197185
198220
  constructor(pool, schema) {
197186
198221
  this.pool = pool;
197187
- this.s = `"${ident14(schema)}"`;
198222
+ this.s = `"${ident15(schema)}"`;
197188
198223
  }
197189
198224
  s;
197190
198225
  static async open(pool, schema = "public") {
197191
- const s2 = ident14(schema);
198226
+ const s2 = ident15(schema);
197192
198227
  await pool.query(`CREATE SCHEMA IF NOT EXISTS "${s2}"`);
197193
198228
  await pool.query(`
197194
198229
  CREATE TABLE IF NOT EXISTS "${s2}".artifact_types (
@@ -197218,14 +198253,14 @@ var init_postgres_type_registry = __esm({
197218
198253
  });
197219
198254
 
197220
198255
  // ../storage/src/postgres-actor-memory.ts
197221
- var import_node_crypto46, matchClause, ident15, PostgresActorMemoryStore, rowToIndexEntry, rowToRecord;
198256
+ var import_node_crypto47, matchClause, ident16, PostgresActorMemoryStore, rowToIndexEntry, rowToRecord;
197222
198257
  var init_postgres_actor_memory = __esm({
197223
198258
  "../storage/src/postgres-actor-memory.ts"() {
197224
198259
  "use strict";
197225
- import_node_crypto46 = require("node:crypto");
198260
+ import_node_crypto47 = require("node:crypto");
197226
198261
  init_src2();
197227
198262
  matchClause = (q) => q.requireMatch && q.keywords.length > 0 ? " WHERE m > 0" : "";
197228
- ident15 = (s2) => {
198263
+ ident16 = (s2) => {
197229
198264
  if (!/^[a-z_][a-z0-9_]*$/.test(s2)) throw new Error(`invalid schema name: ${s2}`);
197230
198265
  return s2;
197231
198266
  };
@@ -197233,11 +198268,11 @@ var init_postgres_actor_memory = __esm({
197233
198268
  constructor(pool, schema, trigram) {
197234
198269
  this.pool = pool;
197235
198270
  this.trigram = trigram;
197236
- this.s = `"${ident15(schema)}"`;
198271
+ this.s = `"${ident16(schema)}"`;
197237
198272
  }
197238
198273
  s;
197239
198274
  static async open(pool, schema = "public") {
197240
- const s2 = ident15(schema);
198275
+ const s2 = ident16(schema);
197241
198276
  await pool.query(`CREATE SCHEMA IF NOT EXISTS "${s2}"`);
197242
198277
  await pool.query(`
197243
198278
  CREATE TABLE IF NOT EXISTS "${s2}".actor_memories (
@@ -197427,7 +198462,7 @@ var init_postgres_actor_memory = __esm({
197427
198462
  }
197428
198463
  async write(input, now) {
197429
198464
  if (input.memId === void 0) {
197430
- const memId = `mem:${(0, import_node_crypto46.randomUUID)()}`;
198465
+ const memId = `mem:${(0, import_node_crypto47.randomUUID)()}`;
197431
198466
  const r2 = await this.pool.query(
197432
198467
  `INSERT INTO ${this.s}.actor_memories
197433
198468
  (mem_id, actor_id, project_id, keywords, content, version, created_at, updated_at, accessed_at, source_artifact_id, source_session_id)
@@ -197521,23 +198556,23 @@ var init_postgres_actor_memory = __esm({
197521
198556
  });
197522
198557
 
197523
198558
  // ../storage/src/postgres-inbox-read.ts
197524
- var ident16, PostgresReadMarkerStore;
198559
+ var ident17, PostgresReadMarkerStore;
197525
198560
  var init_postgres_inbox_read = __esm({
197526
198561
  "../storage/src/postgres-inbox-read.ts"() {
197527
198562
  "use strict";
197528
198563
  init_esm();
197529
- ident16 = (s2) => {
198564
+ ident17 = (s2) => {
197530
198565
  if (!/^[a-z_][a-z0-9_]*$/.test(s2)) throw new Error(`invalid schema name: ${s2}`);
197531
198566
  return s2;
197532
198567
  };
197533
198568
  PostgresReadMarkerStore = class _PostgresReadMarkerStore {
197534
198569
  constructor(pool, schema) {
197535
198570
  this.pool = pool;
197536
- this.s = `"${ident16(schema)}"`;
198571
+ this.s = `"${ident17(schema)}"`;
197537
198572
  }
197538
198573
  s;
197539
198574
  static async open(pool, schema = "public") {
197540
- const s2 = ident16(schema);
198575
+ const s2 = ident17(schema);
197541
198576
  await pool.query(`CREATE SCHEMA IF NOT EXISTS "${s2}"`);
197542
198577
  await pool.query(`
197543
198578
  CREATE TABLE IF NOT EXISTS "${s2}".inbox_read_markers (
@@ -197615,18 +198650,18 @@ function rowToRecord2(r) {
197615
198650
  reason: r.reason
197616
198651
  };
197617
198652
  }
197618
- var ident17, PostgresDispatchStore;
198653
+ var ident18, PostgresDispatchStore;
197619
198654
  var init_postgres_dispatches = __esm({
197620
198655
  "../storage/src/postgres-dispatches.ts"() {
197621
198656
  "use strict";
197622
- ident17 = (schema) => schema.replace(/[^a-zA-Z0-9_]/g, "");
198657
+ ident18 = (schema) => schema.replace(/[^a-zA-Z0-9_]/g, "");
197623
198658
  PostgresDispatchStore = class _PostgresDispatchStore {
197624
198659
  constructor(pool, s2) {
197625
198660
  this.pool = pool;
197626
198661
  this.s = s2;
197627
198662
  }
197628
198663
  static async open(pool, schema = "public") {
197629
- const s2 = ident17(schema);
198664
+ const s2 = ident18(schema);
197630
198665
  await pool.query(`CREATE SCHEMA IF NOT EXISTS "${s2}"`);
197631
198666
  await pool.query(`
197632
198667
  CREATE TABLE IF NOT EXISTS "${s2}".dispatches (
@@ -197764,6 +198799,7 @@ __export(src_exports, {
197764
198799
  PostgresBlobStore: () => PostgresBlobStore,
197765
198800
  PostgresChannelStore: () => PostgresChannelStore,
197766
198801
  PostgresChatSessionStore: () => PostgresChatSessionStore,
198802
+ PostgresChatTurnStore: () => PostgresChatTurnStore,
197767
198803
  PostgresControlPlaneStore: () => PostgresControlPlaneStore,
197768
198804
  PostgresDispatchStore: () => PostgresDispatchStore,
197769
198805
  PostgresHumanPrefsStore: () => PostgresHumanPrefsStore,
@@ -197780,8 +198816,10 @@ __export(src_exports, {
197780
198816
  buildTrigger: () => buildTrigger,
197781
198817
  createPgPool: () => createPgPool,
197782
198818
  dropSchemaIfExists: () => dropSchemaIfExists,
198819
+ ensureChatTurnsTable: () => ensureChatTurnsTable,
197783
198820
  genWebhookToken: () => genWebhookToken2,
197784
198821
  hasPgUnstorable: () => hasPgUnstorable,
198822
+ isUniqueViolation: () => isUniqueViolation2,
197785
198823
  scrubPgJson: () => scrubPgJson,
197786
198824
  scrubPgString: () => scrubPgString
197787
198825
  });
@@ -197799,6 +198837,7 @@ var init_src11 = __esm({
197799
198837
  init_pg_sanitize();
197800
198838
  init_postgres_automations();
197801
198839
  init_postgres_chat_sessions();
198840
+ init_postgres_chat_turns();
197802
198841
  init_postgres_nodes();
197803
198842
  init_postgres_model_prices();
197804
198843
  init_postgres_type_registry();
@@ -197940,20 +198979,20 @@ var fs42 = __toESM(require("node:fs"));
197940
198979
  var os13 = __toESM(require("node:os"));
197941
198980
  var path36 = __toESM(require("node:path"));
197942
198981
  var import_node_child_process20 = require("node:child_process");
197943
- var import_node_crypto53 = require("node:crypto");
198982
+ var import_node_crypto54 = require("node:crypto");
197944
198983
 
197945
198984
  // ../cli/src/cli.ts
197946
198985
  var fs37 = __toESM(require("node:fs"), 1);
197947
198986
  var os11 = __toESM(require("node:os"), 1);
197948
198987
  var path31 = __toESM(require("node:path"), 1);
197949
- var import_node_crypto51 = require("node:crypto");
198988
+ var import_node_crypto52 = require("node:crypto");
197950
198989
 
197951
198990
  // ../cli/src/serve.ts
197952
198991
  var fs31 = __toESM(require("node:fs"), 1);
197953
198992
  var os7 = __toESM(require("node:os"), 1);
197954
198993
  var path25 = __toESM(require("node:path"), 1);
197955
198994
  var import_node_child_process15 = require("node:child_process");
197956
- var import_node_crypto47 = require("node:crypto");
198995
+ var import_node_crypto48 = require("node:crypto");
197957
198996
  var import_node_url7 = require("node:url");
197958
198997
  init_src5();
197959
198998
  init_src10();
@@ -198217,6 +199256,14 @@ init_src10();
198217
199256
  init_src9();
198218
199257
  init_src2();
198219
199258
  init_trajectory();
199259
+ function deterministicUuid(seed) {
199260
+ const h = (0, import_node_crypto48.createHash)("sha256").update(seed).digest();
199261
+ const b2 = Buffer.from(h.subarray(0, 16));
199262
+ b2[6] = b2[6] & 15 | 80;
199263
+ b2[8] = b2[8] & 63 | 128;
199264
+ const hex = b2.toString("hex");
199265
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
199266
+ }
198220
199267
  function builtinSkillsDir() {
198221
199268
  const override = process.env["OASIS_BUILTIN_SKILLS_DIR"]?.trim();
198222
199269
  if (override) return override;
@@ -198844,11 +199891,19 @@ async function startServe(opts) {
198844
199891
  let projectStateStore;
198845
199892
  let artifactStateStore;
198846
199893
  let chatSessionStore;
199894
+ let chatTurnStore;
198847
199895
  let readMarkerStore;
198848
199896
  if (pgDsn && pgPool) {
198849
199897
  projectStateStore = await PostgresProjectStateStore.open(pgPool, pgSchema);
198850
199898
  artifactStateStore = await PostgresArtifactStateStore.open(pgPool, pgSchema);
198851
- chatSessionStore = await PostgresChatSessionStore.open(pgPool, pgSchema);
199899
+ const pgChatSessions = await PostgresChatSessionStore.open(pgPool, pgSchema);
199900
+ chatSessionStore = pgChatSessions;
199901
+ chatTurnStore = await PostgresChatTurnStore.open(pgPool, pgSchema);
199902
+ const backfilled = await pgChatSessions.backfillCompanyIds().catch((e) => {
199903
+ console.warn(`[serve] \u4F1A\u8BDD\u516C\u53F8\u5F52\u5C5E\u56DE\u586B\u8DF3\u8FC7\uFF1A${String(e)}`);
199904
+ return 0;
199905
+ });
199906
+ if (backfilled > 0) console.log(`[serve] \u4F1A\u8BDD\u516C\u53F8\u5F52\u5C5E\uFF1A\u56DE\u586B ${backfilled} \u6761\uFF08\u552F\u4E00\u53EF\u5224\u5B9A\u7684\u5386\u53F2\u4F1A\u8BDD\uFF09`);
198852
199907
  const pgReadMarkers = await PostgresReadMarkerStore.open(pgPool, pgSchema);
198853
199908
  readMarkerStore = pgReadMarkers;
198854
199909
  const seeded = await pgReadMarkers.seedFromChatSessions();
@@ -198860,6 +199915,7 @@ async function startServe(opts) {
198860
199915
  projectStateStore = await FileProjectStateStore.open(path25.join(opts.dir, "artifact-document-projects.json"));
198861
199916
  artifactStateStore = await FileArtifactStateStore.open(path25.join(opts.dir, "artifact-document-state.json"));
198862
199917
  chatSessionStore = await FileChatSessionStore.open(path25.join(opts.dir, "chat-sessions.json"));
199918
+ chatTurnStore = await FileChatTurnStore.open(path25.join(opts.dir, "chat-session-turns.json"));
198863
199919
  readMarkerStore = await FileReadMarkerStore.open(path25.join(opts.dir, "inbox-read-markers.json"));
198864
199920
  console.log("[serve] \u4EA4\u4ED8\u7269\u72B6\u6001\u4F53\u7CFB\uFF1A\u672C\u5730 JSON dev store");
198865
199921
  console.log("[serve] \u8282\u70B9/\u8FD0\u884C\u65F6\u4F53\u7CFB\uFF1A\u672C\u5730 JSON dev store");
@@ -199052,6 +200108,19 @@ async function startServe(opts) {
199052
200108
  }
199053
200109
  });
199054
200110
  let automationDispatchChat = null;
200111
+ const inspectChatTarget = async (args) => {
200112
+ const session = await chatSessionStore.getSession(args.sessionId).catch(() => null);
200113
+ if (!session) return { ok: false, issue: "session_missing" };
200114
+ const base = { title: session.title ?? null, touchedAt: session.touchedAt };
200115
+ if (!session.companyId) return { ok: false, issue: "company_unknown", ...base };
200116
+ if (session.companyId !== args.companyId) return { ok: false, issue: "cross_company", ...base };
200117
+ if (!args.ownerHumanActorId || session.humanActorId !== args.ownerHumanActorId) {
200118
+ return { ok: false, issue: "owner_mismatch", ...base };
200119
+ }
200120
+ if (!args.agentId || session.aiActorId !== args.agentId) return { ok: false, issue: "agent_mismatch", ...base };
200121
+ const messageCount = chatSessionStore.countMessages ? await chatSessionStore.countMessages(args.sessionId).catch(() => 0) : (await chatSessionStore.listMessages(args.sessionId).catch(() => [])).length;
200122
+ return { ok: true, title: session.title ?? null, touchedAt: session.touchedAt, messageCount };
200123
+ };
199055
200124
  const automations = createAutomationsDomain({
199056
200125
  store: automationStore,
199057
200126
  fire: {
@@ -199075,48 +200144,131 @@ async function startServe(opts) {
199075
200144
  workorderState: makeAutomationWorkorderStateResolver(kernel),
199076
200145
  // 回复抓取(ADR 0157):工单 done 时把最终交付物正文并进 run.result.reply,运行历史直接展示。
199077
200146
  workorderReply: makeAutomationReplyResolver(kernel, blobs),
199078
- // chat 执行(ADR 0159,≈ multica run_only):照飞书渠道同一套路——建会话、落用户消息、
199079
- // dispatchChat 收流、done 后落助手消息并回报最终回复。会话在控制台 chat 页可回看。
199080
- runChat: async ({ agentId, humanActorId, title, prompt }, onDone) => {
199081
- if (!automationDispatchChat) throw new Error("chat \u6D3E\u53D1\u5C1A\u672A\u5C31\u7EEA\uFF08serve \u542F\u52A8\u4E2D\uFF09");
199082
- const chatSessionId = (0, import_node_crypto47.randomUUID)();
199083
- const nowIso = (/* @__PURE__ */ new Date()).toISOString();
199084
- await chatSessionStore.createSession({
199085
- id: chatSessionId,
199086
- humanActorId: humanActorId ?? agentId,
199087
- aiActorId: agentId,
199088
- runtimeId: "",
199089
- runtimeSessionId: null,
199090
- title: `\u81EA\u52A8\u5316 \xB7 ${title}`.slice(0, 80),
199091
- touchedAt: nowIso,
199092
- createdAt: nowIso
199093
- }).catch(() => void 0);
199094
- await chatSessionStore.appendMessage({ id: (0, import_node_crypto47.randomUUID)(), sessionId: chatSessionId, role: "user", content: prompt, createdAt: nowIso }).catch(() => void 0);
199095
- const session = await automationDispatchChat({ actorId: agentId, message: prompt, chatSessionId });
199096
- let buf = "";
199097
- session.onOutput((c) => {
199098
- buf += c;
199099
- });
199100
- void session.done.then(async () => {
199101
- if (buf || session.runId) {
199102
- await chatSessionStore.appendMessage({
199103
- id: (0, import_node_crypto47.randomUUID)(),
199104
- sessionId: chatSessionId,
199105
- role: "assistant",
199106
- content: buf,
199107
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
199108
- ...session.runId ? { runId: session.runId } : {}
200147
+ // 轮次账本(ADR 会话投递 D4):busy 的唯一口径。
200148
+ chatTurns: chatTurnStore,
200149
+ // chat 执行(ADR 0159 + 会话投递 D2):**建会话与派发已经拆开**。
200150
+ // resolveTarget —— 只决定「发到哪个会话」;
200151
+ // dispatchTurn —— 只消费一条已经持久占位的 turn(写消息 + 真 dispatch + 收流)。
200152
+ // 拆开之前这两件事挤在一个 runChat 里,于是「发到已有会话」无处插入、「会话在跑吗」无处判定。
200153
+ chat: {
200154
+ async resolveTarget({ automation, runId, title }) {
200155
+ const target = normalizeChatTarget(automation.chatTarget);
200156
+ const agentId = automation.agentId;
200157
+ if (target.mode === "existing_session") {
200158
+ const check2 = await inspectChatTarget({
200159
+ companyId: automation.companyId,
200160
+ agentId: automation.agentId,
200161
+ ownerHumanActorId: automation.ownerHumanActorId,
200162
+ sessionId: target.sessionId
200163
+ });
200164
+ if (!check2.ok) {
200165
+ return {
200166
+ ok: false,
200167
+ reasonCode: "target_unavailable",
200168
+ error: `\u56FA\u5B9A\u76EE\u6807\u4F1A\u8BDD\u4E0D\u53EF\u7528\uFF08${check2.issue}\uFF09\uFF1A${target.sessionId}`,
200169
+ result: { targetKind: "chat_session", chatSessionId: target.sessionId, targetIssue: check2.issue }
200170
+ };
200171
+ }
200172
+ return { ok: true, chatSessionId: target.sessionId };
200173
+ }
200174
+ const chatSessionId = deterministicUuid(`automation-chat-session:${runId}`);
200175
+ const existing = await chatSessionStore.getSession(chatSessionId);
200176
+ if (existing) return { ok: true, chatSessionId };
200177
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
200178
+ try {
200179
+ await chatSessionStore.createSession({
200180
+ id: chatSessionId,
200181
+ companyId: automation.companyId,
200182
+ humanActorId: automation.ownerHumanActorId ?? agentId,
200183
+ aiActorId: agentId,
200184
+ runtimeId: "",
200185
+ runtimeSessionId: null,
200186
+ title: `\u81EA\u52A8\u5316 \xB7 ${title}`.slice(0, 80),
200187
+ touchedAt: nowIso,
200188
+ createdAt: nowIso
200189
+ });
200190
+ } catch (e) {
200191
+ if (!await chatSessionStore.getSession(chatSessionId)) throw e;
200192
+ }
200193
+ return { ok: true, chatSessionId };
200194
+ },
200195
+ async dispatchTurn({ automation, chatSessionId, turnId, messageKey, prompt }, onDone) {
200196
+ if (!automationDispatchChat) throw new Error("chat \u6D3E\u53D1\u5C1A\u672A\u5C31\u7EEA\uFF08serve \u542F\u52A8\u4E2D\uFF09");
200197
+ const agentId = automation.agentId;
200198
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
200199
+ await chatSessionStore.appendMessage({
200200
+ id: deterministicUuid(`automation-chat-message:${messageKey}`),
200201
+ sessionId: chatSessionId,
200202
+ role: "user",
200203
+ content: prompt,
200204
+ createdAt: nowIso,
200205
+ messageKey
200206
+ });
200207
+ await chatSessionStore.updateSession(chatSessionId, { touchedAt: nowIso }).catch(() => void 0);
200208
+ const targetSession = await chatSessionStore.getSession(chatSessionId).catch(() => null);
200209
+ const agentBinding = await registryStore.getBinding(agentId).catch(() => null);
200210
+ const plan = targetSession ? await planChatContinuation({
200211
+ session: targetSession,
200212
+ userMessage: prompt,
200213
+ listMessages: (sid, limit) => chatSessionStore.listMessages(sid, limit),
200214
+ currentRuntime: agentBinding?.status === "active" ? { nodeId: agentBinding.nodeId, runtimeKind: agentBinding.runtimeKind } : null,
200215
+ excludeMessageKey: messageKey
200216
+ }) : null;
200217
+ const session = await automationDispatchChat({
200218
+ actorId: agentId,
200219
+ message: plan?.runtimeMessage ?? prompt,
200220
+ ...plan?.effectiveSessionId ? { sessionId: plan.effectiveSessionId } : {},
200221
+ ...plan?.fallbackMessage ? { fallbackMessage: plan.fallbackMessage } : {},
200222
+ chatSessionId,
200223
+ companyId: automation.companyId
200224
+ });
200225
+ if (plan?.currentRuntimeId) {
200226
+ await chatSessionStore.updateSession(chatSessionId, {
200227
+ runtimeId: plan.currentRuntimeId,
200228
+ ...plan.currentRuntimeKind ? { runtimeKind: plan.currentRuntimeKind } : {},
200229
+ runtimeSessionId: session.nativeSessionId ?? session.id
199109
200230
  }).catch(() => void 0);
199110
200231
  }
199111
- await onDone({ ok: true, reply: buf });
199112
- }).catch(async (err) => {
199113
- await onDone({ ok: false, error: err instanceof Error ? err.message : String(err) });
199114
- });
199115
- return { chatSessionId };
200232
+ await chatTurnStore.settleTurn(turnId, {
200233
+ status: "running",
200234
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
200235
+ ...session.runId ? { assistantRunId: session.runId, dispatchId: session.runId } : {}
200236
+ }).catch((e) => console.warn(`[automations] \u8F6E\u6B21\u8F6C running \u5931\u8D25 ${turnId}: ${String(e)}`));
200237
+ let buf = "";
200238
+ session.onOutput((c) => {
200239
+ buf += c;
200240
+ });
200241
+ void session.done.then(async () => {
200242
+ const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
200243
+ if (buf || session.runId) {
200244
+ await chatSessionStore.appendMessage({
200245
+ id: (0, import_node_crypto48.randomUUID)(),
200246
+ sessionId: chatSessionId,
200247
+ role: "assistant",
200248
+ content: buf,
200249
+ createdAt: finishedAt,
200250
+ ...session.runId ? { runId: session.runId } : {}
200251
+ }).catch(() => void 0);
200252
+ }
200253
+ await chatSessionStore.updateSession(chatSessionId, {
200254
+ ...plan?.currentRuntimeId ? { runtimeId: plan.currentRuntimeId } : {},
200255
+ ...plan?.currentRuntimeKind ? { runtimeKind: plan.currentRuntimeKind } : {},
200256
+ runtimeSessionId: session.nativeSessionId ?? session.id,
200257
+ touchedAt: finishedAt
200258
+ }).catch(() => void 0);
200259
+ await chatTurnStore.settleTurn(turnId, { status: "succeeded", completedAt: (/* @__PURE__ */ new Date()).toISOString() }).catch(() => void 0);
200260
+ await onDone({ ok: true, reply: buf });
200261
+ }).catch(async (err) => {
200262
+ const msg = err instanceof Error ? err.message : String(err);
200263
+ await chatTurnStore.settleTurn(turnId, { status: "failed", completedAt: (/* @__PURE__ */ new Date()).toISOString(), lastError: msg }).catch(() => void 0);
200264
+ await onDone({ ok: false, error: msg });
200265
+ });
200266
+ }
199116
200267
  },
199117
- // 订阅触发 × chat 的 fail-closed 开关(ADR 0163 §4.4)。当前 runChat 每次 randomUUID 建会话、
199118
- // 没有任何持久去重:占位后崩溃重试会多开一个会话。**在 chat 适配器补上持久幂等键之前保持 false**,
199119
- // 由此 event+chat 在配置期就被拒(能力位 supported=false),而不是上线后偶发重复开会话。
200268
+ // 订阅触发 × chat 的 fail-closed 开关(ADR 0163 §4.4 / 会话投递 §4)。
200269
+ // 本次交付补齐了持久 messageKey、turn 占位与确定性 run→session 映射,但 §4.2 的 8 条
200270
+ // 开闸条件里还有几条没有证据(真库并发、多进程崩溃点、dispatch 侧稳定幂等查询、指标告警),
200271
+ // **本 ADR 明确不授权改这里**。放开是一次独立变更,须先补齐那些证据。
199120
200272
  chatSupportsIdempotency: false,
199121
200273
  // chat 准入(≈ multica AgentReadiness):在岗 + 有 active runtime 绑定,注定失败的触发别去开会话。
199122
200274
  agentReady: async (agentId) => {
@@ -199131,6 +200283,51 @@ async function startServe(opts) {
199131
200283
  return { ok: true };
199132
200284
  }
199133
200285
  },
200286
+ // 固定目标会话接缝(ADR 会话投递 D3/D8):可用性判据、选择器搜索、长期会话创建三件事
200287
+ // 一律在服务端,前端只消费结论。
200288
+ chatTargets: {
200289
+ inspect: inspectChatTarget,
200290
+ createTargetSession: async ({ companyId, agentId, ownerHumanActorId, title }) => {
200291
+ const sessionId = (0, import_node_crypto48.randomUUID)();
200292
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
200293
+ const binding = await registryStore.getBinding(agentId).catch(() => null);
200294
+ await chatSessionStore.createSession({
200295
+ id: sessionId,
200296
+ companyId,
200297
+ humanActorId: ownerHumanActorId,
200298
+ aiActorId: agentId,
200299
+ runtimeId: binding?.nodeId ?? "",
200300
+ runtimeSessionId: null,
200301
+ title,
200302
+ touchedAt: nowIso,
200303
+ createdAt: nowIso
200304
+ });
200305
+ return { sessionId };
200306
+ },
200307
+ search: async ({ companyId, agentId, ownerHumanActorId, text, limit, offset }) => {
200308
+ if (!chatSessionStore.searchSessionsForTarget) return { items: [], hasMore: false };
200309
+ const page = await chatSessionStore.searchSessionsForTarget({
200310
+ companyId,
200311
+ humanActorId: ownerHumanActorId,
200312
+ aiActorId: agentId,
200313
+ ...text ? { text } : {},
200314
+ limit,
200315
+ offset
200316
+ });
200317
+ return {
200318
+ items: page.items.map((x2) => ({ id: x2.id, title: x2.title ?? null, touchedAt: x2.touchedAt, messageCount: x2.messageCount })),
200319
+ hasMore: page.hasMore
200320
+ };
200321
+ },
200322
+ deleteSession: async (sessionId) => {
200323
+ await chatSessionStore.deleteSession(sessionId);
200324
+ },
200325
+ resolveOwnerHuman: async (actor) => {
200326
+ if (actor.startsWith("actor:human:")) return actor;
200327
+ const bound = await assistantBindStore.getByAgent(actor).catch(() => null);
200328
+ return bound?.humanActorId ?? null;
200329
+ }
200330
+ },
199134
200331
  isAdmin: async (companyId, actor) => {
199135
200332
  const member = await controlPlaneStore.getMember(companyId, actor);
199136
200333
  return member?.role === "owner" || member?.role === "admin";
@@ -199565,7 +200762,7 @@ async function startServe(opts) {
199565
200762
  }));
199566
200763
  }
199567
200764
  const limits = { wallClockMs: wallClockForKind("planner") };
199568
- const artifactId = `artifact:planner:${(0, import_node_crypto47.randomUUID)()}`;
200765
+ const artifactId = `artifact:planner:${(0, import_node_crypto48.randomUUID)()}`;
199569
200766
  const handle = await chatRemoteAdapter.spawn({
199570
200767
  actor: planner.id,
199571
200768
  // companyId 同协调者:planner 用的就是默认公司的 actors 服务,漏签会让它一调公司域端点就 404。
@@ -199659,6 +200856,9 @@ async function startServe(opts) {
199659
200856
  store: chatSessionStore,
199660
200857
  registry: registryStore,
199661
200858
  trace: traceStore,
200859
+ // 会话槽的观测与运维出口(ADR 会话投递 D4):看这条会话占着哪一轮、把卡死的那一轮置终态。
200860
+ chatTurns: chatTurnStore,
200861
+ getRun: (id) => traceStore.getRun(id),
199662
200862
  kernel,
199663
200863
  artifactState: artifactStateStore,
199664
200864
  workdir: () => workdirBridge ?? void 0,
@@ -199669,6 +200869,8 @@ async function startServe(opts) {
199669
200869
  assistants: assistantsService,
199670
200870
  liveChat,
199671
200871
  ...chatSessionStore ? { chatSession: chatSessionStore } : {},
200872
+ // ADR 会话投递 D4:/api/chat 与渠道入站据它占同一个会话槽(自动化侧经 automations 域接同一实例)。
200873
+ chatTurns: chatTurnStore,
199672
200874
  knowledge: {
199673
200875
  configStore: knowledgeConfigStore,
199674
200876
  runStore: knowledgeRunStore,
@@ -199982,10 +201184,10 @@ async function startServe(opts) {
199982
201184
  { nodeId: priorChatSession?.runtimeId, runtimeKind: priorChatSession?.runtimeKind },
199983
201185
  { nodeId: binding.nodeId, runtimeKind: binding.runtimeKind }
199984
201186
  ) : false;
199985
- const runtimeSessionId = sessionId ?? (0, import_node_crypto47.randomUUID)();
201187
+ const runtimeSessionId = sessionId ?? (0, import_node_crypto48.randomUUID)();
199986
201188
  const resumeRuntimeSession = Boolean(sessionId);
199987
- const traceRunId = `chat-run:${(0, import_node_crypto47.randomUUID)()}`;
199988
- const artifactId = `artifact:chat:${(0, import_node_crypto47.randomUUID)()}`;
201189
+ const traceRunId = `chat-run:${(0, import_node_crypto48.randomUUID)()}`;
201190
+ const artifactId = `artifact:chat:${(0, import_node_crypto48.randomUUID)()}`;
199989
201191
  const traceStartedAt = (/* @__PURE__ */ new Date()).toISOString();
199990
201192
  let lastProgressTouchMs = 0;
199991
201193
  const PROGRESS_TOUCH_THROTTLE_MS2 = 2e4;
@@ -200533,6 +201735,20 @@ async function startServe(opts) {
200533
201735
  chatSweepTimer.unref?.();
200534
201736
  }
200535
201737
  }
201738
+ const chatTurnOrphanGraceMs = Number(process.env.OASIS_CHAT_TURN_ORPHAN_GRACE_MS ?? 10 * 6e4);
201739
+ const chatTurnSweepTimer = setInterval(() => {
201740
+ void sweepStaleChatTurns({
201741
+ turns: chatTurnStore,
201742
+ getRun: (id) => traceStore.getRun(id),
201743
+ // 本进程在飞的轮不动——它自己的收尾路径会写终态(liveChat 只认人工/渠道那条,自动化轮
201744
+ // 在 dispatch 后立刻有 assistantRunId、其 run 处于 running,走上面的权威判据同样保住)。
201745
+ isOwned: (turn) => Boolean(turn.assistantRunId && liveChat.runFor(turn.chatSessionId) === turn.assistantRunId),
201746
+ now: Date.now(),
201747
+ noRunGraceMs: chatTurnOrphanGraceMs,
201748
+ log: (m2) => console.log(m2)
201749
+ }).catch((err) => console.error(`[chat-turn-sweep] tick \u5931\u8D25: ${String(err)}`));
201750
+ }, 3e4);
201751
+ chatTurnSweepTimer.unref?.();
200536
201752
  const hubAdapterOpts = {
200537
201753
  log: (m2) => console.warn(m2),
200538
201754
  health: nodeHealth,
@@ -200544,7 +201760,16 @@ async function startServe(opts) {
200544
201760
  }
200545
201761
  };
200546
201762
  chatRemoteAdapter = new DaemonHubAdapter(hub, { ...hubAdapterOpts, stashUnknownFrames: true });
200547
- dispatchRemoteAdapter = new DaemonHubAdapter(hub, { ...hubAdapterOpts, stashUnknownFrames: true });
201763
+ dispatchRemoteAdapter = new DaemonHubAdapter(hub, {
201764
+ ...hubAdapterOpts,
201765
+ stashUnknownFrames: true,
201766
+ // 服务端自判死(存在性快照 / 会话失联收割 / socket 非 OPEN)没有退出帧,走不到下面那个
201767
+ // message listener 里的 work.response 桥 → work 空转到 30 分钟超时。这里补上同一半逻辑。
201768
+ // 只挂 dispatch 路:chat 路的派发不对应 work,它自有 chat-recovery 收尾。
201769
+ onSettleWithoutFrame: (dispatchId, info) => {
201770
+ void bridgeServerSideExitToEngine(dispatchId, info);
201771
+ }
201772
+ });
200548
201773
  let newEngineProduce = null;
200549
201774
  let newEngineReplyWork = null;
200550
201775
  let newEngineReview = null;
@@ -200580,6 +201805,51 @@ async function startServe(opts) {
200580
201805
  const reviewToSession = /* @__PURE__ */ new Map();
200581
201806
  const dispatchToReview = /* @__PURE__ */ new Map();
200582
201807
  let busRef = null;
201808
+ async function bridgeServerSideExitToEngine(dispatchId, info) {
201809
+ const bus = busRef;
201810
+ if (!bus) return;
201811
+ const outcome = info.code === 0 ? "completed" : "failed";
201812
+ const detail = {
201813
+ reason: `server-side settle: ${info.reason ?? "unknown"}`,
201814
+ ...info.errorMessage ? { errorMessage: info.errorMessage } : {}
201815
+ };
201816
+ try {
201817
+ const mapping = dispatchToWork.get(dispatchId);
201818
+ if (mapping) {
201819
+ dispatchToWork.delete(dispatchId);
201820
+ workToSession.delete(mapping.workId);
201821
+ console.log(`[new-engine] \u670D\u52A1\u7AEF\u81EA\u5224\u6B7B\uFF08\u65E0\u9000\u51FA\u5E27\uFF09\uFF1A${dispatchId} \u2192 work.response(${outcome}) ${mapping.workId}`);
201822
+ await bus.submit({
201823
+ companyId: "",
201824
+ workorderId: mapping.workorderId,
201825
+ actorId: SYSTEM_ACTOR,
201826
+ event: {
201827
+ kind: "work.response",
201828
+ workorderId: mapping.workorderId,
201829
+ workId: mapping.workId,
201830
+ outcome,
201831
+ detail
201832
+ }
201833
+ });
201834
+ } else {
201835
+ await fallbackDispatchExitToWork({ dispatchId, info }, bus);
201836
+ }
201837
+ const revMapping = dispatchToReview.get(dispatchId);
201838
+ if (revMapping) {
201839
+ dispatchToReview.delete(dispatchId);
201840
+ reviewToSession.delete(revMapping.reviewId);
201841
+ console.log(`[new-engine] \u670D\u52A1\u7AEF\u81EA\u5224\u6B7B\uFF08\u65E0\u9000\u51FA\u5E27\uFF09\uFF1A${dispatchId} \u2192 review.timeout ${revMapping.reviewId}`);
201842
+ await bus.submit({
201843
+ companyId: "",
201844
+ workorderId: revMapping.workorderId,
201845
+ actorId: SYSTEM_ACTOR,
201846
+ event: { kind: "review.timeout", workorderId: revMapping.workorderId, reviewId: revMapping.reviewId }
201847
+ });
201848
+ }
201849
+ } catch (err) {
201850
+ console.error(`[new-engine] \u670D\u52A1\u7AEF\u81EA\u5224\u6B7B\u6536\u53E3\u5931\u8D25 ${dispatchId}:`, err);
201851
+ }
201852
+ }
200583
201853
  async function fallbackDispatchExitToWork(msg, bus) {
200584
201854
  try {
200585
201855
  const entry = journalRing.find(
@@ -200653,7 +201923,7 @@ async function startServe(opts) {
200653
201923
  console.log(`[new-engine] dispatchWork ${workId} \u662F human work\u2014\u2014\u4E0D\u6D3E runtime\uFF0C\u7B49\u5F85\u4EBA\u5DE5\u5904\u7406`);
200654
201924
  return;
200655
201925
  }
200656
- const ledgerId = `dispatch:${(0, import_node_crypto47.randomUUID)()}`;
201926
+ const ledgerId = `dispatch:${(0, import_node_crypto48.randomUUID)()}`;
200657
201927
  try {
200658
201928
  await dispatchLedger.insertOpen({
200659
201929
  id: ledgerId,
@@ -200729,7 +201999,7 @@ async function startServe(opts) {
200729
201999
  const kernelModel = newKernel.model;
200730
202000
  const art = kernelModel.artifacts.get(nodeId);
200731
202001
  const revWid = art?.workspace ?? "";
200732
- const ledgerId = `dispatch:${(0, import_node_crypto47.randomUUID)()}`;
202002
+ const ledgerId = `dispatch:${(0, import_node_crypto48.randomUUID)()}`;
200733
202003
  try {
200734
202004
  await dispatchLedger.insertOpen({
200735
202005
  id: ledgerId,
@@ -201239,8 +202509,8 @@ async function startServe(opts) {
201239
202509
  const agentMs = opts.sla?.agentMs ?? 10 * 6e4;
201240
202510
  const quotaStarvationMs = opts.sla?.quotaStarvationMs ?? 6 * 36e5;
201241
202511
  const reviewStallMs = opts.sla?.reviewStallMs ?? 60 * 6e4;
201242
- const fmtSince = (iso4, now) => {
201243
- const min2 = Math.floor(Math.max(0, now - Date.parse(iso4)) / 6e4);
202512
+ const fmtSince = (iso5, now) => {
202513
+ const min2 = Math.floor(Math.max(0, now - Date.parse(iso5)) / 6e4);
201244
202514
  if (!Number.isFinite(min2)) return "\u521A\u624D";
201245
202515
  if (min2 < 1) return "\u4E0D\u5230 1 \u5206\u949F\u524D";
201246
202516
  if (min2 < 60) return `${min2} \u5206\u949F\u524D`;
@@ -202187,6 +203457,10 @@ var SessionProcessState = class {
202187
203457
  },
202188
203458
  onHandle: (h) => {
202189
203459
  this.handle = h;
203460
+ if (this.stopping) {
203461
+ void h.kill().catch(() => {
203462
+ });
203463
+ }
202190
203464
  },
202191
203465
  onExited: () => {
202192
203466
  this.exited = true;
@@ -202358,7 +203632,7 @@ var SessionProcessState = class {
202358
203632
  // ../cli/src/daemon/ws-client.ts
202359
203633
  init_wrapper();
202360
203634
  var import_node_os9 = require("node:os");
202361
- var import_node_crypto48 = require("node:crypto");
203635
+ var import_node_crypto49 = require("node:crypto");
202362
203636
  init_src8();
202363
203637
  init_src7();
202364
203638
 
@@ -202693,8 +203967,21 @@ var DaemonWsClient = class {
202693
203967
  ws = null;
202694
203968
  pingTimer = null;
202695
203969
  stopped = false;
202696
- /** 收到 update 但有活跃会话时置真:延后到所有会话结束再自更新,避免打断执行中的 agent。 */
203970
+ /** 收到 update 但本进程内还有活跃会话时置真:延后到全部结束再自更新,避免打断执行中的 agent。 */
202697
203971
  pendingUpdate = false;
203972
+ /** 第一次延后这次更新的时刻。用于给「等空闲」封顶——否则长跑会话能把更新无限期挂住(ADR 0115 缺陷 3)。 */
203973
+ pendingUpdateSince = null;
203974
+ /** 「等空闲」的上限;到点就照做(会打断本进程内的会话)。env OASIS_UPDATE_DEFER_MAX_MS 可调。 */
203975
+ updateDeferMaxMs = (() => {
203976
+ const raw = Number(process.env["OASIS_UPDATE_DEFER_MAX_MS"]);
203977
+ return Number.isFinite(raw) && raw >= 0 ? raw : 30 * 6e4;
203978
+ })();
203979
+ /** 记下首次延后时刻并回答「还在上限内吗」。 */
203980
+ deferUpdateWithinLimit() {
203981
+ this.pendingUpdate = true;
203982
+ this.pendingUpdateSince ??= Date.now();
203983
+ return Date.now() - this.pendingUpdateSince < this.updateDeferMaxMs;
203984
+ }
202698
203985
  /** 停机排空中(见 {@link drain}):不再接新派发、不再触发延后的自更新。 */
202699
203986
  draining = false;
202700
203987
  /** 已回 dispatch_received、尚未完成 runtime spawn 的派发;也随 hello 上报供重连对账。 */
@@ -203011,9 +204298,13 @@ var DaemonWsClient = class {
203011
204298
  break;
203012
204299
  }
203013
204300
  const active = this.sessions.activeCount();
203014
- if (active > 0) {
203015
- this.pendingUpdate = true;
203016
- log2("[node-cli]", `\u2190 update\uFF1A\u5F53\u524D\u6709 ${active} \u4E2A\u6267\u884C\u4E2D\u4F1A\u8BDD,\u5EF6\u540E\u5230\u5168\u90E8\u7ED3\u675F\u540E\u518D\u66F4\u65B0`);
204301
+ if (active > 0 && this.deferUpdateWithinLimit()) {
204302
+ log2("[node-cli]", `\u2190 update\uFF1A\u5F53\u524D\u6709 ${active} \u4E2A\u6267\u884C\u4E2D\u4F1A\u8BDD\uFF08\u672C\u8FDB\u7A0B\u5185\uFF09\uFF0C\u5EF6\u540E\u5230\u5168\u90E8\u7ED3\u675F\u6216 ${Math.round(this.updateDeferMaxMs / 6e4)} \u5206\u949F\u4E0A\u9650`);
204303
+ } else if (active > 0) {
204304
+ log2("[node-cli]", `\u2190 update\uFF1A\u5DF2\u5EF6\u540E ${Math.round((Date.now() - (this.pendingUpdateSince ?? Date.now())) / 6e4)} \u5206\u949F\u4ECD\u672A\u7A7A\u95F2\uFF0C\u8D85\u8FC7\u4E0A\u9650\uFF0C\u73B0\u5728\u5C31\u66F4\u65B0\uFF08\u4F1A\u6253\u65AD ${active} \u4E2A\u672C\u8FDB\u7A0B\u5185\u4F1A\u8BDD\uFF09`);
204305
+ this.pendingUpdate = false;
204306
+ this.pendingUpdateSince = null;
204307
+ this.onUpdate();
203017
204308
  } else {
203018
204309
  log2("[node-cli]", "\u2190 update\uFF1A\u7A7A\u95F2,\u7ACB\u5373\u62C9\u53D6\u6700\u65B0\u7248\u672C\u5E76\u91CD\u542F");
203019
204310
  this.onUpdate();
@@ -203030,7 +204321,7 @@ var DaemonWsClient = class {
203030
204321
  */
203031
204322
  async queryArtifactStates(artifactIds, timeoutMs = 15e3) {
203032
204323
  if (artifactIds.length === 0) return {};
203033
- const requestId = (0, import_node_crypto48.randomUUID)();
204324
+ const requestId = (0, import_node_crypto49.randomUUID)();
203034
204325
  return new Promise((resolve10, reject) => {
203035
204326
  const timer = setTimeout(() => {
203036
204327
  this.gcPending.delete(requestId);
@@ -203097,9 +204388,11 @@ var DaemonWsClient = class {
203097
204388
  }
203098
204389
  /** 会话结束后调用:若之前收到过 update 但因忙延后了,且现已空闲,则触发自更新。 */
203099
204390
  maybeRunPendingUpdate() {
204391
+ if (!this.pendingUpdate) return;
203100
204392
  if (this.draining) return;
203101
204393
  if (this.pendingUpdate && this.sessions.activeCount() === 0) {
203102
204394
  this.pendingUpdate = false;
204395
+ this.pendingUpdateSince = null;
203103
204396
  log2("[node-cli]", "\u6240\u6709\u4F1A\u8BDD\u5DF2\u7ED3\u675F,\u6267\u884C\u5EF6\u540E\u7684\u66F4\u65B0\uFF1A\u62C9\u53D6\u6700\u65B0\u7248\u672C\u5E76\u91CD\u542F");
203104
204397
  this.onUpdate?.();
203105
204398
  }
@@ -203374,7 +204667,7 @@ function forwardSignal(child, sig, killGroup = (pgid, signal) => {
203374
204667
  // ../cli/src/daemon/machine-id.ts
203375
204668
  var import_node_child_process18 = require("node:child_process");
203376
204669
  var import_node_fs21 = require("node:fs");
203377
- var import_node_crypto49 = require("node:crypto");
204670
+ var import_node_crypto50 = require("node:crypto");
203378
204671
  var import_node_os12 = require("node:os");
203379
204672
  function linuxMachineId() {
203380
204673
  for (const p2 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
@@ -203440,7 +204733,7 @@ var defaultSources = {
203440
204733
  function resolveNodeId(sources = {}) {
203441
204734
  const s2 = { ...defaultSources, ...sources };
203442
204735
  const material = `${s2.machineFingerprint()}:${s2.osUser()}`;
203443
- const digest = (0, import_node_crypto49.createHash)("sha256").update(material).digest("hex").slice(0, 12);
204736
+ const digest = (0, import_node_crypto50.createHash)("sha256").update(material).digest("hex").slice(0, 12);
203444
204737
  return `node-${digest}`;
203445
204738
  }
203446
204739
 
@@ -203704,18 +204997,19 @@ var COMMAND_DECLS = {
203704
204997
  "automation-get": {
203705
204998
  group: "\u81EA\u52A8\u5316",
203706
204999
  usage: "oasis automation-get <id>",
203707
- description: "\u81EA\u52A8\u5316\u8BE6\u60C5\uFF1A\u89C4\u5219 + \u89E6\u53D1\u5668 + \u6700\u8FD1\u8FD0\u884C\u6295\u5F71\u3002",
205000
+ description: "\u81EA\u52A8\u5316\u8BE6\u60C5\uFF1A\u89C4\u5219 + \u89E6\u53D1\u5668 + \u6700\u8FD1\u8FD0\u884C\u6295\u5F71\u3002\u5BF9\u8BDD\u6267\u884C\u4F1A\u5E26 automation.chatTarget\uFF08\u76EE\u6807\u4F1A\u8BDD\u7B56\u7565\uFF09\u4E0E chatTargetView\uFF08\u56FA\u5B9A\u4F1A\u8BDD\u662F\u5426\u4ECD\u53EF\u6295\u9012\u3001\u6D88\u606F\u91CF\u3001\u6700\u8FD1\u6D3B\u52A8\uFF09\u3002",
203708
205001
  positional: [{ name: "<id>", required: true, desc: "\u81EA\u52A8\u5316 id" }],
203709
205002
  examples: ["oasis automation-get auto_abc123"]
203710
205003
  },
203711
205004
  "automation-create": {
203712
205005
  group: "\u81EA\u52A8\u5316",
203713
- usage: "oasis automation-create --name <\u540D> [--mode chat|workorder] [--agent <actorId>] [--playbook <ref>] [--payload '<JSON>'] [--cron '0 9 * * 1' --tz Asia/Shanghai] [--webhook generic|github] [--project <id>] [--title-template '\u6668\u62A5 {{date}}'] [--dispatch true] [--subscribers a,b]",
205006
+ usage: "oasis automation-create --name <\u540D> [--mode chat|workorder] [--agent <actorId>] [--chat-target new|session:<\u4F1A\u8BDDid>|create] [--playbook <ref>] [--payload '<JSON>'] [--cron '0 9 * * 1' --tz Asia/Shanghai] [--webhook generic|github] [--project <id>] [--title-template '\u6668\u62A5 {{date}}'] [--dispatch true] [--subscribers a,b]",
203714
205007
  description: "\u65B0\u5EFA\u81EA\u52A8\u5316\u3002chat \u6A21\u5F0F\uFF08\u9ED8\u8BA4\u7ED9\u4E86 --agent \u5373\u662F\uFF09\uFF1A\u5F00\u4F1A\u8BDD\u628A\u4EFB\u52A1\u53D1\u7ED9\u6267\u884C\u4EBA\u3001\u56DE\u590D\u8FDB\u8FD0\u884C\u5386\u53F2\uFF1Bworkorder \u6A21\u5F0F\uFF1A\u6309\u5267\u672C\u5EFA\u5DE5\u5355\uFF08\u9ED8\u8BA4 dispatch-hold \u5F85\u4EBA\u653E\u884C\uFF09\u3002",
203715
205008
  flags: [
203716
205009
  { name: "name", desc: "\u663E\u793A\u540D", required: true },
203717
205010
  { name: "mode", desc: "chat|workorder\uFF08\u7F3A\u7701\uFF1A\u7ED9\u4E86 --agent \u4E3A chat\uFF0C\u5426\u5219 workorder\uFF09" },
203718
205011
  { name: "agent", desc: "\u6267\u884C\u4EBA agent actor id\uFF08chat \u6A21\u5F0F\u5FC5\u586B\uFF09" },
205012
+ { name: "chat-target", desc: "\u5BF9\u8BDD\u6267\u884C\u7684\u76EE\u6807\u4F1A\u8BDD\uFF1Anew=\u6BCF\u6B21\u65B0\u5F00\uFF08\u7F3A\u7701\uFF09\uFF5Csession:<\u4F1A\u8BDDid>=\u56FA\u5B9A\u53D1\u5230\u8BE5\u4F1A\u8BDD\uFF5Ccreate=\u73B0\u5EFA\u4E00\u4E2A\u957F\u671F\u76EE\u6807\u4F1A\u8BDD\u5E76\u7ED1\u4E0A" },
203719
205013
  { name: "playbook", desc: "\u5267\u672C ref\uFF08workorder \u6A21\u5F0F\u5FC5\u586B\uFF1Boasis playbooks \u53EF\u67E5\uFF09" },
203720
205014
  { name: "payload", desc: "\u5267\u672C\u5165\u53C2\uFF08JSON\uFF0C\u5BF9\u5E94\u5267\u672C inputs\uFF09" },
203721
205015
  { name: "cron", desc: "5 \u5B57\u6BB5 cron\uFF08\u7ED9\u4E86\u5373\u5EFA schedule \u89E6\u53D1\u5668\uFF09" },
@@ -203730,13 +205024,14 @@ var COMMAND_DECLS = {
203730
205024
  },
203731
205025
  "automation-update": {
203732
205026
  group: "\u81EA\u52A8\u5316",
203733
- usage: "oasis automation-update <id> [--name <\u540D>] [--mode chat|workorder] [--agent <actorId>] [--playbook <ref>] [--payload '<JSON>'] [--project <id>] [--title-template <t>] [--dispatch true|false] [--enabled true|false] [--subscribers a,b]",
205027
+ usage: "oasis automation-update <id> [--name <\u540D>] [--mode chat|workorder] [--agent <actorId>] [--chat-target new|session:<\u4F1A\u8BDDid>|create] [--playbook <ref>] [--payload '<JSON>'] [--project <id>] [--title-template <t>] [--dispatch true|false] [--enabled true|false] [--subscribers a,b]",
203734
205028
  description: "\u66F4\u65B0\u81EA\u52A8\u5316\u3002\u5B9E\u8D28\u53D8\u66F4\uFF08\u5267\u672C/\u5165\u53C2/\u6A21\u677F/dispatch/\u542F\u505C\uFF09\u4F1A\u628A\u95EE\u8D23\u8F6C\u79FB\u7ED9\u7F16\u8F91\u8005\uFF08\u5F52\u56E0\uFF09\u3002",
203735
205029
  positional: [{ name: "<id>", required: true, desc: "\u81EA\u52A8\u5316 id" }],
203736
205030
  flags: [
203737
205031
  { name: "name", desc: "\u663E\u793A\u540D" },
203738
205032
  { name: "mode", desc: "chat|workorder" },
203739
205033
  { name: "agent", desc: "\u6267\u884C\u4EBA agent actor id" },
205034
+ { name: "chat-target", desc: "\u5BF9\u8BDD\u6267\u884C\u7684\u76EE\u6807\u4F1A\u8BDD\uFF1Anew=\u6BCF\u6B21\u65B0\u5F00\uFF5Csession:<\u4F1A\u8BDDid>=\u56FA\u5B9A\u53D1\u5230\u8BE5\u4F1A\u8BDD\uFF5Ccreate=\u73B0\u5EFA\u4E00\u4E2A\u957F\u671F\u76EE\u6807\u4F1A\u8BDD\u5E76\u7ED1\u4E0A\uFF08\u4E0D\u7ED9=\u4E0D\u52A8\uFF09" },
203740
205035
  { name: "playbook", desc: "\u5267\u672C ref" },
203741
205036
  { name: "payload", desc: "\u5267\u672C\u5165\u53C2\uFF08JSON\uFF09" },
203742
205037
  { name: "project", desc: "\u76EE\u6807\u9879\u76EE id" },
@@ -204330,6 +205625,30 @@ var COMMAND_DECLS = {
204330
205625
  description: "\u5B64\u513F blob \u56DE\u6536\uFF08mark-and-sweep\uFF1B\u64CD\u4F5C\u8005\u547D\u4EE4\uFF0CF1\uFF09\u3002",
204331
205626
  examples: ["oasis gc"]
204332
205627
  },
205628
+ "chat-turn": {
205629
+ group: "\u8FD0\u7EF4",
205630
+ usage: "oasis chat-turn <\u4F1A\u8BDDid>",
205631
+ description: "\u770B\u8FD9\u6761 chat \u4F1A\u8BDD\u5F53\u524D\u5360\u7740\u7684\u90A3\u4E00\u8F6E\uFF08\u8F6E\u6B21\u8D26\u672C\u7684 active \u884C\uFF09\u3002\u300C\u76EE\u6807\u4F1A\u8BDD\u6B63\u5FD9 / session_busy\u300D\u7684\u552F\u4E00\u53E3\u5F84\u5C31\u662F\u5B83\u2014\u2014\u6392\u67E5\u81EA\u52A8\u5316\u4E3A\u4EC0\u4E48\u4E00\u76F4\u88AB\u8DF3\u8FC7\u5148\u770B\u8FD9\u91CC\u3002",
205632
+ positional: [{ name: "<\u4F1A\u8BDDid>", required: true, desc: "chat \u4F1A\u8BDD id" }],
205633
+ examples: ["oasis chat-turn 84f48795-1c2d-4e5f-8a9b-0c1d2e3f4a5b"]
205634
+ },
205635
+ "chat-turn-settle": {
205636
+ group: "\u8FD0\u7EF4",
205637
+ usage: "oasis chat-turn-settle <\u4F1A\u8BDDid> <\u8F6E\u6B21id> [--force true] [--reason <\u8BF4\u660E>]",
205638
+ description: "\u628A\u5361\u6B7B\u7684\u4E00\u8F6E\u7F6E\u7EC8\u6001\u3001\u653E\u5F00\u4F1A\u8BDD\u69FD\uFF08\u8FDB\u7A0B\u88AB\u6740\u7559\u4E0B\u7684 running \u884C\u4F1A\u8BA9\u4F1A\u8BDD\u6C38\u4E45\u5224\u5FD9\uFF09\u3002\u9ED8\u8BA4\u5148\u5411 trace \u8D26\u672C\u6838\u5B9E\uFF1A\u8FD8\u5728\u8DD1\u5C31\u62D2\u7EDD\uFF1B--force true \u662F\u660E\u786E\u7684\u4EBA\u5DE5\u63A8\u7FFB\uFF0C\u843D cancelled \u5E76\u8BB0\u4E0B\u539F\u56E0\u3002",
205639
+ positional: [
205640
+ { name: "<\u4F1A\u8BDDid>", required: true, desc: "chat \u4F1A\u8BDD id" },
205641
+ { name: "<\u8F6E\u6B21id>", required: true, desc: "\u8F6E\u6B21 id\uFF08oasis chat-turn \u91CC\u7684 id\uFF0Crun.result.blockedByTurnId \u540C\u503C\uFF09" }
205642
+ ],
205643
+ flags: [
205644
+ { name: "force", desc: "true=\u4E0D\u6838\u5B9E\u76F4\u63A5\u7F6E cancelled\uFF08\u786E\u8BA4\u6267\u884C\u4EBA\u771F\u7684\u6B7B\u4E86\u518D\u7528\uFF09" },
205645
+ { name: "reason", desc: "\u5F3A\u5236\u7F6E\u7EC8\u6001\u7684\u539F\u56E0\uFF08\u4F1A\u8BB0\u8FDB\u8F6E\u6B21\u7684 lastError\uFF09" }
205646
+ ],
205647
+ examples: [
205648
+ "oasis chat-turn-settle 84f48795-\u2026 cturn_nbgv5c7g8u",
205649
+ 'oasis chat-turn-settle 84f48795-\u2026 cturn_nbgv5c7g8u --force true --reason "\u8282\u70B9\u5DF2\u91CD\u88C5\uFF0C\u8FD9\u4E00\u8F6E\u7684\u8FDB\u7A0B\u4E0D\u5B58\u5728\u4E86"'
205650
+ ]
205651
+ },
204333
205652
  // —— 读产物 ——
204334
205653
  status: {
204335
205654
  group: "\u8BFB\u4EA7\u7269",
@@ -205057,13 +206376,13 @@ function ownFlagsFromDecl(command) {
205057
206376
  }
205058
206377
 
205059
206378
  // ../cli/src/connector-effect-runner.ts
205060
- var import_node_crypto50 = require("node:crypto");
206379
+ var import_node_crypto51 = require("node:crypto");
205061
206380
  var import_node_child_process19 = require("node:child_process");
205062
206381
  init_src8();
205063
206382
  function stableKey(effect) {
205064
206383
  const keyFields = Object.fromEntries(Object.entries(effect.keyFields).sort(([a], [b2]) => a.localeCompare(b2)));
205065
206384
  const canonical = JSON.stringify({ kind: effect.kind, target: effect.target, keyFields });
205066
- return `sha256:${(0, import_node_crypto50.createHash)("sha256").update(canonical).digest("hex")}`;
206385
+ return `sha256:${(0, import_node_crypto51.createHash)("sha256").update(canonical).digest("hex")}`;
205067
206386
  }
205068
206387
  async function runConnectorEffect(connector, toolArgs, deps) {
205069
206388
  const effect = connector.describeExternalEffect?.(toolArgs) ?? null;
@@ -205190,6 +206509,18 @@ function need(flags, name) {
205190
206509
  if (v2 === void 0) throw new Error(`\u7F3A\u5C11 --${name}\uFF08oasis \u4E0D\u5E26\u53C2\u6570\u53EF\u770B\u7528\u6CD5\uFF09`);
205191
206510
  return v2;
205192
206511
  }
206512
+ function parseChatTargetFlag(raw) {
206513
+ if (raw === void 0) return {};
206514
+ const v2 = raw.trim();
206515
+ if (v2 === "new") return { chatTarget: { mode: "new_session" } };
206516
+ if (v2 === "create") return { createChatTargetSession: true };
206517
+ if (v2.startsWith("session:")) {
206518
+ const sessionId = v2.slice("session:".length).trim();
206519
+ if (!sessionId) throw new Error("--chat-target session:<\u4F1A\u8BDDid> \u7F3A\u5C11\u4F1A\u8BDD id");
206520
+ return { chatTarget: { mode: "existing_session", sessionId } };
206521
+ }
206522
+ throw new Error(`--chat-target \u53EA\u80FD\u662F new | session:<\u4F1A\u8BDDid> | create\uFF08\u6536\u5230\uFF1A${raw}\uFF09`);
206523
+ }
205193
206524
  function needPos(positional, i, usage) {
205194
206525
  const v2 = positional[i];
205195
206526
  if (v2 === void 0) throw new Error(`\u7528\u6CD5: ${usage}`);
@@ -205658,7 +206989,7 @@ async function runCli(argv, println = console.log, progressln = console.error) {
205658
206989
  const store = createNodeTokenStore(path31.join(dir, "node-tokens.json"));
205659
206990
  const sub = positional[0];
205660
206991
  if (sub === "issue") {
205661
- const id = flags.get("id") ?? `node-${(0, import_node_crypto51.randomUUID)()}`;
206992
+ const id = flags.get("id") ?? `node-${(0, import_node_crypto52.randomUUID)()}`;
205662
206993
  const token2 = store.issue(id);
205663
206994
  println(token2);
205664
206995
  process.stderr.write(
@@ -206214,10 +207545,12 @@ ${p2.ref} ${p2.name}`);
206214
207545
  triggers.push({ kind: "webhook", webhook: { provider: flags.get("webhook") || "generic" } });
206215
207546
  }
206216
207547
  const mode = flags.get("mode") ?? (flags.has("agent") ? "chat" : "workorder");
207548
+ const chatTargetFlag = parseChatTargetFlag(flags.get("chat-target"));
206217
207549
  const result = await api.post("/api/automations", {
206218
207550
  name: need(flags, "name"),
206219
207551
  executionMode: mode,
206220
207552
  ...flags.has("agent") ? { agentId: flags.get("agent") } : {},
207553
+ ...chatTargetFlag,
206221
207554
  ...mode === "workorder" ? { playbookRef: need(flags, "playbook") } : {},
206222
207555
  ...flags.has("payload") ? { payload: JSON.parse(flags.get("payload")) } : {},
206223
207556
  ...flags.has("project") ? { projectId: flags.get("project") } : {},
@@ -206231,8 +207564,14 @@ ${p2.ref} ${p2.name}`);
206231
207564
  }
206232
207565
  case "automation-update": {
206233
207566
  const id = needPos(positional, 0, "oasis automation-update <id> [--name ...] [--enabled true|false] ...");
207567
+ const chatTargetFlag = parseChatTargetFlag(flags.get("chat-target"));
207568
+ if ("createChatTargetSession" in chatTargetFlag) {
207569
+ println(JSON.stringify(await api.post(`/api/automations/${encodeURIComponent(id)}/chat-target-session`, {}), null, 2));
207570
+ break;
207571
+ }
206234
207572
  const result = await api.request("PATCH", `/api/automations/${encodeURIComponent(id)}`, {
206235
207573
  ...flags.has("name") ? { name: flags.get("name") } : {},
207574
+ ...chatTargetFlag,
206236
207575
  ...flags.has("mode") ? { executionMode: flags.get("mode") } : {},
206237
207576
  ...flags.has("agent") ? { agentId: flags.get("agent") } : {},
206238
207577
  ...flags.has("playbook") ? { playbookRef: flags.get("playbook") } : {},
@@ -207576,6 +208915,34 @@ ${res.warning}`);
207576
208915
  println(message);
207577
208916
  break;
207578
208917
  }
208918
+ case "chat-turn": {
208919
+ const sid = needPos(positional, 0, "oasis chat-turn <\u4F1A\u8BDDid>");
208920
+ const { turn } = await api.request(
208921
+ "GET",
208922
+ `/api/chat-sessions/${encodeURIComponent(sid)}/turns/active`
208923
+ );
208924
+ if (!turn) {
208925
+ println("\u8FD9\u6761\u4F1A\u8BDD\u5F53\u524D\u7A7A\u95F2\uFF08\u6CA1\u6709 active \u8F6E\u6B21\uFF09\u3002");
208926
+ break;
208927
+ }
208928
+ println(JSON.stringify(turn, null, 2));
208929
+ println("");
208930
+ println(`\u5361\u4F4F\u4E86\uFF1F\u5148\u6838\u5B9E\u6267\u884C\u4EBA\u6B7B\u6CA1\u6B7B\uFF0C\u518D \`oasis chat-turn-settle ${sid} ${String(turn["id"])}\`\u3002`);
208931
+ break;
208932
+ }
208933
+ case "chat-turn-settle": {
208934
+ const sid = needPos(positional, 0, "oasis chat-turn-settle <\u4F1A\u8BDDid> <\u8F6E\u6B21id>");
208935
+ const turnId = needPos(positional, 1, "oasis chat-turn-settle <\u4F1A\u8BDDid> <\u8F6E\u6B21id>");
208936
+ const res = await api.post(
208937
+ `/api/chat-sessions/${encodeURIComponent(sid)}/turns/${encodeURIComponent(turnId)}/settle`,
208938
+ {
208939
+ ...flags.get("force") === "true" ? { force: true } : {},
208940
+ ...flags.has("reason") ? { reason: flags.get("reason") } : {}
208941
+ }
208942
+ );
208943
+ println(res.settled ? `\u5DF2\u7F6E\u7EC8\u6001\uFF1A${res.status}${res.reason ? `\uFF08${res.reason}\uFF09` : ""}\u2014\u2014\u4F1A\u8BDD\u69FD\u5DF2\u653E\u5F00\u3002` : `\u672A\u5904\u7F6E\uFF1A${res.reason ?? "\u8FD9\u4E00\u8F6E\u5DF2\u7ECF\u662F\u7EC8\u6001"}\u3002`);
208944
+ break;
208945
+ }
207579
208946
  case "queue": {
207580
208947
  const artifactId = needPos(positional, 0, "oasis queue <artifactId>");
207581
208948
  const { head, queue } = await api.view("queue", artifactId);
@@ -208204,12 +209571,12 @@ var LEGACY_SYSTEMD_UNIT = "oasis-node.service";
208204
209571
  var LEGACY_LAUNCHD_LABEL = "com.oasis.node";
208205
209572
 
208206
209573
  // src/install.ts
208207
- var import_node_crypto52 = require("node:crypto");
209574
+ var import_node_crypto53 = require("node:crypto");
208208
209575
  var fs41 = __toESM(require("node:fs"));
208209
209576
  var path35 = __toESM(require("node:path"));
208210
209577
  function versionLabel(sourceFile, pkgVersion, npmPrefixDir) {
208211
209578
  if (isFromNpmPrefix(sourceFile, npmPrefixDir)) return pkgVersion;
208212
- const digest = (0, import_node_crypto52.createHash)("sha256").update(fs41.readFileSync(sourceFile)).digest("hex").slice(0, 8);
209579
+ const digest = (0, import_node_crypto53.createHash)("sha256").update(fs41.readFileSync(sourceFile)).digest("hex").slice(0, 8);
208213
209580
  return `${pkgVersion}+local.${digest}`;
208214
209581
  }
208215
209582
  function isFromNpmPrefix(sourceFile, npmPrefixDir) {
@@ -208332,7 +209699,7 @@ function shimScript() {
208332
209699
  }
208333
209700
 
208334
209701
  // src/index.ts
208335
- var PKG_VERSION = true ? "0.1.120" : "dev";
209702
+ var PKG_VERSION = true ? "0.1.122" : "dev";
208336
209703
  var LOCAL_BIN = localBin();
208337
209704
  var NPM_PREFIX = npmPrefix();
208338
209705
  var INSTANCE = DEFAULT_INSTANCE;
@@ -208592,7 +209959,7 @@ function newInstanceName() {
208592
209959
  const existing = new Set(listInstances());
208593
209960
  if (!existing.has(DEFAULT_INSTANCE)) return DEFAULT_INSTANCE;
208594
209961
  for (; ; ) {
208595
- const n = `inst-${(0, import_node_crypto53.randomBytes)(3).toString("hex")}`;
209962
+ const n = `inst-${(0, import_node_crypto54.randomBytes)(3).toString("hex")}`;
208596
209963
  if (!existing.has(n)) return n;
208597
209964
  }
208598
209965
  }