lazyopencode-core 0.0.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.
Files changed (89) hide show
  1. package/ATTRIBUTION.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +357 -0
  4. package/dist/agents/councillor.d.ts +1 -0
  5. package/dist/agents/councillor.js +14 -0
  6. package/dist/agents/designer.d.ts +1 -0
  7. package/dist/agents/designer.js +31 -0
  8. package/dist/agents/explorer.d.ts +1 -0
  9. package/dist/agents/explorer.js +15 -0
  10. package/dist/agents/fixer.d.ts +1 -0
  11. package/dist/agents/fixer.js +23 -0
  12. package/dist/agents/index.d.ts +2 -0
  13. package/dist/agents/index.js +55 -0
  14. package/dist/agents/lazy.d.ts +1 -0
  15. package/dist/agents/lazy.js +3 -0
  16. package/dist/agents/librarian.d.ts +1 -0
  17. package/dist/agents/librarian.js +26 -0
  18. package/dist/agents/observer.d.ts +1 -0
  19. package/dist/agents/observer.js +20 -0
  20. package/dist/agents/oracle.d.ts +1 -0
  21. package/dist/agents/oracle.js +30 -0
  22. package/dist/council/council-manager.d.ts +42 -0
  23. package/dist/council/council-manager.js +223 -0
  24. package/dist/council/index.d.ts +2 -0
  25. package/dist/council/index.js +1 -0
  26. package/dist/hooks/apply-patch-rescue.d.ts +7 -0
  27. package/dist/hooks/apply-patch-rescue.js +150 -0
  28. package/dist/hooks/background-job-board.d.ts +92 -0
  29. package/dist/hooks/background-job-board.js +452 -0
  30. package/dist/hooks/chat-params.d.ts +16 -0
  31. package/dist/hooks/chat-params.js +30 -0
  32. package/dist/hooks/deepwork.d.ts +9 -0
  33. package/dist/hooks/deepwork.js +55 -0
  34. package/dist/hooks/error-recovery.d.ts +21 -0
  35. package/dist/hooks/error-recovery.js +216 -0
  36. package/dist/hooks/index.d.ts +3 -0
  37. package/dist/hooks/index.js +61 -0
  38. package/dist/hooks/lazy-command.d.ts +16 -0
  39. package/dist/hooks/lazy-command.js +178 -0
  40. package/dist/hooks/messages-transform.d.ts +40 -0
  41. package/dist/hooks/messages-transform.js +358 -0
  42. package/dist/hooks/permission-guard.d.ts +5 -0
  43. package/dist/hooks/permission-guard.js +38 -0
  44. package/dist/hooks/runtime.d.ts +169 -0
  45. package/dist/hooks/runtime.js +653 -0
  46. package/dist/hooks/session-events.d.ts +16 -0
  47. package/dist/hooks/session-events.js +65 -0
  48. package/dist/hooks/system-transform.d.ts +8 -0
  49. package/dist/hooks/system-transform.js +113 -0
  50. package/dist/hooks/task-session.d.ts +32 -0
  51. package/dist/hooks/task-session.js +177 -0
  52. package/dist/hooks/workflow-classifier.d.ts +17 -0
  53. package/dist/hooks/workflow-classifier.js +170 -0
  54. package/dist/index.d.ts +13 -0
  55. package/dist/index.js +85 -0
  56. package/dist/opencode-control-plane.d.ts +20 -0
  57. package/dist/opencode-control-plane.js +95 -0
  58. package/dist/ponytail.d.ts +1 -0
  59. package/dist/ponytail.js +33 -0
  60. package/dist/skills/index.d.ts +5 -0
  61. package/dist/skills/index.js +10 -0
  62. package/dist/skills/lazy/build/SKILL.md +62 -0
  63. package/dist/skills/lazy/debug/SKILL.md +17 -0
  64. package/dist/skills/lazy/grill/SKILL.md +54 -0
  65. package/dist/skills/lazy/plan/SKILL.md +52 -0
  66. package/dist/skills/lazy/review/SKILL.md +29 -0
  67. package/dist/skills/lazy/security/SKILL.md +29 -0
  68. package/dist/skills/lazy/simplify/SKILL.md +52 -0
  69. package/dist/skills/lazy/specify/SKILL.md +62 -0
  70. package/dist/skills/lazy/worktree/SKILL.md +66 -0
  71. package/dist/tools/cancel-task.d.ts +3 -0
  72. package/dist/tools/cancel-task.js +37 -0
  73. package/dist/tools/council.d.ts +6 -0
  74. package/dist/tools/council.js +41 -0
  75. package/dist/tools/index.d.ts +2 -0
  76. package/dist/tools/index.js +2 -0
  77. package/dist/v2.d.ts +1 -0
  78. package/dist/v2.js +42 -0
  79. package/docs/architecture.md +47 -0
  80. package/docs/council.md +200 -0
  81. package/docs/desktop-distribution.md +36 -0
  82. package/docs/opencode-integration.md +54 -0
  83. package/docs/positioning.md +44 -0
  84. package/docs/product-audit.md +187 -0
  85. package/docs/product-plan.md +56 -0
  86. package/docs/state-machine.md +35 -0
  87. package/docs/user-manual.md +439 -0
  88. package/docs/work-plan.md +190 -0
  89. package/package.json +44 -0
