@a-t-h-i/bot-lobby 0.6.38 → 0.6.40
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 +54 -11
- package/package.json +1 -1
- package/prompts/global.md +12 -0
- package/prompts/master.md +21 -3
- package/prompts/quickfix.md +10 -3
- package/prompts/scout.md +8 -4
- package/prompts/worker.md +5 -3
- package/src/classifier/chat.ts +122 -0
- package/src/classifier/classifier.ts +46 -11
- package/src/classifier/files.ts +91 -23
- package/src/classifier/instance.ts +35 -2
- package/src/classifier/models.ts +115 -0
- package/src/execution/git.ts +108 -2
- package/src/index.ts +3 -0
- package/src/knowledge/file-notes.ts +104 -0
- package/src/knowledge/repo-map.ts +165 -0
- package/src/lobby/host.ts +2 -0
- package/src/lobby/quickfix.ts +55 -2
- package/src/lobby/service.ts +2 -1
- package/src/master/master.ts +39 -3
- package/src/master/scout-cache.ts +70 -0
- package/src/master/synthesis.ts +10 -2
- package/src/pi/model-settings.ts +5 -3
- package/src/pi/plan-checklist.ts +2 -1
- package/src/pi/tool-budget.ts +66 -0
- package/src/pi/tools.ts +1 -0
- package/src/roles/markdown.ts +12 -4
- package/src/roles/scout.ts +2 -1
- package/src/schemas/configuration.ts +21 -2
- package/src/schemas/findings.ts +2 -0
- package/src/state/work-time.ts +4 -2
- package/src/webui/api/plan-facts.ts +17 -2
- package/src/webui/api/settings.ts +2 -1
- package/src/webui/api/tasks.ts +1 -1
- package/src/webui/dev/fake-service.ts +1 -0
- package/src/webui/protocol.ts +18 -1
- package/src/workflow/workflow.ts +41 -2
- package/webui/dist/assets/index-BV9aLD_v.js +109 -0
- package/webui/dist/assets/index-CzPF26MA.js +109 -0
- package/webui/dist/assets/index-Glz3FAPp.css +2 -0
- package/webui/dist/assets/index-uTndyUfm.css +2 -0
- package/webui/dist/build.json +17 -21
- package/webui/dist/index.html +2 -2
- package/webui/dist/assets/index-BFsMFIr5.js +0 -110
- package/webui/dist/assets/index-Ccc6rsPD.css +0 -2
|
@@ -38,6 +38,8 @@ export interface ScoutConfig {
|
|
|
38
38
|
mcpTools?: string[];
|
|
39
39
|
model: ModelRef;
|
|
40
40
|
timeoutMs: number;
|
|
41
|
+
/** Tool calls a scout may make: it is told to wrap up at 60% and refused more past this, so it reports with what it has; 0 = no limit. */
|
|
42
|
+
maxToolCalls: number;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
/** Subagent kinds with their own settings entry. */
|
|
@@ -91,6 +93,10 @@ export interface WorkflowConfig {
|
|
|
91
93
|
routeQuickFixes: boolean;
|
|
92
94
|
/** A new task gets a git branch or worktree of its own, named after it; `--branch` and `--worktree` decide for one task. */
|
|
93
95
|
gitIsolation: GitIsolation;
|
|
96
|
+
/** Scouts, workers, quick fixes and the planner get the repository map: the layout and the files matching their step, with each file's purpose, names and unchanged scout notes. */
|
|
97
|
+
repoMap: boolean;
|
|
98
|
+
/** A scout asked the same thing again for the same task, on a tree that has not changed, reuses its findings instead of running. */
|
|
99
|
+
reuseScouts: boolean;
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
/**
|
|
@@ -206,9 +212,15 @@ export interface ClassifierThresholds {
|
|
|
206
212
|
quickFixLargeAt: number;
|
|
207
213
|
}
|
|
208
214
|
|
|
209
|
-
/** The
|
|
215
|
+
/** The classifier: a fast model for obvious decisions, so large models spend fewer tokens on them. */
|
|
210
216
|
export interface ClassifierConfig {
|
|
211
217
|
enabled: boolean;
|
|
218
|
+
/**
|
|
219
|
+
* The model that answers, as `provider/id`: any classifier model pi lists
|
|
220
|
+
* (Jev, Cloudflare's clef, Liquid's d1, …) or any chat model you are signed
|
|
221
|
+
* in to (OpenAI, Anthropic, Google, …). Empty calls Jev on `provider`.
|
|
222
|
+
*/
|
|
223
|
+
engine: string;
|
|
212
224
|
/** Where Jev is called; the key comes from pi's own key store for that host. */
|
|
213
225
|
provider: JevHostName;
|
|
214
226
|
/** A pinned Jev model; empty uses the host's default. */
|
|
@@ -250,7 +262,7 @@ export const DEFAULT_CONFIG: BotLobbyConfig = {
|
|
|
250
262
|
backend: { model: INHERIT_MODEL, thinking: DEFAULT_THINKING, instructions: "", timeoutMs: 15 * 60 * 1000 },
|
|
251
263
|
qa: { model: INHERIT_MODEL, thinking: DEFAULT_THINKING, instructions: "", timeoutMs: 15 * 60 * 1000 },
|
|
252
264
|
},
|
|
253
|
-
scout: { model: INHERIT_MODEL, timeoutMs: 8 * 60 * 1000 },
|
|
265
|
+
scout: { model: INHERIT_MODEL, timeoutMs: 8 * 60 * 1000, maxToolCalls: 30 },
|
|
254
266
|
researcher: { model: INHERIT_MODEL, thinking: "low", instructions: "", timeoutMs: 10 * 60 * 1000 },
|
|
255
267
|
quickFix: { model: INHERIT_MODEL, thinking: "low", instructions: "", timeoutMs: 10 * 60 * 1000 },
|
|
256
268
|
planner: { model: INHERIT_MODEL, thinking: "high", instructions: "", timeoutMs: 5 * 60 * 1000 },
|
|
@@ -272,6 +284,8 @@ export const DEFAULT_CONFIG: BotLobbyConfig = {
|
|
|
272
284
|
briefCheck: true,
|
|
273
285
|
routeQuickFixes: true,
|
|
274
286
|
gitIsolation: "off",
|
|
287
|
+
repoMap: true,
|
|
288
|
+
reuseScouts: true,
|
|
275
289
|
},
|
|
276
290
|
lint: { mode: "advise", command: "", extensions: [], timeoutMs: 2 * 60 * 1000 },
|
|
277
291
|
knowledge: {
|
|
@@ -292,6 +306,7 @@ export const DEFAULT_CONFIG: BotLobbyConfig = {
|
|
|
292
306
|
},
|
|
293
307
|
classifier: {
|
|
294
308
|
enabled: false,
|
|
309
|
+
engine: "",
|
|
295
310
|
provider: "auto",
|
|
296
311
|
model: "",
|
|
297
312
|
baseUrl: "",
|
|
@@ -346,6 +361,7 @@ function normalizeScout(override: Partial<ScoutConfig> | undefined): ScoutConfig
|
|
|
346
361
|
return {
|
|
347
362
|
model: typeof override?.model === "string" && override.model.trim() ? override.model : base.model,
|
|
348
363
|
timeoutMs: positive(override?.timeoutMs) ?? base.timeoutMs,
|
|
364
|
+
maxToolCalls: typeof override?.maxToolCalls === "number" && Number.isFinite(override.maxToolCalls) && override.maxToolCalls >= 0 ? Math.round(override.maxToolCalls) : base.maxToolCalls,
|
|
349
365
|
...(mcpTools.length ? { mcpTools } : {}),
|
|
350
366
|
};
|
|
351
367
|
}
|
|
@@ -432,6 +448,7 @@ function normalizeClassifier(value: unknown): ClassifierConfig {
|
|
|
432
448
|
const provider = typeof source.provider === "string" && (JEV_HOSTS as readonly string[]).includes(source.provider) ? source.provider as JevHostName : defaults.provider;
|
|
433
449
|
return {
|
|
434
450
|
enabled: flag(source.enabled, defaults.enabled),
|
|
451
|
+
engine: text(source.engine, defaults.engine).trim(),
|
|
435
452
|
provider,
|
|
436
453
|
model: text(source.model, defaults.model),
|
|
437
454
|
baseUrl: text(source.baseUrl, defaults.baseUrl),
|
|
@@ -456,6 +473,8 @@ export function resolveConfig(partial: unknown): BotLobbyConfig {
|
|
|
456
473
|
workflow.fastTrack = workflow.fastTrack !== false;
|
|
457
474
|
workflow.briefCheck = workflow.briefCheck !== false;
|
|
458
475
|
workflow.routeQuickFixes = workflow.routeQuickFixes !== false;
|
|
476
|
+
workflow.repoMap = workflow.repoMap !== false;
|
|
477
|
+
workflow.reuseScouts = workflow.reuseScouts !== false;
|
|
459
478
|
workflow.gitIsolation = isGitIsolation(workflow.gitIsolation) ? workflow.gitIsolation : DEFAULT_CONFIG.workflow.gitIsolation;
|
|
460
479
|
workflow.taskBudgetMinutes = typeof workflow.taskBudgetMinutes === "number" && workflow.taskBudgetMinutes > 0 ? Math.min(24 * 60, Math.round(workflow.taskBudgetMinutes)) : 0;
|
|
461
480
|
const knowledge = { ...DEFAULT_CONFIG.knowledge, ...(src.knowledge as Partial<KnowledgeConfig> | undefined) };
|
package/src/schemas/findings.ts
CHANGED
package/src/state/work-time.ts
CHANGED
|
@@ -16,6 +16,8 @@ import { taskDirFor } from "./persistence.ts";
|
|
|
16
16
|
export interface ActiveRun {
|
|
17
17
|
runId: string;
|
|
18
18
|
parentRunId?: string;
|
|
19
|
+
/** The worker's domain (`backend`, `designer`), for who is doing what. */
|
|
20
|
+
domain?: string;
|
|
19
21
|
stepInstruction?: string;
|
|
20
22
|
instruction: string;
|
|
21
23
|
startedAt: string;
|
|
@@ -104,7 +106,7 @@ export function taskWork(root: string, configDir: string, taskId: string, now =
|
|
|
104
106
|
const noted = new Map<string, Set<string>>();
|
|
105
107
|
|
|
106
108
|
/** A worker run of the task started or ended: the steps it works on read as active while it runs. */
|
|
107
|
-
export function noteWorkerRun(root: string, configDir: string, taskId: string, run: { runId: string; parentRunId?: string; stepInstruction?: string; instruction?: string; startedAt: string; status: string }): void {
|
|
109
|
+
export function noteWorkerRun(root: string, configDir: string, taskId: string, run: { runId: string; parentRunId?: string; stepInstruction?: string; instruction?: string; domain?: string; startedAt: string; status: string }): void {
|
|
108
110
|
const running = run.status === "running";
|
|
109
111
|
const ids = noted.get(taskId) ?? new Set<string>();
|
|
110
112
|
if (running === ids.has(run.runId)) return;
|
|
@@ -113,7 +115,7 @@ export function noteWorkerRun(root: string, configDir: string, taskId: string, r
|
|
|
113
115
|
noted.set(taskId, ids);
|
|
114
116
|
updateWork(root, configDir, taskId, (work) => {
|
|
115
117
|
work.active = work.active.filter((entry) => entry.runId !== run.runId);
|
|
116
|
-
if (running) work.active.push({ runId: run.runId, instruction: run.instruction ?? "", startedAt: run.startedAt, ...(run.parentRunId ? { parentRunId: run.parentRunId } : {}), ...(run.stepInstruction ? { stepInstruction: run.stepInstruction } : {}) });
|
|
118
|
+
if (running) work.active.push({ runId: run.runId, instruction: run.instruction ?? "", startedAt: run.startedAt, ...(run.parentRunId ? { parentRunId: run.parentRunId } : {}), ...(run.domain ? { domain: run.domain } : {}), ...(run.stepInstruction ? { stepInstruction: run.stepInstruction } : {}) });
|
|
117
119
|
});
|
|
118
120
|
}
|
|
119
121
|
|
|
@@ -30,7 +30,7 @@ function activeRuns(task: Task, work: WorkProjection | undefined): AgentRun[] {
|
|
|
30
30
|
const finished = new Set((task.workerRuns ?? []).map((record) => record.runId));
|
|
31
31
|
return (work?.active ?? [])
|
|
32
32
|
.filter((run) => !finished.has(run.runId))
|
|
33
|
-
.map((run) => ({ runId: run.runId, parentRunId: run.parentRunId, stepInstruction: run.stepInstruction, taskId: task.id, domain: "backend" as
|
|
33
|
+
.map((run) => ({ runId: run.runId, parentRunId: run.parentRunId, stepInstruction: run.stepInstruction, taskId: task.id, domain: (run.domain === "designer" || run.domain === "qa" ? run.domain : "backend") as AgentRun["domain"], role: "worker" as const, status: "running" as const, instruction: run.instruction, output: "", attempts: 1, startedAt: run.startedAt }));
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/** One step as the lobby shows it: its state, and while a worker is on it, how long they have worked on it. */
|
|
@@ -40,10 +40,20 @@ export interface StepView {
|
|
|
40
40
|
active: boolean;
|
|
41
41
|
activeAgentCount: number;
|
|
42
42
|
runIds: string[];
|
|
43
|
+
/** The agents on it right now: who, doing what, since when; a child names its parent. */
|
|
44
|
+
agents: StepAgent[];
|
|
43
45
|
/** Worker time on the step, in ms. */
|
|
44
46
|
workedMs: number;
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
export interface StepAgent {
|
|
50
|
+
runId: string;
|
|
51
|
+
parentRunId?: string;
|
|
52
|
+
domain: string;
|
|
53
|
+
instruction: string;
|
|
54
|
+
startedAt: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
47
57
|
/**
|
|
48
58
|
* The plan's steps with their state, which ones a worker is on right now and
|
|
49
59
|
* the worker time on each. A step being worked on is never done, so steps run
|
|
@@ -54,8 +64,13 @@ export function stepViews(task: Task, work: WorkProjection | undefined, now = Da
|
|
|
54
64
|
const runs = [...persistedTaskRuns(task), ...activeRuns(task, work)];
|
|
55
65
|
const checklist = planChecklist(task.plan, runs);
|
|
56
66
|
const times = stepWork(planSteps(task.plan), runs, now);
|
|
67
|
+
const byId = new Map(runs.map((run) => [run.runId, run]));
|
|
57
68
|
return checklist.map((step, index) => {
|
|
58
69
|
const time = times[index] ?? { ms: 0, active: false, activeAgentCount: 0, runIds: [] };
|
|
59
|
-
|
|
70
|
+
const agents = time.runIds.flatMap((runId) => {
|
|
71
|
+
const run = byId.get(runId);
|
|
72
|
+
return run ? [{ runId, ...(run.parentRunId ? { parentRunId: run.parentRunId } : {}), domain: run.domain, instruction: (run.instruction ?? "").split("\n")[0]!.trim(), startedAt: run.startedAt }] : [];
|
|
73
|
+
});
|
|
74
|
+
return { text: step.text, status: time.active ? "current" : step.status, active: time.active, activeAgentCount: time.activeAgentCount, runIds: time.runIds, agents, workedMs: Math.round(time.ms) };
|
|
60
75
|
});
|
|
61
76
|
}
|
|
@@ -90,7 +90,8 @@ export function settingsGet(ctx: ApiContext): SettingsInfo {
|
|
|
90
90
|
config = prefilled.config;
|
|
91
91
|
pushNotice(`${prefilled.filled.map(kindLabel).join(", ")} now run on the session model; change them here any time.`);
|
|
92
92
|
}
|
|
93
|
-
|
|
93
|
+
const classifiers = ctx.service.classifierModels?.() ?? [];
|
|
94
|
+
return { config: stripSecrets(config), models: ctx.service.models?.() ?? [], ...(classifiers.length > 0 ? { classifierModels: classifiers } : {}) };
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
/** The linters the project configures (its top and a few folders below), and whether each is installed. */
|
package/src/webui/api/tasks.ts
CHANGED
|
@@ -311,7 +311,7 @@ export function tasksGet(body: { taskId: string }, ctx: ApiContext): TaskDetail
|
|
|
311
311
|
steps: stepViews(found, work, now).map((step) => ({
|
|
312
312
|
text: step.text,
|
|
313
313
|
status: step.status === "pending" ? "open" : step.status,
|
|
314
|
-
...(step.active ? { active: true, workedMs: step.workedMs, activeAgentCount: step.activeAgentCount } : {}),
|
|
314
|
+
...(step.active ? { active: true, workedMs: step.workedMs, activeAgentCount: step.activeAgentCount, agents: step.agents } : {}),
|
|
315
315
|
})),
|
|
316
316
|
amendments: [...found.amendments],
|
|
317
317
|
waiting: waitingOf(found),
|
|
@@ -455,6 +455,7 @@ export function createFixtureService(name: string, feed = new LobbyFeed()): Lobb
|
|
|
455
455
|
},
|
|
456
456
|
configChanged: () => {},
|
|
457
457
|
models: () => models.map((model) => ({ id: model.id, label: model.label, thinkingLevels: [...model.thinkingLevels] })),
|
|
458
|
+
classifierModels: () => [{ id: "opencode/jev-1.13-free", label: "Jev 1.13 (free)" }, { id: "openrouter/liquid/d1", label: "Liquid D1" }],
|
|
458
459
|
issuesEnabled: () => fixture.status.issuesEnabled,
|
|
459
460
|
workspace: () => ({ ...fixture.status.workspace }),
|
|
460
461
|
zen: () => ({ ...(fixture.zen.task ? { task: { ...fixture.zen.task } } : {}), runs: [...fixture.zen.runs] }),
|
package/src/webui/protocol.ts
CHANGED
|
@@ -200,6 +200,14 @@ export interface SettingsLinter {
|
|
|
200
200
|
export interface SettingsInfo {
|
|
201
201
|
config: BotLobbyConfig;
|
|
202
202
|
models: SettingsModelInfo[];
|
|
203
|
+
/** Classifier models pi can call with the logins it holds (Jev on each host, Cloudflare's clef, Liquid's d1, …). */
|
|
204
|
+
classifierModels?: SettingsClassifierModel[];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** A classifier model for the classifier's picker: `provider/id` and its name. */
|
|
208
|
+
export interface SettingsClassifierModel {
|
|
209
|
+
id: string;
|
|
210
|
+
label: string;
|
|
203
211
|
}
|
|
204
212
|
|
|
205
213
|
/** One message on the event stream (`GET /api/events`, server-sent events). */
|
|
@@ -420,7 +428,7 @@ export interface TaskDetail {
|
|
|
420
428
|
* The plan as a checklist, in order; `current` is the step under way, `active` the steps a worker is on right now,
|
|
421
429
|
* each with its worker time (ms, as of the reply).
|
|
422
430
|
*/
|
|
423
|
-
steps: Array<{ text: string; status: "done" | "current" | "open"; active?: boolean; workedMs?: number; activeAgentCount?: number }>;
|
|
431
|
+
steps: Array<{ text: string; status: "done" | "current" | "open"; active?: boolean; workedMs?: number; activeAgentCount?: number; agents?: StepAgentInfo[] }>;
|
|
424
432
|
/** How long the task's agents have worked on it, idle time left out (ms, as of the reply); `running` while they work. */
|
|
425
433
|
work?: WorkClock;
|
|
426
434
|
amendments: string[];
|
|
@@ -676,3 +684,12 @@ export interface Api {
|
|
|
676
684
|
}
|
|
677
685
|
|
|
678
686
|
export type ApiName = keyof Api;
|
|
687
|
+
|
|
688
|
+
/** An agent on a step right now: its domain, what it was told (first line), since when; a child names its parent. */
|
|
689
|
+
export interface StepAgentInfo {
|
|
690
|
+
runId: string;
|
|
691
|
+
parentRunId?: string;
|
|
692
|
+
domain: string;
|
|
693
|
+
instruction: string;
|
|
694
|
+
startedAt: string;
|
|
695
|
+
}
|
package/src/workflow/workflow.ts
CHANGED
|
@@ -32,7 +32,7 @@ import { mapConcurrent } from "../execution/agent-runner.ts";
|
|
|
32
32
|
import { parseWorkerResult } from "../roles/worker.ts";
|
|
33
33
|
import { autoNote, DESK_TOOLS, DeskSession } from "../desk/session.ts";
|
|
34
34
|
import type { Handover } from "../desk/desk.ts";
|
|
35
|
-
import { changedFiles, commitBefore, headCommit, readRepositoryDiff } from "../execution/git.ts";
|
|
35
|
+
import { changedFiles, commitBefore, commitFiles, commitMessage, headCommit, readRepositoryDiff, uncommitted } from "../execution/git.ts";
|
|
36
36
|
import { appendChange, explainChanges, provenanceLines, provenanceSummary, readChanges, type FileProvenance } from "../state/changes.ts";
|
|
37
37
|
import { budgetLine, budgetState, formatMinutes, MIN_READ_MS, parseMinutes, qaAllotment, readBudget, readerAllotment, updateBudget, workerAllotment, type Allotment, type BudgetState, type TaskBudget } from "../state/budget.ts";
|
|
38
38
|
import type { AgentTime } from "../execution/agent-runner.ts";
|
|
@@ -141,6 +141,8 @@ export interface OrchestrateParams {
|
|
|
141
141
|
note?: string;
|
|
142
142
|
reason?: string;
|
|
143
143
|
text?: string;
|
|
144
|
+
/** complete: the commit message for the agents' changes. */
|
|
145
|
+
message?: string;
|
|
144
146
|
/** track: the path the task takes. */
|
|
145
147
|
track?: "fast" | "full";
|
|
146
148
|
/** track: who takes part (designer, backend, qa, researcher). */
|
|
@@ -1570,6 +1572,7 @@ async function handleComplete(task: Task, params: OrchestrateParams, deps: Workf
|
|
|
1570
1572
|
// The gate refuses before any side effect, so a refusal leaves the task untouched.
|
|
1571
1573
|
const oversized = overThreshold(readDataRoots(deps.root, deps.configDir), deps.config.knowledge.compactionThreshold);
|
|
1572
1574
|
if (oversized.length > 0) throw new Error(`cannot complete: knowledge files over the compaction threshold: ${oversized.map((entry) => `${entry.agent}/${entry.file} (${entry.chars})`).join(", ")}. First record domain-relevant facts where they belong with action=knowledge (domain=designer|backend|qa), then rewrite each oversized file with action=compact, then call complete again.`);
|
|
1575
|
+
const committed = await commitTaskWork(task, params, deps);
|
|
1573
1576
|
const summary = params.text?.trim() || task.proposal || task.title;
|
|
1574
1577
|
recordCompletion(deps, task, summary);
|
|
1575
1578
|
removeTaskScratchpads(deps.root, deps.configDir, task.id);
|
|
@@ -1581,7 +1584,43 @@ async function handleComplete(task: Task, params: OrchestrateParams, deps: Workf
|
|
|
1581
1584
|
flushDecisions(deps, task);
|
|
1582
1585
|
const delivery = await offerDelivery(task, deps);
|
|
1583
1586
|
const left = lint?.state === "failing" ? ` ${task.lintWaiver ? "Accepted by the user with lint errors" : "Lint (advisory)"}: ${lintSummary(lint)} Tell the user.` : "";
|
|
1584
|
-
return `Task ${task.id} completed
|
|
1587
|
+
return `Task ${task.id} completed.${committed} History recorded and temporary scratchpads removed. ${delivery}${left}`;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
/** The files a task's own workers changed: not one that held changes before they started, nor another task's. */
|
|
1591
|
+
function agentsWork(files: readonly FileProvenance[]): string[] {
|
|
1592
|
+
return files.filter((file) => file.kinds.includes("planned") && !file.kinds.includes("pre-existing") && !file.kinds.includes("other-task")).map((file) => file.path);
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
/**
|
|
1596
|
+
* The oracle commits what its agents changed before the task is called done,
|
|
1597
|
+
* with its own short message (`message`); nothing else in the tree is
|
|
1598
|
+
* staged or committed. "" when git is not in use or nothing is left to commit.
|
|
1599
|
+
*/
|
|
1600
|
+
async function commitTaskWork(task: Task, params: OrchestrateParams, deps: WorkflowDeps): Promise<string> {
|
|
1601
|
+
const state = await changeState(deps, task, await reviewBase(task, deps));
|
|
1602
|
+
if (!state) return "";
|
|
1603
|
+
const ours = agentsWork(state.files);
|
|
1604
|
+
const dirty = await uncommitted(state.top, ours).catch(() => []);
|
|
1605
|
+
if (dirty.length === 0) return "";
|
|
1606
|
+
const listed = dirty.slice(0, 20).join(", ") + (dirty.length > 20 ? ` and ${dirty.length - 20} more` : "");
|
|
1607
|
+
if (!params.message?.trim()) throw new Error(`cannot complete: commit the agents' changes first. Call complete again with message: a concise commit message for them (a first line of 72 characters or fewer saying what changed, at most three short lines below it). The engine commits only these files: ${listed}`);
|
|
1608
|
+
let message: string;
|
|
1609
|
+
try {
|
|
1610
|
+
message = commitMessage(params.message);
|
|
1611
|
+
} catch (error) {
|
|
1612
|
+
throw new Error(`cannot complete: ${(error as Error).message}`);
|
|
1613
|
+
}
|
|
1614
|
+
let result: Awaited<ReturnType<typeof commitFiles>>;
|
|
1615
|
+
try {
|
|
1616
|
+
result = await commitFiles(state.top, dirty, message);
|
|
1617
|
+
} catch (error) {
|
|
1618
|
+
throw new Error(`cannot complete: git refused the commit: ${(error as Error).message}`);
|
|
1619
|
+
}
|
|
1620
|
+
if (!result) return "";
|
|
1621
|
+
const others = await uncommitted(state.top, state.files.map((file) => file.path).filter((path) => !ours.includes(path))).catch(() => []);
|
|
1622
|
+
const left = others.length > 0 ? ` Left uncommitted, not this task's agents' work: ${others.slice(0, 10).join(", ")}${others.length > 10 ? ` and ${others.length - 10} more` : ""}.` : "";
|
|
1623
|
+
return ` Committed ${result.files.length} file${result.files.length === 1 ? "" : "s"} as ${result.commit.slice(0, 7)}: "${message.split("\n")[0]}".${left}`;
|
|
1585
1624
|
}
|
|
1586
1625
|
|
|
1587
1626
|
/** Completion always offers delivery to the user, including in auto mode. */
|