pi-orchestrator-config 4.3.11 → 4.3.13
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/README.md +66 -66
- package/extensions/coms/coms-p2p.ts +53 -16
- package/extensions/orchestrator/async-agents.ts +127 -42
- package/extensions/orchestrator/async-result-format.ts +58 -0
- package/extensions/orchestrator/async-runner.ts +20 -4
- package/extensions/orchestrator/cron-status-format.ts +6 -6
- package/extensions/orchestrator/cron-status-ui.ts +3 -3
- package/extensions/orchestrator/cron-store.ts +208 -0
- package/extensions/orchestrator/cron.ts +168 -699
- package/extensions/orchestrator/extended-autocomplete.ts +27 -27
- package/extensions/orchestrator/reviewer-output-archive.ts +69 -0
- package/extensions/orchestrator/status.ts +7 -2
- package/extensions/pidash/pidash-ui/dist/assets/{index-BoNVhD5K.js → index-Ehfwjr9d.js} +1 -1
- package/extensions/pidash/pidash-ui/dist/index.html +1 -1
- package/extensions/pidash/pidash-ui/src/components/InfoBar.tsx +3 -7
- package/extensions/pidash/pidash-ui/src/components/MessageList.tsx +0 -4
- package/extensions/pidash/pidash-ui/tsconfig.json +1 -0
- package/extensions/pidiff/pidiff-ui/package.json +1 -1
- package/extensions/pitasks/index.ts +2 -5
- package/extensions/pitasks/task-tools.ts +2 -2
- package/package.json +2 -1
- package/scripts/build-extension-uis.sh +6 -6
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* /review-handler <Tab> → --autorabbit, --autoqodo
|
|
17
17
|
* /review-status <Tab> → active worktree paths
|
|
18
18
|
* /create-skill <Tab> → (free-text name)
|
|
19
|
-
* /cron <Tab> → add, list,
|
|
19
|
+
* /cron <Tab> → add, list, remove
|
|
20
20
|
* /dream-auto <Tab> → on, off
|
|
21
21
|
* /async-kill <Tab> → all (or type name / id prefix)
|
|
22
22
|
* /mcpc <Tab> → connect
|
|
@@ -25,10 +25,9 @@
|
|
|
25
25
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
26
26
|
import type { AutocompleteItem, AutocompleteProvider, AutocompleteSuggestions } from "@earendil-works/pi-tui";
|
|
27
27
|
import { fuzzyFilter } from "@earendil-works/pi-tui";
|
|
28
|
-
import * as fs from "node:fs";
|
|
29
28
|
import { execFileSync } from "node:child_process";
|
|
30
29
|
import * as path from "node:path";
|
|
31
|
-
import {
|
|
30
|
+
import { getCronRemoveAutocompleteItems } from "./cron.js";
|
|
32
31
|
import { mcpcArgumentCompletions } from "./mcpc.js";
|
|
33
32
|
|
|
34
33
|
// ── Cache infrastructure ────────────────────────────────────────────
|
|
@@ -287,36 +286,28 @@ function registerCompletions(
|
|
|
287
286
|
const parts = prefix.split(/\s+/);
|
|
288
287
|
const lastPart = parts[parts.length - 1] || "";
|
|
289
288
|
const sub = parts[0]?.toLowerCase();
|
|
290
|
-
|
|
291
289
|
// First level: subcommands
|
|
292
|
-
if (parts.length <= 1 && !(["add", "list", "list-all", "remove", "rm", "delete"
|
|
290
|
+
if (parts.length <= 1 && !(["add", "list", "list-all", "remove", "rm", "delete"].includes(sub))) {
|
|
293
291
|
return filter([
|
|
294
292
|
{ value: "add ", label: "add", description: "Add a scheduled task" },
|
|
295
|
-
{ value: "list", label: "list", description: "List scheduled tasks" },
|
|
296
|
-
{ value: "list-all", label: "list-all", description: "
|
|
293
|
+
{ value: "list", label: "list", description: "List visible scheduled tasks" },
|
|
294
|
+
{ value: "list-all", label: "list-all", description: "Show session and project cron tasks" },
|
|
297
295
|
{ value: "remove ", label: "remove", description: "Remove a scheduled task" },
|
|
298
296
|
], lastPart);
|
|
299
297
|
}
|
|
300
|
-
// After "add"
|
|
301
298
|
if (sub === "add" && parts.length <= 2) {
|
|
302
|
-
return filter([
|
|
299
|
+
return filter([
|
|
300
|
+
{ value: "every", label: "every", description: "Interval-based (e.g., every 2h)" },
|
|
301
|
+
{ value: "at", label: "at", description: "Time-based (e.g., at 12:00)" },
|
|
302
|
+
{ value: "--persist", label: "--persist", description: "Keep this cron across Pi sessions" },
|
|
303
|
+
], lastPart);
|
|
303
304
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if (Array.isArray(cronTasks)) {
|
|
311
|
-
const alreadySelected = new Set(parts.slice(1).filter(p => p !== lastPart));
|
|
312
|
-
return filter(cronTasks.filter((t: any) => !alreadySelected.has(String(t.id))).map((t: any) => ({
|
|
313
|
-
value: String(t.id),
|
|
314
|
-
label: `#${t.id}`,
|
|
315
|
-
description: t.description || t.task || "",
|
|
316
|
-
})), lastPart);
|
|
317
|
-
}
|
|
318
|
-
} catch (e: any) { console.debug("[autocomplete] cron task fetch failed:", e?.message || e); }
|
|
319
|
-
return null;
|
|
305
|
+
if (["add", "list"].includes(sub) && !parts.includes("--persist")) {
|
|
306
|
+
return filter([{ value: "--persist", label: "--persist", description: "Keep this cron across Pi sessions" }], lastPart);
|
|
307
|
+
}
|
|
308
|
+
if (sub === "remove" || sub === "rm" || sub === "delete") {
|
|
309
|
+
const selected = new Set(parts.slice(1, -1));
|
|
310
|
+
return filter(getCronRemoveAutocompleteItems().filter((item) => !selected.has(item.value)), lastPart);
|
|
320
311
|
}
|
|
321
312
|
return null;
|
|
322
313
|
},
|
|
@@ -357,7 +348,7 @@ function setupPromptTemplateInterceptor(
|
|
|
357
348
|
// Set of prompt template names that we handle
|
|
358
349
|
const promptTemplateCommands = new Set([
|
|
359
350
|
"external-ai", "pr-review", "issue-review", "coderabbit-rate-limit",
|
|
360
|
-
"review-local", "release", "review-handler", "
|
|
351
|
+
"review-local", "release", "review-handler", "create-skill", "create-coms-feature-manager",
|
|
361
352
|
"pi-config-settings",
|
|
362
353
|
]);
|
|
363
354
|
|
|
@@ -432,7 +423,16 @@ function setupPromptTemplateInterceptor(
|
|
|
432
423
|
item: AutocompleteItem,
|
|
433
424
|
prefix: string,
|
|
434
425
|
) {
|
|
435
|
-
|
|
426
|
+
// Suggestions above are for an argument token, but the wrapped provider
|
|
427
|
+
// only knows its own command completion prefix and can replace `/cron add`.
|
|
428
|
+
const line = lines[cursorLine] ?? "";
|
|
429
|
+
// Pi passes the prior provider's prefix here, which can be `add ev`
|
|
430
|
+
// instead of this provider's `ev`. Derive the active argument token
|
|
431
|
+
// from the editor text so accepting completion never eats `/cron add`.
|
|
432
|
+
const start = line.lastIndexOf(" ", Math.max(0, cursorCol - 1)) + 1;
|
|
433
|
+
const nextLines = [...lines];
|
|
434
|
+
nextLines[cursorLine] = `${line.slice(0, start)}${item.value}${line.slice(cursorCol)}`;
|
|
435
|
+
return { lines: nextLines, cursorLine, cursorCol: start + item.value.length };
|
|
436
436
|
},
|
|
437
437
|
|
|
438
438
|
shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** Durable reviewer-output archival and bounded retention. */
|
|
2
|
+
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { createLogger } from "../shared/logger.js";
|
|
6
|
+
|
|
7
|
+
const log = createLogger("async_agents");
|
|
8
|
+
|
|
9
|
+
export const REVIEWER_ARCHIVE_RETENTION = {
|
|
10
|
+
maxAgeMs: 7 * 24 * 60 * 60 * 1000,
|
|
11
|
+
maxFiles: 100,
|
|
12
|
+
maxBytes: 50 * 1024 * 1024,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type Retention = typeof REVIEWER_ARCHIVE_RETENTION;
|
|
16
|
+
|
|
17
|
+
/** Copy reviewer output into a durable owner-only archive. */
|
|
18
|
+
export function archiveReviewerOutput(outputPath: string, archivePath: string): void {
|
|
19
|
+
fs.chmodSync(outputPath, 0o600);
|
|
20
|
+
fs.mkdirSync(path.dirname(archivePath), { recursive: true, mode: 0o700 });
|
|
21
|
+
fs.copyFileSync(outputPath, archivePath);
|
|
22
|
+
fs.chmodSync(archivePath, 0o600);
|
|
23
|
+
cleanupReviewerOutputArchives(path.dirname(archivePath));
|
|
24
|
+
log.info("archived_reviewer_output", { outputPath: archivePath });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Remove expired and oldest regular-file archives while preserving bounded storage. */
|
|
28
|
+
export function cleanupReviewerOutputArchives(
|
|
29
|
+
archiveDir: string,
|
|
30
|
+
retention: Retention = REVIEWER_ARCHIVE_RETENTION,
|
|
31
|
+
): void {
|
|
32
|
+
let archives: Array<{ path: string; mtimeMs: number; size: number }>;
|
|
33
|
+
try {
|
|
34
|
+
archives = fs.readdirSync(archiveDir).flatMap((entry) => {
|
|
35
|
+
const archivePath = path.join(archiveDir, entry);
|
|
36
|
+
try {
|
|
37
|
+
const stat = fs.lstatSync(archivePath);
|
|
38
|
+
return stat.isFile() ? [{ path: archivePath, mtimeMs: stat.mtimeMs, size: stat.size }] : [];
|
|
39
|
+
} catch {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
} catch (error: any) {
|
|
44
|
+
if (error?.code !== "ENOENT") log.warn("reviewer_archive_cleanup_scan_failed", { archiveDir, error: error?.message });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
const retained = archives
|
|
50
|
+
.filter((archive) => {
|
|
51
|
+
if (now - archive.mtimeMs <= retention.maxAgeMs) return true;
|
|
52
|
+
try { fs.unlinkSync(archive.path); } catch (error: any) { log.warn("reviewer_archive_expiry_delete_failed", { archivePath: archive.path, error: error?.message }); return true; }
|
|
53
|
+
return false;
|
|
54
|
+
})
|
|
55
|
+
.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
|
56
|
+
|
|
57
|
+
let totalBytes = retained.reduce((total, archive) => total + archive.size, 0);
|
|
58
|
+
for (let index = retained.length - 1; index >= 0; index--) {
|
|
59
|
+
const archive = retained[index];
|
|
60
|
+
if (index < retention.maxFiles && totalBytes <= retention.maxBytes) continue;
|
|
61
|
+
try {
|
|
62
|
+
fs.unlinkSync(archive.path);
|
|
63
|
+
totalBytes -= archive.size;
|
|
64
|
+
} catch (error: any) {
|
|
65
|
+
log.warn("reviewer_archive_retention_delete_failed", { archivePath: archive.path, error: error?.message });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
log.debug("reviewer_archive_cleanup_complete", { archiveDir, retainedFiles: retained.length, totalBytes });
|
|
69
|
+
}
|
|
@@ -8,6 +8,9 @@ import { tryGetSystemPromptOptions } from "./utils.js";
|
|
|
8
8
|
import type { AsyncJob } from "./async-agents.js";
|
|
9
9
|
import type { CronTask } from "./cron.js";
|
|
10
10
|
import { getCurrentBranch, runGit } from "./git-helpers.js";
|
|
11
|
+
import { createLogger } from "../shared/logger.js";
|
|
12
|
+
|
|
13
|
+
const log = createLogger("status");
|
|
11
14
|
|
|
12
15
|
function formatDuration(ms: number): string {
|
|
13
16
|
const s = Math.floor(ms / 1000);
|
|
@@ -67,7 +70,8 @@ export function registerStatus(
|
|
|
67
70
|
lines.push(`⏰ Cron tasks: ${crons.length} active`);
|
|
68
71
|
for (const t of crons) {
|
|
69
72
|
const last = t.lastRun ? new Date(t.lastRun).toLocaleTimeString() : "never";
|
|
70
|
-
|
|
73
|
+
const leader = t.scope !== "session" ? `, ${t.leader === false ? "non-leader" : "leader"}` : "";
|
|
74
|
+
lines.push(` • ${t.scope === "project" ? "persist" : "session"}:${t.id} ${formatSchedule(t)} — ${t.description} (last: ${last}${leader})`);
|
|
71
75
|
}
|
|
72
76
|
} else {
|
|
73
77
|
lines.push("⏰ Cron tasks: none");
|
|
@@ -85,7 +89,7 @@ export function registerStatus(
|
|
|
85
89
|
const stateText = dirtyCount > 0 ? `${dirtyCount} changed file${dirtyCount > 1 ? "s" : ""}` : "clean";
|
|
86
90
|
lines.push(`🔀 Git: ${branch} ${stateIcon} ${stateText} (${repoName})`);
|
|
87
91
|
}
|
|
88
|
-
} catch (e: any) {
|
|
92
|
+
} catch (e: any) { log.debug("status_git_failed", { code: e?.code }); }
|
|
89
93
|
|
|
90
94
|
// ── Container ──────────────────────────────────────────────────
|
|
91
95
|
if (inContainer) {
|
|
@@ -104,6 +108,7 @@ export function registerStatus(
|
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
|
|
111
|
+
log.debug("status_rendered", { jobs: jobs.length, crons: crons.length, lines: lines.length });
|
|
107
112
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
108
113
|
},
|
|
109
114
|
});
|