@agimon-ai/doompi 0.0.1-alpha.100 → 0.0.1-alpha.101

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -437,8 +437,8 @@ configured.
437
437
  A promise to "only plan" is not a permission boundary. [`@agimon-ai/doompi-plan`][pkg-doompi-plan]
438
438
  removes Pi's `edit` and `write` tools while the agent explores, persists the plan, and hands it back
439
439
  for approval. It does not sandbox Bash, external tools, or the operating system. `SPC p e` enters
440
- normal planning and leaves it again once on; `SPC p d` is debug planning and `SPC p f` the Fable
441
- flow.
440
+ normal planning and leaves it again once on; `SPC p d` is debug planning and `SPC p r` is Code Review
441
+ planning, with actionable findings and proposed fixes for approval.
442
442
  Turn it on when the approach should be settled before the files move.
443
443
 
444
444
  #### Loop mode
@@ -1,10 +1,15 @@
1
1
  #!/usr/bin/env node
2
+ const require_run = require("../src/cli/server/run.cjs");
2
3
  //#region src/bin/serve.ts
3
- require("../src/cli/server/run.cjs").runServer(process.argv.slice(2)).then((code) => {
4
+ function finish(code) {
4
5
  process.exitCode = code;
6
+ setTimeout(() => process.exit(code), 2e3).unref();
7
+ }
8
+ require_run.runServer(process.argv.slice(2)).then((code) => {
9
+ finish(code);
5
10
  }, (error) => {
6
11
  process.stderr.write(`[doompi-server] ${error instanceof Error ? error.message : String(error)}\n`);
7
- process.exitCode = 1;
12
+ finish(1);
8
13
  });
9
14
  //#endregion
10
15
 
@@ -1 +1 @@
1
- {"version":3,"file":"serve.cjs","names":["runServer"],"sources":["../../src/bin/serve.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { runServer } from '../cli/server/run';\n\nrunServer(process.argv.slice(2)).then(\n (code) => {\n process.exitCode = code;\n },\n (error: unknown) => {\n process.stderr.write(`[doompi-server] ${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n },\n);\n"],"mappings":";;mCAGAA,CAAAA,CAAAA,UAAU,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,MAC9B,SAAS;CACR,QAAQ,WAAW;AACrB,IACC,UAAmB;CAClB,QAAQ,OAAO,MAAM,mBAAmB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,GAAG;CAClG,QAAQ,WAAW;AACrB,CACF"}
1
+ {"version":3,"file":"serve.cjs","names":["runServer"],"sources":["../../src/bin/serve.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { runServer } from '../cli/server/run';\n\n// The library finishes bounded cleanup first. A stale handle must not keep this\n// standalone CLI alive forever; an unreferenced deadline still lets output drain.\nfunction finish(code: number): void {\n process.exitCode = code;\n setTimeout(() => process.exit(code), 2_000).unref();\n}\n\nrunServer(process.argv.slice(2)).then(\n (code) => {\n finish(code);\n },\n (error: unknown) => {\n process.stderr.write(`[doompi-server] ${error instanceof Error ? error.message : String(error)}\\n`);\n finish(1);\n },\n);\n"],"mappings":";;;AAKA,SAAS,OAAO,MAAoB;CAClC,QAAQ,WAAW;CACnB,iBAAiB,QAAQ,KAAK,IAAI,GAAG,GAAK,CAAC,CAAC,MAAM;AACpD;AAEAA,YAAAA,UAAU,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,MAC9B,SAAS;CACR,OAAO,IAAI;AACb,IACC,UAAmB;CAClB,QAAQ,OAAO,MAAM,mBAAmB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,GAAG;CAClG,OAAO,CAAC;AACV,CACF"}
@@ -1,11 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { runServer } from "../src/cli/server/run.mjs";
3
3
  //#region src/bin/serve.ts
4
- runServer(process.argv.slice(2)).then((code) => {
4
+ function finish(code) {
5
5
  process.exitCode = code;
6
+ setTimeout(() => process.exit(code), 2e3).unref();
7
+ }
8
+ runServer(process.argv.slice(2)).then((code) => {
9
+ finish(code);
6
10
  }, (error) => {
7
11
  process.stderr.write(`[doompi-server] ${error instanceof Error ? error.message : String(error)}\n`);
8
- process.exitCode = 1;
12
+ finish(1);
9
13
  });
10
14
  //#endregion
11
15
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"serve.mjs","names":[],"sources":["../../src/bin/serve.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { runServer } from '../cli/server/run';\n\nrunServer(process.argv.slice(2)).then(\n (code) => {\n process.exitCode = code;\n },\n (error: unknown) => {\n process.stderr.write(`[doompi-server] ${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n },\n);\n"],"mappings":";;;AAGA,UAAU,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,MAC9B,SAAS;CACR,QAAQ,WAAW;AACrB,IACC,UAAmB;CAClB,QAAQ,OAAO,MAAM,mBAAmB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,GAAG;CAClG,QAAQ,WAAW;AACrB,CACF"}
1
+ {"version":3,"file":"serve.mjs","names":[],"sources":["../../src/bin/serve.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { runServer } from '../cli/server/run';\n\n// The library finishes bounded cleanup first. A stale handle must not keep this\n// standalone CLI alive forever; an unreferenced deadline still lets output drain.\nfunction finish(code: number): void {\n process.exitCode = code;\n setTimeout(() => process.exit(code), 2_000).unref();\n}\n\nrunServer(process.argv.slice(2)).then(\n (code) => {\n finish(code);\n },\n (error: unknown) => {\n process.stderr.write(`[doompi-server] ${error instanceof Error ? error.message : String(error)}\\n`);\n finish(1);\n },\n);\n"],"mappings":";;;AAKA,SAAS,OAAO,MAAoB;CAClC,QAAQ,WAAW;CACnB,iBAAiB,QAAQ,KAAK,IAAI,GAAG,GAAK,CAAC,CAAC,MAAM;AACpD;AAEA,UAAU,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,MAC9B,SAAS;CACR,OAAO,IAAI;AACb,IACC,UAAmB;CAClB,QAAQ,OAAO,MAAM,mBAAmB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,GAAG;CAClG,OAAO,CAAC;AACV,CACF"}
@@ -0,0 +1,64 @@
1
+ //#region src/builders/server/executionBudget.ts
2
+ const MAX_QUEUED_JOBS = 128;
3
+ /** One host owns the queue; session callers only borrow execution slots. */
4
+ function createExecutionBudget(limit = 2) {
5
+ if (!Number.isSafeInteger(limit) || limit < 1) throw new RangeError("Heavy-job limit must be a positive integer");
6
+ let running = 0;
7
+ let closed = false;
8
+ const queue = [];
9
+ const drain = () => {
10
+ while (!closed && running < limit && queue.length) queue.shift().grant();
11
+ };
12
+ const lease = () => {
13
+ running++;
14
+ let released = false;
15
+ return () => {
16
+ if (released) return;
17
+ released = true;
18
+ running--;
19
+ drain();
20
+ };
21
+ };
22
+ return {
23
+ async acquire(signal) {
24
+ if (closed) throw new Error("The host execution budget is closed");
25
+ signal?.throwIfAborted();
26
+ if (running < limit) return lease();
27
+ if (queue.length >= MAX_QUEUED_JOBS) throw new Error("The host heavy-job queue is full");
28
+ return new Promise((resolve, reject) => {
29
+ const detach = () => signal?.removeEventListener("abort", onAbort);
30
+ const waiter = {
31
+ grant: () => {
32
+ detach();
33
+ if (signal?.aborted) reject(signal.reason);
34
+ else resolve(lease());
35
+ },
36
+ cancel: (reason) => {
37
+ detach();
38
+ const index = queue.indexOf(waiter);
39
+ if (index >= 0) queue.splice(index, 1);
40
+ reject(reason);
41
+ }
42
+ };
43
+ const onAbort = () => waiter.cancel(signal?.reason);
44
+ queue.push(waiter);
45
+ signal?.addEventListener("abort", onAbort, { once: true });
46
+ if (signal?.aborted) onAbort();
47
+ });
48
+ },
49
+ getSnapshot: () => ({
50
+ running,
51
+ queued: queue.length,
52
+ limit
53
+ }),
54
+ close() {
55
+ if (closed) return;
56
+ closed = true;
57
+ for (const waiter of queue.splice(0)) waiter.cancel(/* @__PURE__ */ new Error("The host execution budget is closed"));
58
+ }
59
+ };
60
+ }
61
+ //#endregion
62
+ exports.createExecutionBudget = createExecutionBudget;
63
+
64
+ //# sourceMappingURL=executionBudget.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executionBudget.cjs","names":[],"sources":["../../../../src/builders/server/executionBudget.ts"],"sourcesContent":["import type { DoomHostExecutionBudget } from '@agimon-ai/doompi-core/packageApi';\n\nconst MAX_QUEUED_JOBS = 128;\n\n/** One host owns the queue; session callers only borrow execution slots. */\nexport function createExecutionBudget(limit = 2): DoomHostExecutionBudget & { close(): void } {\n if (!Number.isSafeInteger(limit) || limit < 1) throw new RangeError('Heavy-job limit must be a positive integer');\n let running = 0;\n let closed = false;\n const queue: Array<{ grant(): void; cancel(reason: unknown): void }> = [];\n\n const drain = (): void => {\n while (!closed && running < limit && queue.length) queue.shift()!.grant();\n };\n const lease = (): (() => void) => {\n running++;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n running--;\n drain();\n };\n };\n\n return {\n async acquire(signal) {\n if (closed) throw new Error('The host execution budget is closed');\n signal?.throwIfAborted();\n if (running < limit) return lease();\n if (queue.length >= MAX_QUEUED_JOBS) throw new Error('The host heavy-job queue is full');\n return new Promise<() => void>((resolve, reject) => {\n const detach = (): void => signal?.removeEventListener('abort', onAbort);\n const waiter = {\n grant: () => {\n detach();\n if (signal?.aborted) reject(signal.reason);\n else resolve(lease());\n },\n cancel: (reason: unknown) => {\n detach();\n const index = queue.indexOf(waiter);\n if (index >= 0) queue.splice(index, 1);\n reject(reason);\n },\n };\n const onAbort = (): void => waiter.cancel(signal?.reason);\n queue.push(waiter);\n signal?.addEventListener('abort', onAbort, { once: true });\n if (signal?.aborted) onAbort();\n });\n },\n getSnapshot: () => ({ running, queued: queue.length, limit }),\n close() {\n if (closed) return;\n closed = true;\n for (const waiter of queue.splice(0)) waiter.cancel(new Error('The host execution budget is closed'));\n },\n };\n}\n"],"mappings":";AAEA,MAAM,kBAAkB;;AAGxB,SAAgB,sBAAsB,QAAQ,GAAgD;CAC5F,IAAI,CAAC,OAAO,cAAc,KAAK,KAAK,QAAQ,GAAG,MAAM,IAAI,WAAW,4CAA4C;CAChH,IAAI,UAAU;CACd,IAAI,SAAS;CACb,MAAM,QAAiE,CAAC;CAExE,MAAM,cAAoB;EACxB,OAAO,CAAC,UAAU,UAAU,SAAS,MAAM,QAAQ,MAAM,MAAM,CAAC,CAAE,MAAM;CAC1E;CACA,MAAM,cAA4B;EAChC;EACA,IAAI,WAAW;EACf,aAAa;GACX,IAAI,UAAU;GACd,WAAW;GACX;GACA,MAAM;EACR;CACF;CAEA,OAAO;EACL,MAAM,QAAQ,QAAQ;GACpB,IAAI,QAAQ,MAAM,IAAI,MAAM,qCAAqC;GACjE,QAAQ,eAAe;GACvB,IAAI,UAAU,OAAO,OAAO,MAAM;GAClC,IAAI,MAAM,UAAU,iBAAiB,MAAM,IAAI,MAAM,kCAAkC;GACvF,OAAO,IAAI,SAAqB,SAAS,WAAW;IAClD,MAAM,eAAqB,QAAQ,oBAAoB,SAAS,OAAO;IACvE,MAAM,SAAS;KACb,aAAa;MACX,OAAO;MACP,IAAI,QAAQ,SAAS,OAAO,OAAO,MAAM;WACpC,QAAQ,MAAM,CAAC;KACtB;KACA,SAAS,WAAoB;MAC3B,OAAO;MACP,MAAM,QAAQ,MAAM,QAAQ,MAAM;MAClC,IAAI,SAAS,GAAG,MAAM,OAAO,OAAO,CAAC;MACrC,OAAO,MAAM;KACf;IACF;IACA,MAAM,gBAAsB,OAAO,OAAO,QAAQ,MAAM;IACxD,MAAM,KAAK,MAAM;IACjB,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;IACzD,IAAI,QAAQ,SAAS,QAAQ;GAC/B,CAAC;EACH;EACA,oBAAoB;GAAE;GAAS,QAAQ,MAAM;GAAQ;EAAM;EAC3D,QAAQ;GACN,IAAI,QAAQ;GACZ,SAAS;GACT,KAAK,MAAM,UAAU,MAAM,OAAO,CAAC,GAAG,OAAO,uBAAO,IAAI,MAAM,qCAAqC,CAAC;EACtG;CACF;AACF"}
@@ -0,0 +1,64 @@
1
+ //#region src/builders/server/executionBudget.ts
2
+ const MAX_QUEUED_JOBS = 128;
3
+ /** One host owns the queue; session callers only borrow execution slots. */
4
+ function createExecutionBudget(limit = 2) {
5
+ if (!Number.isSafeInteger(limit) || limit < 1) throw new RangeError("Heavy-job limit must be a positive integer");
6
+ let running = 0;
7
+ let closed = false;
8
+ const queue = [];
9
+ const drain = () => {
10
+ while (!closed && running < limit && queue.length) queue.shift().grant();
11
+ };
12
+ const lease = () => {
13
+ running++;
14
+ let released = false;
15
+ return () => {
16
+ if (released) return;
17
+ released = true;
18
+ running--;
19
+ drain();
20
+ };
21
+ };
22
+ return {
23
+ async acquire(signal) {
24
+ if (closed) throw new Error("The host execution budget is closed");
25
+ signal?.throwIfAborted();
26
+ if (running < limit) return lease();
27
+ if (queue.length >= MAX_QUEUED_JOBS) throw new Error("The host heavy-job queue is full");
28
+ return new Promise((resolve, reject) => {
29
+ const detach = () => signal?.removeEventListener("abort", onAbort);
30
+ const waiter = {
31
+ grant: () => {
32
+ detach();
33
+ if (signal?.aborted) reject(signal.reason);
34
+ else resolve(lease());
35
+ },
36
+ cancel: (reason) => {
37
+ detach();
38
+ const index = queue.indexOf(waiter);
39
+ if (index >= 0) queue.splice(index, 1);
40
+ reject(reason);
41
+ }
42
+ };
43
+ const onAbort = () => waiter.cancel(signal?.reason);
44
+ queue.push(waiter);
45
+ signal?.addEventListener("abort", onAbort, { once: true });
46
+ if (signal?.aborted) onAbort();
47
+ });
48
+ },
49
+ getSnapshot: () => ({
50
+ running,
51
+ queued: queue.length,
52
+ limit
53
+ }),
54
+ close() {
55
+ if (closed) return;
56
+ closed = true;
57
+ for (const waiter of queue.splice(0)) waiter.cancel(/* @__PURE__ */ new Error("The host execution budget is closed"));
58
+ }
59
+ };
60
+ }
61
+ //#endregion
62
+ export { createExecutionBudget };
63
+
64
+ //# sourceMappingURL=executionBudget.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executionBudget.mjs","names":[],"sources":["../../../../src/builders/server/executionBudget.ts"],"sourcesContent":["import type { DoomHostExecutionBudget } from '@agimon-ai/doompi-core/packageApi';\n\nconst MAX_QUEUED_JOBS = 128;\n\n/** One host owns the queue; session callers only borrow execution slots. */\nexport function createExecutionBudget(limit = 2): DoomHostExecutionBudget & { close(): void } {\n if (!Number.isSafeInteger(limit) || limit < 1) throw new RangeError('Heavy-job limit must be a positive integer');\n let running = 0;\n let closed = false;\n const queue: Array<{ grant(): void; cancel(reason: unknown): void }> = [];\n\n const drain = (): void => {\n while (!closed && running < limit && queue.length) queue.shift()!.grant();\n };\n const lease = (): (() => void) => {\n running++;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n running--;\n drain();\n };\n };\n\n return {\n async acquire(signal) {\n if (closed) throw new Error('The host execution budget is closed');\n signal?.throwIfAborted();\n if (running < limit) return lease();\n if (queue.length >= MAX_QUEUED_JOBS) throw new Error('The host heavy-job queue is full');\n return new Promise<() => void>((resolve, reject) => {\n const detach = (): void => signal?.removeEventListener('abort', onAbort);\n const waiter = {\n grant: () => {\n detach();\n if (signal?.aborted) reject(signal.reason);\n else resolve(lease());\n },\n cancel: (reason: unknown) => {\n detach();\n const index = queue.indexOf(waiter);\n if (index >= 0) queue.splice(index, 1);\n reject(reason);\n },\n };\n const onAbort = (): void => waiter.cancel(signal?.reason);\n queue.push(waiter);\n signal?.addEventListener('abort', onAbort, { once: true });\n if (signal?.aborted) onAbort();\n });\n },\n getSnapshot: () => ({ running, queued: queue.length, limit }),\n close() {\n if (closed) return;\n closed = true;\n for (const waiter of queue.splice(0)) waiter.cancel(new Error('The host execution budget is closed'));\n },\n };\n}\n"],"mappings":";AAEA,MAAM,kBAAkB;;AAGxB,SAAgB,sBAAsB,QAAQ,GAAgD;CAC5F,IAAI,CAAC,OAAO,cAAc,KAAK,KAAK,QAAQ,GAAG,MAAM,IAAI,WAAW,4CAA4C;CAChH,IAAI,UAAU;CACd,IAAI,SAAS;CACb,MAAM,QAAiE,CAAC;CAExE,MAAM,cAAoB;EACxB,OAAO,CAAC,UAAU,UAAU,SAAS,MAAM,QAAQ,MAAM,MAAM,CAAC,CAAE,MAAM;CAC1E;CACA,MAAM,cAA4B;EAChC;EACA,IAAI,WAAW;EACf,aAAa;GACX,IAAI,UAAU;GACd,WAAW;GACX;GACA,MAAM;EACR;CACF;CAEA,OAAO;EACL,MAAM,QAAQ,QAAQ;GACpB,IAAI,QAAQ,MAAM,IAAI,MAAM,qCAAqC;GACjE,QAAQ,eAAe;GACvB,IAAI,UAAU,OAAO,OAAO,MAAM;GAClC,IAAI,MAAM,UAAU,iBAAiB,MAAM,IAAI,MAAM,kCAAkC;GACvF,OAAO,IAAI,SAAqB,SAAS,WAAW;IAClD,MAAM,eAAqB,QAAQ,oBAAoB,SAAS,OAAO;IACvE,MAAM,SAAS;KACb,aAAa;MACX,OAAO;MACP,IAAI,QAAQ,SAAS,OAAO,OAAO,MAAM;WACpC,QAAQ,MAAM,CAAC;KACtB;KACA,SAAS,WAAoB;MAC3B,OAAO;MACP,MAAM,QAAQ,MAAM,QAAQ,MAAM;MAClC,IAAI,SAAS,GAAG,MAAM,OAAO,OAAO,CAAC;MACrC,OAAO,MAAM;KACf;IACF;IACA,MAAM,gBAAsB,OAAO,OAAO,QAAQ,MAAM;IACxD,MAAM,KAAK,MAAM;IACjB,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;IACzD,IAAI,QAAQ,SAAS,QAAQ;GAC/B,CAAC;EACH;EACA,oBAAoB;GAAE;GAAS,QAAQ,MAAM;GAAQ;EAAM;EAC3D,QAAQ;GACN,IAAI,QAAQ;GACZ,SAAS;GACT,KAAK,MAAM,UAAU,MAAM,OAAO,CAAC,GAAG,OAAO,uBAAO,IAAI,MAAM,qCAAqC,CAAC;EACtG;CACF;AACF"}
@@ -7,10 +7,12 @@ 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
9
  const require_defaultWorkspace = require("./defaultWorkspace.cjs");
10
+ const require_executionBudget = require("./executionBudget.cjs");
10
11
  const require_logSink = require("./logSink.cjs");
11
12
  const require_selectionStatus = require("./selectionStatus.cjs");
12
13
  const require_sessionArguments = require("./sessionArguments.cjs");
13
14
  const require_sessionArtifact = require("./sessionArtifact.cjs");
15
+ const require_shutdown = require("./shutdown.cjs");
14
16
  const require_workspaceCheckout = require("./workspaceCheckout.cjs");
15
17
  let node_fs = require("node:fs");
16
18
  node_fs = require_runtime.__toESM(node_fs, 1);
@@ -43,17 +45,11 @@ ws = require_runtime.__toESM(ws, 1);
43
45
  let _agimon_ai_doompi_config_harnessStore = require("@agimon-ai/doompi-config/harnessStore");
44
46
  let _agimon_ai_doompi_config_harnessState = require("@agimon-ai/doompi-config/harnessState");
45
47
  //#region src/builders/server/runtime.ts
46
- const TELEMETRY_SHUTDOWN_TIMEOUT_MS = 2e3;
47
48
  async function bounded(operation, label, notice) {
48
- let timeout;
49
49
  try {
50
- await Promise.race([operation, new Promise((resolve) => {
51
- timeout = setTimeout(resolve, TELEMETRY_SHUTDOWN_TIMEOUT_MS);
52
- })]);
50
+ await require_shutdown.withShutdownDeadline(() => operation, label);
53
51
  } catch (error) {
54
52
  notice(`${label} failed: ${error instanceof Error ? error.message : String(error)}`);
55
- } finally {
56
- if (timeout) clearTimeout(timeout);
57
53
  }
58
54
  }
59
55
  async function runServerRuntime(options, runtime) {
@@ -62,6 +58,7 @@ async function runServerRuntime(options, runtime) {
62
58
  ...incomingEnvironment,
63
59
  LOG_SINK_INSTANCE: "global"
64
60
  };
61
+ const executionBudget = require_executionBudget.createExecutionBudget(Number(baseEnvironment.DOOM_RUNNER_MAX_HEAVY_JOBS ?? 2));
65
62
  await require_logSink.ensureGlobalLogSink({
66
63
  cwd: baseCwd,
67
64
  env: baseEnvironment,
@@ -111,9 +108,21 @@ async function runServerRuntime(options, runtime) {
111
108
  homeDirectory,
112
109
  onNotice: notice
113
110
  });
111
+ let lastBudgetSnapshot = "";
114
112
  let nextEventLoopTick = performance.now() + 1e3;
115
113
  const eventLoopMonitor = setInterval(() => {
116
114
  const now = performance.now();
115
+ const budget = executionBudget.getSnapshot();
116
+ const snapshot = JSON.stringify(budget);
117
+ if (snapshot !== lastBudgetSnapshot) {
118
+ lastBudgetSnapshot = snapshot;
119
+ telemetry.recordEvent("doompi_server.execution_budget", {
120
+ host_pid: process.pid,
121
+ running_jobs: budget.running,
122
+ queued_jobs: budget.queued,
123
+ limit: budget.limit
124
+ }).catch((error) => notice(`execution budget telemetry failed: ${String(error)}`));
125
+ }
117
126
  const delay = Math.max(0, now - nextEventLoopTick);
118
127
  nextEventLoopTick = now + 1e3;
119
128
  if (delay >= 100) telemetry.recordEvent("doompi_server.event_loop_delay", { duration_ms: Math.round(delay) }).catch((error) => notice(`event loop telemetry failed: ${String(error)}`));
@@ -142,8 +151,8 @@ async function runServerRuntime(options, runtime) {
142
151
  * it exists to survive.
143
152
  */
144
153
  let shuttingDown = false;
145
- const closeManagedSession = async (sessionId) => {
146
- if (!shuttingDown) openSessions.remove(sessionId);
154
+ const closeManagedSession = async (sessionId, options) => {
155
+ if (!shuttingDown && options?.keepDormant !== true) openSessions.remove(sessionId);
147
156
  const artifacts = sessionArtifacts.get(sessionId);
148
157
  sessionArtifacts.delete(sessionId);
149
158
  webCompositions?.remove({
@@ -152,17 +161,17 @@ async function runServerRuntime(options, runtime) {
152
161
  });
153
162
  const failures = [];
154
163
  try {
155
- await baseSessionManager.closeSession(sessionId);
164
+ await require_shutdown.withShutdownDeadline(() => baseSessionManager.closeSession(sessionId), `Session '${sessionId}' runtime shutdown`);
156
165
  } catch (error) {
157
166
  failures.push(error);
158
167
  }
159
168
  try {
160
- await artifacts?.apis.close();
169
+ await require_shutdown.withShutdownDeadline(() => artifacts?.apis.close(), `Session '${sessionId}' API shutdown`);
161
170
  } catch (error) {
162
171
  failures.push(error);
163
172
  }
164
173
  try {
165
- await artifacts?.cleanup();
174
+ await require_shutdown.withShutdownDeadline(() => artifacts?.cleanup(), `Session '${sessionId}' artifact cleanup`);
166
175
  } catch (error) {
167
176
  failures.push(error);
168
177
  }
@@ -318,6 +327,7 @@ async function runServerRuntime(options, runtime) {
318
327
  hubToken: token,
319
328
  sessionService: hub.sessionService,
320
329
  mediaArbitration,
330
+ executionBudget,
321
331
  peerAgents,
322
332
  directEvents: hub.directEvents,
323
333
  requestApi: (mount, basePath, request) => hub.requestApi(mount, basePath, request),
@@ -498,7 +508,7 @@ async function runServerRuntime(options, runtime) {
498
508
  notice(`Workspace '${workspace.root}' could not be restored: ${error instanceof Error ? error.message : String(error)}`);
499
509
  }
500
510
  }
501
- const sessionHostOptions = (context, bundle, mcpBundle, registration, identity, workspace) => {
511
+ const sessionHostOptions = (context, bundle, mcpBundle, registration, identity, workspace, explicit = { pinned: [] }) => {
502
512
  const policyOptions = context.options;
503
513
  const sessionSelection = {
504
514
  root: policyOptions.repoRoot,
@@ -506,7 +516,7 @@ async function runServerRuntime(options, runtime) {
506
516
  activeLayers: context.selectedLayers,
507
517
  domains: policyOptions.domains,
508
518
  profile: context.profile,
509
- state: { "minor-mode": [] }
519
+ state: { "minor-mode": [...explicit.minorModes ?? []] }
510
520
  };
511
521
  const piBootstrap = require_index$3.readRegisteredBootstrapStatus(registration, void 0, homeDirectory);
512
522
  if (!piBootstrap.fresh || piBootstrap.bootstrap === void 0) throw new Error(`The admitted DoomPi bootstrap for generation '${registration.generation}' is unavailable.`);
@@ -528,11 +538,13 @@ async function runServerRuntime(options, runtime) {
528
538
  environment: Object.freeze({ ...context.environment }),
529
539
  piExtensionPaths: [piBootstrap.bootstrap],
530
540
  selection: sessionSelection,
541
+ ...explicit.allowedTools === void 0 ? {} : { allowedTools: explicit.allowedTools },
531
542
  selectionOverrides: [
532
543
  "majorMode",
533
544
  "domains",
534
545
  "profile"
535
546
  ].filter((axis) => {
547
+ if (explicit.pinned.includes(axis)) return true;
536
548
  const flag = axis === "majorMode" ? "--major-mode" : `--${axis}`;
537
549
  return identity.sessionId === resolved.identity.sessionId && resolved.agentArgs.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
538
550
  }),
@@ -583,6 +595,7 @@ async function runServerRuntime(options, runtime) {
583
595
  hubToken: token,
584
596
  sessionService: hub.sessionService,
585
597
  mediaArbitration,
598
+ executionBudget,
586
599
  peerAgents,
587
600
  pluginRegistry: hub.pluginRegistry,
588
601
  apis: [],
@@ -687,9 +700,13 @@ async function runServerRuntime(options, runtime) {
687
700
  };
688
701
  const inheritedWorkspaceId = groupingWorkspaceId ?? (request.parentSessionId ? hub.session(request.parentSessionId)?.workspaceId ?? openSessions.list().find((record) => record.sessionId === request.parentSessionId)?.workspaceId : void 0);
689
702
  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;
703
+ const explicit = require_sessionArguments.sessionSelectionArgs(request);
690
704
  const start = (async () => {
691
705
  const childIdentity = require_sessionArguments.resolveSessionIdentity([], identity);
692
- const childEnvironment = { ...baseEnvironment };
706
+ const childEnvironment = {
707
+ ...baseEnvironment,
708
+ ...request.environment
709
+ };
693
710
  for (const key of [_agimon_ai_doompi_config_harnessStore.HARNESS_STATE_POINTER, ...Object.values(_agimon_ai_doompi_config_harnessState.HARNESS_STATE_KEYS)]) delete childEnvironment[key];
694
711
  const workspace = inheritedWorkspaceId === void 0 ? await admitWorkspace(request.cwd) : hub.workspaces().find((candidate) => candidate.id === inheritedWorkspaceId && candidate.available !== false);
695
712
  if (workspace === void 0) throw new Error("The session workspace is unavailable.");
@@ -698,7 +715,8 @@ async function runServerRuntime(options, runtime) {
698
715
  args: [
699
716
  "--cwd",
700
717
  request.cwd,
701
- ...childIdentity.agentArgs
718
+ ...childIdentity.agentArgs,
719
+ ...explicit.args
702
720
  ],
703
721
  cwd: request.cwd,
704
722
  environment: childEnvironment,
@@ -732,7 +750,11 @@ async function runServerRuntime(options, runtime) {
732
750
  mcpBundle,
733
751
  registration
734
752
  });
735
- const created = await hub.create(sessionHostOptions(childContext, bundle, mcpBundle, registration, identity, workspace));
753
+ const created = await hub.create(sessionHostOptions(childContext, bundle, mcpBundle, registration, identity, workspace, {
754
+ pinned: explicit.pinned,
755
+ ...request.selection?.minorModes === void 0 ? {} : { minorModes: request.selection.minorModes },
756
+ ...request.tools === void 0 ? {} : { allowedTools: request.tools }
757
+ }));
736
758
  request.signal?.throwIfAborted();
737
759
  if (created.workspaceId !== void 0) {
738
760
  if (openSessions.add({
@@ -939,29 +961,22 @@ async function runServerRuntime(options, runtime) {
939
961
  }
940
962
  } finally {
941
963
  shuttingDown = true;
964
+ executionBudget.close();
942
965
  stopPersistSessionNames();
943
966
  clearInterval(eventLoopMonitor);
944
967
  await bounded(telemetry.recordEvent("doompi_server.shutdown"), "shutdown telemetry", notice);
945
- await Promise.allSettled([cockpit?.close()]);
968
+ await bounded(require_shutdown.withShutdownDeadline(() => cockpit?.close(), "listener shutdown"), "listener shutdown", notice);
946
969
  await bounded(remoteRuntime?.close() ?? Promise.resolve(), "remote control shutdown", notice);
947
- try {
948
- await hub.close();
949
- } catch (error) {
950
- notice(error instanceof Error ? error.message : String(error));
951
- }
952
- try {
953
- await requestReceipts.close();
954
- } catch (error) {
955
- notice(error instanceof Error ? error.message : String(error));
956
- }
957
970
  try {
958
971
  await sessionManager.close();
959
972
  } catch (error) {
960
973
  notice(error instanceof Error ? error.message : String(error));
961
974
  }
962
- computerUse?.close?.();
963
- webCompositions?.close();
964
- const pendingCleanups = [...pendingSessions.values()].map((setup) => setup.cleanup());
975
+ await bounded(require_shutdown.withShutdownDeadline(() => hub.close(), "hub shutdown"), "hub shutdown", notice);
976
+ await bounded(require_shutdown.withShutdownDeadline(() => requestReceipts.close(), "receipt shutdown"), "receipt shutdown", notice);
977
+ await bounded(require_shutdown.withShutdownDeadline(() => computerUse?.close?.(), "computer use shutdown"), "computer use shutdown", notice);
978
+ await bounded(require_shutdown.withShutdownDeadline(() => webCompositions?.close(), "web composition shutdown"), "web composition shutdown", notice);
979
+ const pendingCleanups = [...pendingSessions.values()].map((setup) => bounded(require_shutdown.withShutdownDeadline(() => setup.cleanup(), "pending session cleanup"), "pending session cleanup", notice));
965
980
  pendingSessions.clear();
966
981
  await Promise.allSettled(pendingCleanups);
967
982
  await bounded(harnessTelemetry.flush(), "harness telemetry flush", notice);