@agimon-ai/doompi 0.0.1-alpha.91 → 0.0.1-alpha.92

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.
@@ -0,0 +1,57 @@
1
+ let node_child_process = require("node:child_process");
2
+ let node_timers_promises = require("node:timers/promises");
3
+ let node_url = require("node:url");
4
+ let node_util = require("node:util");
5
+ let _agimon_ai_log_sink_mcp = require("@agimon-ai/log-sink-mcp");
6
+ //#region src/builders/server/logSink.ts
7
+ const START_TIMEOUT_MS = 5e3;
8
+ const DISCOVERY_TIMEOUT_MS = 4e3;
9
+ const HEALTH_TIMEOUT_MS = 750;
10
+ const DISCOVERY_INTERVAL_MS = 1100;
11
+ const execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
12
+ async function healthy(env) {
13
+ let timer;
14
+ try {
15
+ return await Promise.race([(0, _agimon_ai_log_sink_mcp.resolveLogSinkPort)({
16
+ env,
17
+ healthCheck: true
18
+ }), new Promise((resolve) => {
19
+ timer = setTimeout(() => resolve(void 0), HEALTH_TIMEOUT_MS);
20
+ })]) !== void 0;
21
+ } finally {
22
+ if (timer) clearTimeout(timer);
23
+ }
24
+ }
25
+ /** Attach to the shared daemon or start it through its published CLI. Never own its shutdown. */
26
+ async function ensureGlobalLogSink(options) {
27
+ const { cwd, env, notice } = options;
28
+ let failure;
29
+ try {
30
+ if (await healthy(env)) return;
31
+ const cli = (0, node_url.fileURLToPath)({}.resolve("@agimon-ai/log-sink-mcp/cli"));
32
+ await execFileAsync(process.execPath, [
33
+ cli,
34
+ "start",
35
+ "--global",
36
+ "--http-only"
37
+ ], {
38
+ cwd,
39
+ env,
40
+ timeout: START_TIMEOUT_MS,
41
+ maxBuffer: 65536
42
+ });
43
+ } catch (error) {
44
+ failure = error instanceof Error ? error.message : String(error);
45
+ }
46
+ const deadline = Date.now() + DISCOVERY_TIMEOUT_MS;
47
+ do {
48
+ if (await healthy(env)) return;
49
+ if (Date.now() + DISCOVERY_INTERVAL_MS > deadline) break;
50
+ await (0, node_timers_promises.setTimeout)(DISCOVERY_INTERVAL_MS);
51
+ } while (Date.now() < deadline);
52
+ notice(`Global log sink unavailable: ${failure ?? "healthy discovery timed out"}. Telemetry remains nonfatal.`);
53
+ }
54
+ //#endregion
55
+ exports.ensureGlobalLogSink = ensureGlobalLogSink;
56
+
57
+ //# sourceMappingURL=logSink.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logSink.cjs","names":["promisify","execFile","resolveLogSinkPort","fileURLToPath","sleep"],"sources":["../../../../src/builders/server/logSink.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { fileURLToPath } from 'node:url';\nimport { promisify } from 'node:util';\n\nimport { resolveLogSinkPort } from '@agimon-ai/log-sink-mcp';\n\nconst START_TIMEOUT_MS = 5_000;\nconst DISCOVERY_TIMEOUT_MS = 4_000;\nconst HEALTH_TIMEOUT_MS = 750;\nconst DISCOVERY_INTERVAL_MS = 1_100;\n\nconst execFileAsync = promisify(execFile);\n\nasync function healthy(env: NodeJS.ProcessEnv): Promise<boolean> {\n // Registry discovery may wait on a broken daemon's health endpoint. The\n // server must still start, without allowing that endpoint to select a local\n // fallback or hold the startup path indefinitely.\n let timer: NodeJS.Timeout | undefined;\n try {\n const result = await Promise.race([\n resolveLogSinkPort({ env, healthCheck: true }),\n new Promise<undefined>((resolve) => {\n timer = setTimeout(() => resolve(undefined), HEALTH_TIMEOUT_MS);\n }),\n ]);\n return result !== undefined;\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n/** Attach to the shared daemon or start it through its published CLI. Never own its shutdown. */\nexport async function ensureGlobalLogSink(options: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n notice: (message: string) => void;\n}): Promise<void> {\n const { cwd, env, notice } = options;\n let failure: string | undefined;\n try {\n if (await healthy(env)) return;\n const cli = fileURLToPath(import.meta.resolve('@agimon-ai/log-sink-mcp/cli'));\n await execFileAsync(process.execPath, [cli, 'start', '--global', '--http-only'], {\n cwd,\n env,\n timeout: START_TIMEOUT_MS,\n maxBuffer: 64 * 1024,\n });\n } catch (error) {\n failure = error instanceof Error ? error.message : String(error);\n }\n\n const deadline = Date.now() + DISCOVERY_TIMEOUT_MS;\n do {\n if (await healthy(env)) return;\n if (Date.now() + DISCOVERY_INTERVAL_MS > deadline) break;\n await sleep(DISCOVERY_INTERVAL_MS);\n } while (Date.now() < deadline);\n\n notice(`Global log sink unavailable: ${failure ?? 'healthy discovery timed out'}. Telemetry remains nonfatal.`);\n}\n"],"mappings":";;;;;;AAOA,MAAM,mBAAmB;AACzB,MAAM,uBAAuB;AAC7B,MAAM,oBAAoB;AAC1B,MAAM,wBAAwB;AAE9B,MAAM,iBAAA,GAAgBA,UAAAA,UAAAA,CAAUC,mBAAAA,QAAQ;AAExC,eAAe,QAAQ,KAA0C;CAI/D,IAAI;CACJ,IAAI;EAOF,OAAO,MANc,QAAQ,KAAK,EAAA,GAChCC,wBAAAA,mBAAAA,CAAmB;GAAE;GAAK,aAAa;EAAK,CAAC,GAC7C,IAAI,SAAoB,YAAY;GAClC,QAAQ,iBAAiB,QAAQ,KAAA,CAAS,GAAG,iBAAiB;EAChE,CAAC,CACH,CAAC,MACiB,KAAA;CACpB,UAAU;EACR,IAAI,OAAO,aAAa,KAAK;CAC/B;AACF;;AAGA,eAAsB,oBAAoB,SAIxB;CAChB,MAAM,EAAE,KAAK,KAAK,WAAW;CAC7B,IAAI;CACJ,IAAI;EACF,IAAI,MAAM,QAAQ,GAAG,GAAG;EACxB,MAAM,OAAA,GAAMC,SAAAA,cAAAA,CAAAA,CAAAA,EAA0B,QAAQ,6BAA6B,CAAC;EAC5E,MAAM,cAAc,QAAQ,UAAU;GAAC;GAAK;GAAS;GAAY;EAAa,GAAG;GAC/E;GACA;GACA,SAAS;GACT,WAAW;EACb,CAAC;CACH,SAAS,OAAO;EACd,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CACjE;CAEA,MAAM,WAAW,KAAK,IAAI,IAAI;CAC9B,GAAG;EACD,IAAI,MAAM,QAAQ,GAAG,GAAG;EACxB,IAAI,KAAK,IAAI,IAAI,wBAAwB,UAAU;EACnD,OAAA,GAAMC,qBAAAA,WAAAA,CAAM,qBAAqB;CACnC,SAAS,KAAK,IAAI,IAAI;CAEtB,OAAO,gCAAgC,WAAW,8BAA8B,8BAA8B;AAChH"}
@@ -0,0 +1,57 @@
1
+ import { execFile } from "node:child_process";
2
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
3
+ import { fileURLToPath } from "node:url";
4
+ import { promisify } from "node:util";
5
+ import { resolveLogSinkPort } from "@agimon-ai/log-sink-mcp";
6
+ //#region src/builders/server/logSink.ts
7
+ const START_TIMEOUT_MS = 5e3;
8
+ const DISCOVERY_TIMEOUT_MS = 4e3;
9
+ const HEALTH_TIMEOUT_MS = 750;
10
+ const DISCOVERY_INTERVAL_MS = 1100;
11
+ const execFileAsync = promisify(execFile);
12
+ async function healthy(env) {
13
+ let timer;
14
+ try {
15
+ return await Promise.race([resolveLogSinkPort({
16
+ env,
17
+ healthCheck: true
18
+ }), new Promise((resolve) => {
19
+ timer = setTimeout(() => resolve(void 0), HEALTH_TIMEOUT_MS);
20
+ })]) !== void 0;
21
+ } finally {
22
+ if (timer) clearTimeout(timer);
23
+ }
24
+ }
25
+ /** Attach to the shared daemon or start it through its published CLI. Never own its shutdown. */
26
+ async function ensureGlobalLogSink(options) {
27
+ const { cwd, env, notice } = options;
28
+ let failure;
29
+ try {
30
+ if (await healthy(env)) return;
31
+ const cli = fileURLToPath(import.meta.resolve("@agimon-ai/log-sink-mcp/cli"));
32
+ await execFileAsync(process.execPath, [
33
+ cli,
34
+ "start",
35
+ "--global",
36
+ "--http-only"
37
+ ], {
38
+ cwd,
39
+ env,
40
+ timeout: START_TIMEOUT_MS,
41
+ maxBuffer: 65536
42
+ });
43
+ } catch (error) {
44
+ failure = error instanceof Error ? error.message : String(error);
45
+ }
46
+ const deadline = Date.now() + DISCOVERY_TIMEOUT_MS;
47
+ do {
48
+ if (await healthy(env)) return;
49
+ if (Date.now() + DISCOVERY_INTERVAL_MS > deadline) break;
50
+ await setTimeout$1(DISCOVERY_INTERVAL_MS);
51
+ } while (Date.now() < deadline);
52
+ notice(`Global log sink unavailable: ${failure ?? "healthy discovery timed out"}. Telemetry remains nonfatal.`);
53
+ }
54
+ //#endregion
55
+ export { ensureGlobalLogSink };
56
+
57
+ //# sourceMappingURL=logSink.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logSink.mjs","names":["sleep"],"sources":["../../../../src/builders/server/logSink.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { fileURLToPath } from 'node:url';\nimport { promisify } from 'node:util';\n\nimport { resolveLogSinkPort } from '@agimon-ai/log-sink-mcp';\n\nconst START_TIMEOUT_MS = 5_000;\nconst DISCOVERY_TIMEOUT_MS = 4_000;\nconst HEALTH_TIMEOUT_MS = 750;\nconst DISCOVERY_INTERVAL_MS = 1_100;\n\nconst execFileAsync = promisify(execFile);\n\nasync function healthy(env: NodeJS.ProcessEnv): Promise<boolean> {\n // Registry discovery may wait on a broken daemon's health endpoint. The\n // server must still start, without allowing that endpoint to select a local\n // fallback or hold the startup path indefinitely.\n let timer: NodeJS.Timeout | undefined;\n try {\n const result = await Promise.race([\n resolveLogSinkPort({ env, healthCheck: true }),\n new Promise<undefined>((resolve) => {\n timer = setTimeout(() => resolve(undefined), HEALTH_TIMEOUT_MS);\n }),\n ]);\n return result !== undefined;\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n/** Attach to the shared daemon or start it through its published CLI. Never own its shutdown. */\nexport async function ensureGlobalLogSink(options: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n notice: (message: string) => void;\n}): Promise<void> {\n const { cwd, env, notice } = options;\n let failure: string | undefined;\n try {\n if (await healthy(env)) return;\n const cli = fileURLToPath(import.meta.resolve('@agimon-ai/log-sink-mcp/cli'));\n await execFileAsync(process.execPath, [cli, 'start', '--global', '--http-only'], {\n cwd,\n env,\n timeout: START_TIMEOUT_MS,\n maxBuffer: 64 * 1024,\n });\n } catch (error) {\n failure = error instanceof Error ? error.message : String(error);\n }\n\n const deadline = Date.now() + DISCOVERY_TIMEOUT_MS;\n do {\n if (await healthy(env)) return;\n if (Date.now() + DISCOVERY_INTERVAL_MS > deadline) break;\n await sleep(DISCOVERY_INTERVAL_MS);\n } while (Date.now() < deadline);\n\n notice(`Global log sink unavailable: ${failure ?? 'healthy discovery timed out'}. Telemetry remains nonfatal.`);\n}\n"],"mappings":";;;;;;AAOA,MAAM,mBAAmB;AACzB,MAAM,uBAAuB;AAC7B,MAAM,oBAAoB;AAC1B,MAAM,wBAAwB;AAE9B,MAAM,gBAAgB,UAAU,QAAQ;AAExC,eAAe,QAAQ,KAA0C;CAI/D,IAAI;CACJ,IAAI;EAOF,OAAO,MANc,QAAQ,KAAK,CAChC,mBAAmB;GAAE;GAAK,aAAa;EAAK,CAAC,GAC7C,IAAI,SAAoB,YAAY;GAClC,QAAQ,iBAAiB,QAAQ,KAAA,CAAS,GAAG,iBAAiB;EAChE,CAAC,CACH,CAAC,MACiB,KAAA;CACpB,UAAU;EACR,IAAI,OAAO,aAAa,KAAK;CAC/B;AACF;;AAGA,eAAsB,oBAAoB,SAIxB;CAChB,MAAM,EAAE,KAAK,KAAK,WAAW;CAC7B,IAAI;CACJ,IAAI;EACF,IAAI,MAAM,QAAQ,GAAG,GAAG;EACxB,MAAM,MAAM,cAAc,YAAY,QAAQ,6BAA6B,CAAC;EAC5E,MAAM,cAAc,QAAQ,UAAU;GAAC;GAAK;GAAS;GAAY;EAAa,GAAG;GAC/E;GACA;GACA,SAAS;GACT,WAAW;EACb,CAAC;CACH,SAAS,OAAO;EACd,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CACjE;CAEA,MAAM,WAAW,KAAK,IAAI,IAAI;CAC9B,GAAG;EACD,IAAI,MAAM,QAAQ,GAAG,GAAG;EACxB,IAAI,KAAK,IAAI,IAAI,wBAAwB,UAAU;EACnD,MAAMA,aAAM,qBAAqB;CACnC,SAAS,KAAK,IAAI,IAAI;CAEtB,OAAO,gCAAgC,WAAW,8BAA8B,8BAA8B;AAChH"}
@@ -6,6 +6,7 @@ const require_index$3 = require("../cli/bootstrapLocator/index.cjs");
6
6
  const require_index$4 = require("../../composition/syncDrift/index.cjs");
