pi-crew 0.11.7 → 0.11.9

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.
Files changed (66) hide show
  1. package/.githooks/pre-commit +22 -0
  2. package/.githooks/pre-push +56 -0
  3. package/CHANGELOG.md +196 -1
  4. package/dist/index.mjs +533 -515
  5. package/docs/publishing.md +29 -0
  6. package/docs/usage.md +1 -1
  7. package/index.ts +3 -3
  8. package/install.mjs +4 -1
  9. package/package.json +8 -5
  10. package/scripts/install-hooks.mjs +32 -0
  11. package/skills/real-test-pi-crew/SKILL.md +57 -18
  12. package/src/config/config-validation.ts +8 -1
  13. package/src/config/defaults.ts +8 -1
  14. package/src/config/env-vars.ts +16 -1
  15. package/src/config/types.ts +28 -4
  16. package/src/extension/command-completions.ts +44 -22
  17. package/src/extension/registration/foreground-run-controller.ts +19 -10
  18. package/src/extension/registration/lifecycle-handlers.ts +6 -491
  19. package/src/extension/registration/lifecycle.ts +7 -90
  20. package/src/extension/registration/render-loop.ts +532 -0
  21. package/src/extension/team-tool/doctor.ts +6 -0
  22. package/src/extension/team-tool/handle-settings.ts +4 -1
  23. package/src/runtime/child-pi/child-pi-spawn.ts +7 -0
  24. package/src/runtime/child-pi/child-pi.ts +37 -2
  25. package/src/runtime/child-pi/pi-version.ts +187 -0
  26. package/src/runtime/classifier/classifier-service.ts +298 -0
  27. package/src/runtime/dispatch-batch.ts +4 -0
  28. package/src/runtime/live-session/live-session-runtime.ts +31 -0
  29. package/src/runtime/model/pi-args.ts +62 -3
  30. package/src/runtime/process/zombie-scanner.ts +168 -7
  31. package/src/runtime/rpc/README.md +64 -25
  32. package/src/runtime/rpc/rpc-worker.ts +146 -26
  33. package/src/runtime/run-worker.ts +89 -15
  34. package/src/runtime/scheduling/run-coalesced-task-group.ts +3 -0
  35. package/src/runtime/skill-instructions.ts +66 -1
  36. package/src/runtime/surface/resolve-surface.ts +34 -19
  37. package/src/runtime/task-runner/child-executor.ts +132 -0
  38. package/src/runtime/task-runner/pre-execution.ts +8 -0
  39. package/src/runtime/task-runner.ts +6 -0
  40. package/src/schema/config-schema.ts +20 -8
  41. package/src/schema/team-tool-schema.ts +4 -2
  42. package/src/state/contracts.ts +5 -0
  43. package/src/ui/dashboard-panes/agents-pane.ts +3 -2
  44. package/src/ui/dashboard-panes/health-pane.ts +6 -1
  45. package/src/ui/dashboard-panes/mailbox-pane.ts +12 -7
  46. package/src/ui/dashboard-panes/plan-pane.ts +5 -2
  47. package/src/ui/dashboard-panes/progress-pane.ts +8 -2
  48. package/src/ui/format-helpers.ts +11 -0
  49. package/src/ui/live-conversation-overlay.ts +5 -3
  50. package/src/ui/live-duration.ts +5 -4
  51. package/src/ui/live-run-sidebar.ts +90 -20
  52. package/src/ui/overlays/mailbox-compose-overlay.ts +31 -1
  53. package/src/ui/rail.ts +14 -1
  54. package/src/ui/run-action-dispatcher.ts +2 -1
  55. package/src/ui/run-dashboard.ts +62 -22
  56. package/src/ui/run-snapshot-cache.ts +32 -0
  57. package/src/ui/settings-overlay.ts +36 -3
  58. package/src/ui/tool-renderers/brief-mode.ts +2 -2
  59. package/src/ui/transcript-viewer.ts +4 -1
  60. package/src/ui/widget/index.ts +10 -2
  61. package/src/ui/widget/task-list.ts +22 -7
  62. package/src/ui/widget/widget-model.ts +5 -4
  63. package/src/ui/widget/widget-renderer.ts +212 -37
  64. package/src/utils/relative-time.ts +11 -3
  65. package/src/utils/socket-path.ts +48 -8
  66. package/src/utils/visual.ts +32 -2
