taskplane 0.1.1 → 0.1.3

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.
@@ -18,12 +18,14 @@ const { execFileSync, exec } = require("child_process");
18
18
  // ─── Configuration ──────────────────────────────────────────────────────────
19
19
 
20
20
  const PUBLIC_DIR = path.join(__dirname, "public");
21
- const DEFAULT_PORT = 8099;
21
+ const DEFAULT_PORT = 8100;
22
+ const MAX_PORT_ATTEMPTS = 20;
22
23
  const POLL_INTERVAL = 2000; // ms between state checks
23
24
 
24
25
  // REPO_ROOT is resolved after parseArgs() — see initialization below.
25
26
  let REPO_ROOT;
26
27
  let BATCH_STATE_PATH;
28
+ let BATCH_HISTORY_PATH;
27
29
 
28
30
  // ─── CLI Args ───────────────────────────────────────────────────────────────
29
31
 
@@ -373,7 +375,7 @@ function broadcastState() {
373
375
 
374
376
  // ─── Batch History API ──────────────────────────────────────────────────────
375
377
 
376
- const BATCH_HISTORY_PATH = path.join(REPO_ROOT, ".pi", "batch-history.json");
378
+ // BATCH_HISTORY_PATH is initialized in main() alongside REPO_ROOT.
377
379
 
378
380
  function loadHistory() {
379
381
  try {
@@ -426,7 +428,7 @@ function serveHistoryEntry(req, res, batchId) {
426
428
 
427
429
  // ─── HTTP Server ────────────────────────────────────────────────────────────
428
430
 
429
- function createServer(port) {
431
+ function createServer() {
430
432
  const server = http.createServer((req, res) => {
431
433
  const pathname = new URL(req.url, "http://localhost").pathname;
432
434
 
@@ -455,10 +457,6 @@ function createServer(port) {
455
457
  }
456
458
  });
457
459
 
458
- server.listen(port, () => {
459
- console.log(`\n Orchestrator Dashboard → http://localhost:${port}\n`);
460
- });
461
-
462
460
  return server;
463
461
  }
464
462
 
@@ -472,14 +470,69 @@ function openBrowser(url) {
472
470
 
473
471
  // ─── Main ───────────────────────────────────────────────────────────────────
474
472
 
475
- function main() {
473
+ /** Try to listen on a port. Resolves with the port on success, rejects on EADDRINUSE. */
474
+ function tryListen(server, port) {
475
+ return new Promise((resolve, reject) => {
476
+ const onError = (err) => {
477
+ server.removeListener("listening", onListening);
478
+ reject(err);
479
+ };
480
+ const onListening = () => {
481
+ server.removeListener("error", onError);
482
+ resolve(port);
483
+ };
484
+ server.once("error", onError);
485
+ server.once("listening", onListening);
486
+ server.listen(port);
487
+ });
488
+ }
489
+
490
+ /** Find an available port starting from `start`, trying up to MAX_PORT_ATTEMPTS. */
491
+ async function findPort(server, start, explicit) {
492
+ // If the user explicitly passed --port, only try that one
493
+ if (explicit) {
494
+ try {
495
+ return await tryListen(server, start);
496
+ } catch (err) {
497
+ if (err.code === "EADDRINUSE") {
498
+ console.error(`\n Port ${start} is already in use.`);
499
+ console.error(` Try: taskplane dashboard --port ${start + 1}\n`);
500
+ process.exit(1);
501
+ }
502
+ throw err;
503
+ }
504
+ }
505
+ // Auto-scan for an available port
506
+ for (let port = start; port < start + MAX_PORT_ATTEMPTS; port++) {
507
+ try {
508
+ return await tryListen(server, port);
509
+ } catch (err) {
510
+ if (err.code === "EADDRINUSE") {
511
+ // Close the server so we can retry on the next port
512
+ server.close();
513
+ server = createServer();
514
+ continue;
515
+ }
516
+ throw err;
517
+ }
518
+ }
519
+ console.error(`\n No available port found in range ${start}-${start + MAX_PORT_ATTEMPTS - 1}.\n`);
520
+ process.exit(1);
521
+ }
522
+
523
+ async function main() {
476
524
  const opts = parseArgs();
477
525
 
478
526
  // Resolve project root: --root flag > cwd
479
527
  REPO_ROOT = path.resolve(opts.root || process.cwd());
480
528
  BATCH_STATE_PATH = path.join(REPO_ROOT, ".pi", "batch-state.json");
529
+ BATCH_HISTORY_PATH = path.join(REPO_ROOT, ".pi", "batch-history.json");
530
+
531
+ const server = createServer();
532
+ const explicitPort = process.argv.slice(2).includes("--port");
533
+ const port = await findPort(server, opts.port, explicitPort);
481
534
 
482
- const server = createServer(opts.port);
535
+ console.log(`\n Orchestrator Dashboard http://localhost:${port}\n`);
483
536
 
484
537
  // Broadcast state to all SSE clients on interval
485
538
  const pollTimer = setInterval(broadcastState, POLL_INTERVAL);
@@ -505,7 +558,7 @@ function main() {
505
558
 
506
559
  // Auto-open browser
507
560
  if (opts.open) {
508
- setTimeout(() => openBrowser(`http://localhost:${opts.port}`), 500);
561
+ setTimeout(() => openBrowser(`http://localhost:${port}`), 500);
509
562
  }
510
563
 
511
564
  // Graceful shutdown
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",