@pasko70/pibo 3.0.2 → 3.1.1
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/compute-image/Dockerfile +47 -0
- package/compute-image/Dockerfile.dockerignore +5 -0
- package/dist/agent-runtime/contract.js +78 -28
- package/dist/agent-runtime/portable-history.js +19 -9
- package/dist/agent-runtime/registry.js +8 -2
- package/dist/agent-runtime/routed-session.js +12 -6
- package/dist/agent-runtimes/pi/routed-session.js +10 -4
- package/dist/apps/chat/agent-store.js +55 -3
- package/dist/apps/chat/data/project-service.js +119 -15
- package/dist/apps/chat/data/room-service.js +21 -0
- package/dist/apps/chat/loop-api.js +10 -2
- package/dist/apps/chat/web-app.js +84 -25
- package/dist/apps/chat-ui/assets/{dist-CvaPTBTN.js → dist-CUQJqggN.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BPotHecb.js → dist-ClUlQWYN.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BzqQKeVO.js → dist-Djul9BmZ.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-paagejNj.js → dist-GD0JGdCi.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-B6GZgWSj.js → dist-W5HOqHym.js} +1 -1
- package/dist/apps/chat-ui/assets/index-BcjkX-iP.js +228 -0
- package/dist/apps/chat-ui/assets/index-CmqRSbBU.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-JvrPUGvI.js +43 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/cli-ui/InkSessionApp.js +111 -12
- package/dist/compute/cli.js +35 -16
- package/dist/compute/docker.js +341 -94
- package/dist/config/config.js +3 -0
- package/dist/core/output-render-sequence.js +148 -1
- package/dist/core/session-router.js +33 -105
- package/dist/data/ingest-service.js +14 -6
- package/dist/data/pibo-store.js +8 -1
- package/dist/data/schema.js +135 -5
- package/dist/debug/agents.js +91 -89
- package/dist/debug/index.js +2 -0
- package/dist/debug/pty.js +127 -48
- package/dist/gateway/cli.js +4 -0
- package/dist/loops/channel.js +3 -1
- package/dist/loops/cli.js +2 -1
- package/dist/loops/service.js +90 -11
- package/dist/loops/store.js +100 -74
- package/dist/mcp/agent-context.js +13 -26
- package/dist/mcp/commands/info.js +3 -1
- package/dist/mcp/config-command.js +46 -2
- package/dist/mcp/config.js +18 -4
- package/dist/plugins/context-files-store.js +6 -6
- package/dist/previews/base-url.js +34 -0
- package/dist/previews/config.js +2 -32
- package/dist/ralph/store.js +5 -0
- package/dist/reliability/store.js +174 -111
- package/dist/session-ui/terminalRows.js +2 -1
- package/dist/sessions/pibo-data-store.js +81 -0
- package/dist/subagents/observation-query.js +121 -0
- package/dist/tools/agent-browser-leases.js +58 -23
- package/dist/tools/agent-browser-wrapper.js +1 -1
- package/dist/tools/browser-use-leases.js +129 -16
- package/dist/tools/browser-use-wrapper.js +1 -1
- package/dist/tools/index.js +27 -11
- package/dist/tools/python-runtime.js +10 -3
- package/dist/tools/registry.js +1 -1
- package/dist/tools/runtime/node-backend.js +16 -5
- package/dist/tools/runtime/node-worker-source.js +112 -33
- package/dist/tools/runtime/python-backend.js +16 -5
- package/dist/tools/runtime/python-worker-source.js +167 -16
- package/dist/tools/runtime/registry.js +23 -10
- package/dist/user-skills/store.js +0 -1
- package/npm-shrinkwrap.json +9 -2
- package/package.json +8 -2
- package/scripts/docker-entrypoint.sh +46 -0
- package/scripts/prepare-agent-browser-wrapper.sh +70 -0
- package/scripts/prepare-browser-use-wrapper.sh +255 -0
- package/dist/apps/chat-ui/assets/index-6JJV-eic.js +0 -228
- package/dist/apps/chat-ui/assets/index-CeL9JPP5.css +0 -1
- package/dist/apps/chat-vscode-web/assets/index-U-MSErGa.js +0 -43
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="theme-color" content="#101d22" />
|
|
7
7
|
<title>Pibo</title>
|
|
8
|
-
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-JvrPUGvI.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-SluZr_-r.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
@@ -5,10 +5,23 @@ import { buildCompactTerminalRows, buildSlashCommandCatalog, buildTerminalStatus
|
|
|
5
5
|
import { InkTerminalView } from "./InkTerminalView.js";
|
|
6
6
|
import { isExpandableTerminalRow } from "./InkTerminalRow.js";
|
|
7
7
|
const IDENTIFIER_PATTERN = /^(?:user|room|ps|pi|entry|evt|run|create|provider|model|agent)[_:]/i;
|
|
8
|
+
export async function runInkPickerActivation(guard, picker, activate) {
|
|
9
|
+
if (guard.inFlight || guard.lastPicker === picker)
|
|
10
|
+
return undefined;
|
|
11
|
+
guard.inFlight = true;
|
|
12
|
+
guard.lastPicker = picker;
|
|
13
|
+
try {
|
|
14
|
+
return await activate();
|
|
15
|
+
}
|
|
16
|
+
finally {
|
|
17
|
+
guard.inFlight = false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
8
20
|
const INITIAL_STATE = {
|
|
9
21
|
loading: true,
|
|
10
22
|
rows: [],
|
|
11
23
|
input: "",
|
|
24
|
+
inputCursor: 0,
|
|
12
25
|
mode: "transcript",
|
|
13
26
|
};
|
|
14
27
|
export function InkSessionApp({ source, initialSessionId, maxRows, maxLineChars, onExit }) {
|
|
@@ -18,6 +31,7 @@ export function InkSessionApp({ source, initialSessionId, maxRows, maxLineChars,
|
|
|
18
31
|
const unsubscribeRef = useRef(undefined);
|
|
19
32
|
const closedRef = useRef(false);
|
|
20
33
|
const stateRef = useRef(state);
|
|
34
|
+
const pickerActivationRef = useRef({ inFlight: false });
|
|
21
35
|
useEffect(() => {
|
|
22
36
|
stateRef.current = state;
|
|
23
37
|
}, [state]);
|
|
@@ -120,7 +134,7 @@ export function InkSessionApp({ source, initialSessionId, maxRows, maxLineChars,
|
|
|
120
134
|
error: undefined,
|
|
121
135
|
}));
|
|
122
136
|
}, [source]);
|
|
123
|
-
const
|
|
137
|
+
const activatePickerItem = useCallback(async () => {
|
|
124
138
|
const picker = stateRef.current.picker;
|
|
125
139
|
const item = picker?.items[picker.selectedIndex];
|
|
126
140
|
if (!picker || !item) {
|
|
@@ -136,8 +150,12 @@ export function InkSessionApp({ source, initialSessionId, maxRows, maxLineChars,
|
|
|
136
150
|
return;
|
|
137
151
|
}
|
|
138
152
|
if (picker.action === "select-room") {
|
|
139
|
-
const
|
|
140
|
-
|
|
153
|
+
const currentSession = stateRef.current.session;
|
|
154
|
+
const preserveSession = currentSession?.roomId === room.id;
|
|
155
|
+
const status = await source.getStatus({ sessionId: preserveSession ? currentSession.id : undefined });
|
|
156
|
+
if (!preserveSession)
|
|
157
|
+
closeOpenSession();
|
|
158
|
+
setState((current) => selectInkSessionRoom(current, room, status));
|
|
141
159
|
return;
|
|
142
160
|
}
|
|
143
161
|
await openSessionPickerForRoom(room, picker);
|
|
@@ -174,7 +192,13 @@ export function InkSessionApp({ source, initialSessionId, maxRows, maxLineChars,
|
|
|
174
192
|
catch (error) {
|
|
175
193
|
setState((current) => ({ ...current, mode: "transcript", picker: undefined, error: formatCliSessionError(error), message: undefined }));
|
|
176
194
|
}
|
|
177
|
-
}, [openSession, openSessionPickerForRoom, source]);
|
|
195
|
+
}, [closeOpenSession, openSession, openSessionPickerForRoom, source]);
|
|
196
|
+
const selectPickerItem = useCallback(async () => {
|
|
197
|
+
const picker = stateRef.current.picker;
|
|
198
|
+
if (!picker)
|
|
199
|
+
return;
|
|
200
|
+
await runInkPickerActivation(pickerActivationRef.current, picker, activatePickerItem);
|
|
201
|
+
}, [activatePickerItem]);
|
|
178
202
|
useEffect(() => {
|
|
179
203
|
closedRef.current = false;
|
|
180
204
|
void (async () => {
|
|
@@ -215,6 +239,22 @@ export function InkSessionApp({ source, initialSessionId, maxRows, maxLineChars,
|
|
|
215
239
|
setState((current) => reduceInkSessionInputState(current, { type: "down" }));
|
|
216
240
|
return;
|
|
217
241
|
}
|
|
242
|
+
if (key.leftArrow) {
|
|
243
|
+
setState((current) => reduceInkSessionInputState(current, { type: "left" }));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (key.rightArrow) {
|
|
247
|
+
setState((current) => reduceInkSessionInputState(current, { type: "right" }));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (key.home) {
|
|
251
|
+
setState((current) => reduceInkSessionInputState(current, { type: "home" }));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (key.end) {
|
|
255
|
+
setState((current) => reduceInkSessionInputState(current, { type: "end" }));
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
218
258
|
if (key.return) {
|
|
219
259
|
const submitted = stateRef.current.input;
|
|
220
260
|
if (submitted.length === 0 && !stateRef.current.picker && !stateRef.current.slashSuggestions && canToggleSelectedRowDetails(stateRef.current)) {
|
|
@@ -224,11 +264,11 @@ export function InkSessionApp({ source, initialSessionId, maxRows, maxLineChars,
|
|
|
224
264
|
if (stateRef.current.slashSuggestions && !stateRef.current.picker) {
|
|
225
265
|
const accepted = acceptSlashSuggestion(stateRef.current);
|
|
226
266
|
if (accepted.runInput) {
|
|
227
|
-
setState((current) => ({ ...current, input: "", slashSuggestions: undefined, message: undefined, error: undefined }));
|
|
267
|
+
setState((current) => ({ ...current, input: "", inputCursor: 0, slashSuggestions: undefined, message: undefined, error: undefined }));
|
|
228
268
|
void submitCommandOrMessage(accepted.runInput);
|
|
229
269
|
}
|
|
230
270
|
else {
|
|
231
|
-
setState((current) => ({ ...current, input: accepted.input, slashSuggestions: undefined, message: `Accepted ${accepted.input.trim()}. Press Enter to run or add arguments.`, error: undefined }));
|
|
271
|
+
setState((current) => ({ ...current, input: accepted.input, inputCursor: accepted.input.length, slashSuggestions: undefined, message: `Accepted ${accepted.input.trim()}. Press Enter to run or add arguments.`, error: undefined }));
|
|
232
272
|
}
|
|
233
273
|
return;
|
|
234
274
|
}
|
|
@@ -238,6 +278,7 @@ export function InkSessionApp({ source, initialSessionId, maxRows, maxLineChars,
|
|
|
238
278
|
return;
|
|
239
279
|
}
|
|
240
280
|
if (stateRef.current.picker) {
|
|
281
|
+
setState((current) => reduceInkSessionInputState(current, { type: "enter" }));
|
|
241
282
|
void selectPickerItem();
|
|
242
283
|
return;
|
|
243
284
|
}
|
|
@@ -292,19 +333,44 @@ export function InkSessionPickerView({ picker, maxLineChars }) {
|
|
|
292
333
|
return React.createElement(Box, { flexDirection: "column" }, React.createElement(Text, { color: "yellow" }, title), ...picker.items.map((item, index) => React.createElement(Text, { key: item.id, color: overlayItemColor(index === picker.selectedIndex, item.disabled === true) }, formatOverlayItemLine(item, index === picker.selectedIndex, lineLimit))), React.createElement(Text, { color: "gray" }, "↑↓ select · enter confirm · esc back/cancel · ctrl-c exit"));
|
|
293
334
|
}
|
|
294
335
|
export function reduceInkSessionInputState(state, action) {
|
|
295
|
-
if (action.type === "text")
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
336
|
+
if (action.type === "text") {
|
|
337
|
+
const inputCursor = normalizeInputCursor(state.input, state.inputCursor);
|
|
338
|
+
return withSlashSuggestions({
|
|
339
|
+
...state,
|
|
340
|
+
input: state.input.slice(0, inputCursor) + action.value + state.input.slice(inputCursor),
|
|
341
|
+
inputCursor: inputCursor + action.value.length,
|
|
342
|
+
message: undefined,
|
|
343
|
+
error: undefined,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
if (action.type === "backspace") {
|
|
347
|
+
const inputCursor = normalizeInputCursor(state.input, state.inputCursor);
|
|
348
|
+
const previousCursor = adjacentInputCursor(state.input, inputCursor, -1);
|
|
349
|
+
if (previousCursor === inputCursor)
|
|
350
|
+
return state;
|
|
351
|
+
return withSlashSuggestions({
|
|
352
|
+
...state,
|
|
353
|
+
input: state.input.slice(0, previousCursor) + state.input.slice(inputCursor),
|
|
354
|
+
inputCursor: previousCursor,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
if (action.type === "left" || action.type === "right") {
|
|
358
|
+
const inputCursor = normalizeInputCursor(state.input, state.inputCursor);
|
|
359
|
+
return { ...state, inputCursor: adjacentInputCursor(state.input, inputCursor, action.type === "left" ? -1 : 1) };
|
|
360
|
+
}
|
|
361
|
+
if (action.type === "home")
|
|
362
|
+
return { ...state, inputCursor: 0 };
|
|
363
|
+
if (action.type === "end")
|
|
364
|
+
return { ...state, inputCursor: state.input.length };
|
|
299
365
|
if (action.type === "toggle-details")
|
|
300
366
|
return toggleSelectedRowDetails(state);
|
|
301
367
|
if (action.type === "escape") {
|
|
302
368
|
if (state.slashSuggestions)
|
|
303
369
|
return { ...state, slashSuggestions: undefined, overlayStack: popInkSessionOverlay(state.overlayStack), message: "Closed slash suggestions." };
|
|
304
370
|
if (state.picker?.parent) {
|
|
305
|
-
return withPickerOverlay({ ...state, picker: state.picker.parent, mode: pickerMode(state.picker.parent), message: "Back." }, state.picker.parent);
|
|
371
|
+
return withPickerOverlay({ ...state, input: "", picker: state.picker.parent, mode: pickerMode(state.picker.parent), message: "Back." }, state.picker.parent);
|
|
306
372
|
}
|
|
307
|
-
return { ...state, input: "", mode: "transcript", picker: undefined, overlayStack: popInkSessionOverlay(state.overlayStack), message: "Canceled." };
|
|
373
|
+
return { ...state, input: "", inputCursor: 0, mode: "transcript", picker: undefined, overlayStack: popInkSessionOverlay(state.overlayStack), message: "Canceled." };
|
|
308
374
|
}
|
|
309
375
|
if (action.type === "up" || action.type === "down") {
|
|
310
376
|
if (state.slashSuggestions && !state.picker && state.slashSuggestions.items.length > 0) {
|
|
@@ -323,11 +389,30 @@ export function reduceInkSessionInputState(state, action) {
|
|
|
323
389
|
return {
|
|
324
390
|
...state,
|
|
325
391
|
input: "",
|
|
392
|
+
inputCursor: 0,
|
|
326
393
|
slashSuggestions: undefined,
|
|
327
394
|
message: undefined,
|
|
328
395
|
error: undefined,
|
|
329
396
|
};
|
|
330
397
|
}
|
|
398
|
+
const INPUT_GRAPHEME_SEGMENTER = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
399
|
+
function normalizeInputCursor(input, inputCursor) {
|
|
400
|
+
if (inputCursor === undefined)
|
|
401
|
+
return input.length;
|
|
402
|
+
const clamped = Math.max(0, Math.min(input.length, inputCursor));
|
|
403
|
+
return inputGraphemeBoundaries(input).findLast((boundary) => boundary <= clamped) ?? 0;
|
|
404
|
+
}
|
|
405
|
+
function adjacentInputCursor(input, inputCursor, direction) {
|
|
406
|
+
const boundaries = inputGraphemeBoundaries(input);
|
|
407
|
+
const index = boundaries.indexOf(inputCursor);
|
|
408
|
+
return boundaries[Math.max(0, Math.min(boundaries.length - 1, index + direction))] ?? inputCursor;
|
|
409
|
+
}
|
|
410
|
+
function inputGraphemeBoundaries(input) {
|
|
411
|
+
const boundaries = [0];
|
|
412
|
+
for (const segment of INPUT_GRAPHEME_SEGMENTER.segment(input))
|
|
413
|
+
boundaries.push(segment.index + segment.segment.length);
|
|
414
|
+
return boundaries;
|
|
415
|
+
}
|
|
331
416
|
export function normalizeInkRowSelection(state) {
|
|
332
417
|
const expandableIds = expandableRowIds(state.rows);
|
|
333
418
|
const expandedRowIds = (state.expandedRowIds ?? []).filter((id) => expandableIds.includes(id));
|
|
@@ -338,6 +423,20 @@ export function normalizeInkRowSelection(state) {
|
|
|
338
423
|
: expandableIds[expandableIds.length - 1];
|
|
339
424
|
return { ...state, selectedRowId, expandedRowIds };
|
|
340
425
|
}
|
|
426
|
+
export function selectInkSessionRoom(state, room, status) {
|
|
427
|
+
const preserveSession = state.session?.roomId === room.id;
|
|
428
|
+
return {
|
|
429
|
+
...state,
|
|
430
|
+
status,
|
|
431
|
+
activeRoom: room,
|
|
432
|
+
...(preserveSession ? {} : { session: undefined, rows: [], selectedRowId: undefined, expandedRowIds: [] }),
|
|
433
|
+
mode: "transcript",
|
|
434
|
+
picker: undefined,
|
|
435
|
+
overlayStack: undefined,
|
|
436
|
+
message: `Selected room ${room.title}.`,
|
|
437
|
+
error: undefined,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
341
440
|
function selectExpandableRow(state, direction) {
|
|
342
441
|
const normalized = normalizeInkRowSelection(state);
|
|
343
442
|
const ids = expandableRowIds(normalized.rows);
|
package/dist/compute/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import {
|
|
1
|
+
import { Command, InvalidArgumentError } from "commander";
|
|
2
|
+
import { imageExists, shouldRebuild, shouldRebuildDeps, dockerBuild, resolveComputeImageBuildConfig, saveHash, saveDepHash, spawnWorker, spawnDevWorker, listWorkers, releaseWorker, planReapWorkers, applyComputeWorkerReapPlan, getComputeDiskDiagnostics, } from "./docker.js";
|
|
3
3
|
import { getComputeResourceHealth } from "./resource-health.js";
|
|
4
4
|
import { mkdir } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
@@ -7,6 +7,9 @@ import os from "node:os";
|
|
|
7
7
|
const WORKSPACE_DIR = process.env.PIBO_COMPUTE_WORKSPACE || process.cwd();
|
|
8
8
|
const HASH_FILE = path.join(os.homedir(), ".pibo", "compute-image-hash");
|
|
9
9
|
const DEP_HASH_FILE = path.join(os.homedir(), ".pibo", "compute-dep-hash");
|
|
10
|
+
function computeImageBuildConfig() {
|
|
11
|
+
return resolveComputeImageBuildConfig(WORKSPACE_DIR);
|
|
12
|
+
}
|
|
10
13
|
function printJson(value) {
|
|
11
14
|
console.log(JSON.stringify(value, null, 2));
|
|
12
15
|
}
|
|
@@ -16,6 +19,12 @@ function parsePositiveIntegerOption(value) {
|
|
|
16
19
|
const parsed = Number(value);
|
|
17
20
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
18
21
|
}
|
|
22
|
+
function parseNonNegativeNumber(value) {
|
|
23
|
+
const parsed = Number(value);
|
|
24
|
+
if (!Number.isFinite(parsed) || parsed < 0)
|
|
25
|
+
throw new InvalidArgumentError("Value must be a non-negative number");
|
|
26
|
+
return parsed;
|
|
27
|
+
}
|
|
19
28
|
function hostPort(value) {
|
|
20
29
|
if (!value || value === "-")
|
|
21
30
|
return undefined;
|
|
@@ -233,18 +242,24 @@ Creates a worker container and starts the gateway. Prints JSON with the containe
|
|
|
233
242
|
|
|
234
243
|
Use this for quick isolated checks. For code changes, prefer:
|
|
235
244
|
$ pibo compute dev spawn --worktree my-fix
|
|
245
|
+
|
|
246
|
+
Environment:
|
|
247
|
+
PIBO_COMPUTE_IMAGE Override the Docker image name.
|
|
248
|
+
PIBO_COMPUTE_WORKSPACE Use an explicit workspace or source checkout.
|
|
236
249
|
`)
|
|
237
250
|
.action(async (options) => {
|
|
238
251
|
await mkdir(path.dirname(HASH_FILE), { recursive: true });
|
|
239
|
-
const
|
|
252
|
+
const image = computeImageBuildConfig();
|
|
253
|
+
const needsBuild = !(await imageExists(image.imageName)) || (await shouldRebuild(image.buildContext, HASH_FILE, image.dockerfile));
|
|
240
254
|
if (needsBuild) {
|
|
241
|
-
console.error(`Building ${
|
|
242
|
-
await dockerBuild(
|
|
243
|
-
await saveHash(
|
|
255
|
+
console.error(`Building ${image.imageName} from ${image.buildContext}...`);
|
|
256
|
+
await dockerBuild(image);
|
|
257
|
+
await saveHash(image.buildContext, HASH_FILE, image.dockerfile);
|
|
244
258
|
console.error("Build complete.");
|
|
245
259
|
}
|
|
246
260
|
const worker = await spawnWorker({
|
|
247
261
|
workspaceDir: WORKSPACE_DIR,
|
|
262
|
+
imageName: image.imageName,
|
|
248
263
|
name: options.name,
|
|
249
264
|
holder: options.holder,
|
|
250
265
|
ttlSeconds: parsePositiveIntegerOption(options.ttlSeconds),
|
|
@@ -282,12 +297,13 @@ Example:
|
|
|
282
297
|
`)
|
|
283
298
|
.action(async (options) => {
|
|
284
299
|
await mkdir(path.dirname(DEP_HASH_FILE), { recursive: true });
|
|
300
|
+
const image = resolveComputeImageBuildConfig(options.repo, { packageRoot: options.repo });
|
|
285
301
|
console.error("[pibo compute] Checking Docker image status...");
|
|
286
|
-
const needsBuild = !(await imageExists(
|
|
302
|
+
const needsBuild = !(await imageExists(image.imageName)) || (await shouldRebuildDeps(options.repo, DEP_HASH_FILE));
|
|
287
303
|
if (needsBuild) {
|
|
288
304
|
console.error(`[pibo compute] Dependencies changed (package.json, package-lock.json, or Dockerfile).`);
|
|
289
|
-
console.error(`[pibo compute] Rebuilding Docker image ${
|
|
290
|
-
await dockerBuild(
|
|
305
|
+
console.error(`[pibo compute] Rebuilding Docker image ${image.imageName} — this takes 1-2 minutes...`);
|
|
306
|
+
await dockerBuild(image);
|
|
291
307
|
await saveDepHash(options.repo, DEP_HASH_FILE);
|
|
292
308
|
console.error("[pibo compute] Docker image build complete.");
|
|
293
309
|
}
|
|
@@ -298,6 +314,7 @@ Example:
|
|
|
298
314
|
const worker = await spawnDevWorker({
|
|
299
315
|
repoDir: options.repo,
|
|
300
316
|
worktreeName: options.worktree,
|
|
317
|
+
imageName: image.imageName,
|
|
301
318
|
holder: options.holder,
|
|
302
319
|
ttlSeconds: parsePositiveIntegerOption(options.ttlSeconds),
|
|
303
320
|
idleSeconds: parsePositiveIntegerOption(options.idleSeconds),
|
|
@@ -317,10 +334,11 @@ Example:
|
|
|
317
334
|
Rebuilds from the current workspace and refreshes compute image hashes.
|
|
318
335
|
`)
|
|
319
336
|
.action(async () => {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
await
|
|
323
|
-
await
|
|
337
|
+
const image = computeImageBuildConfig();
|
|
338
|
+
console.error(`Rebuilding ${image.imageName} from ${image.buildContext}...`);
|
|
339
|
+
await dockerBuild(image);
|
|
340
|
+
await saveHash(image.buildContext, HASH_FILE, image.dockerfile);
|
|
341
|
+
await saveDepHash(image.buildContext, DEP_HASH_FILE);
|
|
324
342
|
console.error("Build complete.");
|
|
325
343
|
});
|
|
326
344
|
program
|
|
@@ -403,7 +421,7 @@ Use the name or id shown by:
|
|
|
403
421
|
program
|
|
404
422
|
.command("reap")
|
|
405
423
|
.description("Preview or apply compute worker container cleanup")
|
|
406
|
-
.option("--max-age-minutes <n>", "Select workers older than this many minutes",
|
|
424
|
+
.option("--max-age-minutes <n>", "Select workers older than this many minutes", parseNonNegativeNumber, 60)
|
|
407
425
|
.option("--include-dev", "Also select dev-worker containers; dev workers are skipped by default")
|
|
408
426
|
.option("--no-stopped", "Do not select stopped, dead, created, or restarting containers")
|
|
409
427
|
.option("--no-dirty", "Do not select dirty or OOM-killed containers")
|
|
@@ -416,12 +434,13 @@ Stopped, dirty/OOM, and old workers are selected unless disabled by selector fla
|
|
|
416
434
|
Worktrees are preserved; container cleanup never deletes Git worktrees.
|
|
417
435
|
`)
|
|
418
436
|
.action(async (options) => {
|
|
419
|
-
|
|
437
|
+
if (options.apply && options.dryRun)
|
|
438
|
+
throw new Error("Use either --apply or --dry-run, not both");
|
|
420
439
|
const plan = await planReapWorkers({
|
|
421
440
|
includeDev: options.includeDev === true,
|
|
422
441
|
includeStopped: options.stopped !== false,
|
|
423
442
|
includeDirty: options.dirty !== false,
|
|
424
|
-
maxAgeMinutes:
|
|
443
|
+
maxAgeMinutes: options.maxAgeMinutes,
|
|
425
444
|
});
|
|
426
445
|
const shouldApply = options.apply === true;
|
|
427
446
|
const removed = shouldApply ? await applyComputeWorkerReapPlan(plan) : [];
|