@@ -53,31 +53,53 @@ function statusIcon(status: TeamRunManifest["status"]): string {
53
53
  * process cwd matches the session cwd.
54
54
  */
55
55
  export function suggestRunIds(_prefix: string, cwd?: string): AutocompleteItem[] | null {
56
- const resolvedCwd = cwd ?? process.cwd();
57
- const runs = listRecentRuns(resolvedCwd, MAX_RUN_SUGGESTIONS);
58
- if (runs.length === 0) return null;
59
- const items: AutocompleteItem[] = runs.map((run) => ({
60
- value: run.runId,
61
- label: run.runId,
62
- description: `${statusIcon(run.status)} ${run.status} · ${run.team} · ${(run.goal ?? "").slice(0, 48)}`,
63
- }));
64
- return filterByPrefix(items, _prefix);
56
+ // L-crash (2026-10-08): completion callbacks run inside pi's autocomplete
57
+ // provider, which does not catch extension errors — never throw here.
58
+ try {
59
+ const resolvedCwd = cwd ?? process.cwd();
60
+ const runs = listRecentRuns(resolvedCwd, MAX_RUN_SUGGESTIONS);
61
+ if (runs.length === 0) return null;
62
+ const items: AutocompleteItem[] = runs.map((run) => ({
63
+ value: run.runId,
64
+ label: run.runId,
65
+ description: `${statusIcon(run.status)} ${run.status} · ${run.team} · ${(run.goal ?? "").slice(0, 48)}`,
66
+ }));
67
+ return filterByPrefix(items, _prefix);
68
+ } catch {
69
+ return null;
70
+ }
65
71
  }
66
72
 
67
- /** Suggest task IDs within a specific run (for /team-result <runId> <taskId>). */
73
+ /**
74
+ * Suggest task IDs within a specific run (for /team-result <runId> <taskId>).
75
+ *
76
+ * L-crash (2026-10-08, P0): this runs inside pi's autocomplete provider
77
+ * (`CombinedAutocompleteProvider.getSuggestions`), which does NOT catch
78
+ * extension errors — a throw here kills the whole pi process (live-caught:
79
+ * uncaught_exception "Invalid runId: team_x/team-transcript" after the
80
+ * autocomplete popup raced typed input and glued a "/…" suffix onto the
81
+ * runId token; assertSafePathId correctly rejected it, but the throw
82
+ * escaped). Completion callbacks must NEVER throw: any failure -> null.
83
+ */
68
84
  export async function suggestTaskIds(runId: string, prefix: string, cwd?: string): Promise<AutocompleteItem[] | null> {
69
- const resolvedCwd = cwd ?? process.cwd();
70
- // Dynamic import to avoid pulling state-store into the hot command-registration path.
71
- // LAZY: defer dynamic import of ../state/stores/state-store.ts to its call site.
72
- const { loadRunManifestById } = await import("../state/stores/state-store.ts");
73
- const loaded = loadRunManifestById(resolvedCwd, runId);
74
- if (!loaded) return null;
75
- const items: AutocompleteItem[] = loaded.tasks.map((task) => ({
76
- value: task.id,
77
- label: task.id,
78
- description: `${task.status} · ${task.role} · ${task.title?.slice(0, 40) ?? ""}`,
79
- }));
80
- return filterByPrefix(items, prefix);
85
+ try {
86
+ const resolvedCwd = cwd ?? process.cwd();
87
+ // Dynamic import to avoid pulling state-store into the hot command-registration path.
88
+ // LAZY: defer dynamic import of ../state/stores/state-store.ts to its call site.
89
+ const { loadRunManifestById } = await import("../state/stores/state-store.ts");
90
+ const loaded = loadRunManifestById(resolvedCwd, runId);
91
+ if (!loaded) return null;
92
+ const items: AutocompleteItem[] = loaded.tasks.map((task) => ({
93
+ value: task.id,
94
+ label: task.id,
95
+ description: `${task.status} · ${task.role} · ${task.title?.slice(0, 40) ?? ""}`,
96
+ }));
97
+ return filterByPrefix(items, prefix);
98
+ } catch {
99
+ // Invalid runId (path-unsafe chars), import/state-store/cache failure —
100
+ // degrade to "no suggestions" instead of crashing the host process.
101
+ return null;
102
+ }
81
103
  }
82
104
 
83
105
  /** Suggest available teams for /team-run <team>. */
@@ -92,6 +92,15 @@ function startForegroundRunImpl(
92
92
  runId?: string,
93
93
  ): void {
94
94
  const ownerGeneration = ctx.captureSessionGeneration();
95
+ // Issue #62: tool-dispatched runs receive a SPREAD COPY of the session
96
+ // context (withSessionId() always returns { ...ctx }), so the object-
97
+ // identity check (isContextCurrent) was false from the very first moment
98
+ // and the whole completion block below was silently skipped for every
99
+ // foreground run started via the team/subagent tools — stale working
100
+ // message, no crew:run-completed entry, no crew.run.* metrics. Liveness
101
+ // for the reporting half is therefore session-ID based; the UI clear is
102
+ // unconditional (try/catch guards a disposed ctx).
103
+ const ownerSessionId = extensionCtx.sessionManager?.getSessionId?.();
95
104
  const controller = new AbortController();
96
105
  const key = runId ?? Symbol();
97
106
  ctx.foregroundTeamRunControllers.set(key, controller);
@@ -130,7 +139,7 @@ function startForegroundRunImpl(
130
139
  logInternalError("register.foreground-run-failure", statusError, `runId=${runId}`);
131
140
  }
132
141
  }
133
- if (ctx.isContextCurrent(extensionCtx, ownerGeneration)) {
142
+ if (ctx.isOwnerSessionCurrent(ownerGeneration, ownerSessionId)) {
134
143
  extensionCtx.ui.notify(`pi-crew foreground run failed: ${message}`, "error");
135
144
  } else {
136
145
  logInternalError("register.foreground-run-failure", error, `runId=${runId} context disposed`);
@@ -145,18 +154,18 @@ function startForegroundRunImpl(
145
154
  })
146
155
  .catch((error) => logInternalError("register.foreground-watchdog", error, `runId=${runId}`));
147
156
  }
148
- const ownerCurrent = ctx.isContextCurrent(extensionCtx, ownerGeneration);
149
- // The owner ctx can go stale while the run is in flight (e.g. the
150
- // user opened an agent session view via /crew-view and the run
151
- // finishes there): the `hasUI` getter THROWS on a stale ctx, so
152
- // it must only be touched for the still-current owner.
153
- if (ownerCurrent && extensionCtx.hasUI) {
154
- try {
157
+ const ownerCurrent = ctx.isOwnerSessionCurrent(ownerGeneration, ownerSessionId);
158
+ // Issue #62: clear the working message UNCONDITIONALLY. The `hasUI`
159
+ // getter THROWS on a disposed ctx, so touch it inside try/catch —
160
+ // a stale ctx just means the host already reset its UI; a live one
161
+ // must never keep spinning a finished run's label.
162
+ try {
163
+ if (extensionCtx.hasUI) {
155
164
  setWorkingIndicator(extensionCtx);
156
165
  extensionCtx.ui.setWorkingMessage();
157
- } catch {
158
- /* ignore */
159
166
  }
167
+ } catch {
168
+ /* disposed ctx — host-side UI reset already happened */
160
169
  }
161
170
  if (ownerCurrent && runId) {
162
171
  const loaded = loadRunManifestById(extensionCtx.cwd, runId);