@roodriigoooo/pi-docket 0.4.0
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/CHANGELOG.md +132 -0
- package/LICENSE +21 -0
- package/README.md +241 -0
- package/assets/docket_logo.jpeg +0 -0
- package/docs/adr/0001-bundle-first-checkpoints.md +21 -0
- package/docs/adr/0002-rename-to-docket.md +44 -0
- package/docs/architecture.md +101 -0
- package/docs/bundle-guidelines.md +39 -0
- package/docs/configuration.md +191 -0
- package/docs/releases/0.4.0.md +93 -0
- package/extensions/artifact-catalog.ts +467 -0
- package/extensions/background-work.ts +510 -0
- package/extensions/checkpoint-commands.ts +147 -0
- package/extensions/checkpoint-lifecycle.ts +195 -0
- package/extensions/checkpoint-selector.ts +162 -0
- package/extensions/checkpoint-store.ts +230 -0
- package/extensions/checkpoint-summarizer.ts +141 -0
- package/extensions/docket-command-grammar.ts +319 -0
- package/extensions/docket-command-router.ts +626 -0
- package/extensions/docket-config.ts +88 -0
- package/extensions/docket-extension-surface.ts +43 -0
- package/extensions/docket-navigator.ts +585 -0
- package/extensions/docket.README.md +46 -0
- package/extensions/docket.ts +2965 -0
- package/extensions/event-log.ts +121 -0
- package/extensions/git-context.ts +44 -0
- package/extensions/loaded-artifact-context.ts +228 -0
- package/extensions/search-index.ts +140 -0
- package/extensions/types.ts +40 -0
- package/extensions/worker-activity.ts +402 -0
- package/extensions/worker-changes.ts +180 -0
- package/extensions/worker-commands.ts +251 -0
- package/extensions/worker-dock-cache.ts +147 -0
- package/extensions/worker-events.ts +87 -0
- package/extensions/worker-eviction.ts +55 -0
- package/extensions/worker-guardrails.md +125 -0
- package/extensions/worker-kinds/patcher.md +23 -0
- package/extensions/worker-kinds/scout.md +17 -0
- package/extensions/worker-kinds.ts +280 -0
- package/extensions/worker-result.ts +193 -0
- package/extensions/worker-store.ts +621 -0
- package/extensions/worker-summary-embed.ts +98 -0
- package/package.json +53 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { complete, type Message } from "@mariozechner/pi-ai";
|
|
2
|
+
import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
3
|
+
import { gitSnapshotLabel } from "./git-context.js";
|
|
4
|
+
import type { CheckpointMode, GitSnapshot } from "./types.js";
|
|
5
|
+
|
|
6
|
+
export type CheckpointSummarizerConfig = {
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
provider?: string;
|
|
9
|
+
model?: string;
|
|
10
|
+
maxOutputTokens: number;
|
|
11
|
+
maxInputChars: number;
|
|
12
|
+
timeoutMs: number;
|
|
13
|
+
systemPrompt?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type CheckpointSummarizerInput = {
|
|
17
|
+
id: string;
|
|
18
|
+
mode: CheckpointMode;
|
|
19
|
+
note: string;
|
|
20
|
+
consumeOnUse: boolean;
|
|
21
|
+
cwd: string;
|
|
22
|
+
sourceSession?: string;
|
|
23
|
+
git?: GitSnapshot;
|
|
24
|
+
artifactsFile: string;
|
|
25
|
+
payload: Array<Record<string, unknown>>;
|
|
26
|
+
references: string;
|
|
27
|
+
activeModel: ExtensionCommandContext["model"];
|
|
28
|
+
modelRegistry: ExtensionCommandContext["modelRegistry"];
|
|
29
|
+
config: CheckpointSummarizerConfig;
|
|
30
|
+
overrides: { model?: string; maxOutputTokens?: number };
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type CheckpointSummarizer = {
|
|
34
|
+
summarize(input: CheckpointSummarizerInput): Promise<string>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function truncate(text: string, max: number): string {
|
|
38
|
+
if (text.length <= max) return text;
|
|
39
|
+
return `${text.slice(0, max)}\n\n[Docket truncated ${text.length - max} chars]`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function checkpointSystemPrompt(mode: CheckpointMode, maxOutputTokens: number): string {
|
|
43
|
+
const modeGuidance: Record<CheckpointMode, string> = {
|
|
44
|
+
handoff: "Preserve continuity for a fresh coding-agent session. Prioritize current goal, decisions, edited/important files, and next steps.",
|
|
45
|
+
compact: "Make the smallest useful continuation note. Ruthlessly remove transcript noise and low-value details.",
|
|
46
|
+
debug: "Focus on failing commands, error messages, hypotheses already tried, likely root causes, and safest next debugging steps.",
|
|
47
|
+
review: "Focus on review state: changed files, design decisions, risks, test status, and what a reviewer should inspect next.",
|
|
48
|
+
};
|
|
49
|
+
return [
|
|
50
|
+
"You are Docket, a context distillation assistant for Pi coding sessions.",
|
|
51
|
+
"Summarize session artifacts into a fresh-session checkpoint.",
|
|
52
|
+
"Optimize for fresh-session continuation, not transcript preservation.",
|
|
53
|
+
"Include only restart-critical context: goal, current state, decisions, failed attempts, dead ends, next steps, and references.",
|
|
54
|
+
"Prefer compact artifact references over pasted content; quote exact output only when it changes the next action.",
|
|
55
|
+
"Separate facts from hypotheses. Never present unknowns, guesses, or stale plans as decisions.",
|
|
56
|
+
"Make failure history explicit enough to avoid repeats, but remove redundant logs and low-signal output.",
|
|
57
|
+
"Do not produce a transcript search result or artifact dump.",
|
|
58
|
+
"Use compact markdown. Target the requested maximum output length.",
|
|
59
|
+
"Reference artifacts by IDs like [file:f12] or [command:c8] when useful instead of copying large excerpts.",
|
|
60
|
+
"If file-reference guidance is needed, say it once for the reference list, not once per file.",
|
|
61
|
+
"Never invent files, commands, decisions, or outcomes not present in artifacts.",
|
|
62
|
+
`Mode: ${mode}. ${modeGuidance[mode]}`,
|
|
63
|
+
`Maximum output tokens: ${maxOutputTokens}.`,
|
|
64
|
+
].join("\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function checkpointInput(input: CheckpointSummarizerInput): string {
|
|
68
|
+
return truncate([
|
|
69
|
+
`cwd: ${input.cwd}`,
|
|
70
|
+
input.sourceSession ? `sourceSession: ${input.sourceSession}` : undefined,
|
|
71
|
+
gitSnapshotLabel(input.git) ? `git: ${gitSnapshotLabel(input.git)}` : undefined,
|
|
72
|
+
`mode: ${input.mode}`,
|
|
73
|
+
input.note ? `userNote: ${input.note}` : undefined,
|
|
74
|
+
"",
|
|
75
|
+
"Write checkpoint markdown with these sections:",
|
|
76
|
+
"## Summary",
|
|
77
|
+
"## Decisions / constraints",
|
|
78
|
+
"## Current state",
|
|
79
|
+
"## Next steps",
|
|
80
|
+
"## Avoid repeating",
|
|
81
|
+
"## References",
|
|
82
|
+
"",
|
|
83
|
+
"References available:",
|
|
84
|
+
input.references,
|
|
85
|
+
"",
|
|
86
|
+
"Artifacts JSON:",
|
|
87
|
+
JSON.stringify(input.payload, null, 2),
|
|
88
|
+
].filter((line): line is string => line !== undefined).join("\n"), input.config.maxInputChars);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function createCheckpointSummarizer(): CheckpointSummarizer {
|
|
92
|
+
return {
|
|
93
|
+
async summarize(input: CheckpointSummarizerInput): Promise<string> {
|
|
94
|
+
const maxOutputTokens = input.overrides.maxOutputTokens ?? input.config.maxOutputTokens;
|
|
95
|
+
const modelName = input.overrides.model ?? (input.config.provider && input.config.model ? `${input.config.provider}/${input.config.model}` : undefined);
|
|
96
|
+
const model = modelName
|
|
97
|
+
? (() => {
|
|
98
|
+
const [provider, ...rest] = modelName.split("/");
|
|
99
|
+
return provider && rest.length ? input.modelRegistry.find(provider, rest.join("/")) : undefined;
|
|
100
|
+
})()
|
|
101
|
+
: input.activeModel;
|
|
102
|
+
if (!model) throw new Error("No Docket summarizer model configured and no active model selected");
|
|
103
|
+
|
|
104
|
+
const auth = await input.modelRegistry.getApiKeyAndHeaders(model);
|
|
105
|
+
if (!auth.ok || !auth.apiKey) throw new Error(auth.ok ? `No API key for ${model.provider}` : auth.error);
|
|
106
|
+
|
|
107
|
+
const userMessage: Message = {
|
|
108
|
+
role: "user",
|
|
109
|
+
content: [{ type: "text", text: checkpointInput(input) }],
|
|
110
|
+
timestamp: Date.now(),
|
|
111
|
+
};
|
|
112
|
+
const response = await complete(
|
|
113
|
+
model,
|
|
114
|
+
{ systemPrompt: input.config.systemPrompt ?? checkpointSystemPrompt(input.mode, maxOutputTokens), messages: [userMessage] },
|
|
115
|
+
{ apiKey: auth.apiKey, headers: auth.headers, maxTokens: maxOutputTokens, timeoutMs: input.config.timeoutMs },
|
|
116
|
+
);
|
|
117
|
+
const summary = response.content.filter((c): c is { type: "text"; text: string } => c.type === "text").map((c) => c.text).join("\n").trim();
|
|
118
|
+
if (!summary) throw new Error("Docket summarizer returned empty checkpoint");
|
|
119
|
+
|
|
120
|
+
const lines: string[] = [];
|
|
121
|
+
lines.push(`# Docket checkpoint ${input.id}`);
|
|
122
|
+
lines.push("");
|
|
123
|
+
lines.push(`mode: ${input.mode}`);
|
|
124
|
+
lines.push("summary: llm");
|
|
125
|
+
lines.push(`cwd: ${input.cwd}`);
|
|
126
|
+
lines.push(`created: ${new Date().toISOString()}`);
|
|
127
|
+
if (input.sourceSession) lines.push(`sourceSession: ${input.sourceSession}`);
|
|
128
|
+
const gitLabel = gitSnapshotLabel(input.git);
|
|
129
|
+
if (gitLabel) lines.push(`git: ${gitLabel}`);
|
|
130
|
+
if (input.note) lines.push(`note: ${input.note}`);
|
|
131
|
+
if (input.consumeOnUse) lines.push("consumeOnUse: true");
|
|
132
|
+
lines.push(`artifacts: ${input.artifactsFile}`);
|
|
133
|
+
lines.push("");
|
|
134
|
+
lines.push(summary);
|
|
135
|
+
lines.push("");
|
|
136
|
+
lines.push("## Docket artifact references");
|
|
137
|
+
lines.push(input.references);
|
|
138
|
+
return lines.join("\n");
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
export type CheckpointCreateOptions = {
|
|
2
|
+
note: string;
|
|
3
|
+
consumeOnUse: boolean;
|
|
4
|
+
/** Opt-in: add a model-written prose summary on top of the deterministic bundle header. */
|
|
5
|
+
summarize: boolean;
|
|
6
|
+
model?: string;
|
|
7
|
+
maxOutputTokens?: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type DocketIntent =
|
|
11
|
+
| { kind: "help"; advanced?: boolean }
|
|
12
|
+
| { kind: "browse"; mode?: "review" | "answers" | "log" }
|
|
13
|
+
| { kind: "clear" }
|
|
14
|
+
| { kind: "save"; options: CheckpointCreateOptions }
|
|
15
|
+
| { kind: "delete"; target: string | undefined; targetKind: "checkpoint" | "worker" }
|
|
16
|
+
| { kind: "list"; includeConsumed?: boolean; workers?: boolean; allProjects?: boolean }
|
|
17
|
+
| { kind: "load"; ref?: string; includeConsumed?: boolean; refKind: "checkpoint" | "worker" }
|
|
18
|
+
| { kind: "unload"; target: string; targetKind: "checkpoint" | "worker" | "all" }
|
|
19
|
+
| { kind: "spawn"; task: string; worktree?: boolean; fresh?: boolean; as?: string }
|
|
20
|
+
| { kind: "kinds" }
|
|
21
|
+
| { kind: "respawn"; target: string }
|
|
22
|
+
| { kind: "workers"; allProjects?: boolean }
|
|
23
|
+
| { kind: "verdict"; worker?: string }
|
|
24
|
+
| { kind: "tell"; worker: string; text?: string }
|
|
25
|
+
| { kind: "attach"; worker?: string }
|
|
26
|
+
| { kind: "worker-state"; state: "needs_input" | "ready" | "failed"; text?: string }
|
|
27
|
+
| { kind: "answers"; query?: string }
|
|
28
|
+
| { kind: "search"; query: string }
|
|
29
|
+
| { kind: "artifact"; action: "ref" | "inject-full" | "copy"; idOrRef: string };
|
|
30
|
+
|
|
31
|
+
export type ParseResult =
|
|
32
|
+
| { ok: true; intent: DocketIntent }
|
|
33
|
+
| { ok: false; message: string; usage: string };
|
|
34
|
+
|
|
35
|
+
export const DOCKET_COMMANDS = ["answers", "attach", "clear", "copy", "delete", "done", "fail", "help", "inject-full", "kinds", "list", "load", "log", "ref", "respawn", "save", "search", "spawn", "tell", "unload", "verdict", "wait", "workers"] as const;
|
|
36
|
+
|
|
37
|
+
const WORKER_PREFIX = "w:";
|
|
38
|
+
const WORKER_SHORT = /^w(\d+)$/i;
|
|
39
|
+
|
|
40
|
+
function stripWorkerPrefix(value: string): { id: string; isWorker: boolean } {
|
|
41
|
+
if (value.startsWith(WORKER_PREFIX)) return { id: value.slice(WORKER_PREFIX.length), isWorker: true };
|
|
42
|
+
if (WORKER_SHORT.test(value)) return { id: value, isWorker: true };
|
|
43
|
+
return { id: value, isWorker: false };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const SAVE_USAGE = "/docket save [--once] [--summarize [--model <provider/model>] [--max-output <tokens>]] [--] [note]";
|
|
47
|
+
const BOOLEAN_FLAGS = new Set(["--once", "--delete-on-use", "--summarize"]);
|
|
48
|
+
const VALUE_FLAGS = new Set(["--model", "--max-output"]);
|
|
49
|
+
|
|
50
|
+
export function docketUsage(advanced = false): string {
|
|
51
|
+
const primary = [
|
|
52
|
+
"Docket · core loop:",
|
|
53
|
+
"/docket open decision docket",
|
|
54
|
+
"/docket spawn [--fresh] [--as <kind>] <task> start explicit background worker",
|
|
55
|
+
"/docket tell w<N> [text] reply to a worker",
|
|
56
|
+
"/docket attach [w<N>] print/copy tmux attach command for the shared worker session",
|
|
57
|
+
"/docket save [flags] [note] save selected evidence as a zero-token bundle",
|
|
58
|
+
"/docket load [id|last|w<N>] mount bundle/worker artifacts without model tokens",
|
|
59
|
+
"",
|
|
60
|
+
"more: /docket help advanced",
|
|
61
|
+
];
|
|
62
|
+
if (!advanced) return primary.join("\n");
|
|
63
|
+
return [
|
|
64
|
+
...primary,
|
|
65
|
+
"",
|
|
66
|
+
"Docket · advanced:",
|
|
67
|
+
"/docket answers [query] browse assistant/worker answers",
|
|
68
|
+
"/docket log audit timeline grouped by episode",
|
|
69
|
+
"/docket search <query> ranked artifact search",
|
|
70
|
+
"/docket workers [--all] worker dashboard",
|
|
71
|
+
"/docket verdict [w<N>] decide worker outcome (accept/reject/chat)",
|
|
72
|
+
"/docket kinds list registered worker kinds",
|
|
73
|
+
"/docket respawn <w<N>|all> relaunch a worker whose tmux window died",
|
|
74
|
+
SAVE_USAGE,
|
|
75
|
+
"/docket load [id|last|w<N>] [--include-consumed] mount bundle or worker artifacts (no model tokens)",
|
|
76
|
+
"/docket unload <id|w<N>|all> drop a loaded slot",
|
|
77
|
+
"/docket delete [id|last|w<N>]",
|
|
78
|
+
"/docket list [--include-consumed] [--workers|--all]",
|
|
79
|
+
"/docket ref <artifact-id> attach compact chip (@id) above editor",
|
|
80
|
+
"/docket inject-full <artifact-id> attach full chip (@id*) above editor",
|
|
81
|
+
"/docket copy <artifact-id> copy artifact to clipboard",
|
|
82
|
+
"/docket clear drop all pending chips",
|
|
83
|
+
"/docket wait <question> worker fallback: ask parent for input",
|
|
84
|
+
"/docket done [summary] worker fallback: mark output ready",
|
|
85
|
+
"/docket fail <reason> worker fallback: mark work failed",
|
|
86
|
+
].join("\n");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseError(message: string, usage = docketUsage()): ParseResult {
|
|
90
|
+
return { ok: false, message, usage };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function tokenize(input: string): { ok: true; tokens: string[] } | { ok: false; message: string } {
|
|
94
|
+
const tokens: string[] = [];
|
|
95
|
+
let current = "";
|
|
96
|
+
let quote: "'" | '"' | undefined;
|
|
97
|
+
let escaping = false;
|
|
98
|
+
|
|
99
|
+
for (const char of input) {
|
|
100
|
+
if (escaping) {
|
|
101
|
+
current += char;
|
|
102
|
+
escaping = false;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (char === "\\") {
|
|
106
|
+
escaping = true;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (quote) {
|
|
110
|
+
if (char === quote) quote = undefined;
|
|
111
|
+
else current += char;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (char === "'" || char === '"') {
|
|
115
|
+
quote = char;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (/\s/.test(char)) {
|
|
119
|
+
if (current) {
|
|
120
|
+
tokens.push(current);
|
|
121
|
+
current = "";
|
|
122
|
+
}
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
current += char;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (escaping) current += "\\";
|
|
129
|
+
if (quote) return { ok: false, message: `Unclosed ${quote} quote` };
|
|
130
|
+
if (current) tokens.push(current);
|
|
131
|
+
return { ok: true, tokens };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function parseDeleteCommand(rest: string[]): ParseResult {
|
|
135
|
+
if (rest.length === 0) return { ok: true, intent: { kind: "delete", target: undefined, targetKind: "checkpoint" } };
|
|
136
|
+
if (rest.length > 1) return parseError("Usage: /docket delete [id|last|w:<worker>]");
|
|
137
|
+
const { id, isWorker } = stripWorkerPrefix(rest[0]!);
|
|
138
|
+
return { ok: true, intent: { kind: "delete", target: id, targetKind: isWorker ? "worker" : "checkpoint" } };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function requireArtifactArg(action: "ref" | "inject-full" | "copy", rest: string[]): ParseResult {
|
|
142
|
+
if (rest.length !== 1) return parseError(`Usage: /docket ${action} <artifact-id>`);
|
|
143
|
+
return { ok: true, intent: { kind: "artifact", action, idOrRef: rest[0]! } };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function parseSave(tokens: string[]): ParseResult {
|
|
147
|
+
let consumeOnUse = false;
|
|
148
|
+
let summarize = false;
|
|
149
|
+
let model: string | undefined;
|
|
150
|
+
let maxOutputTokens: number | undefined;
|
|
151
|
+
const noteParts: string[] = [];
|
|
152
|
+
|
|
153
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
154
|
+
const token = tokens[i]!;
|
|
155
|
+
if (token === "--") {
|
|
156
|
+
noteParts.push(...tokens.slice(i + 1));
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
if (BOOLEAN_FLAGS.has(token)) {
|
|
160
|
+
if (token === "--once" || token === "--delete-on-use") consumeOnUse = true;
|
|
161
|
+
else summarize = true;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (VALUE_FLAGS.has(token)) {
|
|
165
|
+
const value = tokens[++i];
|
|
166
|
+
if (!value) return parseError(`Missing value for ${token}`, SAVE_USAGE);
|
|
167
|
+
if (token === "--model") model = value;
|
|
168
|
+
else {
|
|
169
|
+
const parsed = Number(value);
|
|
170
|
+
if (!Number.isInteger(parsed) || parsed <= 0) return parseError("--max-output must be a positive integer", SAVE_USAGE);
|
|
171
|
+
maxOutputTokens = parsed;
|
|
172
|
+
}
|
|
173
|
+
// --model/--max-output only make sense with a summary; imply it.
|
|
174
|
+
summarize = true;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (token.startsWith("--")) return parseError(`Unknown save flag: ${token}`, SAVE_USAGE);
|
|
178
|
+
noteParts.push(token);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return { ok: true, intent: { kind: "save", options: { note: noteParts.join(" "), consumeOnUse, summarize, model, maxOutputTokens } } };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function parseDocketWorkerShellCommand(command: string): Extract<DocketIntent, { kind: "worker-state" }> | undefined {
|
|
185
|
+
const lines = command.trim().split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
186
|
+
if (lines.length !== 1) return undefined;
|
|
187
|
+
const line = lines[0]!.trim();
|
|
188
|
+
const match = line.match(/^\/?docket(?:\s+([\s\S]*))?$/);
|
|
189
|
+
if (!match) return undefined;
|
|
190
|
+
const parsed = parseDocketCommand(match[1] ?? "");
|
|
191
|
+
if (!parsed.ok || parsed.intent.kind !== "worker-state") return undefined;
|
|
192
|
+
return parsed.intent;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function parseDocketCommand(args: string): ParseResult {
|
|
196
|
+
const tokenized = tokenize(args.trim());
|
|
197
|
+
if (!tokenized.ok) return parseError(tokenized.message);
|
|
198
|
+
const [command = "", ...rest] = tokenized.tokens;
|
|
199
|
+
|
|
200
|
+
if (command === "") return { ok: true, intent: { kind: "browse", mode: "review" } };
|
|
201
|
+
if (command === "log") return { ok: true, intent: { kind: "browse", mode: "log" } };
|
|
202
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
203
|
+
const advanced = rest.some((token) => token === "advanced" || token === "--advanced" || token === "all" || token === "--all");
|
|
204
|
+
return { ok: true, intent: { kind: "help", ...(advanced ? { advanced: true } : {}) } };
|
|
205
|
+
}
|
|
206
|
+
if (command === "save") return parseSave(rest);
|
|
207
|
+
if (command === "delete") return parseDeleteCommand(rest);
|
|
208
|
+
if (command === "list") {
|
|
209
|
+
let includeConsumed = false;
|
|
210
|
+
let workers = false;
|
|
211
|
+
let allProjects = false;
|
|
212
|
+
const extras: string[] = [];
|
|
213
|
+
for (const token of rest) {
|
|
214
|
+
if (token === "--include-consumed") includeConsumed = true;
|
|
215
|
+
else if (token === "--workers") workers = true;
|
|
216
|
+
else if (token === "--all") { workers = true; allProjects = true; }
|
|
217
|
+
else extras.push(token);
|
|
218
|
+
}
|
|
219
|
+
if (extras.length > 0) return parseError("Usage: /docket list [--include-consumed] [--workers|--all]");
|
|
220
|
+
return { ok: true, intent: { kind: "list", includeConsumed, workers, ...(allProjects ? { allProjects } : {}) } };
|
|
221
|
+
}
|
|
222
|
+
if (command === "load") {
|
|
223
|
+
let includeConsumed = false;
|
|
224
|
+
const positional: string[] = [];
|
|
225
|
+
for (const token of rest) {
|
|
226
|
+
if (token === "--include-consumed") includeConsumed = true;
|
|
227
|
+
else positional.push(token);
|
|
228
|
+
}
|
|
229
|
+
if (positional.length > 1) return parseError("Usage: /docket load [id|last|w:<worker>] [--include-consumed]");
|
|
230
|
+
const raw = positional[0];
|
|
231
|
+
if (!raw) return { ok: true, intent: { kind: "load", ref: undefined, includeConsumed, refKind: "checkpoint" } };
|
|
232
|
+
const { id, isWorker } = stripWorkerPrefix(raw);
|
|
233
|
+
return { ok: true, intent: { kind: "load", ref: id, includeConsumed, refKind: isWorker ? "worker" : "checkpoint" } };
|
|
234
|
+
}
|
|
235
|
+
if (command === "unload") {
|
|
236
|
+
if (rest.length !== 1) return parseError("Usage: /docket unload <id|w:<worker>|all>");
|
|
237
|
+
const raw = rest[0]!;
|
|
238
|
+
if (raw === "all") return { ok: true, intent: { kind: "unload", target: "all", targetKind: "all" } };
|
|
239
|
+
const { id, isWorker } = stripWorkerPrefix(raw);
|
|
240
|
+
return { ok: true, intent: { kind: "unload", target: id, targetKind: isWorker ? "worker" : "checkpoint" } };
|
|
241
|
+
}
|
|
242
|
+
if (command === "spawn") {
|
|
243
|
+
let worktree = false;
|
|
244
|
+
let fresh = false;
|
|
245
|
+
let as: string | undefined;
|
|
246
|
+
const taskParts: string[] = [];
|
|
247
|
+
for (let i = 0; i < rest.length; i++) {
|
|
248
|
+
const token = rest[i]!;
|
|
249
|
+
if (token === "--worktree" || token === "-w") worktree = true;
|
|
250
|
+
else if (token === "--fresh") fresh = true;
|
|
251
|
+
else if (token === "--as" || token === "-a") {
|
|
252
|
+
const value = rest[++i];
|
|
253
|
+
if (!value) return parseError("Usage: /docket spawn [--fresh] [--as <kind>] <task>");
|
|
254
|
+
as = value;
|
|
255
|
+
} else if (token.startsWith("--as=")) {
|
|
256
|
+
as = token.slice("--as=".length);
|
|
257
|
+
} else {
|
|
258
|
+
taskParts.push(token);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (taskParts.length === 0) return parseError("Usage: /docket spawn [--fresh] [--as <kind>] <task>");
|
|
262
|
+
return { ok: true, intent: { kind: "spawn", task: taskParts.join(" "), ...(worktree ? { worktree } : {}), ...(fresh ? { fresh } : {}), ...(as ? { as } : {}) } };
|
|
263
|
+
}
|
|
264
|
+
if (command === "workers") {
|
|
265
|
+
let allProjects = false;
|
|
266
|
+
const extras: string[] = [];
|
|
267
|
+
for (const token of rest) {
|
|
268
|
+
if (token === "--all") allProjects = true;
|
|
269
|
+
else extras.push(token);
|
|
270
|
+
}
|
|
271
|
+
if (extras.length > 0) return parseError("Usage: /docket workers [--all]");
|
|
272
|
+
return { ok: true, intent: { kind: "workers", ...(allProjects ? { allProjects } : {}) } };
|
|
273
|
+
}
|
|
274
|
+
if (command === "verdict") {
|
|
275
|
+
if (rest.length > 1) return parseError("Usage: /docket verdict [w<N>]");
|
|
276
|
+
return { ok: true, intent: { kind: "verdict", ...(rest[0] ? { worker: rest[0] } : {}) } };
|
|
277
|
+
}
|
|
278
|
+
if (command === "kinds") {
|
|
279
|
+
if (rest.length > 0) return parseError("Usage: /docket kinds");
|
|
280
|
+
return { ok: true, intent: { kind: "kinds" } };
|
|
281
|
+
}
|
|
282
|
+
if (command === "respawn") {
|
|
283
|
+
if (rest.length !== 1) return parseError("Usage: /docket respawn <w<N>|all>");
|
|
284
|
+
return { ok: true, intent: { kind: "respawn", target: rest[0]! } };
|
|
285
|
+
}
|
|
286
|
+
if (command === "tell") {
|
|
287
|
+
if (rest.length < 1) return parseError("Usage: /docket tell w<N> [text]");
|
|
288
|
+
return { ok: true, intent: { kind: "tell", worker: rest[0]!, text: rest.length > 1 ? rest.slice(1).join(" ") : undefined } };
|
|
289
|
+
}
|
|
290
|
+
if (command === "attach") {
|
|
291
|
+
if (rest.length === 0) return { ok: true, intent: { kind: "attach" } };
|
|
292
|
+
if (rest.length > 1) return parseError("Usage: /docket attach [w<N>]");
|
|
293
|
+
return { ok: true, intent: { kind: "attach", worker: rest[0]! } };
|
|
294
|
+
}
|
|
295
|
+
if (command === "wait") {
|
|
296
|
+
if (rest.length === 0) return parseError("Usage: /docket wait <question>");
|
|
297
|
+
return { ok: true, intent: { kind: "worker-state", state: "needs_input", text: rest.join(" ") } };
|
|
298
|
+
}
|
|
299
|
+
if (command === "done") {
|
|
300
|
+
return { ok: true, intent: { kind: "worker-state", state: "ready", text: rest.length ? rest.join(" ") : undefined } };
|
|
301
|
+
}
|
|
302
|
+
if (command === "fail") {
|
|
303
|
+
if (rest.length === 0) return parseError("Usage: /docket fail <reason>");
|
|
304
|
+
return { ok: true, intent: { kind: "worker-state", state: "failed", text: rest.join(" ") } };
|
|
305
|
+
}
|
|
306
|
+
if (command === "answers") {
|
|
307
|
+
return { ok: true, intent: { kind: "answers", query: rest.length ? rest.join(" ") : undefined } };
|
|
308
|
+
}
|
|
309
|
+
if (command === "clear") {
|
|
310
|
+
if (rest.length > 0) return parseError("Usage: /docket clear");
|
|
311
|
+
return { ok: true, intent: { kind: "clear" } };
|
|
312
|
+
}
|
|
313
|
+
if (command === "search") {
|
|
314
|
+
if (rest.length === 0) return parseError("Usage: /docket search <query>");
|
|
315
|
+
return { ok: true, intent: { kind: "search", query: rest.join(" ") } };
|
|
316
|
+
}
|
|
317
|
+
if (command === "ref" || command === "inject-full" || command === "copy") return requireArtifactArg(command, rest);
|
|
318
|
+
return parseError(`Unknown Docket command: ${command}`);
|
|
319
|
+
}
|