@@ -0,0 +1,30 @@
1
+ export const ORACLE_PROMPT = `<Role>
2
+ You are a strategic technical advisor. You handle architecture decisions, complex debugging, code review, and simplification. You enforce YAGNI. You are a senior dev who has seen every over-engineered mess and been paged at 3am for one.
3
+ </Role>
4
+
5
+ ## Core principles
6
+ - **Deletion over addition.** Your first question is always: "what can we delete?"
7
+ - **Simplest thing that works.** Not cleverest. Not most flexible. Simplest.
8
+ - **YAGNI is law.** Speculative abstraction = technical debt, not foresight.
9
+ - **One line verdicts.** Your review output is: finding + fix suggestion. No essays.
10
+
11
+ ## When you're called
12
+ - Architecture decisions with long-term impact
13
+ - Problems persisting after 2+ fix attempts
14
+ - High-risk refactors
15
+ - Costly trade-offs (performance vs maintainability)
16
+ - Complex debugging with unclear root cause
17
+ - Code review (load \`lazy/review\` for methodology)
18
+ - Simplification audit (load \`lazy/simplify\` for methodology)
19
+
20
+ ## Output format
21
+ 1. **Verdict** (one line): what's the call?
22
+ 2. **Why** (max 3 lines): critical reasoning only
23
+ 3. **What to do** (minimal diff): the change, not the explanation
24
+
25
+ ## Anti-patterns you kill on sight
26
+ - Interface with one implementation
27
+ - Factory for one product
28
+ - Config for a value that never changes
29
+ - "We might need this later"
30
+ - Clever code that someone decodes at 3am`;
@@ -0,0 +1,42 @@
1
+ interface CouncilConfig {
2
+ enabled?: boolean;
3
+ eligibility?: "guarded" | "always";
4
+ default_preset?: string;
5
+ timeout?: number;
6
+ execution_mode?: "parallel" | "serial";
7
+ retries?: number;
8
+ maxCouncillors?: number;
9
+ presets?: Record<string, Record<string, {
10
+ model?: string;
11
+ prompt?: string;
12
+ }>>;
13
+ }
14
+ interface RequiredCouncilConfig {
15
+ enabled: boolean;
16
+ eligibility: "guarded" | "always";
17
+ default_preset: string;
18
+ timeout: number;
19
+ execution_mode: "parallel" | "serial";
20
+ retries: number;
21
+ maxCouncillors: number;
22
+ presets: Record<string, Record<string, {
23
+ model: string;
24
+ prompt?: string;
25
+ }>>;
26
+ }
27
+ interface CouncillorResult {
28
+ name: string;
29
+ status: "success" | "error" | "timeout";
30
+ result?: string;
31
+ error?: string;
32
+ }
33
+ interface CouncilOutput {
34
+ success: boolean;
35
+ error?: string;
36
+ councillorResults: CouncillorResult[];
37
+ formatted: string;
38
+ }
39
+ export declare function defaultCouncilConfig(overrides?: CouncilConfig): RequiredCouncilConfig;
40
+ export declare function runCouncil(prompt: string, client: any, councilConfig: RequiredCouncilConfig, presetName?: string, parentSessionId?: string, abortSignal?: AbortSignal): Promise<CouncilOutput>;
41
+ export declare function formatResults(prompt: string, results: CouncillorResult[]): string;
42
+ export type { CouncilConfig, CouncillorResult, CouncilOutput, RequiredCouncilConfig };
@@ -0,0 +1,223 @@
1
+ export function defaultCouncilConfig(overrides) {
2
+ const presets = {
3
+ default: {
4
+ councillor: { model: "", prompt: undefined },
5
+ },
6
+ };
7
+ if (overrides?.presets) {
8
+ for (const [name, councillors] of Object.entries(overrides.presets)) {
9
+ const resolved = {};
10
+ for (const [key, val] of Object.entries(councillors)) {
11
+ resolved[key] = { model: val.model ?? "", prompt: val.prompt };
12
+ }
13
+ presets[name] = resolved;
14
+ }
15
+ }
16
+ return {
17
+ enabled: overrides?.enabled ?? true,
18
+ eligibility: overrides?.eligibility ?? "guarded",
19
+ default_preset: overrides?.default_preset ?? "default",
20
+ timeout: overrides?.timeout ?? 180_000,
21
+ execution_mode: overrides?.execution_mode ?? "parallel",
22
+ retries: overrides?.retries ?? 2,
23
+ maxCouncillors: overrides?.maxCouncillors ?? 3,
24
+ presets,
25
+ };
26
+ }
27
+ function parseModel(modelStr) {
28
+ if (!modelStr)
29
+ return null;
30
+ const slash = modelStr.indexOf("/");
31
+ if (slash === -1)
32
+ return null;
33
+ return { providerID: modelStr.slice(0, slash), modelID: modelStr.slice(slash + 1) };
34
+ }
35
+ export async function runCouncil(prompt,
36
+ // deno-lint-ignore no-explicit-any
37
+ client, councilConfig, presetName, parentSessionId, abortSignal) {
38
+ if (abortSignal?.aborted) {
39
+ return { success: false, error: "Council aborted", councillorResults: [], formatted: "" };
40
+ }
41
+ if (!councilConfig.enabled) {
42
+ return {
43
+ success: false,
44
+ error: "Council is disabled by config",
45
+ councillorResults: [],
46
+ formatted: "",
47
+ };
48
+ }
49
+ const presetNameResolved = presetName ?? councilConfig.default_preset;
50
+ const preset = councilConfig.presets[presetNameResolved];
51
+ if (!preset) {
52
+ return {
53
+ success: false,
54
+ error: `Council preset "${presetNameResolved}" not found`,
55
+ councillorResults: [],
56
+ formatted: "",
57
+ };
58
+ }
59
+ const entries = Object.entries(preset);
60
+ if (entries.length === 0) {
61
+ return {
62
+ success: false,
63
+ error: "No councillors in preset",
64
+ councillorResults: [],
65
+ formatted: "",
66
+ };
67
+ }
68
+ if (entries.length > councilConfig.maxCouncillors) {
69
+ return {
70
+ success: false,
71
+ error: `Council preset "${presetNameResolved}" has ${entries.length} councillors, exceeding maxCouncillors ${councilConfig.maxCouncillors}`,
72
+ councillorResults: [],
73
+ formatted: "",
74
+ };
75
+ }
76
+ const runOne = async (name, modelStr, SystemPrompt) => {
77
+ if (abortSignal?.aborted) {
78
+ return { name, status: "error", error: "Aborted" };
79
+ }
80
+ const system = SystemPrompt ??
81
+ "You are a member of a coding council. Provide independent analysis. Be concise. Cite evidence.";
82
+ const model = parseModel(modelStr);
83
+ let sessionId;
84
+ try {
85
+ const createRes = await client.session.create({
86
+ body: { parentID: parentSessionId, title: `council: ${name}` },
87
+ });
88
+ if (createRes.error) {
89
+ return {
90
+ name,
91
+ status: "error",
92
+ error: `Session creation failed: ${JSON.stringify(createRes.error)}`,
93
+ };
94
+ }
95
+ sessionId = createRes.data.id;
96
+ const promptRes = await client.session.prompt({
97
+ body: {
98
+ agent: "lazy-councillor",
99
+ ...(model ? { model } : {}),
100
+ ...(system ? { system } : {}),
101
+ parts: [{ type: "text", text: prompt }],
102
+ tools: { read: true, glob: true, grep: true, list: true },
103
+ },
104
+ path: { id: sessionId },
105
+ });
106
+ if (promptRes.error) {
107
+ return { name, status: "error", error: `Prompt failed: ${JSON.stringify(promptRes.error)}` };
108
+ }
109
+ // deno-lint-ignore no-explicit-any
110
+ const textParts = (promptRes.data.parts ?? []).filter((p) => p.type === "text");
111
+ // deno-lint-ignore no-explicit-any
112
+ const result = textParts.map((p) => p.text).join("\n");
113
+ return { name, status: "success", result };
114
+ }
115
+ catch (err) {
116
+ const message = err instanceof Error ? err.message : String(err);
117
+ return { name, status: "error", error: message };
118
+ }
119
+ finally {
120
+ if (sessionId) {
121
+ client.session.delete({ path: { id: sessionId } }).catch((err) => {
122
+ console.error("[council] failed to cleanup session:", err);
123
+ });
124
+ }
125
+ }
126
+ };
127
+ const startTime = Date.now();
128
+ const timeoutMs = councilConfig.timeout;
129
+ const runAll = async () => {
130
+ if (abortSignal?.aborted) {
131
+ return entries.map(([name]) => ({ name, status: "error", error: "Aborted" }));
132
+ }
133
+ if (councilConfig.execution_mode === "serial") {
134
+ const results = [];
135
+ for (const [name, config] of entries) {
136
+ if (abortSignal?.aborted) {
137
+ results.push({ name, status: "error", error: "Aborted" });
138
+ break;
139
+ }
140
+ if (Date.now() - startTime >= timeoutMs) {
141
+ results.push({ name, status: "timeout", error: "Serial execution timeout" });
142
+ break;
143
+ }
144
+ results.push(await runOne(name, config.model, config.prompt));
145
+ }
146
+ return results;
147
+ }
148
+ const promises = entries.map(([name, config]) => runOne(name, config.model, config.prompt));
149
+ let timeoutHandle;
150
+ const timeoutPromise = new Promise((resolve) => {
151
+ timeoutHandle = setTimeout(() => resolve("timeout"), timeoutMs);
152
+ });
153
+ const raceResult = await Promise.race([Promise.allSettled(promises), timeoutPromise]);
154
+ if (timeoutHandle)
155
+ clearTimeout(timeoutHandle);
156
+ if (raceResult === "timeout") {
157
+ return entries.map(([name]) => ({
158
+ name,
159
+ status: "timeout",
160
+ error: "Council timeout exceeded",
161
+ }));
162
+ }
163
+ return raceResult.map((r, i) => {
164
+ if (r.status === "fulfilled")
165
+ return r.value;
166
+ const entry = entries[i];
167
+ if (!entry) {
168
+ return { name: "unknown", status: "error", error: "Unknown error" };
169
+ }
170
+ return {
171
+ name: entry[0],
172
+ status: "error",
173
+ error: r.reason?.message ?? "Unknown error",
174
+ };
175
+ });
176
+ };
177
+ const results = await runAll();
178
+ for (let attempt = 0; attempt < councilConfig.retries; attempt++) {
179
+ if (abortSignal?.aborted)
180
+ break;
181
+ const failed = [];
182
+ for (let i = 0; i < results.length; i++) {
183
+ const r = results[i];
184
+ if (r.status !== "success" || !r.result?.trim()) {
185
+ failed.push(i);
186
+ }
187
+ }
188
+ if (failed.length === 0)
189
+ break;
190
+ if (Date.now() - startTime >= timeoutMs)
191
+ break;
192
+ const retryEntries = failed.map((i) => entries[i]);
193
+ const retryPromises = retryEntries.map(([name, config]) => runOne(name, config.model, config.prompt));
194
+ const retryResults = await Promise.allSettled(retryPromises);
195
+ for (let j = 0; j < failed.length; j++) {
196
+ const rr = retryResults[j];
197
+ if (rr.status === "fulfilled")
198
+ results[failed[j]] = rr.value;
199
+ }
200
+ }
201
+ const formatted = formatResults(prompt, results);
202
+ const allSuccess = results.every((r) => r.status === "success");
203
+ return { success: allSuccess, councillorResults: results, formatted };
204
+ }
205
+ export function formatResults(prompt, results) {
206
+ const lines = [
207
+ `# Council Results\n`,
208
+ `Councillors: ${results.length}`,
209
+ `Estimated model calls: ${results.length}\n`,
210
+ `## Question\n${prompt}\n`,
211
+ ];
212
+ for (const r of results) {
213
+ lines.push(`## ${r.name}`);
214
+ lines.push(`Status: ${r.status}`);
215
+ if (r.error)
216
+ lines.push(`Error: ${r.error}`);
217
+ if (r.result)
218
+ lines.push(r.result);
219
+ lines.push("");
220
+ }
221
+ lines.push("## Synthesis Required\nReview each councillor's response above and synthesize a final recommendation.");
222
+ return lines.join("\n");
223
+ }
@@ -0,0 +1,2 @@
1
+ export { defaultCouncilConfig, formatResults, runCouncil } from "./council-manager.js";
2
+ export type { CouncilConfig, CouncillorResult, CouncilOutput, RequiredCouncilConfig, } from "./council-manager.js";
@@ -0,0 +1 @@
1
+ export { defaultCouncilConfig, formatResults, runCouncil } from "./council-manager.js";
@@ -0,0 +1,7 @@
1
+ export declare function createApplyPatchRescueHook(): (input: {
2
+ tool: string;
3
+ sessionID: string;
4
+ callID: string;
5
+ }, output: {
6
+ args: Record<string, unknown>;
7
+ }) => Promise<void>;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Hunk offset fixup — rescues apply_patch when the LLM hallucinates line numbers.
3
+ * ponytail: prefix/suffix context matching via substring containment, no full diff parser.
4
+ */
5
+ import { existsSync } from "node:fs";
6
+ import { readFile } from "node:fs/promises";
7
+ export function createApplyPatchRescueHook() {
8
+ return async (input, output) => {
9
+ if (input.tool !== "apply_patch")
10
+ return;
11
+ const args = output.args;
12
+ const filePath = args?.file_path;
13
+ const patchContent = args?.content;
14
+ if (!filePath || !patchContent)
15
+ return;
16
+ if (!existsSync(filePath))
17
+ return;
18
+ let fileContents;
19
+ try {
20
+ fileContents = await readFile(filePath, "utf-8");
21
+ }
22
+ catch {
23
+ return;
24
+ }
25
+ const fileLines = fileContents.split("\n");
26
+ const fixedPatch = fixHunkOffsets(patchContent, fileLines);
27
+ if (fixedPatch !== patchContent) {
28
+ output.args = { ...args, content: fixedPatch };
29
+ }
30
+ };
31
+ }
32
+ // ---------------------------------------------------------------------------
33
+ // Hunk offset fixup
34
+ // ---------------------------------------------------------------------------
35
+ function fixHunkOffsets(patchContent, fileLines) {
36
+ const lines = patchContent.split("\n");
37
+ const hunkHeaders = lines.filter((line) => line.startsWith("@@")).length;
38
+ if (hunkHeaders !== 1)
39
+ return patchContent;
40
+ const result = [];
41
+ let i = 0;
42
+ while (i < lines.length) {
43
+ const line = lines[i];
44
+ const hunkMatch = line.match(/^@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)/);
45
+ if (!hunkMatch) {
46
+ result.push(line);
47
+ i++;
48
+ continue;
49
+ }
50
+ const oldStart = parseInt(hunkMatch[1], 10);
51
+ const oldCount = hunkMatch[2];
52
+ const newStart = parseInt(hunkMatch[3], 10);
53
+ const newCount = hunkMatch[4];
54
+ const section = hunkMatch[5];
55
+ if (oldStart !== newStart)
56
+ return patchContent;
57
+ // Collect hunk body — lines between header and next @@ or EOF
58
+ const bodyLines = [];
59
+ i++;
60
+ while (i < lines.length && !lines[i].startsWith("@@")) {
61
+ bodyLines.push(lines[i]);
62
+ i++;
63
+ }
64
+ if (bodyLines.length === 0) {
65
+ result.push(line);
66
+ continue;
67
+ }
68
+ const adjustedStart = findBestMatch(fileLines, bodyLines, oldStart);
69
+ if (adjustedStart === -1) {
70
+ // No match — leave hunk alone
71
+ result.push(line);
72
+ result.push(...bodyLines);
73
+ continue;
74
+ }
75
+ const header = `@@ -${adjustedStart},${oldCount} +${adjustedStart},${newCount} @@` +
76
+ (section ? ` ${section}` : "");
77
+ result.push(header);
78
+ result.push(...bodyLines);
79
+ }
80
+ return result.join("\n");
81
+ }
82
+ // ---------------------------------------------------------------------------
83
+ // Context matching
84
+ // ---------------------------------------------------------------------------
85
+ function findBestMatch(fileLines, hunkBody, estLine) {
86
+ const prefix = getFirstContextLine(hunkBody);
87
+ const suffix = getLastContextLine(hunkBody);
88
+ // Try prefix first
89
+ if (prefix) {
90
+ const match = findLineNear(fileLines, prefix, estLine);
91
+ if (match !== -1)
92
+ return match;
93
+ }
94
+ // Try suffix if different from prefix
95
+ if (suffix && suffix !== prefix) {
96
+ const match = findLineNear(fileLines, suffix, estLine);
97
+ if (match !== -1)
98
+ return match;
99
+ }
100
+ return -1;
101
+ }
102
+ /**
103
+ * First non-empty line in hunk body that is context (not + or - prefixed).
104
+ */
105
+ function getFirstContextLine(body) {
106
+ for (const line of body) {
107
+ const trimmed = line.trim();
108
+ if (!trimmed)
109
+ continue;
110
+ if (trimmed.startsWith("+") || trimmed.startsWith("-"))
111
+ continue;
112
+ return trimmed;
113
+ }
114
+ return null;
115
+ }
116
+ /**
117
+ * Last non-empty line in hunk body that is context (not + or - prefixed).
118
+ */
119
+ function getLastContextLine(body) {
120
+ for (let i = body.length - 1; i >= 0; i--) {
121
+ const trimmed = body[i].trim();
122
+ if (!trimmed)
123
+ continue;
124
+ if (trimmed.startsWith("+") || trimmed.startsWith("-"))
125
+ continue;
126
+ return trimmed;
127
+ }
128
+ return null;
129
+ }
130
+ /**
131
+ * Search +/- 50 lines of estLine (1-indexed) for a line containing `search`.
132
+ * Returns 1-indexed line number or -1.
133
+ */
134
+ function findLineNear(fileLines, search, estLine) {
135
+ const estLine0 = estLine - 1;
136
+ const searchStart = Math.max(0, estLine0 - 50);
137
+ const searchEnd = Math.min(fileLines.length, estLine0 + 50);
138
+ let bestIndex = -1;
139
+ let bestDist = Infinity;
140
+ for (let i = searchStart; i < searchEnd; i++) {
141
+ if (fileLines[i].trim() === search.trim() || fileLines[i].includes(search)) {
142
+ const dist = Math.abs(i - estLine0);
143
+ if (dist < bestDist) {
144
+ bestDist = dist;
145
+ bestIndex = i;
146
+ }
147
+ }
148
+ }
149
+ return bestIndex === -1 ? -1 : bestIndex + 1;
150
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * BackgroundJobBoard — in-memory state machine for subagent task tracking.
3
+ *
4
+ * ponytail: global singletons, no DI. Upgrade: per-session isolation if starvation occurs.
5
+ */
6
+ export type JobState = "running" | "completed" | "error" | "cancelled" | "reconciled" | "stale";
7
+ export interface ContextFile {
8
+ path: string;
9
+ lineCount: number;
10
+ }
11
+ export interface BackgroundJobRecord {
12
+ taskID: string;
13
+ parentSessionID: string;
14
+ agent: string;
15
+ state: JobState;
16
+ terminalUnreconciled: boolean;
17
+ timedOut: boolean;
18
+ cancellationRequested: boolean;
19
+ alias: string;
20
+ callID: string;
21
+ resultSummary?: string;
22
+ contextFiles: ContextFile[];
23
+ launchedAt: number;
24
+ lastLaunchedAt: number;
25
+ lastUsedAt: number;
26
+ updatedAt: number;
27
+ completedAt: number;
28
+ }
29
+ export declare class BackgroundJobBoard {
30
+ private jobs;
31
+ private pendingCalls;
32
+ private agentCounter;
33
+ private processedCompletions;
34
+ private injectedCompletionsSeen;
35
+ private maxReusablePerAgent;
36
+ private dirty;
37
+ constructor(options?: {
38
+ maxReusablePerAgent?: number;
39
+ });
40
+ configure(options: {
41
+ maxReusablePerAgent?: number;
42
+ }): void;
43
+ registerLaunch(parentSessionID: string, agent: string, callID: string): BackgroundJobRecord;
44
+ findJobByCallID(callID: string): BackgroundJobRecord | undefined;
45
+ findJobByTaskID(taskID: string): BackgroundJobRecord | undefined;
46
+ findJobByAlias(alias: string): BackgroundJobRecord | undefined;
47
+ updateStatus(callID: string, taskID: string, state: JobState, resultSummary?: string): void;
48
+ addContext(taskID: string, file: ContextFile): void;
49
+ markReconciled(taskID: string): void;
50
+ trimReusable(taskID: string): void;
51
+ getTerminalUnreconciledJobs(parentSessionID: string): BackgroundJobRecord[];
52
+ getRunningJobs(parentSessionID: string): BackgroundJobRecord[];
53
+ getStaleJobs(parentSessionID: string): BackgroundJobRecord[];
54
+ getReusableJobs(parentSessionID: string): BackgroundJobRecord[];
55
+ resolveReusable(parentSessionID: string, agent: string): BackgroundJobRecord | undefined;
56
+ getReusableJob(taskID: string): BackgroundJobRecord | undefined;
57
+ getActiveCount(parentSessionID: string, agent: string): number;
58
+ isLateCancelledTaskError(callID: string): boolean;
59
+ cancelJob(id: string): void;
60
+ formatForPrompt(parentSessionID: string): string | null;
61
+ isDirty(): boolean;
62
+ markClean(): void;
63
+ formatMini(parentSessionID: string): string | null;
64
+ isInjectedCompletionProcessed(id: string): boolean;
65
+ markInjectedCompletionSeen(taskID: string): void;
66
+ dropSession(sessionID: string): void;
67
+ clear(): void;
68
+ snapshot(): {
69
+ jobs: BackgroundJobRecord[];
70
+ pendingCalls: Array<{
71
+ callID: string;
72
+ sessionID: string;
73
+ alias?: string;
74
+ }>;
75
+ agentCounter: Array<[string, number]>;
76
+ processedCompletions: string[];
77
+ injectedCompletionsSeen: string[];
78
+ };
79
+ restore(snapshot: {
80
+ jobs?: BackgroundJobRecord[];
81
+ pendingCalls?: Array<{
82
+ callID: string;
83
+ sessionID: string;
84
+ alias?: string;
85
+ }>;
86
+ agentCounter?: Array<[string, number]>;
87
+ processedCompletions?: string[];
88
+ injectedCompletionsSeen?: string[];
89
+ }): void;
90
+ get size(): number;
91
+ }
92
+ export declare const jobBoard: BackgroundJobBoard;