taskplane 0.10.0 → 0.10.2

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.
@@ -531,11 +531,11 @@ function renderLanesTasks(batch, tmuxSessions) {
531
531
 
532
532
  // Worker stats from lane state sidecar + telemetry badges
533
533
  let workerHtml = "";
534
- const telemBadges = task.status !== "pending" ? telemetryBadgesHtml(tel, reviewerActive) : "";
535
534
  // Reviewer sub-row should only appear under the task currently being reviewed,
536
535
  // not all tasks in the lane. The lane-state sidecar is per-lane (shared by all
537
536
  // tasks in the lane), so check that the sidecar's current taskId matches this task.
538
537
  const reviewerActive = ls && ls.reviewerStatus === "running" && ls.taskId === task.taskId;
538
+ const telemBadges = task.status !== "pending" ? telemetryBadgesHtml(tel, reviewerActive) : "";
539
539
  if (ls && ls.workerStatus === "running" && task.status === "running") {
540
540
  const elapsed = ls.workerElapsed ? `${Math.round(ls.workerElapsed / 1000)}s` : "";
541
541
  const tools = ls.workerToolCount || 0;
@@ -12,43 +12,81 @@ import type { AllocatedLane, AllocatedTask, DependencyGraph, LaneExecutionResult
12
12
  import { allocateLanes } from "./waves.ts";
13
13
  import { runGit } from "./git.ts";
14
14
 
15
- // ── Task Runner Extension Path Resolution ────────────────────────────
15
+ // ── Taskplane Package File Resolution ────────────────────────────────
16
16
 
17
17
  /**
18
- * Find the task-runner extension path for lane sessions.
18
+ * Cached result of `npm root -g` to avoid repeated child process spawns.
19
+ * null = not yet resolved, "" = resolution failed.
20
+ */
21
+ let _npmGlobalRoot: string | null = null;
22
+
23
+ /**
24
+ * Get the global npm root directory via `npm root -g`.
25
+ * Result is cached for the process lifetime.
26
+ */
27
+ function getNpmGlobalRoot(): string {
28
+ if (_npmGlobalRoot !== null) return _npmGlobalRoot;
29
+ try {
30
+ const result = spawnSync("npm", ["root", "-g"], {
31
+ encoding: "utf-8",
32
+ timeout: 5000,
33
+ shell: true,
34
+ });
35
+ _npmGlobalRoot = result.stdout?.trim() || "";
36
+ } catch {
37
+ _npmGlobalRoot = "";
38
+ }
39
+ return _npmGlobalRoot;
40
+ }
41
+
42
+ /**
43
+ * Resolve a file path within the taskplane package.
19
44
  *
20
45
  * Resolution order:
21
- * 1. Local project: {repoRoot}/extensions/task-runner.ts (for taskplane dev)
22
- * 2. Global npm (Windows): {APPDATA}/npm/node_modules/taskplane/extensions/task-runner.ts
23
- * 3. Global npm (Unix): /usr/local/lib/node_modules/taskplane/extensions/task-runner.ts
24
- * 4. npm peer: resolve from pi's location
25
- *
26
- * @throws ExecutionError if task-runner.ts cannot be found anywhere
46
+ * 1. Local project: {repoRoot}/{relPath} (for taskplane development)
47
+ * 2. `npm root -g` based: {npmGlobalRoot}/taskplane/{relPath}
48
+ * (covers Homebrew, nvm, volta, pnpm, and any custom npm prefix)
49
+ * 3. Well-known global npm paths (Windows/macOS/Linux):
50
+ * - {APPDATA}/npm/node_modules/taskplane/{relPath}
51
+ * - {HOME}/.npm-global/lib/node_modules/taskplane/{relPath}
52
+ * - /usr/local/lib/node_modules/taskplane/{relPath}
53
+ * - /opt/homebrew/lib/node_modules/taskplane/{relPath}
54
+ * 4. Peer of pi's package: resolve from pi's binary location
55
+ *
56
+ * @param repoRoot - Absolute path to the project root
57
+ * @param relPath - Relative path within the taskplane package (e.g., "bin/rpc-wrapper.mjs")
58
+ * @returns Absolute path to the resolved file
27
59
  */
28
- function resolveTaskRunnerExtensionPath(repoRoot: string): string {
29
- const extFile = join("extensions", "task-runner.ts");
30
-
60
+ function resolveTaskplanePackageFile(repoRoot: string, relPath: string): string {
31
61
  // 1. Local project (taskplane development)
32
- const localPath = join(resolve(repoRoot), extFile);
62
+ const localPath = join(resolve(repoRoot), relPath);
33
63
  if (existsSync(localPath)) return localPath;
34
64
 
35
- // 2. Global npm install paths
36
- const home = process.env.HOME || process.env.USERPROFILE || "";
37
65
  const candidates: string[] = [];
66
+
67
+ // 2. Dynamic: `npm root -g` (covers ALL npm setups: nvm, Homebrew, volta, etc.)
68
+ const npmRoot = getNpmGlobalRoot();
69
+ if (npmRoot) {
70
+ candidates.push(join(npmRoot, "taskplane", relPath));
71
+ }
72
+
73
+ // 3. Well-known static paths
74
+ const home = process.env.HOME || process.env.USERPROFILE || "";
38
75
  if (process.env.APPDATA) {
39
- candidates.push(join(process.env.APPDATA, "npm", "node_modules", "taskplane", extFile));
76
+ candidates.push(join(process.env.APPDATA, "npm", "node_modules", "taskplane", relPath));
40
77
  }
41
78
  if (home) {
42
- candidates.push(join(home, "AppData", "Roaming", "npm", "node_modules", "taskplane", extFile));
43
- candidates.push(join(home, ".npm-global", "lib", "node_modules", "taskplane", extFile));
79
+ candidates.push(join(home, "AppData", "Roaming", "npm", "node_modules", "taskplane", relPath));
80
+ candidates.push(join(home, ".npm-global", "lib", "node_modules", "taskplane", relPath));
44
81
  }
45
- candidates.push(join("/usr", "local", "lib", "node_modules", "taskplane", extFile));
82
+ candidates.push(join("/usr", "local", "lib", "node_modules", "taskplane", relPath));
83
+ candidates.push(join("/opt", "homebrew", "lib", "node_modules", "taskplane", relPath));
46
84
 
47
- // 3. Peer of pi's package
85
+ // 4. Peer of pi's package
48
86
  try {
49
87
  const piPath = process.argv[1] || "";
50
88
  const piPkgDir = resolve(piPath, "..", "..");
51
- candidates.push(join(piPkgDir, "..", "taskplane", extFile));
89
+ candidates.push(join(piPkgDir, "..", "taskplane", relPath));
52
90
  } catch { /* ignore */ }
53
91
 
54
92
  for (const candidate of candidates) {
@@ -59,51 +97,24 @@ function resolveTaskRunnerExtensionPath(repoRoot: string): string {
59
97
  return localPath;
60
98
  }
61
99
 
100
+ // ── Task Runner Extension Path Resolution ────────────────────────────
101
+
102
+ /**
103
+ * Find the task-runner extension path for lane sessions.
104
+ * @see resolveTaskplanePackageFile for resolution order
105
+ */
106
+ function resolveTaskRunnerExtensionPath(repoRoot: string): string {
107
+ return resolveTaskplanePackageFile(repoRoot, join("extensions", "task-runner.ts"));
108
+ }
109
+
62
110
  // ── RPC Wrapper Path Resolution ──────────────────────────────────────
63
111
 
64
112
  /**
65
113
  * Find the rpc-wrapper.mjs path for lane sessions.
66
- *
67
- * Resolution order mirrors resolveTaskRunnerExtensionPath:
68
- * 1. Local project: {repoRoot}/bin/rpc-wrapper.mjs (for taskplane dev)
69
- * 2. Global npm (Windows): {APPDATA}/npm/node_modules/taskplane/bin/rpc-wrapper.mjs
70
- * 3. Global npm (Unix): /usr/local/lib/node_modules/taskplane/bin/rpc-wrapper.mjs
71
- * 4. npm peer: resolve from pi's location
72
- *
73
- * @throws ExecutionError if rpc-wrapper.mjs cannot be found anywhere
114
+ * @see resolveTaskplanePackageFile for resolution order
74
115
  */
75
116
  export function resolveRpcWrapperPath(repoRoot: string): string {
76
- const wrapperFile = join("bin", "rpc-wrapper.mjs");
77
-
78
- // 1. Local project (taskplane development)
79
- const localPath = join(resolve(repoRoot), wrapperFile);
80
- if (existsSync(localPath)) return localPath;
81
-
82
- // 2. Global npm install paths
83
- const home = process.env.HOME || process.env.USERPROFILE || "";
84
- const candidates: string[] = [];
85
- if (process.env.APPDATA) {
86
- candidates.push(join(process.env.APPDATA, "npm", "node_modules", "taskplane", wrapperFile));
87
- }
88
- if (home) {
89
- candidates.push(join(home, "AppData", "Roaming", "npm", "node_modules", "taskplane", wrapperFile));
90
- candidates.push(join(home, ".npm-global", "lib", "node_modules", "taskplane", wrapperFile));
91
- }
92
- candidates.push(join("/usr", "local", "lib", "node_modules", "taskplane", wrapperFile));
93
-
94
- // 3. Peer of pi's package
95
- try {
96
- const piPath = process.argv[1] || "";
97
- const piPkgDir = resolve(piPath, "..", "..");
98
- candidates.push(join(piPkgDir, "..", "taskplane", wrapperFile));
99
- } catch { /* ignore */ }
100
-
101
- for (const candidate of candidates) {
102
- if (existsSync(candidate)) return candidate;
103
- }
104
-
105
- // Fallback: return the local path (will fail at spawn time with a clear error)
106
- return localPath;
117
+ return resolveTaskplanePackageFile(repoRoot, join("bin", "rpc-wrapper.mjs"));
107
118
  }
108
119
 
109
120
  // ── Telemetry Helpers ────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",