@gajae-code/coding-agent 0.4.2 → 0.4.4
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 +13 -0
- package/dist/types/async/job-manager.d.ts +44 -1
- package/dist/types/cli/setup-cli.d.ts +14 -1
- package/dist/types/commands/coordinator.d.ts +19 -0
- package/dist/types/commands/mcp-serve.d.ts +24 -0
- package/dist/types/commands/setup.d.ts +41 -0
- package/dist/types/commit/model-selection.d.ts +1 -1
- package/dist/types/config/model-registry.d.ts +3 -1
- package/dist/types/config/model-resolver.d.ts +1 -19
- package/dist/types/config/models-config-schema.d.ts +12 -0
- package/dist/types/config/settings-schema.d.ts +15 -1
- package/dist/types/coordinator/contract.d.ts +4 -0
- package/dist/types/coordinator-mcp/policy.d.ts +24 -0
- package/dist/types/coordinator-mcp/safety.d.ts +26 -0
- package/dist/types/coordinator-mcp/server.d.ts +52 -0
- package/dist/types/extensibility/extensions/types.d.ts +13 -0
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +8 -1
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +13 -0
- package/dist/types/harness-control-plane/types.d.ts +7 -2
- package/dist/types/modes/acp/acp-event-mapper.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +7 -0
- package/dist/types/modes/components/hook-selector.d.ts +11 -0
- package/dist/types/modes/shared/agent-wire/command-contract.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +84 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +14 -7
- package/dist/types/modes/shared/agent-wire/event-observation.d.ts +37 -0
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +13 -34
- package/dist/types/session/agent-session.d.ts +12 -1
- package/dist/types/session/session-manager.d.ts +1 -1
- package/dist/types/setup/hermes-setup.d.ts +71 -0
- package/dist/types/task/render.d.ts +7 -1
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/browser/actions.d.ts +54 -0
- package/dist/types/tools/browser.d.ts +80 -0
- package/dist/types/tools/image-gen.d.ts +1 -0
- package/dist/types/tools/index.d.ts +3 -1
- package/dist/types/tools/job.d.ts +1 -1
- package/dist/types/tools/subagent-render.d.ts +25 -0
- package/dist/types/tools/subagent.d.ts +5 -1
- package/package.json +7 -7
- package/src/async/job-manager.ts +163 -2
- package/src/cli/setup-cli.ts +86 -2
- package/src/cli.ts +2 -0
- package/src/commands/coordinator.ts +70 -0
- package/src/commands/mcp-serve.ts +62 -0
- package/src/commands/setup.ts +30 -1
- package/src/commands/ultragoal.ts +7 -1
- package/src/commit/agentic/index.ts +2 -2
- package/src/commit/model-selection.ts +7 -22
- package/src/commit/pipeline.ts +2 -2
- package/src/config/model-registry.ts +17 -9
- package/src/config/model-resolver.ts +14 -84
- package/src/config/models-config-schema.ts +2 -0
- package/src/config/settings-schema.ts +14 -1
- package/src/coordinator/contract.ts +20 -0
- package/src/coordinator-mcp/policy.ts +160 -0
- package/src/coordinator-mcp/safety.ts +80 -0
- package/src/coordinator-mcp/server.ts +1316 -0
- package/src/extensibility/extensions/types.ts +13 -0
- package/src/gjc-runtime/goal-mode-request.ts +21 -1
- package/src/gjc-runtime/session-state-sidecar.ts +79 -0
- package/src/harness-control-plane/owner.ts +3 -3
- package/src/harness-control-plane/rpc-adapter.ts +7 -1
- package/src/harness-control-plane/types.ts +8 -11
- package/src/internal-urls/docs-index.generated.ts +6 -5
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-agent.ts +17 -9
- package/src/modes/acp/acp-event-mapper.ts +33 -1
- package/src/modes/components/custom-editor.ts +19 -3
- package/src/modes/components/hook-selector.ts +109 -5
- package/src/modes/controllers/extension-ui-controller.ts +16 -1
- package/src/modes/controllers/input-controller.ts +27 -7
- package/src/modes/controllers/selector-controller.ts +7 -1
- package/src/modes/interactive-mode.ts +3 -1
- package/src/modes/rpc/rpc-client.ts +16 -3
- package/src/modes/rpc/rpc-mode.ts +5 -2
- package/src/modes/shared/agent-wire/command-contract.ts +18 -0
- package/src/modes/shared/agent-wire/event-contract.ts +147 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +35 -16
- package/src/modes/shared/agent-wire/event-observation.ts +397 -0
- package/src/modes/shared/agent-wire/protocol.ts +24 -81
- package/src/modes/utils/context-usage.ts +2 -2
- package/src/prompts/agents/architect.md +6 -0
- package/src/prompts/agents/critic.md +6 -0
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/plan.md +1 -1
- package/src/prompts/agents/planner.md +8 -1
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/tools/browser.md +3 -2
- package/src/runtime-mcp/manager.ts +15 -2
- package/src/sdk.ts +3 -1
- package/src/session/agent-session.ts +66 -4
- package/src/session/session-manager.ts +1 -1
- package/src/setup/hermes/templates/operator-instructions.v1.md +29 -0
- package/src/setup/hermes-setup.ts +429 -0
- package/src/task/agents.ts +1 -1
- package/src/task/index.ts +2 -0
- package/src/task/render.ts +14 -0
- package/src/tools/ask.ts +30 -10
- package/src/tools/bash.ts +6 -1
- package/src/tools/browser/actions.ts +189 -0
- package/src/tools/browser.ts +91 -1
- package/src/tools/image-gen.ts +42 -15
- package/src/tools/index.ts +7 -1
- package/src/tools/inspect-image.ts +10 -8
- package/src/tools/job.ts +12 -2
- package/src/tools/monitor.ts +98 -17
- package/src/tools/renderers.ts +2 -0
- package/src/tools/subagent-render.ts +160 -0
- package/src/tools/subagent.ts +49 -7
- package/src/utils/commit-message-generator.ts +6 -13
- package/src/utils/title-generator.ts +1 -1
- package/dist/types/harness-control-plane/frame-mapper.d.ts +0 -29
- package/src/harness-control-plane/frame-mapper.ts +0 -286
- package/src/priority.json +0 -37
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI renderer for the `subagent` tool.
|
|
3
|
+
*
|
|
4
|
+
* The await panel surfaces each awaited subagent's live streaming status at
|
|
5
|
+
* parity with the inline `task` panel by reusing `renderSubagentLiveProgress`.
|
|
6
|
+
* Falls back to a `running, no activity yet` placeholder when a live producer
|
|
7
|
+
* exists but has not emitted yet, and to a static status line when no live
|
|
8
|
+
* producer is available (resumed-from-disk or backward-compat records).
|
|
9
|
+
*/
|
|
10
|
+
import type { Component } from "@gajae-code/tui";
|
|
11
|
+
import { Text } from "@gajae-code/tui";
|
|
12
|
+
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
13
|
+
import type { Theme } from "../modes/theme/theme";
|
|
14
|
+
import { renderSubagentLiveProgress } from "../task/render";
|
|
15
|
+
import { Ellipsis, Hasher, type RenderCache, renderStatusLine } from "../tui";
|
|
16
|
+
import {
|
|
17
|
+
formatDuration,
|
|
18
|
+
formatStatusIcon,
|
|
19
|
+
getPreviewLines,
|
|
20
|
+
replaceTabs,
|
|
21
|
+
type ToolUIStatus,
|
|
22
|
+
truncateToWidth,
|
|
23
|
+
} from "./render-utils";
|
|
24
|
+
import type { SubagentSnapshot, SubagentToolDetails } from "./subagent";
|
|
25
|
+
|
|
26
|
+
const PREVIEW_LINES_COLLAPSED = 1;
|
|
27
|
+
const PREVIEW_LINES_EXPANDED = 4;
|
|
28
|
+
const PREVIEW_LINE_WIDTH = 80;
|
|
29
|
+
|
|
30
|
+
function statusIconKind(status: SubagentSnapshot["status"]): ToolUIStatus {
|
|
31
|
+
switch (status) {
|
|
32
|
+
case "completed":
|
|
33
|
+
case "already_completed":
|
|
34
|
+
return "success";
|
|
35
|
+
case "failed":
|
|
36
|
+
return "error";
|
|
37
|
+
case "cancelled":
|
|
38
|
+
case "not_found":
|
|
39
|
+
return "warning";
|
|
40
|
+
case "queued":
|
|
41
|
+
return "pending";
|
|
42
|
+
default:
|
|
43
|
+
return "info";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function renderSubagentSnapshot(
|
|
48
|
+
snapshot: SubagentSnapshot,
|
|
49
|
+
expanded: boolean,
|
|
50
|
+
theme: Theme,
|
|
51
|
+
spinnerFrame: number | undefined,
|
|
52
|
+
): string[] {
|
|
53
|
+
const lines: string[] = [];
|
|
54
|
+
const icon = formatStatusIcon(
|
|
55
|
+
statusIconKind(snapshot.status),
|
|
56
|
+
theme,
|
|
57
|
+
snapshot.status === "running" ? spinnerFrame : undefined,
|
|
58
|
+
);
|
|
59
|
+
const id = theme.fg("muted", snapshot.id);
|
|
60
|
+
const status = theme.fg("dim", snapshot.status);
|
|
61
|
+
const duration = theme.fg("dim", formatDuration(snapshot.durationMs));
|
|
62
|
+
lines.push(`${icon} ${id} ${status} ${duration}`);
|
|
63
|
+
|
|
64
|
+
// Static receipt fields (parity with the markdown content for non-await actions).
|
|
65
|
+
if (snapshot.jobId !== snapshot.id) lines.push(` ${theme.fg("dim", `Job: ${snapshot.jobId}`)}`);
|
|
66
|
+
if (snapshot.agent && snapshot.agent !== "unknown") {
|
|
67
|
+
lines.push(` ${theme.fg("dim", `Agent: ${snapshot.agent} (${snapshot.agentSource})`)}`);
|
|
68
|
+
}
|
|
69
|
+
if (snapshot.description) lines.push(` ${theme.fg("dim", `Description: ${snapshot.description}`)}`);
|
|
70
|
+
if (snapshot.outputRef) lines.push(` ${theme.fg("dim", `Output: ${snapshot.outputRef}`)}`);
|
|
71
|
+
if (snapshot.assignment) {
|
|
72
|
+
lines.push(` ${theme.fg("dim", "Assignment:")}`);
|
|
73
|
+
for (const al of snapshot.assignment.split("\n")) lines.push(` ${theme.fg("toolOutput", replaceTabs(al))}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (snapshot.progress) {
|
|
77
|
+
// Live streaming panel (full task-panel parity), indented under the header.
|
|
78
|
+
for (const pl of renderSubagentLiveProgress(snapshot.progress, expanded, theme, spinnerFrame)) {
|
|
79
|
+
lines.push(` ${pl}`);
|
|
80
|
+
}
|
|
81
|
+
} else if (snapshot.liveProgressAvailable && (snapshot.status === "running" || snapshot.status === "queued")) {
|
|
82
|
+
lines.push(` ${theme.fg("dim", "running, no activity yet")}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const preview = snapshot.errorText?.trim() || snapshot.resultText?.trim();
|
|
86
|
+
if (preview) {
|
|
87
|
+
const maxLines = expanded ? PREVIEW_LINES_EXPANDED : PREVIEW_LINES_COLLAPSED;
|
|
88
|
+
const tone = snapshot.errorText ? "error" : "dim";
|
|
89
|
+
for (const pl of getPreviewLines(preview, maxLines, PREVIEW_LINE_WIDTH, Ellipsis.Unicode)) {
|
|
90
|
+
lines.push(` ${theme.fg(tone, replaceTabs(pl))}`);
|
|
91
|
+
}
|
|
92
|
+
if (snapshot.truncated) {
|
|
93
|
+
lines.push(
|
|
94
|
+
` ${theme.fg("dim", "Preview truncated; use the output ref or explicit ids with `verbosity=full` for more.")}`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (snapshot.guidance) lines.push(` ${theme.fg("dim", snapshot.guidance)}`);
|
|
100
|
+
return lines;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const subagentToolRenderer = {
|
|
104
|
+
inline: true,
|
|
105
|
+
|
|
106
|
+
renderCall(_args: unknown, _options: RenderResultOptions, theme: Theme): Component {
|
|
107
|
+
return new Text(renderStatusLine({ icon: "pending", title: "Subagent" }, theme), 0, 0);
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
renderResult(
|
|
111
|
+
result: { content: Array<{ type: string; text?: string }>; details?: SubagentToolDetails },
|
|
112
|
+
options: RenderResultOptions,
|
|
113
|
+
theme: Theme,
|
|
114
|
+
): Component {
|
|
115
|
+
const subagents = result.details?.subagents ?? [];
|
|
116
|
+
if (subagents.length === 0) {
|
|
117
|
+
const fallback = result.content.find(c => c.type === "text")?.text || "No subagents";
|
|
118
|
+
return new Text(theme.fg("dim", truncateToWidth(fallback, 100)), 0, 0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const runningCount = subagents.filter(s => s.status === "running").length;
|
|
122
|
+
|
|
123
|
+
let cached: RenderCache | undefined;
|
|
124
|
+
return {
|
|
125
|
+
render(width: number): string[] {
|
|
126
|
+
const expanded = options.expanded;
|
|
127
|
+
const spinnerFrame = options.spinnerFrame ?? 0;
|
|
128
|
+
const key = new Hasher().bool(expanded).u32(width).u32(spinnerFrame).digest();
|
|
129
|
+
if (cached?.key === key) return cached.lines;
|
|
130
|
+
|
|
131
|
+
const header = renderStatusLine(
|
|
132
|
+
{
|
|
133
|
+
icon: runningCount > 0 ? "info" : "success",
|
|
134
|
+
spinnerFrame: runningCount > 0 ? options.spinnerFrame : undefined,
|
|
135
|
+
title: "Subagent",
|
|
136
|
+
description:
|
|
137
|
+
runningCount > 0
|
|
138
|
+
? `awaiting ${runningCount} of ${subagents.length}`
|
|
139
|
+
: `${subagents.length} ${subagents.length === 1 ? "subagent" : "subagents"}`,
|
|
140
|
+
},
|
|
141
|
+
theme,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const lines: string[] = [header];
|
|
145
|
+
for (const snapshot of subagents) {
|
|
146
|
+
lines.push(...renderSubagentSnapshot(snapshot, expanded, theme, options.spinnerFrame));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const out = lines.map(l => (l.length > 0 ? truncateToWidth(l, width, Ellipsis.Omit) : ""));
|
|
150
|
+
cached = { key, lines: out };
|
|
151
|
+
return out;
|
|
152
|
+
},
|
|
153
|
+
invalidate() {
|
|
154
|
+
cached = undefined;
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
mergeCallAndResult: true,
|
|
160
|
+
};
|
package/src/tools/subagent.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { prompt } from "@gajae-code/utils";
|
|
|
4
4
|
import * as z from "zod/v4";
|
|
5
5
|
import { type AsyncJob, AsyncJobManager, type SubagentRecord } from "../async";
|
|
6
6
|
import subagentDescription from "../prompts/tools/subagent.md" with { type: "text" };
|
|
7
|
-
import type { AgentSource } from "../task/types";
|
|
7
|
+
import type { AgentProgress, AgentSource } from "../task/types";
|
|
8
8
|
import { Ellipsis, truncateToWidth } from "../tui";
|
|
9
9
|
import type { ToolSession } from "./index";
|
|
10
10
|
import { replaceTabs } from "./render-utils";
|
|
@@ -63,6 +63,10 @@ export interface SubagentSnapshot {
|
|
|
63
63
|
outputRef?: string;
|
|
64
64
|
truncated?: boolean;
|
|
65
65
|
guidance?: string;
|
|
66
|
+
/** Live streaming progress for the awaited subagent (await panel only; UI detail). */
|
|
67
|
+
progress?: AgentProgress;
|
|
68
|
+
/** True when a live in-session progress producer exists for this subagent. */
|
|
69
|
+
liveProgressAvailable?: boolean;
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
export interface SubagentToolDetails {
|
|
@@ -322,10 +326,10 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
322
326
|
manager.watchJobs(watchedJobIds);
|
|
323
327
|
const progressTimer = onUpdate
|
|
324
328
|
? setInterval(() => {
|
|
325
|
-
onUpdate(this.#progressResult(manager, records));
|
|
329
|
+
onUpdate(this.#progressResult(manager, records, true));
|
|
326
330
|
}, 500)
|
|
327
331
|
: undefined;
|
|
328
|
-
onUpdate?.(this.#progressResult(manager, records));
|
|
332
|
+
onUpdate?.(this.#progressResult(manager, records, true));
|
|
329
333
|
|
|
330
334
|
let timedOut = false;
|
|
331
335
|
try {
|
|
@@ -355,6 +359,7 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
355
359
|
notFoundIds,
|
|
356
360
|
timedOut,
|
|
357
361
|
verbosity: params.verbosity ?? "receipt",
|
|
362
|
+
attachLiveProgress: true,
|
|
358
363
|
});
|
|
359
364
|
}
|
|
360
365
|
|
|
@@ -450,17 +455,29 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
450
455
|
return ids.filter(id => !this.#findVisibleRecord(manager, id, ownerFilter));
|
|
451
456
|
}
|
|
452
457
|
|
|
453
|
-
#progressResult(
|
|
458
|
+
#progressResult(
|
|
459
|
+
manager: AsyncJobManager,
|
|
460
|
+
records: SubagentRecord[],
|
|
461
|
+
attachLiveProgress = false,
|
|
462
|
+
): AgentToolResult<SubagentToolDetails> {
|
|
454
463
|
return {
|
|
455
464
|
content: [{ type: "text", text: "" }],
|
|
456
|
-
details: {
|
|
465
|
+
details: {
|
|
466
|
+
subagents: this.#recordSnapshots(manager, records, false, "receipt", new Set(), attachLiveProgress),
|
|
467
|
+
},
|
|
457
468
|
};
|
|
458
469
|
}
|
|
459
470
|
|
|
460
471
|
async #buildRecordResult(
|
|
461
472
|
manager: AsyncJobManager,
|
|
462
473
|
records: SubagentRecord[],
|
|
463
|
-
options: {
|
|
474
|
+
options: {
|
|
475
|
+
title: string;
|
|
476
|
+
notFoundIds?: string[];
|
|
477
|
+
timedOut?: boolean;
|
|
478
|
+
verbosity?: SubagentParams["verbosity"];
|
|
479
|
+
attachLiveProgress?: boolean;
|
|
480
|
+
},
|
|
464
481
|
): Promise<AgentToolResult<SubagentToolDetails>> {
|
|
465
482
|
const verifiedOutputIds = await this.#verifiedOutputIds(records);
|
|
466
483
|
const snapshots = this.#recordSnapshots(
|
|
@@ -469,6 +486,7 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
469
486
|
options.timedOut,
|
|
470
487
|
options.verbosity ?? "receipt",
|
|
471
488
|
verifiedOutputIds,
|
|
489
|
+
options.attachLiveProgress ?? false,
|
|
472
490
|
);
|
|
473
491
|
for (const id of options.notFoundIds ?? []) {
|
|
474
492
|
snapshots.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
@@ -513,8 +531,28 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
513
531
|
timedOut = false,
|
|
514
532
|
verbosity: SubagentParams["verbosity"] = "receipt",
|
|
515
533
|
verifiedOutputIds: ReadonlySet<string>,
|
|
534
|
+
attachLiveProgress = false,
|
|
516
535
|
): SubagentSnapshot[] {
|
|
517
|
-
return records.map(record =>
|
|
536
|
+
return records.map(record =>
|
|
537
|
+
this.#recordSnapshot(manager, record, timedOut, verbosity, verifiedOutputIds, attachLiveProgress),
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
#liveProgressFields(
|
|
542
|
+
manager: AsyncJobManager,
|
|
543
|
+
record: SubagentRecord,
|
|
544
|
+
attachLiveProgress: boolean,
|
|
545
|
+
): Pick<SubagentSnapshot, "progress" | "liveProgressAvailable"> {
|
|
546
|
+
if (!attachLiveProgress) return {};
|
|
547
|
+
const liveProgressAvailable = manager.hasLiveSubagent(record.subagentId);
|
|
548
|
+
// Only surface progress when a live producer exists; stale/retained progress
|
|
549
|
+
// for a record with no live producer must degrade to a static snapshot (AC5).
|
|
550
|
+
if (!liveProgressAvailable) return { liveProgressAvailable: false };
|
|
551
|
+
const progress = manager.getSubagentProgress(record.subagentId);
|
|
552
|
+
return {
|
|
553
|
+
liveProgressAvailable: true,
|
|
554
|
+
...(progress ? { progress } : {}),
|
|
555
|
+
};
|
|
518
556
|
}
|
|
519
557
|
|
|
520
558
|
#recordSnapshot(
|
|
@@ -523,7 +561,9 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
523
561
|
timedOut = false,
|
|
524
562
|
verbosity: SubagentParams["verbosity"] = "receipt",
|
|
525
563
|
verifiedOutputIds: ReadonlySet<string>,
|
|
564
|
+
attachLiveProgress = false,
|
|
526
565
|
): SubagentSnapshot {
|
|
566
|
+
const liveFields = this.#liveProgressFields(manager, record, attachLiveProgress);
|
|
527
567
|
const job = record.currentJobId ? manager.getJob(record.currentJobId) : undefined;
|
|
528
568
|
if (job) {
|
|
529
569
|
return {
|
|
@@ -531,6 +571,7 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
531
571
|
id: record.subagentId,
|
|
532
572
|
jobId: record.currentJobId ?? job.id,
|
|
533
573
|
status: record.status,
|
|
574
|
+
...liveFields,
|
|
534
575
|
};
|
|
535
576
|
}
|
|
536
577
|
return {
|
|
@@ -542,6 +583,7 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
542
583
|
agentSource: "bundled",
|
|
543
584
|
durationMs: 0,
|
|
544
585
|
...(verifiedOutputIds.has(record.subagentId) ? { outputRef: `agent://${record.subagentId}` } : {}),
|
|
586
|
+
...liveFields,
|
|
545
587
|
};
|
|
546
588
|
}
|
|
547
589
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generate commit messages from diffs using
|
|
2
|
+
* Generate commit messages from diffs using the default model.
|
|
3
3
|
* Follows the same pattern as title-generator.ts.
|
|
4
4
|
*/
|
|
5
5
|
import type { ThinkingLevel } from "@gajae-code/agent-core";
|
|
@@ -9,7 +9,6 @@ import { logger, prompt } from "@gajae-code/utils";
|
|
|
9
9
|
import type { ModelRegistry } from "../config/model-registry";
|
|
10
10
|
import { resolveModelRoleValue } from "../config/model-resolver";
|
|
11
11
|
import type { Settings } from "../config/settings";
|
|
12
|
-
import MODEL_PRIO from "../priority.json" with { type: "json" };
|
|
13
12
|
import commitSystemPrompt from "../prompts/system/commit-message-system.md" with { type: "text" };
|
|
14
13
|
import { toReasoningEffort } from "../thinking";
|
|
15
14
|
|
|
@@ -36,7 +35,7 @@ function filterDiffNoise(diff: string): string {
|
|
|
36
35
|
return filtered.join("\n");
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
function
|
|
38
|
+
function getModelCandidates(
|
|
40
39
|
registry: ModelRegistry,
|
|
41
40
|
settings: Settings,
|
|
42
41
|
): Array<{ model: Model<Api>; thinkingLevel?: ThinkingLevel }> {
|
|
@@ -51,18 +50,12 @@ function getSmolModelCandidates(
|
|
|
51
50
|
};
|
|
52
51
|
|
|
53
52
|
const matchPreferences = { usageOrder: settings.getStorage()?.getModelUsageOrder() };
|
|
54
|
-
const
|
|
53
|
+
const configured = resolveModelRoleValue(settings.getModelRole("default"), availableModels, {
|
|
55
54
|
settings,
|
|
56
55
|
matchPreferences,
|
|
57
56
|
modelRegistry: registry,
|
|
58
57
|
});
|
|
59
|
-
addCandidate(
|
|
60
|
-
|
|
61
|
-
for (const pattern of MODEL_PRIO.smol) {
|
|
62
|
-
const needle = pattern.toLowerCase();
|
|
63
|
-
addCandidate(availableModels.find(m => m.id.toLowerCase() === needle));
|
|
64
|
-
addCandidate(availableModels.find(m => m.id.toLowerCase().includes(needle)));
|
|
65
|
-
}
|
|
58
|
+
addCandidate(configured.model, configured.thinkingLevel);
|
|
66
59
|
|
|
67
60
|
for (const model of availableModels) {
|
|
68
61
|
addCandidate(model);
|
|
@@ -81,9 +74,9 @@ export async function generateCommitMessage(
|
|
|
81
74
|
settings: Settings,
|
|
82
75
|
sessionId?: string,
|
|
83
76
|
): Promise<string | null> {
|
|
84
|
-
const candidates =
|
|
77
|
+
const candidates = getModelCandidates(registry, settings);
|
|
85
78
|
if (candidates.length === 0) {
|
|
86
|
-
logger.debug("commit-msg-generator: no
|
|
79
|
+
logger.debug("commit-msg-generator: no model found");
|
|
87
80
|
return null;
|
|
88
81
|
}
|
|
89
82
|
|
|
@@ -40,7 +40,7 @@ function getTitleModel(registry: ModelRegistry, settings: Settings, currentModel
|
|
|
40
40
|
const availableModels = registry.getAvailable();
|
|
41
41
|
if (availableModels.length === 0) return undefined;
|
|
42
42
|
|
|
43
|
-
const titleModel = resolveRoleSelection(["
|
|
43
|
+
const titleModel = resolveRoleSelection(["default"], settings, availableModels, registry)?.model;
|
|
44
44
|
if (titleModel) return titleModel;
|
|
45
45
|
|
|
46
46
|
if (currentModel) return currentModel;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure mapping from `gjc --mode rpc` event frames (docs/rpc.md) to bounded owner event kinds
|
|
3
|
-
* and {@link ObservedSignal}s. The owner feeds raw frames through this mapper and emits the
|
|
4
|
-
* result via its single-writer #emit — the mapper itself performs NO IO and NO appends.
|
|
5
|
-
*
|
|
6
|
-
* Hard rule: evidence is BOUNDED — only ids, names, categories, statuses, cursors, timestamps,
|
|
7
|
-
* and short codes/messages. Never assistant text, message deltas, command output, or raw args.
|
|
8
|
-
*/
|
|
9
|
-
import type { ObservedSignal } from "./types";
|
|
10
|
-
export interface MappedFrame {
|
|
11
|
-
/** Owner event kind (rpc_*). */
|
|
12
|
-
kind: string;
|
|
13
|
-
/** Bounded observed signal, or null when the frame carries no user-facing signal. */
|
|
14
|
-
signal: ObservedSignal | null;
|
|
15
|
-
/** Bounded evidence — ids/names/statuses/cursors/timestamps/short codes only. */
|
|
16
|
-
evidence: Record<string, unknown>;
|
|
17
|
-
/** Severity for the emitted event. */
|
|
18
|
-
severity: "info" | "warn" | "critical";
|
|
19
|
-
/** Never-drop frames (must be enqueued in order, never coalesced away). */
|
|
20
|
-
semantic: boolean;
|
|
21
|
-
/** Coalescing key for high-frequency non-semantic frames (message id / tool id); null otherwise. */
|
|
22
|
-
coalesceKey: string | null;
|
|
23
|
-
}
|
|
24
|
-
export declare function isTestRunnerTool(toolName?: unknown, command?: unknown): boolean;
|
|
25
|
-
/**
|
|
26
|
-
* Map a single RPC frame. Returns null for frames that carry no observability value
|
|
27
|
-
* (or that the adapter handles itself: `ready`, `response`).
|
|
28
|
-
*/
|
|
29
|
-
export declare function mapRpcFrame(frame: Record<string, unknown>): MappedFrame | null;
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure mapping from `gjc --mode rpc` event frames (docs/rpc.md) to bounded owner event kinds
|
|
3
|
-
* and {@link ObservedSignal}s. The owner feeds raw frames through this mapper and emits the
|
|
4
|
-
* result via its single-writer #emit — the mapper itself performs NO IO and NO appends.
|
|
5
|
-
*
|
|
6
|
-
* Hard rule: evidence is BOUNDED — only ids, names, categories, statuses, cursors, timestamps,
|
|
7
|
-
* and short codes/messages. Never assistant text, message deltas, command output, or raw args.
|
|
8
|
-
*/
|
|
9
|
-
import type { ObservedSignal } from "./types";
|
|
10
|
-
|
|
11
|
-
export interface MappedFrame {
|
|
12
|
-
/** Owner event kind (rpc_*). */
|
|
13
|
-
kind: string;
|
|
14
|
-
/** Bounded observed signal, or null when the frame carries no user-facing signal. */
|
|
15
|
-
signal: ObservedSignal | null;
|
|
16
|
-
/** Bounded evidence — ids/names/statuses/cursors/timestamps/short codes only. */
|
|
17
|
-
evidence: Record<string, unknown>;
|
|
18
|
-
/** Severity for the emitted event. */
|
|
19
|
-
severity: "info" | "warn" | "critical";
|
|
20
|
-
/** Never-drop frames (must be enqueued in order, never coalesced away). */
|
|
21
|
-
semantic: boolean;
|
|
22
|
-
/** Coalescing key for high-frequency non-semantic frames (message id / tool id); null otherwise. */
|
|
23
|
-
coalesceKey: string | null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const TEST_RE = /\b(bun test|npm test|yarn test|pnpm test|jest|vitest|pytest|go test|cargo test|mocha|ava)\b/i;
|
|
27
|
-
const TOOL_STATUS_CODES = new Set([
|
|
28
|
-
"aborted",
|
|
29
|
-
"blocked",
|
|
30
|
-
"cancelled",
|
|
31
|
-
"complete",
|
|
32
|
-
"completed",
|
|
33
|
-
"error",
|
|
34
|
-
"failed",
|
|
35
|
-
"ok",
|
|
36
|
-
"pending",
|
|
37
|
-
"running",
|
|
38
|
-
"skipped",
|
|
39
|
-
"success",
|
|
40
|
-
"timeout",
|
|
41
|
-
]);
|
|
42
|
-
|
|
43
|
-
export function isTestRunnerTool(toolName?: unknown, command?: unknown): boolean {
|
|
44
|
-
const name = typeof toolName === "string" ? toolName : "";
|
|
45
|
-
const cmd = typeof command === "string" ? command : "";
|
|
46
|
-
if (/test/i.test(name) && name !== "edit" && name !== "read") return true;
|
|
47
|
-
return TEST_RE.test(cmd);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function str(v: unknown): string | undefined {
|
|
51
|
-
return typeof v === "string" ? v : undefined;
|
|
52
|
-
}
|
|
53
|
-
function num(v: unknown): number | undefined {
|
|
54
|
-
return typeof v === "number" ? v : undefined;
|
|
55
|
-
}
|
|
56
|
-
function boundedMessage(v: unknown): string | undefined {
|
|
57
|
-
const s = typeof v === "string" ? v : undefined;
|
|
58
|
-
return s === undefined ? undefined : s.slice(0, 200);
|
|
59
|
-
}
|
|
60
|
-
function boundedStatus(v: unknown): string | undefined {
|
|
61
|
-
if (typeof v !== "string") return undefined;
|
|
62
|
-
const status = v.trim().toLowerCase();
|
|
63
|
-
return TOOL_STATUS_CODES.has(status) ? status : undefined;
|
|
64
|
-
}
|
|
65
|
-
function recordObject(v: unknown): Record<string, unknown> | undefined {
|
|
66
|
-
return v && typeof v === "object" && !Array.isArray(v) ? (v as Record<string, unknown>) : undefined;
|
|
67
|
-
}
|
|
68
|
-
/** Extract a tool command from real AgentSessionEvent `args` or a flat fixture frame. Bounded use only — never persisted. */
|
|
69
|
-
function toolCommand(frame: Record<string, unknown>): string | undefined {
|
|
70
|
-
const args = recordObject(frame.args);
|
|
71
|
-
const c = args?.command ?? args?.cmd ?? args?.commandLine;
|
|
72
|
-
if (typeof c === "string") return c;
|
|
73
|
-
return str(frame.command) ?? str(frame.commandLine);
|
|
74
|
-
}
|
|
75
|
-
/** Derive a tool status, honoring real `isError` booleans as well as bounded status strings. */
|
|
76
|
-
function toolStatus(frame: Record<string, unknown>): string | undefined {
|
|
77
|
-
if (frame.isError === true) return "error";
|
|
78
|
-
const flatStatus = boundedStatus(frame.status);
|
|
79
|
-
if (flatStatus) return flatStatus;
|
|
80
|
-
for (const candidate of [frame.result, frame.partialResult]) {
|
|
81
|
-
const result = recordObject(candidate);
|
|
82
|
-
if (!result) continue;
|
|
83
|
-
if (result.isError === true) return "error";
|
|
84
|
-
const status = boundedStatus(result.status) ?? boundedStatus(recordObject(result.details)?.status);
|
|
85
|
-
if (status) return status;
|
|
86
|
-
}
|
|
87
|
-
return undefined;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Map a single RPC frame. Returns null for frames that carry no observability value
|
|
92
|
-
* (or that the adapter handles itself: `ready`, `response`).
|
|
93
|
-
*/
|
|
94
|
-
export function mapRpcFrame(frame: Record<string, unknown>): MappedFrame | null {
|
|
95
|
-
const type = str(frame.type);
|
|
96
|
-
if (!type || type === "ready" || type === "response") return null;
|
|
97
|
-
|
|
98
|
-
switch (type) {
|
|
99
|
-
case "agent_start":
|
|
100
|
-
return {
|
|
101
|
-
kind: "rpc_agent_started",
|
|
102
|
-
signal: "SessionStart",
|
|
103
|
-
evidence: {},
|
|
104
|
-
severity: "info",
|
|
105
|
-
semantic: true,
|
|
106
|
-
coalesceKey: null,
|
|
107
|
-
};
|
|
108
|
-
case "turn_start":
|
|
109
|
-
return {
|
|
110
|
-
kind: "rpc_turn_started",
|
|
111
|
-
signal: "prompt-accepted",
|
|
112
|
-
evidence: {},
|
|
113
|
-
severity: "info",
|
|
114
|
-
semantic: true,
|
|
115
|
-
coalesceKey: null,
|
|
116
|
-
};
|
|
117
|
-
case "turn_end":
|
|
118
|
-
return {
|
|
119
|
-
kind: "rpc_turn_ended",
|
|
120
|
-
signal: null,
|
|
121
|
-
evidence: {},
|
|
122
|
-
severity: "info",
|
|
123
|
-
semantic: false,
|
|
124
|
-
coalesceKey: null,
|
|
125
|
-
};
|
|
126
|
-
case "message_start":
|
|
127
|
-
case "message_update":
|
|
128
|
-
case "message_end":
|
|
129
|
-
return {
|
|
130
|
-
kind: "rpc_message_activity",
|
|
131
|
-
signal: null,
|
|
132
|
-
evidence: { phase: type, messageId: str(frame.messageId) ?? null },
|
|
133
|
-
severity: "info",
|
|
134
|
-
semantic: false,
|
|
135
|
-
coalesceKey: `message:${str(frame.messageId) ?? "msg"}`,
|
|
136
|
-
};
|
|
137
|
-
case "tool_execution_start": {
|
|
138
|
-
const toolName = str(frame.toolName);
|
|
139
|
-
const test = isTestRunnerTool(toolName, toolCommand(frame));
|
|
140
|
-
return {
|
|
141
|
-
kind: "rpc_tool_started",
|
|
142
|
-
signal: test ? "test-running" : "tool-call",
|
|
143
|
-
evidence: { toolId: str(frame.toolCallId) ?? null, toolName: toolName ?? null },
|
|
144
|
-
severity: "info",
|
|
145
|
-
semantic: true,
|
|
146
|
-
coalesceKey: null,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
case "tool_execution_update": {
|
|
150
|
-
const toolName = str(frame.toolName);
|
|
151
|
-
const test = isTestRunnerTool(toolName, toolCommand(frame));
|
|
152
|
-
return {
|
|
153
|
-
kind: "rpc_tool_updated",
|
|
154
|
-
signal: test ? "test-running" : null,
|
|
155
|
-
evidence: { toolId: str(frame.toolCallId) ?? null, status: toolStatus(frame) ?? null },
|
|
156
|
-
severity: "info",
|
|
157
|
-
semantic: false,
|
|
158
|
-
coalesceKey: `tool:${str(frame.toolCallId) ?? "tool"}`,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
case "tool_execution_end": {
|
|
162
|
-
const toolName = str(frame.toolName);
|
|
163
|
-
const test = isTestRunnerTool(toolName, toolCommand(frame));
|
|
164
|
-
const status = toolStatus(frame);
|
|
165
|
-
return {
|
|
166
|
-
kind: "rpc_tool_ended",
|
|
167
|
-
signal: test ? "test-running" : "tool-call",
|
|
168
|
-
evidence: {
|
|
169
|
-
toolId: str(frame.toolCallId) ?? null,
|
|
170
|
-
toolName: toolName ?? null,
|
|
171
|
-
status: status ?? null,
|
|
172
|
-
exitCode: num(frame.exitCode) ?? null,
|
|
173
|
-
},
|
|
174
|
-
severity: status === "error" ? "warn" : "info",
|
|
175
|
-
semantic: true,
|
|
176
|
-
coalesceKey: null,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
case "host_tool_call":
|
|
180
|
-
case "host_tool_cancel":
|
|
181
|
-
return {
|
|
182
|
-
kind: "rpc_host_tool",
|
|
183
|
-
signal: "tool-call",
|
|
184
|
-
evidence: { toolName: str(frame.toolName) ?? null },
|
|
185
|
-
severity: "info",
|
|
186
|
-
semantic: false,
|
|
187
|
-
coalesceKey: null,
|
|
188
|
-
};
|
|
189
|
-
case "host_uri_request":
|
|
190
|
-
case "host_uri_cancel":
|
|
191
|
-
return {
|
|
192
|
-
kind: "rpc_host_uri",
|
|
193
|
-
signal: "tool-call",
|
|
194
|
-
evidence: { operation: str(frame.operation) ?? null },
|
|
195
|
-
severity: "info",
|
|
196
|
-
semantic: false,
|
|
197
|
-
coalesceKey: null,
|
|
198
|
-
};
|
|
199
|
-
case "auto_compaction_start":
|
|
200
|
-
case "auto_compaction_end":
|
|
201
|
-
return {
|
|
202
|
-
kind: "rpc_compaction",
|
|
203
|
-
signal: null,
|
|
204
|
-
evidence: { phase: type },
|
|
205
|
-
severity: "info",
|
|
206
|
-
semantic: false,
|
|
207
|
-
coalesceKey: null,
|
|
208
|
-
};
|
|
209
|
-
case "auto_retry_start":
|
|
210
|
-
case "auto_retry_end":
|
|
211
|
-
return {
|
|
212
|
-
kind: "rpc_retry",
|
|
213
|
-
signal: null,
|
|
214
|
-
evidence: { phase: type, reason: boundedMessage(frame.reason) ?? null },
|
|
215
|
-
severity: "warn",
|
|
216
|
-
semantic: false,
|
|
217
|
-
coalesceKey: null,
|
|
218
|
-
};
|
|
219
|
-
case "ttsr_triggered":
|
|
220
|
-
return {
|
|
221
|
-
kind: "rpc_ttsr",
|
|
222
|
-
signal: "error",
|
|
223
|
-
evidence: { reason: boundedMessage(frame.reason) ?? null },
|
|
224
|
-
severity: "warn",
|
|
225
|
-
semantic: true,
|
|
226
|
-
coalesceKey: null,
|
|
227
|
-
};
|
|
228
|
-
case "todo_reminder":
|
|
229
|
-
case "todo_auto_clear":
|
|
230
|
-
return {
|
|
231
|
-
kind: "rpc_todo",
|
|
232
|
-
signal: null,
|
|
233
|
-
evidence: { phase: type },
|
|
234
|
-
severity: "info",
|
|
235
|
-
semantic: false,
|
|
236
|
-
coalesceKey: null,
|
|
237
|
-
};
|
|
238
|
-
case "extension_ui_request":
|
|
239
|
-
return {
|
|
240
|
-
kind: "rpc_extension_request",
|
|
241
|
-
signal: "tool-call",
|
|
242
|
-
evidence: { method: str(frame.method) ?? null },
|
|
243
|
-
severity: "info",
|
|
244
|
-
semantic: false,
|
|
245
|
-
coalesceKey: null,
|
|
246
|
-
};
|
|
247
|
-
case "extension_error":
|
|
248
|
-
return {
|
|
249
|
-
kind: "rpc_extension_error",
|
|
250
|
-
signal: "error",
|
|
251
|
-
evidence: {
|
|
252
|
-
code: str(frame.error) ? boundedMessage(frame.error) : null,
|
|
253
|
-
extensionPath: str(frame.extensionPath) ?? null,
|
|
254
|
-
},
|
|
255
|
-
severity: "critical",
|
|
256
|
-
semantic: true,
|
|
257
|
-
coalesceKey: null,
|
|
258
|
-
};
|
|
259
|
-
case "agent_end": {
|
|
260
|
-
const failed =
|
|
261
|
-
Boolean(frame.error) ||
|
|
262
|
-
frame.aborted === true ||
|
|
263
|
-
str(frame.outcome) === "failed" ||
|
|
264
|
-
str(frame.outcome) === "aborted";
|
|
265
|
-
return failed
|
|
266
|
-
? {
|
|
267
|
-
kind: "rpc_agent_failed",
|
|
268
|
-
signal: "error",
|
|
269
|
-
evidence: { outcome: str(frame.outcome) ?? "failed" },
|
|
270
|
-
severity: "critical",
|
|
271
|
-
semantic: true,
|
|
272
|
-
coalesceKey: null,
|
|
273
|
-
}
|
|
274
|
-
: {
|
|
275
|
-
kind: "rpc_agent_completed",
|
|
276
|
-
signal: "completed",
|
|
277
|
-
evidence: { outcome: "completed" },
|
|
278
|
-
severity: "info",
|
|
279
|
-
semantic: true,
|
|
280
|
-
coalesceKey: null,
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
default:
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
}
|