diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-25 21:35:48 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-25 21:35:48 +0200 |
| commit | 045e33c14ded712a7b33070b0c2a1b20254f4952 (patch) | |
| tree | 780412a30c16c1e4ea4da1ca8860f6cd52211424 /pi | |
| parent | fd0e54ec5bafe232ca69ae594d93b8711ac0e43a (diff) | |
loop-scheduler: add third-level autocomplete for cancel and preset subcommands
- /loop cancel <partial> now suggests "all" and active job IDs
- /loop preset <name> new explicit subcommand with third-level name autocomplete
- Second-level completions now also include "preset" as a discoverable subcommand
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'pi')
| -rw-r--r-- | pi/agent/extensions/loop-scheduler/index.ts | 63 |
1 files changed, 62 insertions, 1 deletions
diff --git a/pi/agent/extensions/loop-scheduler/index.ts b/pi/agent/extensions/loop-scheduler/index.ts index 75f2ae1..a5bad6e 100644 --- a/pi/agent/extensions/loop-scheduler/index.ts +++ b/pi/agent/extensions/loop-scheduler/index.ts @@ -419,10 +419,46 @@ export default function loopSchedulerExtension(pi: ExtensionAPI): void { description: "Schedule a recurring prompt: /loop 10m <prompt>, /loop list, /loop cancel <id|all>, /loop <preset-name>", // Provide autocomplete for subcommands and preset names loaded from loop-presets.md. + // Third-level completions (after a subcommand word) are handled before the top-level list. getArgumentCompletions: (prefix: string) => { + // Third-level: /loop cancel|rm|delete <id|all> + // Suggest "all" and any active job IDs matching the partial input. + const cancelMatch = prefix.match(/^(cancel|rm|delete)\s+(\S*)$/i); + if (cancelMatch) { + const verb = cancelMatch[1]!; + const partial = (cancelMatch[2] ?? "").toLowerCase(); + const results = []; + if ("all".startsWith(partial)) { + results.push({ value: `${verb} all`, label: "all", description: "Cancel all active jobs" }); + } + for (const job of jobs.values()) { + if (job.id.startsWith(partial)) { + results.push({ value: `${verb} ${job.id}`, label: job.id, description: shortenPrompt(job.prompt, 50) }); + } + } + return results.length > 0 ? results : null; + } + + // Third-level: /loop preset <name> + // Suggest preset names from loop-presets.md. + const presetMatch = prefix.match(/^preset\s+(\S*)$/i); + if (presetMatch) { + const partial = (presetMatch[1] ?? "").toLowerCase(); + const results = loadPresets() + .filter((p) => p.name.startsWith(partial)) + .map((p) => ({ + value: `preset ${p.name}`, + label: p.name, + description: `every ${p.intervalLabel} — ${shortenPrompt(p.prompt, 50)}`, + })); + return results.length > 0 ? results : null; + } + + // Second-level: subcommand names and direct preset names. const fixed = [ { value: "list", label: "list", description: "Show active loop jobs" }, { value: "cancel", label: "cancel", description: "Cancel: cancel <id|all>" }, + { value: "preset", label: "preset", description: "Activate a named preset: preset <name>" }, { value: "edit", label: "edit", description: "Edit presets file in $EDITOR" }, { value: "presets", label: "presets", description: "List available presets" }, ]; @@ -448,7 +484,7 @@ export default function loopSchedulerExtension(pi: ExtensionAPI): void { const trimmed = args.trim(); if (!trimmed || trimmed.toLowerCase() === "help") { notify( - "Usage: /loop <interval> <prompt> | /loop <prompt> | /loop list | /loop cancel <id|all> | /loop edit | /loop presets | /loop <preset-name>", + "Usage: /loop <interval> <prompt> | /loop <prompt> | /loop list | /loop cancel <id|all> | /loop edit | /loop presets | /loop preset <name> | /loop <preset-name>", "info", ctx, ); @@ -495,6 +531,31 @@ export default function loopSchedulerExtension(pi: ExtensionAPI): void { return; } + // Explicit "preset <name>" subcommand — mirrors the single-word shorthand but more + // discoverable and supports autocomplete at the third level. + const presetCmd = trimmed.match(/^preset\s+(\S+)$/i); + if (presetCmd) { + const preset = lookupPreset(presetCmd[1]!); + if (!preset) { + notify(`No preset named '${presetCmd[1]}'. Use /loop presets to list available presets.`, "warning", ctx); + return; + } + if (jobs.size >= MAX_JOBS) { + notify(`Too many active loop jobs (${jobs.size}). Cancel one first.`, "warning", ctx); + return; + } + const job = createJob(preset.prompt, preset.intervalMs, preset.intervalLabel); + jobs.set(job.id, job); + scheduleJobTimer(job); + updateUi(ctx); + notify( + `Scheduled loop ${job.id} [${preset.name}] every ${job.intervalLabel}: ${shortenPrompt(job.prompt)}`, + "success", + ctx, + ); + return; + } + // If the argument is a single word (no spaces), check if it matches a preset name. // Note: a preset named "hourly" or "daily" takes precedence over the interval shorthand. if (!/\s/.test(trimmed)) { |
