pi-orchestrator-config 4.3.12 → 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.
@@ -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, list-all, remove
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 { getCronFilePath } from "./cron.js";
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", "kill"].includes(sub))) {
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: "List crons from all sessions" },
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([{ value: "every", label: "every", description: "Interval-based (e.g., every 2h)" }, { value: "at", label: "at", description: "Time-based (e.g., at 12:00)" }], lastPart);
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
- // After "remove" — show task IDs (supports multi-select)
305
- if (sub === "remove" || sub === "rm" || sub === "delete" || sub === "kill") {
306
- try {
307
- const cronFile = getCronFilePath();
308
- if (!cronFile) return null;
309
- const cronTasks = JSON.parse(fs.readFileSync(cronFile, "utf-8"));
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", "cron", "create-skill", "create-coms-feature-manager",
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
- return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
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) {
@@ -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
- lines.push(` • #${t.id} ${formatSchedule(t)} — ${t.description} (last: ${last})`);
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) { console.debug("[status] git status failed:", e?.message || e); }
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
  });