@bastani/atomic 0.9.28-alpha.4 → 0.9.28
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/CHANGELOG.md +99 -0
- package/dist/builtin/intercom/CHANGELOG.md +13 -0
- package/dist/builtin/intercom/README.md +3 -2
- package/dist/builtin/intercom/broker/broker.ts +90 -14
- package/dist/builtin/intercom/broker/paths.ts +4 -0
- package/dist/builtin/intercom/broker/socket-liveness.ts +32 -0
- package/dist/builtin/intercom/broker/socket-replacement-lock.ts +104 -0
- package/dist/builtin/intercom/broker/spawn.ts +108 -63
- package/dist/builtin/intercom/index.bundle.mjs +114 -61
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +6 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +6 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +41 -0
- package/dist/builtin/workflows/builtin/{chunk-aqv8ny38.js → chunk-9ywkdsa9.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-p6jagvyf.js → chunk-bnz4knhy.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-br4ac9a3.js → chunk-dx5sjaxe.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-p3xmajmj.js → chunk-v4scsq3y.js} +41 -13
- package/dist/builtin/workflows/builtin/goal.js +2 -2
- package/dist/builtin/workflows/builtin/index.js +3 -3
- package/dist/builtin/workflows/builtin/ralph.js +2 -2
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +38 -14
- package/dist/builtin/workflows/src/index.js +38 -14
- package/dist/cli/startup-ui.d.ts +3 -2
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +19 -4
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts +5 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +13 -9
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/extensions/session-events.d.ts +2 -0
- package/dist/core/extensions/session-events.d.ts.map +1 -1
- package/dist/core/extensions/session-events.js.map +1 -1
- package/dist/core/provider-alias.d.ts.map +1 -1
- package/dist/core/provider-alias.js +8 -0
- package/dist/core/provider-alias.js.map +1 -1
- package/dist/core/provider-composer-internal.d.ts.map +1 -1
- package/dist/core/provider-composer-internal.js +6 -3
- package/dist/core/provider-composer-internal.js.map +1 -1
- package/dist/core/resource-loader-reload.d.ts.map +1 -1
- package/dist/core/resource-loader-reload.js +1 -0
- package/dist/core/resource-loader-reload.js.map +1 -1
- package/dist/core/resource-loader-types.d.ts +1 -0
- package/dist/core/resource-loader-types.d.ts.map +1 -1
- package/dist/core/resource-loader-types.js.map +1 -1
- package/dist/extensions/herdr/index.d.ts.map +1 -1
- package/dist/extensions/herdr/index.js +20 -2
- package/dist/extensions/herdr/index.js.map +1 -1
- package/dist/extensions/herdr/pane-owner.d.ts +7 -0
- package/dist/extensions/herdr/pane-owner.d.ts.map +1 -1
- package/dist/extensions/herdr/pane-owner.js +53 -11
- package/dist/extensions/herdr/pane-owner.js.map +1 -1
- package/dist/main-deferred-startup.d.ts +8 -0
- package/dist/main-deferred-startup.d.ts.map +1 -1
- package/dist/main-deferred-startup.js +6 -0
- package/dist/main-deferred-startup.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +11 -3
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-auth-routing.js +2 -2
- package/dist/modes/interactive/interactive-auth-routing.js.map +1 -1
- package/dist/modes/interactive/interactive-extension-dialogs.js +8 -0
- package/dist/modes/interactive/interactive-extension-dialogs.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.js +1 -0
- package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-helpers.js +1 -1
- package/dist/modes/interactive/interactive-mode-helpers.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-types.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode-types.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-types.js.map +1 -1
- package/dist/modes/interactive/interactive-process-lifecycle.js +1 -1
- package/dist/modes/interactive/interactive-process-lifecycle.js.map +1 -1
- package/dist/modes/interactive/interactive-startup-trust.d.ts +6 -0
- package/dist/modes/interactive/interactive-startup-trust.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-startup-trust.js +15 -0
- package/dist/modes/interactive/interactive-startup-trust.js.map +1 -0
- package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-startup.js +3 -0
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/dist/modes/interactive-engine/engine-child-liveness.d.ts +1 -0
- package/dist/modes/interactive-engine/engine-child-liveness.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-child-liveness.js +15 -1
- package/dist/modes/interactive-engine/engine-child-liveness.js.map +1 -1
- package/dist/modes/interactive-engine/engine-monitor.d.ts +4 -0
- package/dist/modes/interactive-engine/engine-monitor.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-monitor.js +17 -0
- package/dist/modes/interactive-engine/engine-monitor.js.map +1 -1
- package/dist/modes/interactive-engine/engine-quit-intent.d.ts +16 -0
- package/dist/modes/interactive-engine/engine-quit-intent.d.ts.map +1 -0
- package/dist/modes/interactive-engine/engine-quit-intent.js +27 -0
- package/dist/modes/interactive-engine/engine-quit-intent.js.map +1 -0
- package/dist/modes/interactive-engine/extension-ui-bridge.d.ts +1 -0
- package/dist/modes/interactive-engine/extension-ui-bridge.d.ts.map +1 -1
- package/dist/modes/interactive-engine/extension-ui-bridge.js +5 -0
- package/dist/modes/interactive-engine/extension-ui-bridge.js.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.d.ts +4 -1
- package/dist/modes/interactive-engine/isolated-runtime.d.ts.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.js +19 -2
- package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
- package/dist/modes/interactive-engine/protocol.d.ts +18 -1
- package/dist/modes/interactive-engine/protocol.d.ts.map +1 -1
- package/dist/modes/interactive-engine/protocol.js +6 -0
- package/dist/modes/interactive-engine/protocol.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +11 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +42 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-input-scheduler.js +2 -2
- package/dist/modes/rpc/rpc-input-scheduler.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +23 -6
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-provider-auth.js +1 -1
- package/dist/modes/rpc/rpc-provider-auth.js.map +1 -1
- package/docs/extensions/events.md +3 -0
- package/docs/herdr.md +11 -2
- package/docs/models.md +1 -1
- package/docs/providers.md +2 -0
- package/docs/sdk.md +1 -1
- package/docs/settings.md +1 -1
- package/npm-shrinkwrap.json +32 -32
- package/package.json +3 -3
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
|
-
import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
2
|
+
import { appendFileSync, chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
3
3
|
import { join, dirname } from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import { createRequire } from "module";
|
|
6
|
-
import
|
|
6
|
+
import { isSocketAnswering } from "./socket-liveness.js";
|
|
7
7
|
import { createChildProcessEnvironment, isBunBinary } from "@bastani/atomic";
|
|
8
8
|
import {
|
|
9
9
|
getBrokerLogPath,
|
|
@@ -57,6 +57,12 @@ export const BROKER_POLL_INITIAL_INTERVAL_MS = 10;
|
|
|
57
57
|
/** Upper bound for the readiness poll interval, matching the historic flat poll. */
|
|
58
58
|
export const BROKER_POLL_MAX_INTERVAL_MS = 100;
|
|
59
59
|
|
|
60
|
+
/** How long one spawn attempt waits for the broker socket to answer. */
|
|
61
|
+
export const BROKER_READY_TIMEOUT_MS = 5000;
|
|
62
|
+
|
|
63
|
+
/** Backstop only: a spawn lock normally ends when its broker is ready or the processes it names are gone. */
|
|
64
|
+
export const BROKER_SPAWN_LOCK_MAX_AGE_MS = 60_000;
|
|
65
|
+
|
|
60
66
|
type BrokerRuntime = "node" | "bun-source" | "bun-binary";
|
|
61
67
|
|
|
62
68
|
export const INTERNAL_INTERCOM_BROKER_ARG = "--atomic-internal-intercom-broker";
|
|
@@ -257,10 +263,20 @@ export function getBrokerSpawnOptions(
|
|
|
257
263
|
};
|
|
258
264
|
}
|
|
259
265
|
|
|
260
|
-
/**
|
|
261
|
-
|
|
266
|
+
/**
|
|
267
|
+
* Truncate (or create) the broker log so each spawn starts from a bounded, current file. Returns false when a
|
|
268
|
+
* Windows broker that is still starting holds the log through its launcher's stderr redirect.
|
|
269
|
+
*/
|
|
270
|
+
function resetBrokerLog(logPath: string = BROKER_LOG): boolean {
|
|
262
271
|
ensurePrivateDirectory(dirname(logPath));
|
|
263
|
-
|
|
272
|
+
try {
|
|
273
|
+
closeSync(openSync(logPath, "w"));
|
|
274
|
+
return true;
|
|
275
|
+
} catch (error) {
|
|
276
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
277
|
+
if (process.platform === "win32" && (code === "EBUSY" || code === "EPERM")) return false;
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
264
280
|
}
|
|
265
281
|
|
|
266
282
|
/** Read at most {@link BROKER_LOG_TAIL_BYTES} trailing bytes of the broker log. */
|
|
@@ -299,19 +315,24 @@ function toError(error: unknown): Error {
|
|
|
299
315
|
return error instanceof Error ? error : new Error(String(error));
|
|
300
316
|
}
|
|
301
317
|
|
|
302
|
-
export async function spawnBrokerIfNeeded(
|
|
318
|
+
export async function spawnBrokerIfNeeded(
|
|
319
|
+
brokerCommand: string,
|
|
320
|
+
brokerArgs: string[],
|
|
321
|
+
readyTimeoutMs: number = BROKER_READY_TIMEOUT_MS,
|
|
322
|
+
): Promise<void> {
|
|
303
323
|
ensurePrivateDirectory(INTERCOM_DIR);
|
|
304
324
|
|
|
305
325
|
if (await isBrokerRunning()) {
|
|
306
326
|
return;
|
|
307
327
|
}
|
|
308
328
|
|
|
309
|
-
const
|
|
310
|
-
if (
|
|
311
|
-
await waitForBroker();
|
|
329
|
+
const lockOwner = acquireSpawnLock();
|
|
330
|
+
if (lockOwner === undefined) {
|
|
331
|
+
await waitForBroker(readyTimeoutMs);
|
|
312
332
|
return;
|
|
313
333
|
}
|
|
314
334
|
|
|
335
|
+
let keepLock = false;
|
|
315
336
|
try {
|
|
316
337
|
if (await isBrokerRunning()) {
|
|
317
338
|
return;
|
|
@@ -323,7 +344,10 @@ export async function spawnBrokerIfNeeded(brokerCommand: string, brokerArgs: str
|
|
|
323
344
|
writeWindowsHiddenLauncher(launch.launcherCommandLine, launch.launcherPath);
|
|
324
345
|
}
|
|
325
346
|
// Reset before spawning either way: the Windows launcher appends to this same path.
|
|
326
|
-
resetBrokerLog()
|
|
347
|
+
if (!resetBrokerLog()) {
|
|
348
|
+
await waitForBroker(readyTimeoutMs);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
327
351
|
// The Windows launcher redirects the broker's own stderr, so only the direct spawn
|
|
328
352
|
// needs the descriptor. Node duplicates it during spawn, so the parent copy is closed
|
|
329
353
|
// immediately afterwards rather than being held open for the broker's lifetime.
|
|
@@ -358,23 +382,38 @@ export async function spawnBrokerIfNeeded(brokerCommand: string, brokerArgs: str
|
|
|
358
382
|
reject(new Error(`Intercom broker exited before startup with signal ${signal}\n${describeBrokerLog()}`));
|
|
359
383
|
return;
|
|
360
384
|
}
|
|
361
|
-
|
|
362
|
-
new Error(`Intercom broker exited before startup with code ${code ?? "unknown"}\n${describeBrokerLog()}`)
|
|
363
|
-
)
|
|
385
|
+
const exitedBeforeStartup = () =>
|
|
386
|
+
new Error(`Intercom broker exited before startup with code ${code ?? "unknown"}\n${describeBrokerLog()}`);
|
|
387
|
+
if (code === 0) {
|
|
388
|
+
// A broker that finds a live broker on the socket yields with a clean exit.
|
|
389
|
+
void checkSocketConnectable().then((answering) => (answering ? resolve() : reject(exitedBeforeStartup())));
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
reject(exitedBeforeStartup());
|
|
364
393
|
};
|
|
365
394
|
|
|
366
395
|
child.once("error", onError);
|
|
367
396
|
child.once("exit", onExit);
|
|
368
|
-
waitForBroker().then(() => {
|
|
397
|
+
waitForBroker(readyTimeoutMs).then(() => {
|
|
369
398
|
cleanup();
|
|
370
399
|
resolve();
|
|
371
400
|
}, (error) => {
|
|
372
401
|
cleanup();
|
|
402
|
+
// A broker still starting keeps the lock, so later spawners wait for it instead of starting another.
|
|
403
|
+
// Only now does the lock name the broker: until this hand-off it follows this live spawner, so no other
|
|
404
|
+
// spawner can judge it stale and replace it while this one may still release it.
|
|
405
|
+
// The Windows launcher hides the broker's pid, so its lock could only follow this live spawner and would
|
|
406
|
+
// block every later spawn; there the broker's own live-socket check is what prevents a takeover.
|
|
407
|
+
keepLock =
|
|
408
|
+
launch.kind === "direct" &&
|
|
409
|
+
child.exitCode === null &&
|
|
410
|
+
child.signalCode === null &&
|
|
411
|
+
handSpawnLockToBroker(child.pid, lockOwner);
|
|
373
412
|
reject(toError(error));
|
|
374
413
|
});
|
|
375
414
|
});
|
|
376
415
|
} finally {
|
|
377
|
-
releaseSpawnLock();
|
|
416
|
+
if (!keepLock) releaseSpawnLock(lockOwner);
|
|
378
417
|
}
|
|
379
418
|
}
|
|
380
419
|
|
|
@@ -397,39 +436,37 @@ async function isBrokerRunning(): Promise<boolean> {
|
|
|
397
436
|
}
|
|
398
437
|
|
|
399
438
|
function checkSocketConnectable(): Promise<boolean> {
|
|
400
|
-
return
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
};
|
|
414
|
-
const onError = () => {
|
|
415
|
-
socket.destroy();
|
|
416
|
-
finish(false);
|
|
417
|
-
};
|
|
418
|
-
socket.on("connect", onConnect);
|
|
419
|
-
socket.on("error", onError);
|
|
420
|
-
const timeout = setTimeout(() => {
|
|
421
|
-
socket.destroy();
|
|
422
|
-
finish(false);
|
|
423
|
-
}, 1000);
|
|
424
|
-
});
|
|
439
|
+
return isSocketAnswering(BROKER_SOCKET);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** Records the still-starting broker on the lock so staleness follows that process instead of the spawner. */
|
|
443
|
+
function handSpawnLockToBroker(pid: number | undefined, owner: string): boolean {
|
|
444
|
+
if (pid === undefined || !ownsSpawnLock(owner)) return false;
|
|
445
|
+
try {
|
|
446
|
+
appendFileSync(BROKER_SPAWN_LOCK, `${pid}\n`);
|
|
447
|
+
return true;
|
|
448
|
+
} catch {
|
|
449
|
+
// The startup error must still reach the caller; an unrecorded lock is released like any other.
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
425
452
|
}
|
|
426
453
|
|
|
427
|
-
function
|
|
454
|
+
function ownsSpawnLock(owner: string, lockPath: string = BROKER_SPAWN_LOCK): boolean {
|
|
455
|
+
try {
|
|
456
|
+
return readFileSync(lockPath, "utf-8").startsWith(owner);
|
|
457
|
+
} catch {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** Returns the header this spawner wrote, which proves ownership on release, or undefined when another spawner holds the lock. */
|
|
463
|
+
function acquireSpawnLock(): string | undefined {
|
|
428
464
|
const maxRetries = 5;
|
|
429
465
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
466
|
+
const owner = `${process.pid}\n${Date.now()}\n`;
|
|
430
467
|
try {
|
|
431
|
-
writeFileSync(BROKER_SPAWN_LOCK,
|
|
432
|
-
return
|
|
468
|
+
writeFileSync(BROKER_SPAWN_LOCK, owner, { flag: "wx" });
|
|
469
|
+
return owner;
|
|
433
470
|
} catch (error) {
|
|
434
471
|
if (!(error instanceof Error) || (error as NodeJS.ErrnoException).code !== "EEXIST") {
|
|
435
472
|
throw error;
|
|
@@ -442,42 +479,50 @@ function acquireSpawnLock(): boolean {
|
|
|
442
479
|
}
|
|
443
480
|
continue;
|
|
444
481
|
}
|
|
445
|
-
return
|
|
482
|
+
return undefined;
|
|
446
483
|
}
|
|
447
484
|
}
|
|
448
|
-
return
|
|
485
|
+
return undefined;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function isProcessAlive(pid: number): boolean {
|
|
489
|
+
try {
|
|
490
|
+
process.kill(pid, 0);
|
|
491
|
+
return true;
|
|
492
|
+
} catch (error) {
|
|
493
|
+
return (error as NodeJS.ErrnoException).code === "EPERM";
|
|
494
|
+
}
|
|
449
495
|
}
|
|
450
496
|
|
|
451
|
-
|
|
452
|
-
|
|
497
|
+
/**
|
|
498
|
+
* A lock is stale once the broker it recorded is gone, or, while no broker is recorded, once its
|
|
499
|
+
* spawner is gone. A live broker keeps the lock even if its spawner died, until the age backstop.
|
|
500
|
+
*/
|
|
501
|
+
export function isSpawnLockStale(lockPath: string = BROKER_SPAWN_LOCK, nowMs: number = Date.now()): boolean {
|
|
502
|
+
if (!existsSync(lockPath)) {
|
|
453
503
|
return false;
|
|
454
504
|
}
|
|
455
505
|
|
|
456
506
|
try {
|
|
457
|
-
const [
|
|
458
|
-
const
|
|
507
|
+
const [spawnerLine = "", createdAtLine = "", brokerLine = ""] = readFileSync(lockPath, "utf-8").trim().split("\n");
|
|
508
|
+
const spawnerPid = Number.parseInt(spawnerLine, 10);
|
|
459
509
|
const createdAt = Number.parseInt(createdAtLine, 10);
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
if (Number.isFinite(pid)) {
|
|
463
|
-
try {
|
|
464
|
-
process.kill(pid, 0);
|
|
465
|
-
} catch {
|
|
466
|
-
// The process that created the lock is gone.
|
|
467
|
-
return true;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
510
|
+
const brokerPid = Number.parseInt(brokerLine, 10);
|
|
470
511
|
|
|
471
|
-
|
|
512
|
+
if (!Number.isFinite(spawnerPid) || !Number.isFinite(createdAt)) return true;
|
|
513
|
+
if (nowMs - createdAt > BROKER_SPAWN_LOCK_MAX_AGE_MS) return true;
|
|
514
|
+
return !isProcessAlive(Number.isFinite(brokerPid) ? brokerPid : spawnerPid);
|
|
472
515
|
} catch {
|
|
473
516
|
// Unreadable lock contents are treated as stale so a new broker can start.
|
|
474
517
|
return true;
|
|
475
518
|
}
|
|
476
519
|
}
|
|
477
520
|
|
|
478
|
-
|
|
521
|
+
/** Removes the lock only while it is still the one `owner` wrote; a spawner that replaced a stale lock keeps its own. */
|
|
522
|
+
export function releaseSpawnLock(owner: string, lockPath: string = BROKER_SPAWN_LOCK): void {
|
|
523
|
+
if (!ownsSpawnLock(owner, lockPath)) return;
|
|
479
524
|
try {
|
|
480
|
-
unlinkSync(
|
|
525
|
+
unlinkSync(lockPath);
|
|
481
526
|
} catch {
|
|
482
527
|
// Another cleanup path may already have removed the lock.
|
|
483
528
|
}
|
|
@@ -488,7 +533,7 @@ function releaseSpawnLock(): void {
|
|
|
488
533
|
* Startup is tens of milliseconds, so the previous flat 100 ms sleep dominated it; the
|
|
489
534
|
* overall timeout semantics are unchanged.
|
|
490
535
|
*/
|
|
491
|
-
export async function waitForBroker(timeoutMs =
|
|
536
|
+
export async function waitForBroker(timeoutMs = BROKER_READY_TIMEOUT_MS): Promise<void> {
|
|
492
537
|
const start = Date.now();
|
|
493
538
|
let interval = BROKER_POLL_INITIAL_INTERVAL_MS;
|
|
494
539
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -1406,13 +1406,43 @@ var init_client = __esm(() => {
|
|
|
1406
1406
|
};
|
|
1407
1407
|
});
|
|
1408
1408
|
|
|
1409
|
+
// dist/builtin/intercom/broker/socket-liveness.ts
|
|
1410
|
+
import net2 from "net";
|
|
1411
|
+
function isSocketAnswering(socketPath, timeoutMs = SOCKET_PROBE_TIMEOUT_MS) {
|
|
1412
|
+
return new Promise((resolve) => {
|
|
1413
|
+
const socket = net2.connect(socketPath);
|
|
1414
|
+
const finish = (isConnected) => {
|
|
1415
|
+
clearTimeout(timeout);
|
|
1416
|
+
socket.off("connect", onConnect);
|
|
1417
|
+
socket.off("error", onError);
|
|
1418
|
+
resolve(isConnected);
|
|
1419
|
+
};
|
|
1420
|
+
const onConnect = () => {
|
|
1421
|
+
socket.on("error", () => {});
|
|
1422
|
+
socket.end();
|
|
1423
|
+
finish(true);
|
|
1424
|
+
};
|
|
1425
|
+
const onError = () => {
|
|
1426
|
+
socket.destroy();
|
|
1427
|
+
finish(false);
|
|
1428
|
+
};
|
|
1429
|
+
socket.on("connect", onConnect);
|
|
1430
|
+
socket.on("error", onError);
|
|
1431
|
+
const timeout = setTimeout(() => {
|
|
1432
|
+
socket.destroy();
|
|
1433
|
+
finish(false);
|
|
1434
|
+
}, timeoutMs);
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
var SOCKET_PROBE_TIMEOUT_MS = 1000;
|
|
1438
|
+
var init_socket_liveness = () => {};
|
|
1439
|
+
|
|
1409
1440
|
// dist/builtin/intercom/broker/spawn.ts
|
|
1410
1441
|
import { spawn } from "child_process";
|
|
1411
|
-
import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
1442
|
+
import { appendFileSync, chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
1412
1443
|
import { join as join2, dirname } from "path";
|
|
1413
1444
|
import { fileURLToPath } from "url";
|
|
1414
1445
|
import { createRequire } from "module";
|
|
1415
|
-
import net2 from "net";
|
|
1416
1446
|
import { createChildProcessEnvironment, isBunBinary } from "@bastani/atomic";
|
|
1417
1447
|
function isIntercomPackageRoot(dir) {
|
|
1418
1448
|
return existsSync(join2(dir, "package.json")) && existsSync(join2(dir, "broker"));
|
|
@@ -1537,7 +1567,15 @@ function getBrokerSpawnOptions(extensionDir = EXTENSION_DIR, stderr = "ignore")
|
|
|
1537
1567
|
}
|
|
1538
1568
|
function resetBrokerLog(logPath = BROKER_LOG) {
|
|
1539
1569
|
ensurePrivateDirectory(dirname(logPath));
|
|
1540
|
-
|
|
1570
|
+
try {
|
|
1571
|
+
closeSync(openSync(logPath, "w"));
|
|
1572
|
+
return true;
|
|
1573
|
+
} catch (error) {
|
|
1574
|
+
const code = error.code;
|
|
1575
|
+
if (process.platform === "win32" && (code === "EBUSY" || code === "EPERM"))
|
|
1576
|
+
return false;
|
|
1577
|
+
throw error;
|
|
1578
|
+
}
|
|
1541
1579
|
}
|
|
1542
1580
|
function readBrokerLogTail(logPath = BROKER_LOG, maxBytes = BROKER_LOG_TAIL_BYTES) {
|
|
1543
1581
|
let fd;
|
|
@@ -1571,16 +1609,17 @@ ${tail}`;
|
|
|
1571
1609
|
function toError2(error) {
|
|
1572
1610
|
return error instanceof Error ? error : new Error(String(error));
|
|
1573
1611
|
}
|
|
1574
|
-
async function spawnBrokerIfNeeded(brokerCommand, brokerArgs) {
|
|
1612
|
+
async function spawnBrokerIfNeeded(brokerCommand, brokerArgs, readyTimeoutMs = BROKER_READY_TIMEOUT_MS) {
|
|
1575
1613
|
ensurePrivateDirectory(INTERCOM_DIR);
|
|
1576
1614
|
if (await isBrokerRunning()) {
|
|
1577
1615
|
return;
|
|
1578
1616
|
}
|
|
1579
|
-
const
|
|
1580
|
-
if (
|
|
1581
|
-
await waitForBroker();
|
|
1617
|
+
const lockOwner = acquireSpawnLock();
|
|
1618
|
+
if (lockOwner === undefined) {
|
|
1619
|
+
await waitForBroker(readyTimeoutMs);
|
|
1582
1620
|
return;
|
|
1583
1621
|
}
|
|
1622
|
+
let keepLock = false;
|
|
1584
1623
|
try {
|
|
1585
1624
|
if (await isBrokerRunning()) {
|
|
1586
1625
|
return;
|
|
@@ -1590,7 +1629,10 @@ async function spawnBrokerIfNeeded(brokerCommand, brokerArgs) {
|
|
|
1590
1629
|
if (launch.kind === "windows-launcher") {
|
|
1591
1630
|
writeWindowsHiddenLauncher(launch.launcherCommandLine, launch.launcherPath);
|
|
1592
1631
|
}
|
|
1593
|
-
resetBrokerLog()
|
|
1632
|
+
if (!resetBrokerLog()) {
|
|
1633
|
+
await waitForBroker(readyTimeoutMs);
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1594
1636
|
const logFd = launch.kind === "direct" ? openSync(BROKER_LOG, "a") : undefined;
|
|
1595
1637
|
let child;
|
|
1596
1638
|
try {
|
|
@@ -1620,21 +1662,28 @@ ${describeBrokerLog()}`, { cause: error }));
|
|
|
1620
1662
|
${describeBrokerLog()}`));
|
|
1621
1663
|
return;
|
|
1622
1664
|
}
|
|
1623
|
-
|
|
1624
|
-
${describeBrokerLog()}`)
|
|
1665
|
+
const exitedBeforeStartup = () => new Error(`Intercom broker exited before startup with code ${code ?? "unknown"}
|
|
1666
|
+
${describeBrokerLog()}`);
|
|
1667
|
+
if (code === 0) {
|
|
1668
|
+
checkSocketConnectable().then((answering) => answering ? resolve() : reject(exitedBeforeStartup()));
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
reject(exitedBeforeStartup());
|
|
1625
1672
|
};
|
|
1626
1673
|
child.once("error", onError);
|
|
1627
1674
|
child.once("exit", onExit);
|
|
1628
|
-
waitForBroker().then(() => {
|
|
1675
|
+
waitForBroker(readyTimeoutMs).then(() => {
|
|
1629
1676
|
cleanup();
|
|
1630
1677
|
resolve();
|
|
1631
1678
|
}, (error) => {
|
|
1632
1679
|
cleanup();
|
|
1680
|
+
keepLock = launch.kind === "direct" && child.exitCode === null && child.signalCode === null && handSpawnLockToBroker(child.pid, lockOwner);
|
|
1633
1681
|
reject(toError2(error));
|
|
1634
1682
|
});
|
|
1635
1683
|
});
|
|
1636
1684
|
} finally {
|
|
1637
|
-
|
|
1685
|
+
if (!keepLock)
|
|
1686
|
+
releaseSpawnLock(lockOwner);
|
|
1638
1687
|
}
|
|
1639
1688
|
}
|
|
1640
1689
|
async function isBrokerRunning() {
|
|
@@ -1654,39 +1703,35 @@ async function isBrokerRunning() {
|
|
|
1654
1703
|
}
|
|
1655
1704
|
}
|
|
1656
1705
|
function checkSocketConnectable() {
|
|
1657
|
-
return
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
const timeout = setTimeout(() => {
|
|
1677
|
-
socket.destroy();
|
|
1678
|
-
finish(false);
|
|
1679
|
-
}, 1000);
|
|
1680
|
-
});
|
|
1706
|
+
return isSocketAnswering(BROKER_SOCKET2);
|
|
1707
|
+
}
|
|
1708
|
+
function handSpawnLockToBroker(pid, owner) {
|
|
1709
|
+
if (pid === undefined || !ownsSpawnLock(owner))
|
|
1710
|
+
return false;
|
|
1711
|
+
try {
|
|
1712
|
+
appendFileSync(BROKER_SPAWN_LOCK, `${pid}
|
|
1713
|
+
`);
|
|
1714
|
+
return true;
|
|
1715
|
+
} catch {
|
|
1716
|
+
return false;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
function ownsSpawnLock(owner, lockPath = BROKER_SPAWN_LOCK) {
|
|
1720
|
+
try {
|
|
1721
|
+
return readFileSync(lockPath, "utf-8").startsWith(owner);
|
|
1722
|
+
} catch {
|
|
1723
|
+
return false;
|
|
1724
|
+
}
|
|
1681
1725
|
}
|
|
1682
1726
|
function acquireSpawnLock() {
|
|
1683
1727
|
const maxRetries = 5;
|
|
1684
1728
|
for (let attempt = 0;attempt < maxRetries; attempt++) {
|
|
1685
|
-
|
|
1686
|
-
writeFileSync(BROKER_SPAWN_LOCK, `${process.pid}
|
|
1729
|
+
const owner = `${process.pid}
|
|
1687
1730
|
${Date.now()}
|
|
1688
|
-
|
|
1689
|
-
|
|
1731
|
+
`;
|
|
1732
|
+
try {
|
|
1733
|
+
writeFileSync(BROKER_SPAWN_LOCK, owner, { flag: "wx" });
|
|
1734
|
+
return owner;
|
|
1690
1735
|
} catch (error) {
|
|
1691
1736
|
if (!(error instanceof Error) || error.code !== "EEXIST") {
|
|
1692
1737
|
throw error;
|
|
@@ -1697,39 +1742,46 @@ ${Date.now()}
|
|
|
1697
1742
|
} catch {}
|
|
1698
1743
|
continue;
|
|
1699
1744
|
}
|
|
1700
|
-
return
|
|
1745
|
+
return;
|
|
1701
1746
|
}
|
|
1702
1747
|
}
|
|
1703
|
-
return
|
|
1748
|
+
return;
|
|
1704
1749
|
}
|
|
1705
|
-
function
|
|
1706
|
-
|
|
1750
|
+
function isProcessAlive(pid) {
|
|
1751
|
+
try {
|
|
1752
|
+
process.kill(pid, 0);
|
|
1753
|
+
return true;
|
|
1754
|
+
} catch (error) {
|
|
1755
|
+
return error.code === "EPERM";
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
function isSpawnLockStale(lockPath = BROKER_SPAWN_LOCK, nowMs = Date.now()) {
|
|
1759
|
+
if (!existsSync(lockPath)) {
|
|
1707
1760
|
return false;
|
|
1708
1761
|
}
|
|
1709
1762
|
try {
|
|
1710
|
-
const [
|
|
1763
|
+
const [spawnerLine = "", createdAtLine = "", brokerLine = ""] = readFileSync(lockPath, "utf-8").trim().split(`
|
|
1711
1764
|
`);
|
|
1712
|
-
const
|
|
1765
|
+
const spawnerPid = Number.parseInt(spawnerLine, 10);
|
|
1713
1766
|
const createdAt = Number.parseInt(createdAtLine, 10);
|
|
1714
|
-
const
|
|
1715
|
-
if (Number.isFinite(
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
return !Number.isFinite(createdAt) || ageMs > 1e4;
|
|
1767
|
+
const brokerPid = Number.parseInt(brokerLine, 10);
|
|
1768
|
+
if (!Number.isFinite(spawnerPid) || !Number.isFinite(createdAt))
|
|
1769
|
+
return true;
|
|
1770
|
+
if (nowMs - createdAt > BROKER_SPAWN_LOCK_MAX_AGE_MS)
|
|
1771
|
+
return true;
|
|
1772
|
+
return !isProcessAlive(Number.isFinite(brokerPid) ? brokerPid : spawnerPid);
|
|
1723
1773
|
} catch {
|
|
1724
1774
|
return true;
|
|
1725
1775
|
}
|
|
1726
1776
|
}
|
|
1727
|
-
function releaseSpawnLock() {
|
|
1777
|
+
function releaseSpawnLock(owner, lockPath = BROKER_SPAWN_LOCK) {
|
|
1778
|
+
if (!ownsSpawnLock(owner, lockPath))
|
|
1779
|
+
return;
|
|
1728
1780
|
try {
|
|
1729
|
-
unlinkSync(
|
|
1781
|
+
unlinkSync(lockPath);
|
|
1730
1782
|
} catch {}
|
|
1731
1783
|
}
|
|
1732
|
-
async function waitForBroker(timeoutMs =
|
|
1784
|
+
async function waitForBroker(timeoutMs = BROKER_READY_TIMEOUT_MS) {
|
|
1733
1785
|
const start = Date.now();
|
|
1734
1786
|
let interval = BROKER_POLL_INITIAL_INTERVAL_MS;
|
|
1735
1787
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -1742,8 +1794,9 @@ async function waitForBroker(timeoutMs = 5000) {
|
|
|
1742
1794
|
throw new Error(`Broker failed to start within timeout
|
|
1743
1795
|
${describeBrokerLog()}`);
|
|
1744
1796
|
}
|
|
1745
|
-
var INTERCOM_DIR, EXTENSION_DIR, BROKER_SOCKET2, BROKER_PID, BROKER_SPAWN_LOCK, BROKER_LOG, BROKER_LOG_TAIL_BYTES, BROKER_POLL_INITIAL_INTERVAL_MS = 10, BROKER_POLL_MAX_INTERVAL_MS = 100, INTERNAL_INTERCOM_BROKER_ARG = "--atomic-internal-intercom-broker";
|
|
1797
|
+
var INTERCOM_DIR, EXTENSION_DIR, BROKER_SOCKET2, BROKER_PID, BROKER_SPAWN_LOCK, BROKER_LOG, BROKER_LOG_TAIL_BYTES, BROKER_POLL_INITIAL_INTERVAL_MS = 10, BROKER_POLL_MAX_INTERVAL_MS = 100, BROKER_READY_TIMEOUT_MS = 5000, BROKER_SPAWN_LOCK_MAX_AGE_MS = 60000, INTERNAL_INTERCOM_BROKER_ARG = "--atomic-internal-intercom-broker";
|
|
1746
1798
|
var init_spawn = __esm(() => {
|
|
1799
|
+
init_socket_liveness();
|
|
1747
1800
|
init_paths();
|
|
1748
1801
|
INTERCOM_DIR = getIntercomDirPath();
|
|
1749
1802
|
EXTENSION_DIR = resolveIntercomPackageRoot(import.meta.url);
|
|
@@ -6393,7 +6446,7 @@ __export(exports_index_heavy, {
|
|
|
6393
6446
|
default: () => piIntercomExtension
|
|
6394
6447
|
});
|
|
6395
6448
|
import { APP_NAME, sessionScopedExtensionState } from "@bastani/atomic";
|
|
6396
|
-
import { appendFileSync } from "node:fs";
|
|
6449
|
+
import { appendFileSync as appendFileSync2 } from "node:fs";
|
|
6397
6450
|
function isPendingStageUndeliverableEvent(value) {
|
|
6398
6451
|
if (typeof value !== "object" || value === null)
|
|
6399
6452
|
return false;
|
|
@@ -7352,7 +7405,7 @@ var init_index_heavy = __esm(() => {
|
|
|
7352
7405
|
process.env.ATOMIC_INTERCOM_HEAVY_IMPORTED = "1";
|
|
7353
7406
|
}
|
|
7354
7407
|
if (process.env.ATOMIC_TEST_LAZY_IMPORT_SENTINEL_FILE) {
|
|
7355
|
-
|
|
7408
|
+
appendFileSync2(process.env.ATOMIC_TEST_LAZY_IMPORT_SENTINEL_FILE, `intercom
|
|
7356
7409
|
`);
|
|
7357
7410
|
}
|
|
7358
7411
|
INTERCOM_SESSION_ID_ENV = `${APP_NAME.toUpperCase()}_INTERCOM_SESSION_ID`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/intercom",
|
|
3
|
-
"version": "0.9.28
|
|
3
|
+
"version": "0.9.28",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
|
|
6
6
|
"contributors": [
|
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.28] - 2026-10-08
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Fixed MCP OAuth dynamic registration omitting `application_type`, which caused redirect URI rejections on OpenID Connect servers ([#10493](https://github.com/earendil-works/pi/issues/10493)).
|
|
15
|
+
|
|
10
16
|
## [0.9.28-alpha.1] - 2026-10-05
|
|
11
17
|
|
|
12
18
|
### Fixed
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.28] - 2026-10-08
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Updated the bundled Cua Driver skill to upstream `cua-driver-rs-v0.34.0`, with focused runtime, workflow, and visual guides covering authorization, exact targets, observation, and outcome verification.
|
|
10
|
+
|
|
5
11
|
## [0.9.28-alpha.4] - 2026-10-07
|
|
6
12
|
|
|
7
13
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/subagents",
|
|
3
|
-
"version": "0.9.28
|
|
3
|
+
"version": "0.9.28",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for delegating tasks to subagents with parallel execution. Fork of: https://github.com/nicobailon/pi-subagents",
|
|
6
6
|
"contributors": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/web-access",
|
|
3
|
-
"version": "0.9.28
|
|
3
|
+
"version": "0.9.28",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
|
|
6
6
|
"contributors": [
|