7
7
  const require_harnessContext = require("../cli/harnessContext.cjs");
8
8
  const require_computerUseBinding = require("./computerUseBinding.cjs");
9
+ const require_logSink = require("./logSink.cjs");
9
10
  const require_selectionStatus = require("./selectionStatus.cjs");
10
11
  const require_sessionArguments = require("./sessionArguments.cjs");
11
12
  const require_sessionArtifact = require("./sessionArtifact.cjs");
@@ -53,7 +54,16 @@ async function bounded(operation, label, notice) {
53
54
  }
54
55
  }
55
56
  async function runServerRuntime(options, runtime) {
56
- const { cwd: baseCwd, environment: baseEnvironment, notice, resolveHarnessOptions, signal, syncWorkspace } = runtime;
57
+ const { cwd: baseCwd, environment: incomingEnvironment, notice, resolveHarnessOptions, signal, syncWorkspace } = runtime;
58
+ const baseEnvironment = {
59
+ ...incomingEnvironment,
60
+ LOG_SINK_INSTANCE: "global"
61
+ };
62
+ await require_logSink.ensureGlobalLogSink({
63
+ cwd: baseCwd,
64
+ env: baseEnvironment,
65
+ notice
66
+ }).catch((error) => notice(`Global log sink unavailable: ${error instanceof Error ? error.message : String(error)}`));
57
67
  const telemetry = (0, _agimon_ai_doompi_core_serverTelemetry.createServerTelemetry)({
58
68
  cwd: baseCwd,
59
69
  env: baseEnvironment,
@@ -61,6 +71,34 @@ async function runServerRuntime(options, runtime) {
61
71
  });
62
72
  const homeDirectory = baseEnvironment.HOME ?? node_os.default.homedir();
63
73
  const serverDirectory = node_path.default.join((0, _agimon_ai_doompi_core_piSettings.piAgentDirectory)(baseEnvironment), "server");
74
+ const requestReceipts = (0, _agimon_ai_doompi_core_history.createRequestReceipts)({ directory: node_path.default.join(serverDirectory, "request-receipts") });
75
+ const activeMedia = /* @__PURE__ */ new Set();
76
+ const mediaArbitration = {
77
+ register(busy) {
78
+ activeMedia.add(busy);
79
+ return () => {
80
+ activeMedia.delete(busy);
81
+ };
82
+ },
83
+ available(busy) {
84
+ return activeMedia.has(busy) && [...activeMedia].every((other) => other === busy || !other());
85
+ }
86
+ };
87
+ const activePeerAgents = /* @__PURE__ */ new Map();
88
+ const peerAgents = {
89
+ register(sessionId, agent, hubToken) {
90
+ if (activePeerAgents.has(sessionId)) throw new Error(`Voice peer agent '${sessionId}' is already registered.`);
91
+ const entry = {
92
+ fetch: (request) => agent.fetch(request),
93
+ hubToken
94
+ };
95
+ activePeerAgents.set(sessionId, entry);
96
+ return () => {
97
+ if (activePeerAgents.get(sessionId) === entry) activePeerAgents.delete(sessionId);
98
+ };
99
+ },
100
+ get: (sessionId) => activePeerAgents.get(sessionId)
101
+ };
64
102
  const workspaces = (0, _agimon_ai_doompi_core_history.createWorkspaceRegistry)({
65
103
  directory: serverDirectory,
66
104
  onNotice: notice
@@ -265,7 +303,10 @@ async function runServerRuntime(options, runtime) {
265
303
  environment: baseEnvironment,
266
304
  hubToken: token,
267
305
  sessionService: hub.sessionService,
306
+ mediaArbitration,
307
+ peerAgents,
268
308
  directEvents: hub.directEvents,
309
+ requestApi: (mount, basePath, request) => hub.requestApi(mount, basePath, request),
269
310
  repositories: () => hub.workspaces().map((workspace) => ({
270
311
  id: workspace.id,
271
312
  path: workspace.root,
@@ -316,6 +357,7 @@ async function runServerRuntime(options, runtime) {
316
357
  await hub.mountFacets(globalBundle.facets, {
317
358
  ...sharedApiContext,
318
359
  scope: "global",
360
+ requestReceipts: requestReceipts.receipts,
319
361
  remoteControl: { fetch: (request) => remoteRuntime.fetchLocal(request) }
320
362
  });
321
363
  webCompositions.publish({ scope: "global" }, (0, _agimon_ai_doompi_core_syncRegistration.readSyncRegistration)(globalRoot, homeDirectory), hub.channelTypes());
@@ -382,9 +424,11 @@ async function runServerRuntime(options, runtime) {
382
424
  for (const record of openSessions.list()) {
383
425
  if (workspaces.list().some((workspace) => workspace.id === record.workspaceId)) continue;
384
426
  try {
385
- const root = node_fs.default.realpathSync(require_index$1.findRepositoryRoot(record.cwd));
427
+ const candidate = record.groupingRoot ?? (record.sessionProvenance === "worktree" ? record.artifact?.root : record.cwd);
428
+ if (!candidate) throw new Error("Parent workspace location is unavailable.");
429
+ const root = node_fs.default.realpathSync(require_index$1.findRepositoryRoot(candidate));
386
430
  const id = (0, _agimon_ai_doompi_core_syncLocation.resolveSyncLocation)(root, homeDirectory).identity.worktreeId;
387
- if (id === record.workspaceId) workspaces.add({
431
+ if ((record.groupingRoot === void 0 || root === candidate) && id === record.workspaceId) workspaces.add({
388
432
  id,
389
433
  root
390
434
  });
@@ -427,6 +471,8 @@ async function runServerRuntime(options, runtime) {
427
471
  repoRoot: policyOptions.repoRoot,
428
472
  sessionId: identity.sessionId,
429
473
  workspaceId: workspaceId ?? (0, _agimon_ai_doompi_core_syncLocation.resolveSyncLocation)(policyOptions.repoRoot, homeDirectory).identity.worktreeId,
474
+ groupingRoot: workspaceId === void 0 ? policyOptions.repoRoot : hub.workspaces().find((workspace) => workspace.id === workspaceId)?.root,
475
+ ...identity.sessionProvenance === "worktree" ? { inheritedArtifact: registration } : {},
430
476
  sessionName: identity.sessionName,
431
477
  webComposition: webCompositions?.publish({
432
478
  scope: "session",
@@ -477,6 +523,7 @@ async function runServerRuntime(options, runtime) {
477
523
  cwd: sessionOptions.cwd,
478
524
  environment: sessionOptions.environment,
479
525
  directEvents: hub.directEvents,
526
+ requestApi: (mount, basePath, request) => hub.requestApi(mount, basePath, request),
480
527
  ...computerUse === void 0 ? {} : { computerUse: {
481
528
  get available() {
482
529
  return computerUse.available;
@@ -490,6 +537,8 @@ async function runServerRuntime(options, runtime) {
490
537
  } },
491
538
  hubToken: token,
492
539
  sessionService: hub.sessionService,
540
+ mediaArbitration,
541
+ peerAgents,
493
542
  pluginRegistry: hub.pluginRegistry,
494
543
  apis: [],
495
544
  facets: pendingSessions.get(sessionOptions.sessionId)?.bundle.facets ?? [],
@@ -560,7 +609,7 @@ async function runServerRuntime(options, runtime) {
560
609
  await bounded(harnessTelemetry.flush(), "initial composition telemetry flush", notice);
561
610
  }
562
611
  const reservedOpens = /* @__PURE__ */ new Map();
563
- openSession = async (request, sessionId, pinnedArtifact) => {
612
+ openSession = async (request, sessionId, pinnedArtifact, groupingWorkspaceId) => {
564
613
  request.signal?.throwIfAborted();
565
614
  if (request.reservationId !== void 0) {
566
615
  if (!request.parentSessionId || !cockpit) throw new Error("A reserved session requires its owning parent.");
@@ -591,7 +640,7 @@ async function runServerRuntime(options, runtime) {
591
640
  sessionProvenance: request.sessionProvenance
592
641
  };
593
642
  const isWorktree = request.sessionProvenance === "worktree";
594
- const inheritedWorkspaceId = isWorktree && request.parentSessionId ? hub.session(request.parentSessionId)?.workspaceId ?? openSessions.list().find((record) => record.sessionId === request.parentSessionId)?.workspaceId : void 0;
643
+ const inheritedWorkspaceId = isWorktree && request.parentSessionId ? groupingWorkspaceId ?? hub.session(request.parentSessionId)?.workspaceId ?? openSessions.list().find((record) => record.sessionId === request.parentSessionId)?.workspaceId : void 0;
595
644
  if (isWorktree && inheritedWorkspaceId === void 0) throw new Error("Worktree session requires its parent workspace.");
596
645
  const parentArtifact = request.parentSessionId === void 0 ? void 0 : sessionArtifacts.get(request.parentSessionId)?.registration ?? pendingSessions.get(request.parentSessionId)?.registration ?? openSessions.list().find((record) => record.sessionId === request.parentSessionId)?.artifact;
597
646
  const start = (async () => {
@@ -645,6 +694,8 @@ async function runServerRuntime(options, runtime) {
645
694
  sessionId: created.id,
646
695
  workspaceId: created.workspaceId,
647
696
  cwd: created.cwd,
697
+ repoRoot: childContext.options.repoRoot,
698
+ groupingRoot: hub.workspaces().find((workspace) => workspace.id === created.workspaceId)?.root,
648
699
  name: created.name,
649
700
  createdAt: created.createdAt,
650
701
  ...created.parentSessionId === void 0 ? {} : { parentSessionId: created.parentSessionId },
@@ -675,6 +726,22 @@ async function runServerRuntime(options, runtime) {
675
726
  };
676
727
  });
677
728
  const workspaceResumes = /* @__PURE__ */ new Map();
729
+ const savedHistory = async (workspaceId) => {
730
+ const root = hub.workspaces().find((workspace) => workspace.id === workspaceId && workspace.available !== false)?.root;
731
+ if (!root) throw new Error("Workspace not found.");
732
+ return (0, _agimon_ai_doompi_core_history.listSavedSessionRecords)(node_path.default.join(serverDirectory, "sessions"), root, new Set(hub.snapshot().map((active) => active.id)), workspaceId);
733
+ };
734
+ const validatedExecution = (target, workspaceId) => {
735
+ const root = hub.workspaces().find((workspace) => workspace.id === workspaceId && workspace.available !== false)?.root;
736
+ if (!root || target.groupingRoot !== root || target.workspaceId && target.workspaceId !== workspaceId) throw new Error("Saved session does not belong to this workspace.");
737
+ if (node_fs.default.realpathSync(require_index$1.findRepositoryRoot(target.cwd)) !== target.repoRoot) throw new Error("Saved session checkout is unavailable.");
738
+ if (target.sessionProvenance !== "worktree") return void 0;
739
+ if (!target.parentSessionId || target.inheritedArtifact === void 0) throw new Error("Saved worktree generation is unavailable.");
740
+ const artifact = (0, _agimon_ai_doompi_core_syncRegistration.parseSyncRegistration)(target.inheritedArtifact, "saved worktree generation");
741
+ (0, _agimon_ai_doompi_core_syncRegistration.validateSyncRegistration)(artifact, (0, _agimon_ai_doompi_core_syncLocation.resolveSyncLocation)(artifact.root, homeDirectory));
742
+ if (artifact.root !== root) throw new Error("Saved worktree generation belongs to another workspace.");
743
+ return artifact;
744
+ };
678
745
  cockpit = await (0, _agimon_ai_doompi_core_headlessServer.serveHeadlessServer)({
679
746
  port: options.webPort,
680
747
  headlessHub: hub,
@@ -683,25 +750,22 @@ async function runServerRuntime(options, runtime) {
683
750
  sessionMcpStateDir: serverDirectory,
684
751
  sessionMcpPublicOriginRevision: () => remoteRuntime?.remote.publicOriginRevision() ?? 0,
685
752
  isSessionPersisted: (sessionId, cwd) => openSessions.list().some((record) => record.sessionId === sessionId && record.cwd === cwd),
686
- workspaceHistory: (workspaceId) => {
687
- const workspaceRoot = hub.workspaces().find((workspace) => workspace.id === workspaceId && workspace.available !== false)?.root;
688
- if (!workspaceRoot) throw new Error("Workspace not found.");
689
- return (0, _agimon_ai_doompi_core_history.listSavedSessions)(node_path.default.join(serverDirectory, "sessions"), workspaceRoot, new Set(hub.snapshot().map((active) => active.id)));
690
- },
753
+ workspaceHistory: async (workspaceId) => (await savedHistory(workspaceId)).map((record) => record.summary),
691
754
  resumeWorkspaceSession: async (workspaceId, targetSessionId) => {
692
755
  const key = `${workspaceId}\0${targetSessionId}`;
693
756
  const pending = workspaceResumes.get(key);
694
757
  if (pending) return pending;
695
758
  const resume = (async () => {
696
- const workspaceRoot = hub.workspaces().find((workspace) => workspace.id === workspaceId && workspace.available !== false)?.root;
697
- if (!workspaceRoot) throw new Error("Workspace not found.");
698
- const target = (await (0, _agimon_ai_doompi_core_history.listSavedSessions)(node_path.default.join(serverDirectory, "sessions"), workspaceRoot, new Set(hub.snapshot().map((active) => active.id)))).find((item) => item.id === targetSessionId);
759
+ const target = (await savedHistory(workspaceId)).find((item) => item.summary.id === targetSessionId);
699
760
  if (!target) throw new Error("Saved Pi thread not found in this workspace.");
761
+ const artifact = validatedExecution(target.execution, workspaceId);
700
762
  await openSession({
701
- cwd: workspaceRoot,
702
- name: target.name ?? "untitled"
703
- }, target.id);
704
- return target.id;
763
+ cwd: target.execution.cwd,
764
+ name: target.summary.name ?? "untitled",
765
+ ...target.execution.parentSessionId ? { parentSessionId: target.execution.parentSessionId } : {},
766
+ ...target.execution.sessionProvenance ? { sessionProvenance: target.execution.sessionProvenance } : {}
767
+ }, target.summary.id, artifact, target.execution.sessionProvenance === "worktree" ? workspaceId : void 0);
768
+ return target.summary.id;
705
769
  })();
706
770
  workspaceResumes.set(key, resume);
707
771
  try {
@@ -713,19 +777,24 @@ async function runServerRuntime(options, runtime) {
713
777
  sessionHistory: (session) => {
714
778
  const workspaceRoot = hub.workspaces().find((workspace) => workspace.id === session.workspaceId)?.root;
715
779
  if (!workspaceRoot) throw new Error("Session workspace not found.");
716
- return (0, _agimon_ai_doompi_core_history.listSavedSessions)(node_path.default.join((0, _agimon_ai_doompi_core_piSettings.piAgentDirectory)(baseEnvironment), "server", "sessions"), workspaceRoot, new Set(hub.snapshot().map((active) => active.id)));
780
+ return (0, _agimon_ai_doompi_core_history.listSavedSessions)(node_path.default.join((0, _agimon_ai_doompi_core_piSettings.piAgentDirectory)(baseEnvironment), "server", "sessions"), workspaceRoot, new Set(hub.snapshot().map((active) => active.id)), session.workspaceId);
717
781
  },
718
782
  readDormantTranscript: (record, request, context) => {
719
783
  const workspaceRoot = hub.workspaces().find((workspace) => workspace.id === record.workspaceId)?.root;
720
- if (!workspaceRoot) throw new Error("Saved transcript unavailable.");
784
+ if (!workspaceRoot || record.groupingRoot !== void 0 && record.groupingRoot !== workspaceRoot) throw new Error("Saved transcript unavailable.");
785
+ const owner = node_fs.default.realpathSync(require_index$1.findRepositoryRoot(record.cwd));
786
+ if (record.repoRoot !== void 0 && record.repoRoot !== owner) throw new Error("Saved transcript checkout has changed.");
721
787
  return (0, _agimon_ai_doompi_core_history.readSqliteTranscript)(node_path.default.join((0, _agimon_ai_doompi_core_piSettings.piAgentDirectory)(baseEnvironment), "server", "sessions", `${record.sessionId}.sqlite`), request, context, {
722
788
  sessionId: record.sessionId,
723
- workspaceRoot
789
+ workspaceRoot: owner,
790
+ workspaceId: record.workspaceId,
791
+ groupingRoot: workspaceRoot
724
792
  });
725
793
  },
726
794
  restartSession: async (session) => {
727
795
  const worktree = session.sessionProvenance === "worktree";
728
796
  const artifact = sessionArtifacts.get(session.id)?.registration ?? openSessions.list().find((record) => record.sessionId === session.id)?.artifact;
797
+ if (worktree && !artifact) throw new Error("Worktree generation is unavailable; the session is still running.");
729
798
  if (!worktree) {
730
799
  const workspaceRoot = hub.workspaces().find((workspace) => workspace.id === session.workspaceId)?.root;
731
800
  if (!workspaceRoot) throw new Error("Session workspace not found.");
@@ -750,30 +819,33 @@ async function runServerRuntime(options, runtime) {
750
819
  }, session.id, worktree ? artifact : void 0);
751
820
  },
752
821
  resumeSession: async (session, targetSessionId) => {
753
- const workspaceRoot = hub.workspaces().find((workspace) => workspace.id === session.workspaceId)?.root;
754
- if (!workspaceRoot) throw new Error("Session workspace not found.");
755
- const target = (await (0, _agimon_ai_doompi_core_history.listSavedSessions)(node_path.default.join((0, _agimon_ai_doompi_core_piSettings.piAgentDirectory)(baseEnvironment), "server", "sessions"), workspaceRoot, new Set(hub.snapshot().map((active) => active.id)))).find((item) => item.id === targetSessionId);
822
+ const workspaceId = session.workspaceId;
823
+ if (!workspaceId || !hub.workspaces().some((workspace) => workspace.id === workspaceId)) throw new Error("Session workspace not found.");
824
+ const target = (await savedHistory(workspaceId)).find((item) => item.summary.id === targetSessionId);
756
825
  if (!target) throw new Error("Saved Pi thread not found in this workspace.");
757
- const worktree = session.sessionProvenance === "worktree";
758
- const artifact = sessionArtifacts.get(session.id)?.registration ?? openSessions.list().find((record) => record.sessionId === session.id)?.artifact;
826
+ const artifact = validatedExecution(target.execution, workspaceId);
759
827
  await hub.closeSession(session.id);
760
828
  await openSession({
761
- cwd: session.cwd,
762
- name: target.name ?? "untitled",
763
- ...session.parentSessionId === void 0 ? {} : { parentSessionId: session.parentSessionId },
764
- ...session.sessionProvenance === void 0 ? {} : { sessionProvenance: session.sessionProvenance }
765
- }, target.id, worktree ? artifact : void 0);
766
- return target.id;
829
+ cwd: target.execution.cwd,
830
+ name: target.summary.name ?? "untitled",
831
+ ...target.execution.parentSessionId ? { parentSessionId: target.execution.parentSessionId } : {},
832
+ ...target.execution.sessionProvenance ? { sessionProvenance: target.execution.sessionProvenance } : {}
833
+ }, target.summary.id, artifact, target.execution.sessionProvenance === "worktree" ? workspaceId : void 0);
834
+ return target.summary.id;
767
835
  },
768
836
  dormantSessions: () => openSessions.list(),
769
837
  removeDormantSession: (record) => openSessions.remove(record.sessionId),
770
838
  reviveSession: async (record) => {
839
+ const groupingRoot = hub.workspaces().find((workspace) => workspace.id === record.workspaceId && workspace.available !== false)?.root;
840
+ if (!groupingRoot || record.groupingRoot !== void 0 && groupingRoot !== record.groupingRoot) throw new Error("The session workspace is unavailable.");
841
+ if (record.repoRoot !== void 0 && node_fs.default.realpathSync(require_index$1.findRepositoryRoot(record.cwd)) !== record.repoRoot) throw new Error("The session checkout has changed.");
842
+ if (record.sessionProvenance === "worktree" && record.artifact?.root !== groupingRoot) throw new Error("The inherited worktree generation is unavailable.");
771
843
  await openSession({
772
844
  cwd: record.cwd,
773
845
  name: record.name,
774
846
  ...record.parentSessionId === void 0 ? {} : { parentSessionId: record.parentSessionId },
775
847
  ...record.sessionProvenance === void 0 ? {} : { sessionProvenance: record.sessionProvenance }
776
- }, record.sessionId, record.sessionProvenance === "worktree" ? record.artifact : void 0);
848
+ }, record.sessionId, record.sessionProvenance === "worktree" ? record.artifact : void 0, record.sessionProvenance === "worktree" ? record.workspaceId : void 0);
777
849
  },
778
850
  requestAsset: (request) => webCompositions?.request(request) ?? Promise.resolve(void 0),
779
851
  compositions: () => ({
@@ -816,6 +888,11 @@ async function runServerRuntime(options, runtime) {
816
888
  } catch (error) {
817
889
  notice(error instanceof Error ? error.message : String(error));
818
890
  }
891
+ try {
892
+ await requestReceipts.close();
893
+ } catch (error) {
894
+ notice(error instanceof Error ? error.message : String(error));
895
+ }
819
896
  try {
820
897
  await sessionManager.close();
821
898
  } catch (error) {