pi-crew 0.1.44 → 0.1.45

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 (142) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/agents/analyst.md +11 -11
  3. package/agents/critic.md +11 -11
  4. package/agents/executor.md +11 -11
  5. package/agents/explorer.md +11 -11
  6. package/agents/planner.md +11 -11
  7. package/agents/reviewer.md +11 -11
  8. package/agents/security-reviewer.md +11 -11
  9. package/agents/test-engineer.md +11 -11
  10. package/agents/verifier.md +11 -11
  11. package/agents/writer.md +11 -11
  12. package/docs/refactor-tasks-phase3.md +394 -394
  13. package/docs/refactor-tasks-phase4.md +564 -564
  14. package/docs/refactor-tasks-phase5.md +402 -402
  15. package/docs/refactor-tasks-phase6.md +662 -662
  16. package/docs/research-extension-examples.md +297 -297
  17. package/docs/research-extension-system.md +324 -324
  18. package/docs/research-optimization-plan.md +548 -548
  19. package/docs/research-phase10-distillation.md +198 -198
  20. package/docs/research-phase11-distillation.md +201 -201
  21. package/docs/research-pi-coding-agent.md +357 -357
  22. package/docs/research-source-pi-crew-reference.md +174 -174
  23. package/docs/runtime-flow.md +148 -148
  24. package/docs/source-runtime-refactor-map.md +83 -83
  25. package/index.ts +6 -6
  26. package/package.json +1 -1
  27. package/src/agents/agent-serializer.ts +34 -34
  28. package/src/extension/cross-extension-rpc.ts +82 -82
  29. package/src/extension/register.ts +8 -1
  30. package/src/extension/registration/commands.ts +18 -2
  31. package/src/extension/registration/compaction-guard.ts +125 -125
  32. package/src/extension/registration/subagent-tools.ts +148 -148
  33. package/src/extension/registration/team-tool.ts +26 -8
  34. package/src/extension/run-bundle-schema.ts +89 -89
  35. package/src/extension/run-maintenance.ts +43 -43
  36. package/src/extension/team-tool/cancel.ts +105 -102
  37. package/src/extension/team-tool/context.ts +1 -0
  38. package/src/extension/team-tool/handle-settings.ts +188 -188
  39. package/src/extension/team-tool/inspect.ts +41 -41
  40. package/src/extension/team-tool/lifecycle-actions.ts +79 -79
  41. package/src/extension/team-tool/plan.ts +19 -19
  42. package/src/extension/team-tool/respond.ts +83 -66
  43. package/src/extension/team-tool/run.ts +1 -0
  44. package/src/i18n.ts +184 -184
  45. package/src/observability/exporters/otlp-exporter.ts +77 -77
  46. package/src/prompt/prompt-runtime.ts +72 -72
  47. package/src/runtime/agent-control.ts +63 -63
  48. package/src/runtime/agent-memory.ts +72 -72
  49. package/src/runtime/agent-observability.ts +114 -114
  50. package/src/runtime/async-marker.ts +26 -26
  51. package/src/runtime/attention-events.ts +28 -28
  52. package/src/runtime/background-runner.ts +53 -53
  53. package/src/runtime/child-pi.ts +444 -444
  54. package/src/runtime/completion-guard.ts +190 -190
  55. package/src/runtime/crew-agent-records.ts +8 -0
  56. package/src/runtime/delivery-coordinator.ts +153 -142
  57. package/src/runtime/direct-run.ts +35 -35
  58. package/src/runtime/foreground-control.ts +82 -82
  59. package/src/runtime/green-contract.ts +46 -46
  60. package/src/runtime/group-join.ts +106 -106
  61. package/src/runtime/heartbeat-gradient.ts +28 -28
  62. package/src/runtime/heartbeat-watcher.ts +124 -124
  63. package/src/runtime/live-agent-control.ts +87 -87
  64. package/src/runtime/live-agent-manager.ts +85 -85
  65. package/src/runtime/live-control-realtime.ts +36 -36
  66. package/src/runtime/live-session-runtime.ts +305 -305
  67. package/src/runtime/overflow-recovery.ts +175 -156
  68. package/src/runtime/parallel-research.ts +44 -44
  69. package/src/runtime/pi-json-output.ts +111 -111
  70. package/src/runtime/policy-engine.ts +79 -79
  71. package/src/runtime/progress-event-coalescer.ts +43 -43
  72. package/src/runtime/recovery-recipes.ts +74 -74
  73. package/src/runtime/retry-executor.ts +64 -64
  74. package/src/runtime/role-permission.ts +39 -39
  75. package/src/runtime/session-resources.ts +25 -25
  76. package/src/runtime/session-snapshot.ts +59 -59
  77. package/src/runtime/session-usage.ts +79 -79
  78. package/src/runtime/sidechain-output.ts +29 -29
  79. package/src/runtime/stale-reconciler.ts +199 -179
  80. package/src/runtime/supervisor-contact.ts +59 -59
  81. package/src/runtime/task-display.ts +38 -38
  82. package/src/runtime/task-output-context.ts +127 -127
  83. package/src/runtime/task-runner/live-executor.ts +101 -101
  84. package/src/runtime/task-runner/progress.ts +119 -119
  85. package/src/runtime/task-runner/result-utils.ts +14 -14
  86. package/src/runtime/task-runner/state-helpers.ts +22 -22
  87. package/src/runtime/team-runner.ts +13 -4
  88. package/src/runtime/worker-heartbeat.ts +21 -21
  89. package/src/runtime/worker-startup.ts +57 -57
  90. package/src/state/state-store.ts +43 -0
  91. package/src/state/task-claims.ts +44 -44
  92. package/src/state/types.ts +2 -0
  93. package/src/state/usage.ts +29 -29
  94. package/src/subagents/async-entry.ts +1 -1
  95. package/src/subagents/index.ts +3 -3
  96. package/src/subagents/live/control.ts +1 -1
  97. package/src/subagents/live/manager.ts +1 -1
  98. package/src/subagents/live/realtime.ts +1 -1
  99. package/src/subagents/live/session-runtime.ts +1 -1
  100. package/src/subagents/manager.ts +1 -1
  101. package/src/subagents/spawn.ts +1 -1
  102. package/src/teams/team-serializer.ts +38 -38
  103. package/src/types/diff.d.ts +18 -18
  104. package/src/ui/crew-footer.ts +101 -101
  105. package/src/ui/crew-select-list.ts +111 -111
  106. package/src/ui/crew-widget.ts +5 -1
  107. package/src/ui/dashboard-panes/mailbox-pane.ts +2 -1
  108. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  109. package/src/ui/dynamic-border.ts +25 -25
  110. package/src/ui/layout-primitives.ts +106 -106
  111. package/src/ui/loaders.ts +158 -158
  112. package/src/ui/powerbar-publisher.ts +1 -1
  113. package/src/ui/render-diff.ts +119 -119
  114. package/src/ui/render-scheduler.ts +143 -143
  115. package/src/ui/run-snapshot-cache.ts +56 -37
  116. package/src/ui/snapshot-types.ts +5 -0
  117. package/src/ui/spinner.ts +17 -17
  118. package/src/ui/status-colors.ts +58 -58
  119. package/src/ui/syntax-highlight.ts +116 -116
  120. package/src/utils/atomic-write.ts +33 -33
  121. package/src/utils/completion-dedupe.ts +63 -63
  122. package/src/utils/frontmatter.ts +68 -68
  123. package/src/utils/git.ts +262 -262
  124. package/src/utils/ids.ts +12 -12
  125. package/src/utils/names.ts +27 -27
  126. package/src/utils/redaction.ts +44 -44
  127. package/src/utils/safe-paths.ts +47 -47
  128. package/src/utils/sleep.ts +32 -32
  129. package/src/workflows/validate-workflow.ts +40 -40
  130. package/src/worktree/branch-freshness.ts +45 -45
  131. package/teams/default.team.md +12 -12
  132. package/teams/fast-fix.team.md +11 -11
  133. package/teams/implementation.team.md +18 -18
  134. package/teams/parallel-research.team.md +14 -14
  135. package/teams/research.team.md +11 -11
  136. package/teams/review.team.md +12 -12
  137. package/workflows/default.workflow.md +29 -29
  138. package/workflows/fast-fix.workflow.md +22 -22
  139. package/workflows/implementation.workflow.md +38 -38
  140. package/workflows/parallel-research.workflow.md +46 -46
  141. package/workflows/research.workflow.md +22 -22
  142. package/workflows/review.workflow.md +30 -30
