pi-better-harness 0.3.12 → 0.3.14
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/node_modules/pi-better-background-tasks/package.json +1 -1
- package/node_modules/pi-better-background-tasks/src/shared-navigator.ts +4 -1
- package/node_modules/pi-better-plan/README.md +3 -1
- package/node_modules/pi-better-plan/package.json +1 -1
- package/node_modules/pi-better-plan/src/index.ts +99 -5
- package/node_modules/pi-better-plan/src/workflow-plan.ts +123 -0
- package/node_modules/pi-better-subagents/package.json +1 -1
- package/node_modules/pi-better-subagents/shared-navigator.ts +4 -1
- package/package.json +4 -4
|
@@ -179,7 +179,10 @@ export function ensureBackgroundWorkNavigator(ctx: ExtensionContext, deps: HostD
|
|
|
179
179
|
if (!isNavigatorUiAvailable(ctx)) return;
|
|
180
180
|
const s = state();
|
|
181
181
|
s.uiCtx = ctx;
|
|
182
|
-
s.deps =
|
|
182
|
+
s.deps = {
|
|
183
|
+
...deps,
|
|
184
|
+
createTranscriptComponent: deps.createTranscriptComponent ?? s.deps?.createTranscriptComponent,
|
|
185
|
+
};
|
|
183
186
|
try { (ctx.ui as any).setWidget?.(MAIN_LIST_WIDGET_KEY, undefined); } catch { /* ignore */ }
|
|
184
187
|
s.mainListWidgetInstalled = false;
|
|
185
188
|
s.mainListRequestRender = undefined;
|
|
@@ -23,7 +23,9 @@ Independent foreground and delegated milestones may both be `in_progress`. Use s
|
|
|
23
23
|
|
|
24
24
|
For a DAG, assign stable ids to prerequisite steps and list those ids in dependent steps' `dependsOn`. Dependencies must exist in the same plan; cycles and starting or completing a step before its prerequisites are complete are rejected. `get_plan` reports pending steps whose prerequisites are complete as ready. Plans without edges keep their existing behavior.
|
|
25
25
|
|
|
26
|
-
When an explicitly invoked skill declares `workflow-role: coordinator` in its metadata, that skill's task plan takes precedence. The generic checklist stays persisted but is hidden, and `update_plan` refuses competing updates until workflow ownership is released.
|
|
26
|
+
When an explicitly invoked skill declares `workflow-role: coordinator` in its metadata, that skill's task plan takes precedence. The generic checklist stays persisted but is hidden, and `update_plan` refuses competing updates until workflow ownership is released. `/plan` never opens a stale generic checklist during workflow ownership.
|
|
27
|
+
|
|
28
|
+
For `rush-issues`, call `sync_workflow_plan` with the absolute `.resolve-issues/rush/<run-id>/task-plan.json` path and its persisted `planRevision` after each checkpoint. This binds the active run to the session and displays its fleet stages and actual units in the widget, `/plan`, and `get_plan`. The view reads the file without writing to it; revision mismatches are rejected. The run binding survives Pi session resume, and ownership release restores the prior generic checklist. Other workflow-owned skills retain their own planning and have no generic plan view.
|
|
27
29
|
|
|
28
30
|
## Install
|
|
29
31
|
|
|
@@ -13,6 +13,10 @@ import {
|
|
|
13
13
|
replacePlan,
|
|
14
14
|
} from "./plan-state.js";
|
|
15
15
|
import { createFullPlanComponent, renderCompactPlan } from "./plan-render.js";
|
|
16
|
+
import {
|
|
17
|
+
createRushPlanComponent, readRushPlan, renderRushPlan, workflowBinding,
|
|
18
|
+
WORKFLOW_PLAN_ENTRY, type RushPlan, type WorkflowPlanBinding,
|
|
19
|
+
} from "./workflow-plan.js";
|
|
16
20
|
import {
|
|
17
21
|
EXTENSION_NAME,
|
|
18
22
|
type PlanDisplayMode,
|
|
@@ -59,16 +63,37 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
59
63
|
let currentPlan: PlanSnapshot | null = null;
|
|
60
64
|
let displayMode: PlanDisplayMode = "auto";
|
|
61
65
|
let displayedWorkflowOwner: string | null = null;
|
|
66
|
+
let rushBinding: WorkflowPlanBinding | null = null;
|
|
67
|
+
let rushPlan: RushPlan | null = null;
|
|
68
|
+
let rushError: string | null = null;
|
|
62
69
|
let refreshWidget: ((force?: boolean) => void) | undefined;
|
|
63
70
|
let completedPlanClearTimer: ReturnType<typeof setTimeout> | undefined;
|
|
64
71
|
const unsubscribeWorkflow = pi.events.on("pi-better-workflow:changed", (owner) => {
|
|
65
72
|
displayedWorkflowOwner = owner && typeof owner === "object" && "name" in owner && typeof owner.name === "string"
|
|
66
73
|
? owner.name : null;
|
|
74
|
+
rushBinding = null;
|
|
75
|
+
rushPlan = null;
|
|
76
|
+
rushError = null;
|
|
67
77
|
refreshWidget?.(true);
|
|
68
78
|
});
|
|
69
79
|
|
|
70
80
|
const refresh = (force = false): void => refreshWidget?.(force);
|
|
71
81
|
|
|
82
|
+
const loadRushPlan = (ctx: ExtensionContext): RushPlan | null => {
|
|
83
|
+
if (!rushBinding || workflowPlanOwner(ctx) !== "rush-issues") return null;
|
|
84
|
+
try {
|
|
85
|
+
const plan = readRushPlan(rushBinding.path, ctx.cwd);
|
|
86
|
+
if (plan.runId !== rushBinding.runId) throw new Error("Rush run identity changed.");
|
|
87
|
+
rushPlan = plan;
|
|
88
|
+
rushError = null;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
rushPlan = null;
|
|
91
|
+
rushError = error instanceof Error ? error.message : String(error);
|
|
92
|
+
}
|
|
93
|
+
refresh(true);
|
|
94
|
+
return rushPlan;
|
|
95
|
+
};
|
|
96
|
+
|
|
72
97
|
const cancelCompletedPlanClear = (): void => {
|
|
73
98
|
if (completedPlanClearTimer) clearTimeout(completedPlanClearTimer);
|
|
74
99
|
completedPlanClearTimer = undefined;
|
|
@@ -102,6 +127,10 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
102
127
|
const state = reconstructPlanState(ctx.sessionManager.getBranch());
|
|
103
128
|
currentPlan = state.plan;
|
|
104
129
|
displayedWorkflowOwner = workflowPlanOwner(ctx);
|
|
130
|
+
rushBinding = displayedWorkflowOwner === "rush-issues" ? workflowBinding(ctx.sessionManager.getBranch()) : null;
|
|
131
|
+
rushPlan = null;
|
|
132
|
+
rushError = null;
|
|
133
|
+
if (rushBinding) loadRushPlan(ctx);
|
|
105
134
|
displayMode = state.displayMode;
|
|
106
135
|
if (ctx.hasUI) ctx.ui.setStatus(LEGACY_PLAN_NAV_STATUS_KEY, undefined);
|
|
107
136
|
refresh(true);
|
|
@@ -130,7 +159,14 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
130
159
|
refreshWidget = localRefresh;
|
|
131
160
|
return {
|
|
132
161
|
render(width: number): string[] {
|
|
133
|
-
if (
|
|
162
|
+
if (displayMode === "hidden") return [];
|
|
163
|
+
if (displayedWorkflowOwner) {
|
|
164
|
+
if (displayedWorkflowOwner !== "rush-issues") return [];
|
|
165
|
+
return rushPlan ? renderRushPlan(rushPlan, width) : [
|
|
166
|
+
`rush-issues ${rushError ? `plan unavailable: ${rushError}` : "waiting for task plan"}`,
|
|
167
|
+
].map((line) => line.slice(0, width));
|
|
168
|
+
}
|
|
169
|
+
if (!currentPlan) return [];
|
|
134
170
|
return renderCompactPlan(currentPlan, width, theme as never);
|
|
135
171
|
},
|
|
136
172
|
invalidate() {},
|
|
@@ -144,6 +180,26 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
144
180
|
};
|
|
145
181
|
|
|
146
182
|
const showFullPlan = async (ctx: ExtensionContext): Promise<void> => {
|
|
183
|
+
const owner = workflowPlanOwner(ctx);
|
|
184
|
+
if (owner) {
|
|
185
|
+
const plan = owner === "rush-issues" ? loadRushPlan(ctx) : null;
|
|
186
|
+
if (!plan) {
|
|
187
|
+
ctx.ui.notify(owner === "rush-issues" ? `Rush plan unavailable: ${rushError ?? "not bound"}` : `${owner} owns the task plan.`, "warning");
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (ctx.mode !== "tui") {
|
|
191
|
+
ctx.ui.notify(renderRushPlan(plan, 160, true).join("\n"), "info");
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
await ctx.ui.custom<void>((tui, _theme, _keys, done) => {
|
|
195
|
+
const component = createRushPlanComponent(plan, () => done());
|
|
196
|
+
return { render: (width) => component.render(width), handleInput(data) {
|
|
197
|
+
component.handleInput?.(data);
|
|
198
|
+
tui.requestRender();
|
|
199
|
+
}, invalidate: () => component.invalidate() };
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
147
203
|
if (!currentPlan) {
|
|
148
204
|
if (ctx.hasUI) ctx.ui.notify("No plan is set.", "warning");
|
|
149
205
|
return;
|
|
@@ -167,6 +223,35 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
167
223
|
};
|
|
168
224
|
|
|
169
225
|
|
|
226
|
+
pi.registerTool({
|
|
227
|
+
name: "sync_workflow_plan",
|
|
228
|
+
label: "Sync Workflow Plan",
|
|
229
|
+
description: "Display the persisted rush-issues task plan at its exact checkpoint revision. Read-only; Rush retains plan ownership.",
|
|
230
|
+
parameters: Type.Object({
|
|
231
|
+
path: Type.String({ description: "Absolute path to .resolve-issues/rush/<run-id>/task-plan.json" }),
|
|
232
|
+
revision: Type.Integer({ minimum: 0, description: "Persisted planRevision to display" }),
|
|
233
|
+
}),
|
|
234
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
235
|
+
if (workflowPlanOwner(ctx) !== "rush-issues") throw new Error("Only an active rush-issues workflow can sync its plan.");
|
|
236
|
+
const plan = readRushPlan(params.path, ctx.cwd);
|
|
237
|
+
if (plan.planRevision !== params.revision) {
|
|
238
|
+
rushPlan = null;
|
|
239
|
+
rushError = `revision mismatch: expected ${params.revision}, found ${plan.planRevision}`;
|
|
240
|
+
refresh(true);
|
|
241
|
+
throw new Error(`Rush plan ${rushError}.`);
|
|
242
|
+
}
|
|
243
|
+
const binding: WorkflowPlanBinding = { owner: "rush-issues", path: params.path, runId: plan.runId };
|
|
244
|
+
if (!rushBinding || rushBinding.path !== binding.path || rushBinding.runId !== binding.runId) {
|
|
245
|
+
pi.appendEntry(WORKFLOW_PLAN_ENTRY, { version: 1, kind: "set", ...binding });
|
|
246
|
+
}
|
|
247
|
+
rushBinding = binding;
|
|
248
|
+
rushPlan = plan;
|
|
249
|
+
rushError = null;
|
|
250
|
+
refresh(true);
|
|
251
|
+
return { content: [{ type: "text", text: `Showing rush-issues rev ${plan.planRevision}: ${plan.issues.length} units.` }], details: { runId: plan.runId, revision: plan.planRevision } };
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
|
|
170
255
|
pi.registerTool({
|
|
171
256
|
name: "update_plan",
|
|
172
257
|
label: "Update Plan",
|
|
@@ -214,10 +299,15 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
214
299
|
parameters: Type.Object({}),
|
|
215
300
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
216
301
|
const owner = workflowPlanOwner(ctx);
|
|
217
|
-
if (owner)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
302
|
+
if (owner) {
|
|
303
|
+
const workflowPlan = owner === "rush-issues" ? loadRushPlan(ctx) : null;
|
|
304
|
+
return {
|
|
305
|
+
content: [{ type: "text", text: workflowPlan
|
|
306
|
+
? renderRushPlan(workflowPlan, 160, true).join("\n")
|
|
307
|
+
: `${owner} owns the task plan; ${rushError ?? "consult its persisted workflow state"}.` }],
|
|
308
|
+
details: { hasPlan: !!workflowPlan, plan: workflowPlan, progress: null, workflowOwner: owner },
|
|
309
|
+
};
|
|
310
|
+
}
|
|
221
311
|
if (!currentPlan) {
|
|
222
312
|
return {
|
|
223
313
|
content: [{ type: "text", text: "No plan is set." }],
|
|
@@ -238,6 +328,10 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
238
328
|
const input = args.trim().toLowerCase();
|
|
239
329
|
if (!input) return showFullPlan(ctx);
|
|
240
330
|
if (input === "clear") {
|
|
331
|
+
if (workflowPlanOwner(ctx)) {
|
|
332
|
+
ctx.ui.notify("The workflow owns its plan; /plan clear cannot change it.", "warning");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
241
335
|
clearPlan();
|
|
242
336
|
ctx.ui.notify("Plan cleared.", "info");
|
|
243
337
|
return;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
import type { Component } from "@earendil-works/pi-tui";
|
|
4
|
+
import { matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
5
|
+
|
|
6
|
+
export const WORKFLOW_PLAN_ENTRY = "pi-better-workflow-plan";
|
|
7
|
+
|
|
8
|
+
interface RushUnit {
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
stage: string;
|
|
12
|
+
status: string;
|
|
13
|
+
dependsOn: string[];
|
|
14
|
+
worker?: string | number | undefined;
|
|
15
|
+
note?: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface RushPlan {
|
|
19
|
+
runId: string;
|
|
20
|
+
planRevision: number;
|
|
21
|
+
issues: RushUnit[];
|
|
22
|
+
fleet: Record<string, { status: string }>;
|
|
23
|
+
warehouseCanaryRequired: boolean;
|
|
24
|
+
spec?: { title?: string } | undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface WorkflowPlanBinding {
|
|
28
|
+
owner: "rush-issues";
|
|
29
|
+
path: string;
|
|
30
|
+
runId: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function readRushPlan(path: string, cwd: string): RushPlan {
|
|
34
|
+
if (!isAbsolute(path) || basename(path) !== "task-plan.json") throw new Error("Expected an absolute Rush task-plan.json path.");
|
|
35
|
+
const root = realpathSync(resolve(cwd, ".resolve-issues", "rush"));
|
|
36
|
+
const file = realpathSync(path);
|
|
37
|
+
const dir = relative(root, dirname(file));
|
|
38
|
+
if (!dir || dir.startsWith(".." + sep) || dir === ".." || dir.includes(sep)) {
|
|
39
|
+
throw new Error("Rush plan must be inside this project's .resolve-issues/rush/<run-id> directory.");
|
|
40
|
+
}
|
|
41
|
+
const size = statSync(file).size;
|
|
42
|
+
if (size > 2_000_000) throw new Error("Rush plan exceeds the 2 MB display limit.");
|
|
43
|
+
const data: unknown = JSON.parse(readFileSync(file, "utf8"));
|
|
44
|
+
if (!isRecord(data) || data.runId !== dir || !Number.isSafeInteger(data.planRevision) ||
|
|
45
|
+
(data.planRevision as number) < 0 || !Array.isArray(data.issues) || !isRecord(data.fleet)) {
|
|
46
|
+
throw new Error("Invalid Rush plan identity or revision.");
|
|
47
|
+
}
|
|
48
|
+
const issues = data.issues.map((unit: unknown) => {
|
|
49
|
+
if (!isRecord(unit) || typeof unit.id !== "string" || typeof unit.title !== "string" ||
|
|
50
|
+
typeof unit.stage !== "string" || typeof unit.status !== "string") {
|
|
51
|
+
throw new Error("Invalid Rush plan unit.");
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
id: unit.id, title: unit.title, stage: unit.stage, status: unit.status,
|
|
55
|
+
dependsOn: Array.isArray(unit.dependsOn) ? unit.dependsOn.filter((id): id is string => typeof id === "string") : [],
|
|
56
|
+
worker: typeof unit.worker === "string" || typeof unit.worker === "number" ? unit.worker : undefined,
|
|
57
|
+
note: typeof unit.note === "string" ? unit.note : undefined,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
const fleet: RushPlan["fleet"] = {};
|
|
61
|
+
for (const [stage, value] of Object.entries(data.fleet)) {
|
|
62
|
+
if (!isRecord(value) || typeof value.status !== "string") throw new Error("Invalid Rush fleet stage.");
|
|
63
|
+
fleet[stage] = { status: value.status };
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
runId: data.runId as string,
|
|
67
|
+
planRevision: data.planRevision as number,
|
|
68
|
+
issues,
|
|
69
|
+
fleet,
|
|
70
|
+
warehouseCanaryRequired: data.warehouseCanaryRequired === true,
|
|
71
|
+
spec: isRecord(data.spec) && typeof data.spec.title === "string" ? { title: data.spec.title } : undefined,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
76
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function workflowBinding(entries: Iterable<{ type: string; customType?: string; data?: unknown }>): WorkflowPlanBinding | null {
|
|
80
|
+
let binding: WorkflowPlanBinding | null = null;
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
if (entry.type === "custom" && entry.customType === "pi-better-workflow") {
|
|
83
|
+
const data = entry.data;
|
|
84
|
+
if (isRecord(data) && data.version === 1 && (data.kind === "set" || data.kind === "clear")) binding = null;
|
|
85
|
+
}
|
|
86
|
+
if (entry.type !== "custom" || entry.customType !== WORKFLOW_PLAN_ENTRY) continue;
|
|
87
|
+
const data = entry.data;
|
|
88
|
+
if (!isRecord(data) || data.version !== 1) continue;
|
|
89
|
+
if (data.kind === "clear") binding = null;
|
|
90
|
+
else if (data.kind === "set" && data.owner === "rush-issues" &&
|
|
91
|
+
typeof data.path === "string" && typeof data.runId === "string") {
|
|
92
|
+
binding = { owner: "rush-issues", path: data.path, runId: data.runId };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return binding;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function renderRushPlan(plan: RushPlan, width: number, full = false): string[] {
|
|
99
|
+
const completed = plan.issues.filter((unit) => unit.status === "succeeded").length;
|
|
100
|
+
const lines = [`rush-issues rev ${plan.planRevision} ${completed}/${plan.issues.length} units${plan.spec?.title ? ` ${plan.spec.title}` : ""}`];
|
|
101
|
+
const fleet = ["explore", "combine", "canary", "review", "cicd"]
|
|
102
|
+
.map((stage) => `${stage}: ${stage === "canary" && !plan.warehouseCanaryRequired ? "n/a" : plan.fleet[stage]?.status ?? "pending"}`);
|
|
103
|
+
lines.push(`fleet ${fleet.join(" ")}`);
|
|
104
|
+
for (const unit of plan.issues) {
|
|
105
|
+
const marker = unit.status === "succeeded" ? "✓" : unit.status === "blocked" ? "!" : unit.status === "pending" ? "○" : "●";
|
|
106
|
+
lines.push(`${marker} #${unit.id} ${unit.title} [${unit.stage} · ${unit.status}${unit.worker !== undefined ? ` · worker ${unit.worker}` : ""}]`);
|
|
107
|
+
if (full) {
|
|
108
|
+
if (unit.dependsOn.length) lines.push(` after: ${unit.dependsOn.map((id) => `#${id}`).join(", ")}`);
|
|
109
|
+
if (unit.note) lines.push(` ${unit.note}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return lines.map((line) => truncateToWidth(line, Math.max(1, width)));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function createRushPlanComponent(plan: RushPlan, onClose: () => void): Component {
|
|
116
|
+
return {
|
|
117
|
+
render: (width) => renderRushPlan(plan, width, true),
|
|
118
|
+
handleInput(data) {
|
|
119
|
+
if (matchesKey(data, "escape") || matchesKey(data, "left") || matchesKey(data, "ctrl+c")) onClose();
|
|
120
|
+
},
|
|
121
|
+
invalidate() {},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -179,7 +179,10 @@ export function ensureBackgroundWorkNavigator(ctx: ExtensionContext, deps: HostD
|
|
|
179
179
|
if (!isNavigatorUiAvailable(ctx)) return;
|
|
180
180
|
const s = state();
|
|
181
181
|
s.uiCtx = ctx;
|
|
182
|
-
s.deps =
|
|
182
|
+
s.deps = {
|
|
183
|
+
...deps,
|
|
184
|
+
createTranscriptComponent: deps.createTranscriptComponent ?? s.deps?.createTranscriptComponent,
|
|
185
|
+
};
|
|
183
186
|
try { (ctx.ui as any).setWidget?.(MAIN_LIST_WIDGET_KEY, undefined); } catch { /* ignore */ }
|
|
184
187
|
s.mainListWidgetInstalled = false;
|
|
185
188
|
s.mainListRequestRender = undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-better-harness",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"description": "Pi extension bundle for a write sandbox, subagents, background tasks, SSH, goals, and structured plans.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"test": "node --test test/*.test.mjs"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"pi-better-background-tasks": "0.2.
|
|
53
|
+
"pi-better-background-tasks": "0.2.14",
|
|
54
54
|
"pi-better-goal": "0.4.0",
|
|
55
|
-
"pi-better-plan": "0.
|
|
55
|
+
"pi-better-plan": "0.3.0",
|
|
56
56
|
"pi-better-sandbox": "0.3.0",
|
|
57
57
|
"pi-better-ssh": "0.1.1",
|
|
58
|
-
"pi-better-subagents": "0.1.
|
|
58
|
+
"pi-better-subagents": "0.1.30",
|
|
59
59
|
"yaml": "^2.9.1"
|
|
60
60
|
},
|
|
61
61
|
"bundledDependencies": [
|