pi-crew 0.9.35 → 0.9.37

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 CHANGED
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.37] — cold context + subagent streaming (2026-07-14)
4
+
5
+ Two performance improvements targeting perceived subagent slowness:
6
+
7
+ ### Phase 1: Cold Context (inheritContext default → true)
8
+
9
+ - **`inheritContext` default changed from `false` → `true`** — subagents now receive parent conversation context by default, eliminating redundant re-exploration of files the main session already processed. Configurable via `runtime.inheritContext`.
10
+ - **`buildParentContext()` token budget** — 12K char budget with most-recent-first retention prevents token bloat on large parent sessions.
11
+ - **Noisy content filtering** — skips file dumps (code blocks, `ls` output, import lists >1K chars) and truncates long assistant messages to 200 chars. Compaction summaries kept in full.
12
+ - **Tests**: `test/unit/build-parent-context.test.ts` (10 cases).
13
+
14
+ ### Phase 2: Subagent Streaming (child-process → widget)
15
+
16
+ - **`ProgressTracker.handleWorkerEvent()`** — bridges child-process JSON events (from `onJsonEvent`) into `crewEventBus`, enabling real-time widget display of tool calls, assistant text, and token usage for BOTH runtimes (previously only live-session had streaming).
17
+ - **`register.ts` onJsonEvent** — now forwards events to `globalProgressTracker` (was only feeding `overflowTracker`).
18
+ - **`subagent-tools.ts` inline progress** — enriched with real-time worker progress: ⚡ current tool, 📊 tokens, 💬 partial assistant text.
19
+ - **Throttled to 500ms per worker** to prevent widget flooding.
20
+ - **Tests**: `test/unit/progress-tracker-worker.test.ts` (8 cases).
21
+
22
+ ## [0.9.36] — security audit round 1 (2026-07-14)
23
+
24
+ Three security hardening fixes from the first formal security audit pass (run via `team_20260714045239_bb49d72211b966a2` + verified via `team_20260714062932_638dbb0310cc9f77`, 12/12 targeted tests pass, typecheck clean):
25
+
26
+ - **`writeIntermediate()` path-traversal guard** — `src/workflows/intermediate-store.ts`. `writeIntermediate()` previously had no validation on `phase`/`stepId` before constructing the filename, while the sibling `readIntermediate()` was already hardened (M-2 fix 2026-06-23). An attacker-controlled `phase` or `stepId` could write JSON to arbitrary paths. Fix: added `isSafePathId(phase) && isSafePathId(stepId)` guard at the top of `writeIntermediate()` — throws `Error("Invalid phase or stepId for intermediate store")` on bad input. Closes the write/read asymmetry.
27
+ - **Tests**: `test/unit/intermediate-store-traversal.test.ts` (+3 cases).
28
+ - **`InstinctStore` `projectId` validation** — `src/state/instinct-store.ts`. All 4 public methods that accept `projectId` (`saveInstinct`, `getProjectInstincts`, `promoteInstinct`, `deleteInstinct`) now call `assertSafePathId("projectId", projectId)` before using the value in `path.join()`.
29
+ - **Tests**: `test/unit/instinct-store-projectid-guard.test.ts` (+4 cases).
30
+ - **Verification env sanitization default → opt-out** — `src/runtime/verification-gates.ts`. `isVerificationEnvSanitizeEnabled()` flipped from opt-in (`PI_CREW_VERIFICATION_SANITIZE_ENV=1`) to opt-out (default `true`, explicitly disabled via env var set to `"0"`). Prevents accidental secret leakage to verification commands in untrusted repos — the safe path is now the default. `PI_CREW_VERIFICATION_PRESERVE_ENV=KEY1,KEY2,...` remains the escape hatch for tests/flows that legitimately need specific secrets.
31
+ - **Tests**: `test/unit/verification-gates-env-sanitize.test.ts` (5 cases) + `test/unit/verification-env-sanitize.test.ts` (7 integration cases).
32
+
33
+ **False positives eliminated during the audit** (documented so they don't reappear):
34
+
35
+ - `runtime/run-cache.ts` — cache keys are SHA-256 hex hashes (16 chars) — inherently safe from path injection.
36
+ - `runtime/task-runner/retrieval-orchestrator.ts` — `rg` invocations use `node:child_process.spawn()` with structured args, no shell. No injection vector.
37
+ - `runtime/pi-spawn.ts:159` `execSync("npm root -g")` — hardcoded command, `execSync` is intentional for Windows `PATHEXT` resolution.
38
+
39
+ **Files changed**: 4 src files, 3 test files, 1 comment-only update. +100 / -13 net.
40
+
41
+ ### Audit findings (security survey, see `.gsd/AUDIT-PLAN.md` for full detail)
42
+
43
+ Surveyed 36 process-execution call sites (`execSync`/`spawn`/`execFileSync`), 100+ `import()` sites, 13 `require()` sites, 9 `__proto__` references, path-traversal patterns, env sanitization, and unbounded data structures.
44
+
45
+ - **Path traversal**: 2 real issues fixed (`intermediate-store.ts`, `instinct-store.ts`); 1 false positive (`run-cache.ts`).
46
+ - **Process execution**: 1 critical-pattern (`sh -c` in `verification-gates.ts` — properly guarded by `validateGateCommand()`), 1 `execSync` with hardcoded command (intentional), 34 safe `spawn`/`execFileSync` with args arrays.
47
+ - **Env handling**: 1 real issue fixed (verification env default flipped to opt-out); 2 low/info items (rg inherits full env, `diagnostic-export` writes non-secret env vars — both by design).
48
+ - **Code injection**: 0 `eval`, 0 `new Function`, 0 `vm.*` usage, 0 `prototype[`. 9 `__proto__` references — all prototype-pollution defenses (`POLLUTED_KEYS` deny-lists).
49
+ - **Memory safety**: all major Maps/Sets have MAX_* caps + LRU eviction (workflowCache=32, teamCache=32, asyncAgentReaderCache=128, agentEventSeqCache=1000, liveAgents=5000, NotificationRouter.seen=10000, etc.).
50
+
3
51
  ## [0.9.35] — performance plan Phases 1–3 + 2 wiring fixes (2026-07-13)
4
52
 
5
53
  Shipped the **forward performance optimization plan** in 3 phases (`docs/performance-optimization-execution-plan.md`) plus 2 follow-up wiring fixes for the `pipeline` workflow and `schedule` action. 8 performance optimizations landed + 1 bundle rebuild. End-to-end verified post-restart via `team action='run', workflow='pipeline', team='research'` (`team_20260713102348_529c8e4e79e90e20`, 4/4 tasks, 142,686 tokens, 6.4 min, pipeline-summary.md emitted with "PIPELINE_WORKFLOW_OK" + scheduled job successfully removed via new subAction). Typecheck clean, 63 + 58 + 92 targeted unit tests pass across the modified suites.
@@ -8410,7 +8410,7 @@
8410
8410
  "format": "esm"
8411
8411
  },