@@ -1,25 +1,25 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
- import { logInternalError } from "../utils/internal-error.ts";
3
-
4
- /**
5
- * Try to register a cleanup function with Pi's session resource cleanup API (v0.72+).
6
- * Falls back to returning undefined if the API is not available.
7
- *
8
- * The returned function (if defined) can be called to unregister the cleanup.
9
- */
10
- export function tryRegisterSessionCleanup(pi: ExtensionAPI, cleanup: () => void): (() => void) | undefined {
11
- const api = pi as unknown as Record<string, unknown>;
12
- const registerFn = api["registerSessionResourceCleanup"];
13
- if (typeof registerFn === "function") {
14
- try {
15
- const unregister = (registerFn as (fn: () => void) => (() => void) | void)(cleanup);
16
- if (typeof unregister === "function") return unregister;
17
- // API returned void — cleanup is registered but cannot be unregistered
18
- return undefined;
19
- } catch (error) {
20
- logInternalError("session-resources.register", error);
21
- return undefined;
22
- }
23
- }
24
- return undefined;
25
- }
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import { logInternalError } from "../utils/internal-error.ts";
3
+
4
+ /**
5
+ * Try to register a cleanup function with Pi's session resource cleanup API (v0.72+).
6
+ * Falls back to returning undefined if the API is not available.
7
+ *
8
+ * The returned function (if defined) can be called to unregister the cleanup.
9
+ */
10
+ export function tryRegisterSessionCleanup(pi: ExtensionAPI, cleanup: () => void): (() => void) | undefined {
11
+ const api = pi as unknown as Record<string, unknown>;
12
+ const registerFn = api["registerSessionResourceCleanup"];
13
+ if (typeof registerFn === "function") {
14
+ try {
15
+ const unregister = (registerFn as (fn: () => void) => (() => void) | void)(cleanup);
16
+ if (typeof unregister === "function") return unregister;
17
+ // API returned void — cleanup is registered but cannot be unregistered
18
+ return undefined;
19
+ } catch (error) {
20
+ logInternalError("session-resources.register", error);
21
+ return undefined;
22
+ }
23
+ }
24
+ return undefined;
25
+ }
@@ -1,59 +1,59 @@
1
- import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
2
-
3
- /**
4
- * Creates a lightweight snapshot of task state for event emission.
5
- * Prevents mutation-during-callback issues by copying relevant fields.
6
- */
7
- export function snapshotTaskState(task: TeamTaskState): Readonly<TeamTaskState> {
8
- return {
9
- ...task,
10
- dependsOn: [...task.dependsOn],
11
- usage: task.usage ? { ...task.usage } : undefined,
12
- agentProgress: task.agentProgress ? { ...task.agentProgress } : undefined,
13
- heartbeat: task.heartbeat ? { ...task.heartbeat } : undefined,
14
- modelAttempts: task.modelAttempts?.map((a) => ({ ...a })),
15
- modelRouting: task.modelRouting ? { ...task.modelRouting } : undefined,
16
- claim: task.claim ? { ...task.claim } : undefined,
17
- checkpoint: task.checkpoint ? { ...task.checkpoint } : undefined,
18
- attempts: task.attempts?.map((a) => ({ ...a })),
19
- worktree: task.worktree ? { ...task.worktree } : undefined,
20
- };
21
- }
22
-
23
- /**
24
- * Session state snapshot for persistence before session switches.
25
- * Captures the minimal set of data needed to resume operations.
26
- */
27
- export interface SessionStateSnapshot {
28
- /** ISO timestamp of the snapshot */
29
- capturedAt: string;
30
- /** Active run IDs at time of snapshot */
31
- activeRunIds: string[];
32
- /** Number of pending deliveries */
33
- pendingDeliveryCount: number;
34
- /** Session generation counter */
35
- sessionGeneration: number;
36
- /** Summary of active tasks by status */
37
- taskSummary: Record<string, number>;
38
- }
39
-
40
- /**
41
- * Create a session state snapshot for pre-switch persistence.
42
- */
43
- export function createSessionSnapshot(
44
- activeRuns: TeamRunManifest[],
45
- pendingDeliveryCount: number,
46
- sessionGeneration: number,
47
- ): SessionStateSnapshot {
48
- const taskSummary: Record<string, number> = {};
49
- for (const run of activeRuns) {
50
- taskSummary[run.status] = (taskSummary[run.status] ?? 0) + 1;
51
- }
52
- return {
53
- capturedAt: new Date().toISOString(),
54
- activeRunIds: activeRuns.map((r) => r.runId),
55
- pendingDeliveryCount,
56
- sessionGeneration,
57
- taskSummary,
58
- };
59
- }
1
+ import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
2
+
3
+ /**
4
+ * Creates a lightweight snapshot of task state for event emission.
5
+ * Prevents mutation-during-callback issues by copying relevant fields.
6
+ */
7
+ export function snapshotTaskState(task: TeamTaskState): Readonly<TeamTaskState> {
8
+ return {
9
+ ...task,
10
+ dependsOn: [...task.dependsOn],
11
+ usage: task.usage ? { ...task.usage } : undefined,
12
+ agentProgress: task.agentProgress ? { ...task.agentProgress } : undefined,
13
+ heartbeat: task.heartbeat ? { ...task.heartbeat } : undefined,
14
+ modelAttempts: task.modelAttempts?.map((a) => ({ ...a })),
15
+ modelRouting: task.modelRouting ? { ...task.modelRouting } : undefined,
16
+ claim: task.claim ? { ...task.claim } : undefined,
17
+ checkpoint: task.checkpoint ? { ...task.checkpoint } : undefined,
18
+ attempts: task.attempts?.map((a) => ({ ...a })),
19
+ worktree: task.worktree ? { ...task.worktree } : undefined,
20
+ };
21
+ }
22
+
23
+ /**
24
+ * Session state snapshot for persistence before session switches.
25
+ * Captures the minimal set of data needed to resume operations.
26
+ */
27
+ export interface SessionStateSnapshot {
28
+ /** ISO timestamp of the snapshot */
29
+ capturedAt: string;
30
+ /** Active run IDs at time of snapshot */
31
+ activeRunIds: string[];
32
+ /** Number of pending deliveries */
33
+ pendingDeliveryCount: number;
34
+ /** Session generation counter */
35
+ sessionGeneration: number;
36
+ /** Summary of active tasks by status */
37
+ taskSummary: Record<string, number>;
38
+ }
39
+
40
+ /**
41
+ * Create a session state snapshot for pre-switch persistence.
42
+ */
43
+ export function createSessionSnapshot(
44
+ activeRuns: TeamRunManifest[],
45
+ pendingDeliveryCount: number,
46
+ sessionGeneration: number,
47
+ ): SessionStateSnapshot {
48
+ const taskSummary: Record<string, number> = {};
49
+ for (const run of activeRuns) {
50
+ taskSummary[run.status] = (taskSummary[run.status] ?? 0) + 1;
51
+ }
52
+ return {
53
+ capturedAt: new Date().toISOString(),
54
+ activeRunIds: activeRuns.map((r) => r.runId),
55
+ pendingDeliveryCount,
56
+ sessionGeneration,
57
+ taskSummary,
58
+ };
59
+ }
@@ -1,79 +1,79 @@
1
- import * as fs from "node:fs";
2
- import type { UsageState } from "../state/types.ts";
3
-
4
- function asRecord(value: unknown): Record<string, unknown> | undefined {
5
- return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
6
- }
7
-
8
- function numberField(obj: Record<string, unknown>, keys: string[]): number | undefined {
9
- for (const key of keys) {
10
- const value = obj[key];
11
- if (typeof value === "number" && Number.isFinite(value)) return value;
12
- }
13
- return undefined;
14
- }
15
-
16
- function usageFromValue(value: unknown): UsageState | undefined {
17
- const obj = asRecord(value);
18
- if (!obj) return undefined;
19
- const direct: UsageState = {
20
- input: numberField(obj, ["input", "inputTokens", "input_tokens"]),
21
- output: numberField(obj, ["output", "outputTokens", "output_tokens"]),
22
- cacheRead: numberField(obj, ["cacheRead", "cache_read", "cacheReadTokens", "cache_read_tokens"]),
23
- cacheWrite: numberField(obj, ["cacheWrite", "cache_write", "cacheWriteTokens", "cache_write_tokens"]),
24
- cost: numberField(obj, ["cost", "costUsd", "cost_usd"]),
25
- turns: numberField(obj, ["turns", "turnCount", "turn_count"]),
26
- };
27
- if (Object.values(direct).some((entry) => entry !== undefined)) return direct;
28
- for (const key of ["usage", "tokenUsage", "tokens", "stats"]) {
29
- const nested = usageFromValue(obj[key]);
30
- if (nested) return nested;
31
- }
32
- const message = asRecord(obj.message);
33
- return message ? usageFromValue(message.usage) : undefined;
34
- }
35
-
36
- function addUsage(total: UsageState, usage: UsageState): UsageState {
37
- return {
38
- input: (total.input ?? 0) + (usage.input ?? 0),
39
- output: (total.output ?? 0) + (usage.output ?? 0),
40
- cacheRead: (total.cacheRead ?? 0) + (usage.cacheRead ?? 0),
41
- cacheWrite: (total.cacheWrite ?? 0) + (usage.cacheWrite ?? 0),
42
- cost: (total.cost ?? 0) + (usage.cost ?? 0),
43
- turns: (total.turns ?? 0) + (usage.turns ?? 0),
44
- };
45
- }
46
-
47
- function compactUsage(total: UsageState, foundKeys: Set<keyof UsageState>): UsageState | undefined {
48
- if (foundKeys.size === 0) return undefined;
49
- const compact: UsageState = {};
50
- for (const key of foundKeys) compact[key] = total[key];
51
- return compact;
52
- }
53
-
54
- export function parseSessionUsageFromJsonlText(text: string): UsageState | undefined {
55
- let total: UsageState = {};
56
- const foundKeys = new Set<keyof UsageState>();
57
- for (const line of text.split(/\r?\n/)) {
58
- const trimmed = line.trim();
59
- if (!trimmed) continue;
60
- try {
61
- const usage = usageFromValue(JSON.parse(trimmed) as unknown);
62
- if (!usage) continue;
63
- for (const key of Object.keys(usage) as Array<keyof UsageState>) foundKeys.add(key);
64
- total = addUsage(total, usage);
65
- } catch {
66
- // Session JSONL can contain partial/corrupt lines after interrupted workers.
67
- }
68
- }
69
- return compactUsage(total, foundKeys);
70
- }
71
-
72
- export function parseSessionUsage(filePath: string): UsageState | undefined {
73
- try {
74
- if (!fs.existsSync(filePath)) return undefined;
75
- return parseSessionUsageFromJsonlText(fs.readFileSync(filePath, "utf-8"));
76
- } catch {
77
- return undefined;
78
- }
79
- }
1
+ import * as fs from "node:fs";
2
+ import type { UsageState } from "../state/types.ts";
3
+
4
+ function asRecord(value: unknown): Record<string, unknown> | undefined {
5
+ return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
6
+ }
7
+
8
+ function numberField(obj: Record<string, unknown>, keys: string[]): number | undefined {
9
+ for (const key of keys) {
10
+ const value = obj[key];
11
+ if (typeof value === "number" && Number.isFinite(value)) return value;
12
+ }
13
+ return undefined;
14
+ }
15
+
16
+ function usageFromValue(value: unknown): UsageState | undefined {
17
+ const obj = asRecord(value);
18
+ if (!obj) return undefined;
19
+ const direct: UsageState = {
20
+ input: numberField(obj, ["input", "inputTokens", "input_tokens"]),
21
+ output: numberField(obj, ["output", "outputTokens", "output_tokens"]),
22
+ cacheRead: numberField(obj, ["cacheRead", "cache_read", "cacheReadTokens", "cache_read_tokens"]),
23
+ cacheWrite: numberField(obj, ["cacheWrite", "cache_write", "cacheWriteTokens", "cache_write_tokens"]),
24
+ cost: numberField(obj, ["cost", "costUsd", "cost_usd"]),
25
+ turns: numberField(obj, ["turns", "turnCount", "turn_count"]),
26
+ };
27
+ if (Object.values(direct).some((entry) => entry !== undefined)) return direct;
28
+ for (const key of ["usage", "tokenUsage", "tokens", "stats"]) {
29
+ const nested = usageFromValue(obj[key]);
30
+ if (nested) return nested;
31
+ }
32
+ const message = asRecord(obj.message);
33
+ return message ? usageFromValue(message.usage) : undefined;
34
+ }
35
+
36
+ function addUsage(total: UsageState, usage: UsageState): UsageState {
37
+ return {
38
+ input: (total.input ?? 0) + (usage.input ?? 0),
39
+ output: (total.output ?? 0) + (usage.output ?? 0),
40
+ cacheRead: (total.cacheRead ?? 0) + (usage.cacheRead ?? 0),
41
+ cacheWrite: (total.cacheWrite ?? 0) + (usage.cacheWrite ?? 0),
42
+ cost: (total.cost ?? 0) + (usage.cost ?? 0),
43
+ turns: (total.turns ?? 0) + (usage.turns ?? 0),
44
+ };
45
+ }
46
+
47
+ function compactUsage(total: UsageState, foundKeys: Set<keyof UsageState>): UsageState | undefined {
48
+ if (foundKeys.size === 0) return undefined;
49
+ const compact: UsageState = {};
50
+ for (const key of foundKeys) compact[key] = total[key];
51
+ return compact;
52
+ }
53
+
54
+ export function parseSessionUsageFromJsonlText(text: string): UsageState | undefined {
55
+ let total: UsageState = {};
56
+ const foundKeys = new Set<keyof UsageState>();
57
+ for (const line of text.split(/\r?\n/)) {
58
+ const trimmed = line.trim();
59
+ if (!trimmed) continue;
60
+ try {
61
+ const usage = usageFromValue(JSON.parse(trimmed) as unknown);
62
+ if (!usage) continue;
63
+ for (const key of Object.keys(usage) as Array<keyof UsageState>) foundKeys.add(key);
64
+ total = addUsage(total, usage);
65
+ } catch {
66
+ // Session JSONL can contain partial/corrupt lines after interrupted workers.
67
+ }
68
+ }
69
+ return compactUsage(total, foundKeys);
70
+ }
71
+
72
+ export function parseSessionUsage(filePath: string): UsageState | undefined {
73
+ try {
74
+ if (!fs.existsSync(filePath)) return undefined;
75
+ return parseSessionUsageFromJsonlText(fs.readFileSync(filePath, "utf-8"));
76
+ } catch {
77
+ return undefined;
78
+ }
79
+ }
@@ -1,29 +1,29 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { redactSecrets } from "../utils/redaction.ts";
4
-
5
- export interface SidechainEntry {
6
- isSidechain: true;
7
- agentId: string;
8
- type: string;
9
- message: unknown;
10
- timestamp: string;
11
- cwd: string;
12
- }
13
-
14
- export function writeSidechainEntry(filePath: string, entry: Omit<SidechainEntry, "isSidechain" | "timestamp">): void {
15
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
16
- fs.appendFileSync(filePath, `${JSON.stringify(redactSecrets({ isSidechain: true, timestamp: new Date().toISOString(), ...entry }))}\n`, "utf-8");
17
- }
18
-
19
- export function sidechainOutputPath(stateRoot: string, taskId: string): string {
20
- return path.join(stateRoot, "agents", taskId, "sidechain.output.jsonl");
21
- }
22
-
23
- export function eventToSidechainType(event: unknown): string | undefined {
24
- if (!event || typeof event !== "object" || Array.isArray(event)) return undefined;
25
- const type = (event as { type?: unknown }).type;
26
- if (type === "message_start" || type === "message_update" || type === "message_end") return "message";
27
- if (type === "tool_execution_start" || type === "tool_execution_update" || type === "tool_execution_end") return "tool";
28
- return typeof type === "string" ? type : undefined;
29
- }
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { redactSecrets } from "../utils/redaction.ts";
4
+
5
+ export interface SidechainEntry {
6
+ isSidechain: true;
7
+ agentId: string;
8
+ type: string;
9
+ message: unknown;
10
+ timestamp: string;
11
+ cwd: string;
12
+ }
13
+
14
+ export function writeSidechainEntry(filePath: string, entry: Omit<SidechainEntry, "isSidechain" | "timestamp">): void {
15
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
16
+ fs.appendFileSync(filePath, `${JSON.stringify(redactSecrets({ isSidechain: true, timestamp: new Date().toISOString(), ...entry }))}\n`, "utf-8");
17
+ }
18
+
19
+ export function sidechainOutputPath(stateRoot: string, taskId: string): string {
20
+ return path.join(stateRoot, "agents", taskId, "sidechain.output.jsonl");
21
+ }
22
+
23
+ export function eventToSidechainType(event: unknown): string | undefined {
24
+ if (!event || typeof event !== "object" || Array.isArray(event)) return undefined;
25
+ const type = (event as { type?: unknown }).type;
26
+ if (type === "message_start" || type === "message_update" || type === "message_end") return "message";
27
+ if (type === "tool_execution_start" || type === "tool_execution_update" || type === "tool_execution_end") return "tool";
28
+ return typeof type === "string" ? type : undefined;
29
+ }