@elpapi42/pi-fleet 0.1.0-beta.2 → 0.1.0-beta.5

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.
@@ -1,55 +1,55 @@
1
1
  {
2
- "schemaVersion": 2,
2
+ "schemaVersion": 3,
3
3
  "package": {
4
4
  "name": "@elpapi42/pi-fleet",
5
- "version": "0.1.0-beta.2"
5
+ "version": "0.1.0-beta.5"
6
6
  },
7
7
  "managedPi": "@earendil-works/pi-coding-agent@0.80.10",
8
8
  "files": [
9
9
  {
10
10
  "path": "dist/cli-meta.json",
11
- "bytes": 21246,
12
- "sha256": "a8ff1d8172d10a74c0a2b3a1c01b814551f8a32d28154cb44859a4af1cb6d361"
11
+ "bytes": 21248,
12
+ "sha256": "e4ed20fc94195e21b0da78bb789d54a74913fe9db6ea7b8041a201873447f957"
13
13
  },
14
14
  {
15
15
  "path": "dist/cli.mjs",
16
- "bytes": 144920,
17
- "sha256": "e1ac407f4123f3990bb22d6978307d7f8a70f05faf41399c755a21929dc5d9f5"
16
+ "bytes": 150369,
17
+ "sha256": "4ed384c54fa9b6bc4c9b80fcde2e9c0106ef902a0518baeb71565566fe0501dc"
18
18
  },
19
19
  {
20
20
  "path": "dist/cli.mjs.map",
21
- "bytes": 260315,
22
- "sha256": "4d77fe125e07ce2d12b7b33ca60617860c57783363b882c2981f6926e244e945"
21
+ "bytes": 270001,
22
+ "sha256": "449c833189699a96d06ece97e9e3ba06cce92c0483dc3f1dce2f7dd1e1fdf5ab"
23
23
  },
24
24
  {
25
25
  "path": "dist/installer-meta.json",
26
- "bytes": 6625,
27
- "sha256": "c7570b9253ca9335bf5a074e2467eedac63ce5bef25296694f8faa50c381353d"
26
+ "bytes": 6627,
27
+ "sha256": "d5cb292d680714f5b985347fdcb2b1b67e5e8de0331e3432e5da15389ace2e86"
28
28
  },
29
29
  {
30
30
  "path": "dist/installer.mjs",
31
- "bytes": 19836,
32
- "sha256": "8cbd21b9134c025581e2ae61105e35687b4ff008faa3ac50b40ba8f1865794fd"
31
+ "bytes": 25294,
32
+ "sha256": "1d98fb50b317ac6a1ba240f6ec6e5235aca315e1ea8ac11b5a30e4daac6d0280"
33
33
  },
34
34
  {
35
35
  "path": "dist/installer.mjs.map",
36
- "bytes": 37234,
37
- "sha256": "1253445c252391fe33f5bfebbc31f6cf6167a21ec24c50b2423f5f7108d44812"
36
+ "bytes": 46947,
37
+ "sha256": "b2106cebe6377bce8c2d316ee10a6f0ed778ef0b849a19138e2a092ce77ed59f"
38
38
  },
39
39
  {
40
40
  "path": "dist/runtime-meta.json",
41
- "bytes": 241009,
42
- "sha256": "ddefdd94b406d38ccac9293b83718dd4ef52c3a0e8b7122ec5720d443167cbfb"
41
+ "bytes": 241246,
42
+ "sha256": "2ab87b9b582fa0d7469f038bbddb014fe2f05dfe53bdc99154f4d01dbd683ebc"
43
43
  },
44
44
  {
45
45
  "path": "dist/runtime.mjs",
46
- "bytes": 318107,
47
- "sha256": "cbd2a3de2b5e12cc55d478ddf60ba367a2d21faa24e6a4a658c6128607cfd5f7"
46
+ "bytes": 320605,
47
+ "sha256": "2628c307f631e1ac30ff580252252c20fc517e716c875f5332f9140d72159d2b"
48
48
  },
49
49
  {
50
50
  "path": "dist/runtime.mjs.map",
51
- "bytes": 695723,
52
- "sha256": "7ca452f3b15d7bd73de52c210591dbd8df14a6284902379306833dc6ad0b8010"
51
+ "bytes": 700561,
52
+ "sha256": "c6f300c13c6c3b985e73c30ec4fc32926cdcaf444d9e234954e9623d2862a3ce"
53
53
  },
54
54
  {
55
55
  "path": "dist/sqlite-worker-meta.json",
@@ -79,27 +79,24 @@
79
79
  {
80
80
  "path": "package.json",
81
81
  "bytes": 2375,
82
- "sha256": "967198e3cf00529d57adbe818c27a800727f410552b17aa951e03666082b4c92"
82
+ "sha256": "103d0802e5d1df06d193a9f7d0da60303ae0c24ca23c915b0947140af1c7d29c"
83
83
  }
84
84
  ],
85
- "trees": [
85
+ "dependencies": [
86
86
  {
87
87
  "path": "node_modules/@earendil-works/pi-coding-agent",
88
- "files": 18553,
89
- "bytes": 117237369,
90
- "sha256": "778e81763d0fdcb2fb8bfc528adfcf8aebe36729223cc9898b4eace971c437fb"
88
+ "name": "@earendil-works/pi-coding-agent",
89
+ "version": "0.80.10"
91
90
  },
92
91
  {
93
92
  "path": "node_modules/@sinclair/typebox",
94
- "files": 1074,
95
- "bytes": 1907776,
96
- "sha256": "81ac95c0040ae77129a0f7f0797810bcf3ceff766c7ec20ca033c7358069b911"
93
+ "name": "@sinclair/typebox",
94
+ "version": "0.34.52"
97
95
  },
98
96
  {
99
97
  "path": "node_modules/commander",
100
- "files": 12,
101
- "bytes": 207368,
102
- "sha256": "55c788d06a44c350783898c6f830c0c54fde83b063c471a7efa363e2de081230"
98
+ "name": "commander",
99
+ "version": "15.0.0"
103
100
  }
104
101
  ]
105
102
  }
@@ -1,12 +1,18 @@
1
1
  {
2
2
  "inputs": {
3
3
  "src/platform/runtime/process-tree.ts": {
4
- "bytes": 1413,
5
- "imports": [],
4
+ "bytes": 2999,
5
+ "imports": [
6
+ {
7
+ "path": "node:fs",
8
+ "kind": "import-statement",
9
+ "external": true
10
+ }
11
+ ],
6
12
  "format": "esm"
7
13
  },
8
14
  "src/pi/process.ts": {
9
- "bytes": 8414,
15
+ "bytes": 8906,
10
16
  "imports": [
11
17
  {
12
18
  "path": "node:crypto",
@@ -6244,7 +6250,7 @@
6244
6250
  "format": "esm"
6245
6251
  },
6246
6252
  "src/runtime/agent-coordinator.ts": {
6247
- "bytes": 7786,
6253
+ "bytes": 8287,
6248
6254
  "imports": [
6249
6255
  {
6250
6256
  "path": "src/platform/runtime/process-tree.ts",
@@ -6266,7 +6272,7 @@
6266
6272
  "format": "esm"
6267
6273
  },
6268
6274
  "src/runtime/fleet-service.ts": {
6269
- "bytes": 34550,
6275
+ "bytes": 34926,
6270
6276
  "imports": [
6271
6277
  {
6272
6278
  "path": "node:crypto",
@@ -6379,7 +6385,7 @@
6379
6385
  "imports": [],
6380
6386
  "exports": [],
6381
6387
  "inputs": {},
6382
- "bytes": 695723
6388
+ "bytes": 700561
6383
6389
  },
6384
6390
  "dist/runtime.mjs": {
6385
6391
  "imports": [
@@ -6403,6 +6409,11 @@
6403
6409
  "kind": "import-statement",
6404
6410
  "external": true
6405
6411
  },
6412
+ {
6413
+ "path": "node:fs",
6414
+ "kind": "import-statement",
6415
+ "external": true
6416
+ },
6406
6417
  {
6407
6418
  "path": "node:url",
6408
6419
  "kind": "import-statement",
@@ -6468,10 +6479,10 @@
6468
6479
  "bytesInOutput": 1362
6469
6480
  },
6470
6481
  "src/pi/process.ts": {
6471
- "bytesInOutput": 6698
6482
+ "bytesInOutput": 7122
6472
6483
  },
6473
6484
  "src/platform/runtime/process-tree.ts": {
6474
- "bytesInOutput": 993
6485
+ "bytesInOutput": 2270
6475
6486
  },
6476
6487
  "src/pi/adapter.ts": {
6477
6488
  "bytesInOutput": 880
@@ -6939,7 +6950,7 @@
6939
6950
  "bytesInOutput": 108
6940
6951
  },
6941
6952
  "src/runtime/fleet-service.ts": {
6942
- "bytesInOutput": 29674
6953
+ "bytesInOutput": 29982
6943
6954
  },
6944
6955
  "src/pi/session-selector.ts": {
6945
6956
  "bytesInOutput": 2113
@@ -6948,7 +6959,7 @@
6948
6959
  "bytesInOutput": 2665
6949
6960
  },
6950
6961
  "src/runtime/agent-coordinator.ts": {
6951
- "bytesInOutput": 6762
6962
+ "bytesInOutput": 7251
6952
6963
  },
6953
6964
  "src/runtime/session-tail-subscription.ts": {
6954
6965
  "bytesInOutput": 4844
@@ -6957,7 +6968,7 @@
6957
6968
  "bytesInOutput": 3543
6958
6969
  }
6959
6970
  },
6960
- "bytes": 318107
6971
+ "bytes": 320605
6961
6972
  }
6962
6973
  }
6963
6974
  }
package/dist/runtime.mjs CHANGED
@@ -13,6 +13,7 @@ import { once } from "node:events";
13
13
  import { spawn } from "node:child_process";
14
14
 
15
15
  // src/platform/runtime/process-tree.ts
16
+ import { readFileSync, readdirSync } from "node:fs";
16
17
  function signalProcessTree(pid, signal) {
17
18
  try {
18
19
  if (process.platform === "win32") process.kill(pid, signal);
@@ -22,15 +23,51 @@ function signalProcessTree(pid, signal) {
22
23
  }
23
24
  }
24
25
  function isProcessAlive(pid) {
26
+ if (process.platform === "linux") {
27
+ const record = readLinuxProcess(pid);
28
+ if (record === null) return false;
29
+ if (record !== void 0) return isRunningState(record.state);
30
+ }
25
31
  return canSignal(pid);
26
32
  }
27
33
  function isProcessGroupAlive(processGroupId) {
28
34
  if (process.platform === "win32") return isProcessAlive(processGroupId);
35
+ if (process.platform === "linux") {
36
+ try {
37
+ for (const entry of readdirSync("/proc")) {
38
+ if (!/^\d+$/.test(entry)) continue;
39
+ const record = readLinuxProcess(Number(entry));
40
+ if (record !== null && record !== void 0 && record.processGroupId === processGroupId && isRunningState(record.state)) {
41
+ return true;
42
+ }
43
+ }
44
+ return false;
45
+ } catch {
46
+ }
47
+ }
29
48
  return canSignal(-processGroupId);
30
49
  }
31
50
  async function waitForProcessGroupExit(processGroupId, timeoutMs = 1e3) {
32
51
  return waitUntilGone(() => isProcessGroupAlive(processGroupId), timeoutMs);
33
52
  }
53
+ function readLinuxProcess(pid) {
54
+ try {
55
+ const stat2 = readFileSync(`/proc/${String(pid)}/stat`, "utf8");
56
+ const commandEnd = stat2.lastIndexOf(")");
57
+ if (commandEnd < 0) return void 0;
58
+ const fields = stat2.slice(commandEnd + 2).trim().split(/\s+/);
59
+ const state = fields[0];
60
+ const processGroupId = Number(fields[2]);
61
+ if (state === void 0 || !Number.isSafeInteger(processGroupId)) return void 0;
62
+ return { state, processGroupId };
63
+ } catch (error) {
64
+ if (error.code === "ENOENT") return null;
65
+ return void 0;
66
+ }
67
+ }
68
+ function isRunningState(state) {
69
+ return state !== "Z" && state !== "X";
70
+ }
34
71
  function canSignal(pid) {
35
72
  try {
36
73
  process.kill(pid, 0);
@@ -67,8 +104,14 @@ var PiProcess = class _PiProcess {
67
104
  #stdoutBuffer = Buffer.alloc(0);
68
105
  #stderr = "";
69
106
  #stopping = false;
107
+ #handledExit = false;
108
+ #exitHandled;
109
+ #resolveExitHandled = () => void 0;
70
110
  #maxStdoutFrameBytes;
71
111
  constructor(options) {
112
+ this.#exitHandled = new Promise((resolve2) => {
113
+ this.#resolveExitHandled = resolve2;
114
+ });
72
115
  this.#maxStdoutFrameBytes = options.maxStdoutFrameBytes ?? DEFAULT_MAX_STDOUT_FRAME_BYTES;
73
116
  this.#child = spawn(
74
117
  options.executable,
@@ -156,21 +199,26 @@ var PiProcess = class _PiProcess {
156
199
  }
157
200
  async stop() {
158
201
  if (this.#stopping) {
159
- if (!await waitForProcessGroupExit(this.pid)) {
202
+ if (!await this.#waitForExit(1e3)) {
160
203
  throw new Error(`Pi process group ${String(this.pid)} is still running`);
161
204
  }
162
205
  return;
163
206
  }
164
207
  this.#stopping = true;
165
208
  if (this.#child.exitCode === null) this.#child.stdin.end();
166
- if (await waitForProcessGroupExit(this.pid, 500)) return;
209
+ if (await this.#waitForExit(500)) return;
167
210
  signalProcessTree(this.pid, "SIGTERM");
168
- if (await waitForProcessGroupExit(this.pid, 1e3)) return;
211
+ if (await this.#waitForExit(1e3)) return;
169
212
  signalProcessTree(this.pid, "SIGKILL");
170
- if (!await waitForProcessGroupExit(this.pid, 1e3)) {
213
+ if (!await this.#waitForExit(1e3)) {
171
214
  throw new Error(`Pi process group ${String(this.pid)} did not exit after SIGKILL`);
172
215
  }
173
216
  }
217
+ async #waitForExit(timeoutMs) {
218
+ if (!await waitForProcessGroupExit(this.pid, timeoutMs)) return false;
219
+ await this.#exitHandled;
220
+ return true;
221
+ }
174
222
  async #write(frame) {
175
223
  if (this.#child.stdin.write(`${JSON.stringify(frame)}
176
224
  `)) return;
@@ -219,13 +267,19 @@ var PiProcess = class _PiProcess {
219
267
  }
220
268
  }
221
269
  #handleExit(code, signal, cause) {
270
+ if (this.#handledExit) return;
271
+ this.#handledExit = true;
222
272
  const error = this.#stopping && (code === 0 || signal === "SIGTERM") ? null : cause ?? new Error(`Pi exited unexpectedly (code=${String(code)}, signal=${String(signal)})`);
223
- for (const waiter of this.#responses.values()) {
224
- clearTimeout(waiter.timer);
225
- waiter.reject(error ?? new Error("Pi stopped before responding"));
273
+ try {
274
+ for (const waiter of this.#responses.values()) {
275
+ clearTimeout(waiter.timer);
276
+ waiter.reject(error ?? new Error("Pi stopped before responding"));
277
+ }
278
+ this.#responses.clear();
279
+ for (const listener of this.#exitListeners) listener(error);
280
+ } finally {
281
+ this.#resolveExitHandled();
226
282
  }
227
- this.#responses.clear();
228
- for (const listener of this.#exitListeners) listener(error);
229
283
  }
230
284
  };
231
285
 
@@ -6954,29 +7008,42 @@ var AgentCoordinator = class {
6954
7008
  #lane = Promise.resolve();
6955
7009
  #stopReason = null;
6956
7010
  #handlingFailure = false;
7011
+ #mayBeWorking = false;
6957
7012
  #unsubscribeFrame;
6958
7013
  #unsubscribeExit;
6959
7014
  get storedAgent() {
6960
7015
  return this.agent;
6961
7016
  }
6962
7017
  send(message) {
6963
- return this.#enqueue(() => this.process.prompt(message));
7018
+ return this.#enqueue(async () => {
7019
+ this.#mayBeWorking = true;
7020
+ try {
7021
+ await this.process.prompt(message);
7022
+ } catch (error) {
7023
+ this.#mayBeWorking = false;
7024
+ throw error;
7025
+ }
7026
+ });
6964
7027
  }
6965
7028
  async waitForIdle(signal) {
6966
7029
  let wait = null;
6967
7030
  await this.#enqueue(async () => {
7031
+ if (!this.#mayBeWorking && this.agent.summary.state === "idle") return;
6968
7032
  if (signal?.aborted === true) throw new Error("Receive cancelled");
6969
7033
  let resolveIdle;
6970
7034
  let rejectIdle;
6971
- wait = new Promise((resolve2, reject) => {
7035
+ const pending = new Promise((resolve2, reject) => {
6972
7036
  resolveIdle = resolve2;
6973
7037
  rejectIdle = reject;
6974
7038
  });
7039
+ void pending.catch(() => void 0);
7040
+ wait = pending;
6975
7041
  const waiter = {
6976
7042
  resolve: resolveIdle,
6977
7043
  reject: rejectIdle,
6978
7044
  ...signal === void 0 ? {} : { signal }
6979
7045
  };
7046
+ this.#idleWaiters.add(waiter);
6980
7047
  if (signal !== void 0) {
6981
7048
  const onAbort = () => {
6982
7049
  this.#idleWaiters.delete(waiter);
@@ -6984,8 +7051,9 @@ var AgentCoordinator = class {
6984
7051
  };
6985
7052
  waiter.onAbort = onAbort;
6986
7053
  signal.addEventListener("abort", onAbort, { once: true });
7054
+ if (signal.aborted) onAbort();
6987
7055
  }
6988
- this.#idleWaiters.add(waiter);
7056
+ if (!this.#idleWaiters.has(waiter)) return;
6989
7057
  const state = await this.process.getState();
6990
7058
  if (!state.isStreaming && state.pendingMessageCount === 0) {
6991
7059
  await this.#markIdle();
@@ -7082,6 +7150,7 @@ var AgentCoordinator = class {
7082
7150
  this.onProcessExit(error);
7083
7151
  }
7084
7152
  async #markWorking() {
7153
+ this.#mayBeWorking = true;
7085
7154
  this.agent = {
7086
7155
  ...this.agent,
7087
7156
  summary: {
@@ -7095,6 +7164,7 @@ var AgentCoordinator = class {
7095
7164
  }
7096
7165
  async #markIdle() {
7097
7166
  const latestAssistantText = await this.process.getLastAssistantText();
7167
+ this.#mayBeWorking = false;
7098
7168
  this.agent = {
7099
7169
  ...this.agent,
7100
7170
  latestAssistantText,
@@ -7304,13 +7374,23 @@ var FleetService = class {
7304
7374
  );
7305
7375
  }
7306
7376
  async #createImpl(input, operationId) {
7307
- const profile = createLaunchProfile({
7308
- cwd: input.cwd,
7309
- piArgv: input.piArgv,
7310
- piArtifactId: this.#launcher?.artifactId ?? "fake-pi"
7311
- });
7312
7377
  const replay = await this.#operation(operationId, "create", input);
7313
7378
  if (replay !== null) return replay;
7379
+ let profile;
7380
+ try {
7381
+ profile = createLaunchProfile({
7382
+ cwd: input.cwd,
7383
+ piArgv: input.piArgv,
7384
+ piArtifactId: this.#launcher?.artifactId ?? "fake-pi"
7385
+ });
7386
+ } catch (error) {
7387
+ const result = err({
7388
+ code: "invalid_arguments",
7389
+ message: error instanceof Error ? error.message : "Invalid Pi startup arguments."
7390
+ });
7391
+ await this.#remember(operationId, "create", input, result);
7392
+ return result;
7393
+ }
7314
7394
  if (input.instructions !== void 0 && Buffer.byteLength(input.instructions, "utf8") > this.#limits.maxMessageBytes) {
7315
7395
  const result = err({
7316
7396
  code: "invalid_arguments",