8412
8412
  "src/extension/team-tool/context.ts": {
8413
- "bytes": 3419,
8413
+ "bytes": 5581,
8414
8414
  "imports": [
8415
8415
  {
8416
8416
  "path": "src/extension/tool-result.ts",
@@ -8577,7 +8577,7 @@
8577
8577
  "format": "esm"
8578
8578
  },
8579
8579
  "src/extension/team-tool/handle-settings.ts": {
8580
- "bytes": 17990,
8580
+ "bytes": 17989,
8581
8581
  "imports": [
8582
8582
  {
8583
8583
  "path": "src/config/config.ts",
@@ -12891,7 +12891,7 @@
12891
12891
  "format": "esm"
12892
12892
  },
12893
12893
  "src/runtime/async-runner.ts": {
12894
- "bytes": 14704,
12894
+ "bytes": 14705,
12895
12895
  "imports": [
12896
12896
  {
12897
12897
  "path": "node:child_process",
@@ -14759,7 +14759,7 @@
14759
14759
  "format": "esm"
14760
14760
  },
14761
14761
  "src/runtime/verification-gates.ts": {
14762
- "bytes": 17042,
14762
+ "bytes": 17191,
14763
14763
  "imports": [
14764
14764
  {
14765
14765
  "path": "node:child_process",
@@ -16950,7 +16950,7 @@
16950
16950
  "format": "esm"
16951
16951
  },
16952
16952
  "src/ui/settings-overlay.ts": {
16953
- "bytes": 31026,
16953
+ "bytes": 31025,
16954
16954
  "imports": [
16955
16955
  {
16956
16956
  "path": "src/utils/visual.ts",
@@ -17720,8 +17720,30 @@
17720
17720
  ],
17721
17721
  "format": "esm"
17722
17722
  },
17723
+ "src/observability/event-bus.ts": {
17724
+ "bytes": 2219,
17725
+ "imports": [
17726
+ {
17727
+ "path": "src/utils/internal-error.ts",
17728
+ "kind": "import-statement",
17729
+ "original": "../utils/internal-error.ts"
17730
+ }
17731
+ ],
17732
+ "format": "esm"
17733
+ },
17734
+ "src/runtime/progress-tracker.ts": {
17735
+ "bytes": 7543,
17736
+ "imports": [
17737
+ {
17738
+ "path": "src/observability/event-bus.ts",
17739
+ "kind": "import-statement",
17740
+ "original": "../observability/event-bus.ts"
17741
+ }
17742
+ ],
17743
+ "format": "esm"
17744
+ },
17723
17745
  "src/extension/registration/subagent-tools.ts": {
17724
- "bytes": 17386,
17746
+ "bytes": 18292,
17725
17747
  "imports": [
17726
17748
  {
17727
17749
  "path": "node_modules/@sinclair/typebox/build/esm/index.mjs",
@@ -17753,6 +17775,11 @@
17753
17775
  "kind": "import-statement",
17754
17776
  "original": "../../runtime/crew-agent-records.ts"
17755
17777
  },
17778
+ {
17779
+ "path": "src/runtime/progress-tracker.ts",
17780
+ "kind": "import-statement",
17781
+ "original": "../../runtime/progress-tracker.ts"
17782
+ },
17756
17783
  {
17757
17784
  "path": "src/runtime/role-permission.ts",
17758
17785
  "kind": "import-statement",
@@ -17993,7 +18020,7 @@
17993
18020
  "format": "esm"
17994
18021
  },
17995
18022
  "src/extension/register.ts": {
17996
- "bytes": 68063,
18023
+ "bytes": 68402,
17997
18024
  "imports": [
17998
18025
  {
17999
18026
  "path": "node:fs",
@@ -18315,6 +18342,11 @@
18315
18342
  "kind": "import-statement",
18316
18343
  "original": "../i18n.ts"
18317
18344
  },
18345
+ {
18346
+ "path": "src/runtime/progress-tracker.ts",
18347
+ "kind": "import-statement",
18348
+ "original": "../runtime/progress-tracker.ts"
18349
+ },
18318
18350
  {
18319
18351
  "path": "src/runtime/session-resources.ts",
18320
18352
  "kind": "import-statement",
@@ -18415,7 +18447,7 @@
18415
18447
  "imports": [],
18416
18448
  "exports": [],
18417
18449
  "inputs": {},
18418
- "bytes": 6696933
18450
+ "bytes": 6717985
18419
18451
  },
18420
18452
  "dist/index.mjs": {
18421
18453
  "imports": [
@@ -20931,7 +20963,7 @@
20931
20963
  "bytesInOutput": 1541
20932
20964
  },
20933
20965
  "src/extension/team-tool/context.ts": {
20934
- "bytesInOutput": 2198
20966
+ "bytesInOutput": 3237
20935
20967
  },
20936
20968
  "src/extension/team-tool/intent-policy.ts": {
20937
20969
  "bytesInOutput": 1245
@@ -20952,7 +20984,7 @@
20952
20984
  "bytesInOutput": 1876
20953
20985
  },
20954
20986
  "src/extension/team-tool/handle-settings.ts": {
20955
- "bytesInOutput": 16052
20987
+ "bytesInOutput": 16051
20956
20988
  },
20957
20989
  "src/workflows/validate-workflow.ts": {
20958
20990
  "bytesInOutput": 1927
@@ -21546,7 +21578,7 @@
21546
21578
  "bytesInOutput": 2699
21547
21579
  },
21548
21580
  "src/runtime/async-runner.ts": {
21549
- "bytesInOutput": 8942
21581
+ "bytesInOutput": 8943
21550
21582
  },
21551
21583
  "src/subagents/async-entry.ts": {
21552
21584
  "bytesInOutput": 119
@@ -21792,7 +21824,7 @@
21792
21824
  "bytesInOutput": 810
21793
21825
  },
21794
21826
  "src/runtime/verification-gates.ts": {
21795
- "bytesInOutput": 7561
21827
+ "bytesInOutput": 7597
21796
21828
  },
21797
21829
  "src/runtime/worker-startup.ts": {
21798
21830
  "bytesInOutput": 2344
@@ -21960,7 +21992,7 @@
21960
21992
  "bytesInOutput": 15659
21961
21993
  },
21962
21994
  "src/ui/settings-overlay.ts": {
21963
- "bytesInOutput": 30589
21995
+ "bytesInOutput": 30588
21964
21996
  },
21965
21997
  "src/extension/registration/commands.ts": {
21966
21998
  "bytesInOutput": 40488
@@ -22020,7 +22052,7 @@
22020
22052
  "bytesInOutput": 3150
22021
22053
  },
22022
22054
  "src/extension/register.ts": {
22023
- "bytesInOutput": 45555
22055
+ "bytesInOutput": 45685
22024
22056
  },
22025
22057
  "src/runtime/batch-barrier.ts": {
22026
22058
  "bytesInOutput": 3060
@@ -22104,11 +22136,17 @@
22104
22136
  "bytesInOutput": 647
22105
22137
  },
22106
22138
  "src/extension/registration/subagent-tools.ts": {
22107
- "bytesInOutput": 15548
22139
+ "bytesInOutput": 16197
22108
22140
  },
22109
22141
  "src/i18n.ts": {
22110
22142
  "bytesInOutput": 6837
22111
22143
  },
22144
+ "src/observability/event-bus.ts": {
22145
+ "bytesInOutput": 1170
22146
+ },
22147
+ "src/runtime/progress-tracker.ts": {
22148
+ "bytesInOutput": 6660
22149
+ },
22112
22150
  "src/extension/session-summary.ts": {
22113
22151
  "bytesInOutput": 512
22114
22152
  },
@@ -22125,7 +22163,7 @@
22125
22163
  "bytesInOutput": 81
22126
22164
  }
22127
22165
  },
22128
- "bytes": 3172886
22166
+ "bytes": 3182689
22129
22167
  }
22130
22168
  }
22131
22169
  }
package/dist/index.mjs CHANGED
@@ -24757,6 +24757,7 @@ var init_workflow_serializer = __esm({
24757
24757
  // src/extension/team-tool/context.ts
24758
24758
  var context_exports = {};
24759
24759
  __export(context_exports, {
24760
+ MAX_PARENT_CONTEXT_CHARS: () => MAX_PARENT_CONTEXT_CHARS,
24760
24761
  buildParentContext: () => buildParentContext,
24761
24762
  configRecord: () => configRecord,
24762
24763
  formatScoped: () => formatScoped,
@@ -24780,6 +24781,10 @@ function extractTextContent(content) {
24780
24781
  (part) => part && typeof part === "object" && !Array.isArray(part) && typeof part.text === "string" ? part.text : ""
24781
24782
  ).filter(Boolean).join("\n");
24782
24783
  }
24784
+ function isNoisyContent(text) {
24785
+ if (text.length < NOISY_THRESHOLD) return false;
24786
+ return NOISY_PREFIXES.some((prefix) => text.startsWith(prefix));
24787
+ }
24783
24788
  function buildParentContext(ctx) {
24784
24789
  const branch = ctx.sessionManager?.getBranch?.();
24785
24790
  if (!Array.isArray(branch) || branch.length === 0) return void 0;
@@ -24787,28 +24792,50 @@ function buildParentContext(ctx) {
24787
24792
  for (const entry of branch.slice(-20)) {
24788
24793
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
24789
24794
  const record = entry;
24790
- if (record.type === "compaction" && typeof record.summary === "string") parts.push(`[Summary]: ${record.summary}`);
24795
+ if (record.type === "compaction" && typeof record.summary === "string") {
24796
+ parts.push(`[Summary]: ${record.summary}`);
24797
+ continue;
24798
+ }
24791
24799
  const message = record.message && typeof record.message === "object" && !Array.isArray(record.message) ? record.message : void 0;
24792
24800
  if (!message || message.role !== "user" && message.role !== "assistant") continue;
24793
- const text = extractTextContent(message.content).trim();
24794
- if (text) parts.push(`[${message.role === "user" ? "User" : "Assistant"}]: ${text}`);
24801
+ let text = extractTextContent(message.content).trim();
24802
+ if (!text) continue;
24803
+ if (isNoisyContent(text)) continue;
24804
+ if (message.role === "assistant" && text.length > MAX_ASSISTANT_MSG_CHARS) {
24805
+ text = `${text.slice(0, TRUNCATED_ASSISTANT_CHARS)}\u2026`;
24806
+ }
24807
+ parts.push(`[${message.role === "user" ? "User" : "Assistant"}]: ${text}`);
24795
24808
  }
24796
24809
  if (!parts.length) return void 0;
24810
+ let totalChars = 0;
24811
+ const budgeted = [];
24812
+ for (const part of [...parts].reverse()) {
24813
+ if (totalChars + part.length > MAX_PARENT_CONTEXT_CHARS) break;
24814
+ budgeted.unshift(part);
24815
+ totalChars += part.length;
24816
+ }
24817
+ if (!budgeted.length) return void 0;
24797
24818
  return [
24798
24819
  `# Parent Conversation Context`,
24799
24820
  "The following context was inherited from the parent Pi session. Treat it as reference-only.",
24800
24821
  "",
24801
- parts.join("\n\n")
24822
+ budgeted.join("\n\n")
24802
24823
  ].join("\n");
24803
24824
  }
24804
24825
  function configRecord(config) {
24805
24826
  if (!config || typeof config !== "object" || Array.isArray(config)) return {};
24806
24827
  return config;
24807
24828
  }
24829
+ var MAX_PARENT_CONTEXT_CHARS, MAX_ASSISTANT_MSG_CHARS, TRUNCATED_ASSISTANT_CHARS, NOISY_THRESHOLD, NOISY_PREFIXES;
24808
24830
  var init_context = __esm({
24809
24831
  "src/extension/team-tool/context.ts"() {
24810
24832
  "use strict";
24811
24833
  init_tool_result();
24834
+ MAX_PARENT_CONTEXT_CHARS = 12e3;
24835
+ MAX_ASSISTANT_MSG_CHARS = 500;
24836
+ TRUNCATED_ASSISTANT_CHARS = 200;
24837
+ NOISY_THRESHOLD = 1e3;
24838
+ NOISY_PREFIXES = ["```", "total ", "drwx", "-rw", "import ", "export "];
24812
24839
  }
24813
24840
  });
24814
24841
 
@@ -26191,7 +26218,7 @@ var init_handle_settings = __esm({
26191
26218
  "runtime.mode": "auto",
26192
26219
  "runtime.maxTurns": 1e4,
26193
26220
  "runtime.graceTurns": 5,
26194
- "runtime.inheritContext": false,
26221
+ "runtime.inheritContext": true,
26195
26222
  "runtime.promptMode": "replace",
26196
26223
  "runtime.completionMutationGuard": "warn",
26197
26224
  "runtime.isolationPolicy": void 0,
@@ -48581,7 +48608,7 @@ var init_async_runner = __esm({
48581
48608
  // Phase 1.5: worker-thread atomic writer opt-in (RFC 15).
48582
48609
  "PI_CREW_WORKER_ATOMIC_WRITER",
48583
48610
  "PI_TEAMS_WORKER_ATOMIC_WRITER",
48584
- // Phase 1.5 #1: verification env sanitization opt-in (RFC 13 §6).
48611
+ // Phase 1.5 #1: verification env sanitization opt-out (RFC 13 §6).
48585
48612
  "PI_CREW_VERIFICATION_SANITIZE_ENV",
48586
48613
  "PI_TEAMS_VERIFICATION_SANITIZE_ENV",
48587
48614
  "PI_CREW_VERIFICATION_PRESERVE_ENV",
@@ -58026,7 +58053,10 @@ import { spawn as spawn4 } from "node:child_process";
58026
58053
  import * as fs82 from "node:fs";
58027
58054
  import * as path69 from "node:path";
58028
58055
  function isVerificationEnvSanitizeEnabled() {
58029
- return process.env.PI_CREW_VERIFICATION_SANITIZE_ENV === "1" || process.env.PI_TEAMS_VERIFICATION_SANITIZE_ENV === "1";
58056
+ if (process.env.PI_CREW_VERIFICATION_SANITIZE_ENV === "0" || process.env.PI_TEAMS_VERIFICATION_SANITIZE_ENV === "0") {
58057
+ return false;
58058
+ }
58059
+ return true;
58030
58060
  }
58031
58061
  function buildVerificationEnv() {
58032
58062
  if (!isVerificationEnvSanitizeEnabled()) {
@@ -75791,7 +75821,7 @@ var init_settings_overlay = __esm({
75791
75821
  "runtime.mode": "auto",
75792
75822
  "runtime.maxTurns": 1e4,
75793
75823
  "runtime.graceTurns": 5,
75794
- "runtime.inheritContext": false,
75824
+ "runtime.inheritContext": true,
75795
75825
  "runtime.promptMode": "replace",
75796
75826
  "runtime.completionMutationGuard": "warn",
75797
75827
  "runtime.isolationPolicy": void 0,
@@ -84088,6 +84118,259 @@ function initI18n(pi) {
84088
84118
 
84089
84119
  // src/extension/registration/subagent-tools.ts
84090
84120
  init_crew_agent_records();
84121
+
84122
+ // src/observability/event-bus.ts
84123
+ init_internal_error();
84124
+ var EventBus = class _EventBus {
84125
+ listeners = /* @__PURE__ */ new Map();
84126
+ static _instance;
84127
+ static getInstance() {
84128
+ if (!_EventBus._instance) {
84129
+ _EventBus._instance = new _EventBus();
84130
+ }
84131
+ return _EventBus._instance;
84132
+ }
84133
+ /**
84134
+ * Dispose of the EventBus instance and clear all listeners.
84135
+ * Resets the singleton so a new instance can be created.
84136
+ */
84137
+ dispose() {
84138
+ this.listeners.clear();
84139
+ _EventBus._instance = void 0;
84140
+ }
84141
+ emit(event) {
84142
+ const listeners2 = this.listeners.get(event.type);
84143
+ if (listeners2) {
84144
+ for (const listener of listeners2) {
84145
+ try {
84146
+ listener(event);
84147
+ } catch (e) {
84148
+ logInternalError("event-bus.listener", e, `type=${event.type} runId=${event.runId}`);
84149
+ }
84150
+ }
84151
+ }
84152
+ }
84153
+ on(type, listener) {
84154
+ if (!this.listeners.has(type)) {
84155
+ this.listeners.set(type, /* @__PURE__ */ new Set());
84156
+ }
84157
+ this.listeners.get(type).add(listener);
84158
+ return () => {
84159
+ this.listeners.get(type)?.delete(listener);
84160
+ };
84161
+ }
84162
+ off(type, listener) {
84163
+ this.listeners.get(type)?.delete(listener);
84164
+ }
84165
+ };
84166
+ var crewEventBus = EventBus.getInstance();
84167
+
84168
+ // src/runtime/progress-tracker.ts
84169
+ var ProgressTracker = class _ProgressTracker {
84170
+ sessions = /* @__PURE__ */ new Map();
84171
+ track(session, agentId, runId) {
84172
+ if (this.sessions.has(agentId)) {
84173
+ return this.sessions.get(agentId).progress;
84174
+ }
84175
+ const progress = {
84176
+ toolCalls: 0,
84177
+ currentTool: null,
84178
+ toolStartTime: null,
84179
+ errors: [],
84180
+ turns: 0,
84181
+ tokens: { input: 0, output: 0 },
84182
+ status: "running"
84183
+ };
84184
+ const unsubscribe = session.subscribe((event) => {
84185
+ this.handleEvent(event, progress, agentId, runId);
84186
+ });
84187
+ this.sessions.set(agentId, { unsubscribe, progress });
84188
+ return progress;
84189
+ }
84190
+ handleEvent(event, progress, agentId, runId) {
84191
+ switch (event.type) {
84192
+ case "tool_execution_start":
84193
+ progress.toolCalls++;
84194
+ progress.currentTool = event.toolName;
84195
+ progress.toolStartTime = Date.now();
84196
+ crewEventBus.emit({
84197
+ type: "agent:progress",
84198
+ runId,
84199
+ agentId,
84200
+ payload: { ...progress },
84201
+ timestamp: Date.now()
84202
+ });
84203
+ break;
84204
+ case "tool_execution_end":
84205
+ progress.currentTool = null;
84206
+ progress.toolStartTime = null;
84207
+ if (event.isError) {
84208
+ progress.errors.push(String(event.result ?? "Unknown error"));
84209
+ crewEventBus.emit({
84210
+ type: "agent:error",
84211
+ runId,
84212
+ agentId,
84213
+ payload: String(event.result ?? "Unknown error"),
84214
+ timestamp: Date.now()
84215
+ });
84216
+ }
84217
+ crewEventBus.emit({
84218
+ type: "agent:progress",
84219
+ runId,
84220
+ agentId,
84221
+ payload: { ...progress },
84222
+ timestamp: Date.now()
84223
+ });
84224
+ break;
84225
+ case "turn_start":
84226
+ progress.turns++;
84227
+ break;
84228
+ case "agent_end":
84229
+ progress.status = "completed";
84230
+ crewEventBus.emit({
84231
+ type: "agent:complete",
84232
+ runId,
84233
+ agentId,
84234
+ payload: { ...progress },
84235
+ timestamp: Date.now()
84236
+ });
84237
+ break;
84238
+ case "agent_start":
84239
+ progress.status = "running";
84240
+ break;
84241
+ }
84242
+ }
84243
+ untrack(agentId) {
84244
+ const tracked = this.sessions.get(agentId);
84245
+ if (tracked) {
84246
+ tracked.unsubscribe();
84247
+ this.sessions.delete(agentId);
84248
+ }
84249
+ }
84250
+ getProgress(agentId) {
84251
+ return this.sessions.get(agentId)?.progress;
84252
+ }
84253
+ // ── Child-process worker event bridge ──────────────────────────────────
84254
+ //
84255
+ // For child-process runtime, events arrive as raw JSON from the child pi
84256
+ // process's stdout (via onJsonEvent). These methods bridge those events into
84257
+ // the same crewEventBus stream that live-session uses, so the widget shows
84258
+ // real-time tool calls and assistant text for BOTH runtimes.
84259
+ workerProgress = /* @__PURE__ */ new Map();
84260
+ /** Throttle: don't emit more than 1 progress event per 500ms per worker. */
84261
+ lastEmitTs = /* @__PURE__ */ new Map();
84262
+ static EMIT_THROTTLE_MS = 500;
84263
+ /**
84264
+ * Handle a raw child-process JSON event (from onJsonEvent callback).
84265
+ * Processes tool_execution_start/end, agent_start/end, and assistant text.
84266
+ */
84267
+ handleWorkerEvent(taskId, runId, event) {
84268
+ let progress = this.workerProgress.get(taskId);
84269
+ if (!progress) {
84270
+ progress = {
84271
+ toolCalls: 0,
84272
+ currentTool: null,
84273
+ toolStartTime: null,
84274
+ errors: [],
84275
+ turns: 0,
84276
+ tokens: { input: 0, output: 0 },
84277
+ status: "running"
84278
+ };
84279
+ this.workerProgress.set(taskId, progress);
84280
+ }
84281
+ const eventType = typeof event.type === "string" ? event.type : void 0;
84282
+ switch (eventType) {
84283
+ case "tool_execution_start": {
84284
+ progress.toolCalls++;
84285
+ progress.currentTool = typeof event.toolName === "string" ? event.toolName : "unknown";
84286
+ progress.toolStartTime = Date.now();
84287
+ this.emitThrottled(taskId, runId, progress);
84288
+ break;
84289
+ }
84290
+ case "tool_execution_end": {
84291
+ progress.currentTool = null;
84292
+ progress.toolStartTime = null;
84293
+ if (event.isError) {
84294
+ progress.errors.push(String(event.result ?? "Unknown error"));
84295
+ crewEventBus.emit({
84296
+ type: "agent:error",
84297
+ runId,
84298
+ agentId: taskId,
84299
+ payload: String(event.result ?? "Unknown error"),
84300
+ timestamp: Date.now()
84301
+ });
84302
+ }
84303
+ this.emitThrottled(taskId, runId, progress);
84304
+ break;
84305
+ }
84306
+ case "turn_start":
84307
+ case "turn_end":
84308
+ progress.turns++;
84309
+ break;
84310
+ case "agent_start":
84311
+ progress.status = "running";
84312
+ break;
84313
+ case "agent_end":
84314
+ case "agent_settled":
84315
+ progress.status = "completed";
84316
+ this.emitThrottled(taskId, runId, progress);
84317
+ break;
84318
+ case "message":
84319
+ case "message_end": {
84320
+ const message = event.message;
84321
+ if (message?.role === "assistant") {
84322
+ const text = extractWorkerText(message.content);
84323
+ if (text) {
84324
+ progress.partialText = text.slice(-2e3);
84325
+ this.emitThrottled(taskId, runId, progress);
84326
+ }
84327
+ }
84328
+ if (eventType === "message_end" && event.usage && typeof event.usage === "object") {
84329
+ const usage = event.usage;
84330
+ if (typeof usage.input === "number") progress.tokens.input += usage.input;
84331
+ if (typeof usage.output === "number") progress.tokens.output += usage.output;
84332
+ }
84333
+ break;
84334
+ }
84335
+ }
84336
+ }
84337
+ /** Get the accumulated progress for a child-process worker task. */
84338
+ getWorkerProgress(taskId) {
84339
+ return this.workerProgress.get(taskId);
84340
+ }
84341
+ /** Remove a worker from tracking after completion. */
84342
+ untrackWorker(taskId) {
84343
+ this.workerProgress.delete(taskId);
84344
+ this.lastEmitTs.delete(taskId);
84345
+ }
84346
+ /**
84347
+ * Emit a progress event to crewEventBus, throttled to avoid flooding
84348
+ * the widget with re-renders (max 1 event per EMIT_THROTTLE_MS per worker).
84349
+ */
84350
+ emitThrottled(taskId, runId, progress) {
84351
+ const now = Date.now();
84352
+ const last = this.lastEmitTs.get(taskId) ?? 0;
84353
+ if (now - last < _ProgressTracker.EMIT_THROTTLE_MS) return;
84354
+ this.lastEmitTs.set(taskId, now);
84355
+ crewEventBus.emit({
84356
+ type: "agent:progress",
84357
+ runId,
84358
+ agentId: taskId,
84359
+ payload: { ...progress },
84360
+ timestamp: now
84361
+ });
84362
+ }
84363
+ };
84364
+ var globalProgressTracker = new ProgressTracker();
84365
+ function extractWorkerText(content) {
84366
+ if (typeof content === "string") return content;
84367
+ if (!Array.isArray(content)) return "";
84368
+ return content.map(
84369
+ (part) => part && typeof part === "object" && !Array.isArray(part) && typeof part.text === "string" ? part.text : ""
84370
+ ).filter(Boolean).join("\n");
84371
+ }
84372
+
84373
+ // src/extension/registration/subagent-tools.ts
84091
84374
  init_role_permission();
84092
84375
  init_state_store();
84093
84376
  init_manager();
@@ -84455,7 +84738,20 @@ function startAgentToolProgress(cwd, agentRecordId, onUpdate, manager) {
84455
84738
  agents,
84456
84739
  error: record.error
84457
84740
  });
84458
- onUpdate({ content: [{ type: "text", text }] });
84741
+ let progressLine = text;
84742
+ if (tasks && tasks.length > 0) {
84743
+ const wp = globalProgressTracker.getWorkerProgress(tasks[0].id);
84744
+ if (wp) {
84745
+ const toolLine = wp.currentTool ? `
84746
+ \u26A1 tool: ${wp.currentTool}` : "";
84747
+ const tokenLine = wp.tokens.input + wp.tokens.output > 0 ? `
84748
+ \u{1F4CA} tokens: ${(wp.tokens.input / 1e3).toFixed(1)}K in, ${(wp.tokens.output / 1e3).toFixed(1)}K out` : "";
84749
+ const textLine = wp.partialText ? `
84750
+ \u{1F4AC} ${wp.partialText.slice(-150).replace(/\n/g, " ")}` : "";
84751
+ progressLine = `${text}${toolLine}${tokenLine}${textLine}`;
84752
+ }
84753
+ }
84754
+ onUpdate({ content: [{ type: "text", text: progressLine }] });
84459
84755
  } catch (error) {
84460
84756
  logInternalError("subagent-tools.progress", error, `agentId=${agentRecordId}`);
84461
84757
  }
@@ -85587,6 +85883,9 @@ Subagent may need manual intervention.`
85587
85883
  const record = event;
85588
85884
  const eventType = typeof record.type === "string" ? record.type : void 0;
85589
85885
  if (eventType) lifecycleState.overflowTracker?.feedEvent(taskId, runId, eventType);
85886
+ if (record && typeof record === "object") {
85887
+ globalProgressTracker.handleWorkerEvent(taskId, runId, record);
85888
+ }
85590
85889
  }
85591
85890
  });
85592
85891
  registerSubagentTools(pi, subagentManager, {