@gajae-code/coding-agent 0.8.1 → 0.8.2
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 +34 -0
- package/dist/types/cli/completion-cli.d.ts +41 -0
- package/dist/types/cli/config-cli.d.ts +1 -0
- package/dist/types/cli/notify-cli.d.ts +7 -0
- package/dist/types/cli.d.ts +124 -1
- package/dist/types/commands/completion.d.ts +29 -0
- package/dist/types/commands/config.d.ts +3 -0
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +7 -6
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +6 -0
- package/dist/types/config/settings.d.ts +8 -0
- package/dist/types/extensibility/extensions/runner.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +39 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +12 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +2 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
- package/dist/types/main.d.ts +4 -1
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/print-mode.d.ts +13 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
- package/dist/types/notifications/config.d.ts +8 -1
- package/dist/types/notifications/daemon-paths.d.ts +1 -0
- package/dist/types/notifications/index.d.ts +6 -3
- package/dist/types/notifications/lifecycle-commands.d.ts +11 -3
- package/dist/types/notifications/telegram-daemon.d.ts +15 -3
- package/dist/types/notifications/telegram-reference.d.ts +0 -5
- package/dist/types/runtime-api-key.d.ts +7 -0
- package/dist/types/session/agent-session.d.ts +10 -0
- package/dist/types/session/agent-storage.d.ts +1 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/tools/computer.d.ts +2 -2
- package/dist/types/workflow/workflow-intent-diff.d.ts +8 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
- package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
- package/package.json +7 -7
- package/src/cli/completion-cli.ts +234 -0
- package/src/cli/config-cli.ts +37 -9
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/notify-cli.ts +91 -7
- package/src/cli.ts +3 -1
- package/src/commands/completion.ts +84 -0
- package/src/commands/config.ts +2 -0
- package/src/commands/deep-interview.ts +2 -1
- package/src/config/file-lock.ts +73 -34
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-profile-activation.ts +65 -0
- package/src/config/model-registry.ts +14 -2
- package/src/config/settings.ts +22 -0
- package/src/coordinator-mcp/server.ts +34 -8
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +38 -5
- package/src/defaults/gjc/skills/ralplan/SKILL.md +15 -11
- package/src/defaults/gjc/skills/team/SKILL.md +1 -1
- package/src/extensibility/extensions/runner.ts +2 -0
- package/src/extensibility/extensions/types.ts +9 -0
- package/src/gjc-runtime/deep-interview-ambiguity.ts +206 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +62 -8
- package/src/gjc-runtime/deep-interview-runtime.ts +210 -2
- package/src/gjc-runtime/deep-interview-state.ts +13 -0
- package/src/gjc-runtime/session-state-sidecar.ts +59 -11
- package/src/gjc-runtime/state-runtime.ts +12 -2
- package/src/gjc-runtime/team-runtime.ts +23 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +73 -19
- package/src/hooks/native-skill-hook.ts +29 -0
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/main.ts +52 -41
- package/src/modes/components/model-selector.ts +54 -11
- package/src/modes/components/queued-message-selector.ts +92 -0
- package/src/modes/components/settings-selector.ts +4 -0
- package/src/modes/components/welcome.ts +160 -44
- package/src/modes/controllers/command-controller.ts +1 -2
- package/src/modes/controllers/extension-ui-controller.ts +4 -10
- package/src/modes/controllers/input-controller.ts +179 -17
- package/src/modes/controllers/selector-controller.ts +122 -27
- package/src/modes/interactive-mode.ts +43 -16
- package/src/modes/print-mode.ts +43 -4
- package/src/modes/prompt-action-autocomplete.ts +21 -20
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +2 -2
- package/src/notifications/config.ts +9 -1
- package/src/notifications/daemon-paths.ts +2 -0
- package/src/notifications/index.ts +39 -9
- package/src/notifications/lifecycle-commands.ts +37 -7
- package/src/notifications/lifecycle-orchestrator.ts +19 -6
- package/src/notifications/telegram-daemon-control.ts +3 -1
- package/src/notifications/telegram-daemon.ts +217 -43
- package/src/notifications/telegram-reference.ts +0 -16
- package/src/prompts/system/system-prompt.md +3 -0
- package/src/prompts/tools/computer.md +2 -2
- package/src/runtime-api-key.ts +16 -0
- package/src/sdk.ts +20 -6
- package/src/session/agent-session.ts +225 -31
- package/src/session/agent-storage.ts +4 -0
- package/src/session/session-manager.ts +44 -2
- package/src/slash-commands/builtin-registry.ts +153 -71
- package/src/task/executor.ts +7 -4
- package/src/task/index.ts +9 -3
- package/src/task/output-manager.ts +16 -6
- package/src/task/receipt.ts +26 -7
- package/src/tools/computer.ts +246 -43
- package/src/tools/telegram-send.ts +6 -0
- package/src/workflow/workflow-intent-diff.ts +29 -6
- package/src/workflow/workflow-intent-report-consumer.ts +221 -0
- package/src/workflow/workflow-intent-report-schema.ts +129 -0
- package/src/workflow/workflow-intent-report.ts +135 -0
|
@@ -6,12 +6,13 @@ import { getOAuthProviders } from "@gajae-code/ai/utils/oauth";
|
|
|
6
6
|
import { Spacer, Text } from "@gajae-code/tui";
|
|
7
7
|
import { setProjectDir } from "@gajae-code/utils";
|
|
8
8
|
import { jobElapsedMs } from "../async";
|
|
9
|
-
import {
|
|
9
|
+
import { materializeActiveModelProfileAssignments } from "../config/model-profile-activation";
|
|
10
10
|
import {
|
|
11
11
|
GJC_MODEL_ASSIGNMENT_TARGET_IDS,
|
|
12
12
|
GJC_MODEL_ASSIGNMENT_TARGETS,
|
|
13
13
|
type GjcModelAssignmentTargetId,
|
|
14
14
|
} from "../config/model-registry";
|
|
15
|
+
|
|
15
16
|
import {
|
|
16
17
|
extractExplicitThinkingSelector,
|
|
17
18
|
formatModelSelectorValue,
|
|
@@ -51,6 +52,13 @@ export type { BuiltinSlashCommand, SubcommandDef } from "./types";
|
|
|
51
52
|
/** TUI-specific runtime accepted by `executeBuiltinSlashCommand`. */
|
|
52
53
|
export type BuiltinSlashCommandRuntime = TuiSlashCommandRuntime;
|
|
53
54
|
|
|
55
|
+
type GjcModelBatchAssignmentTargetId = "all-role-agents" | "all-targets";
|
|
56
|
+
type ParsedModelCommandArgs =
|
|
57
|
+
| { kind: "summary" }
|
|
58
|
+
| { kind: "assign"; targetId: GjcModelAssignmentTargetId | GjcModelBatchAssignmentTargetId; selector: string };
|
|
59
|
+
|
|
60
|
+
const GJC_MODEL_ROLE_AGENT_TARGET_IDS: GjcModelAssignmentTargetId[] = ["executor", "architect", "planner", "critic"];
|
|
61
|
+
|
|
54
62
|
function fastStatusRoleTargets(): Array<{ id: GjcModelAssignmentTargetId; label: string; isSubagentRole: boolean }> {
|
|
55
63
|
return GJC_MODEL_ASSIGNMENT_TARGET_IDS.map(id => ({
|
|
56
64
|
id,
|
|
@@ -59,6 +67,32 @@ function fastStatusRoleTargets(): Array<{ id: GjcModelAssignmentTargetId; label:
|
|
|
59
67
|
}));
|
|
60
68
|
}
|
|
61
69
|
|
|
70
|
+
function toSlashCommandRuntime(runtime: TuiSlashCommandRuntime): SlashCommandRuntime {
|
|
71
|
+
const ctx = runtime.ctx;
|
|
72
|
+
return {
|
|
73
|
+
session: ctx.session,
|
|
74
|
+
sessionManager: ctx.sessionManager,
|
|
75
|
+
settings: ctx.settings,
|
|
76
|
+
cwd: ctx.sessionManager.getCwd(),
|
|
77
|
+
output: (text: string) => {
|
|
78
|
+
ctx.showStatus(text);
|
|
79
|
+
},
|
|
80
|
+
refreshCommands: () => ctx.refreshSlashCommandState(),
|
|
81
|
+
reloadPlugins: async () => {
|
|
82
|
+
const projectPath = await resolveActiveProjectRegistryPath(ctx.sessionManager.getCwd());
|
|
83
|
+
clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
|
|
84
|
+
await ctx.refreshSlashCommandState();
|
|
85
|
+
await ctx.session.refreshSshTool({ activateIfAvailable: true });
|
|
86
|
+
},
|
|
87
|
+
notifyTitleChanged: () => {
|
|
88
|
+
ctx.statusLine.invalidate();
|
|
89
|
+
ctx.updateEditorBorderColor();
|
|
90
|
+
ctx.ui.requestRender();
|
|
91
|
+
},
|
|
92
|
+
notifyConfigChanged: () => ctx.notifyConfigChanged?.(),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
62
96
|
function parseProviderSetupSlashArgs(args: string): {
|
|
63
97
|
preset?: string;
|
|
64
98
|
compat?: string;
|
|
@@ -146,22 +180,37 @@ function formatModelAssignmentSummary(runtime: SlashCommandRuntime): string {
|
|
|
146
180
|
return lines.join("\n");
|
|
147
181
|
}
|
|
148
182
|
|
|
149
|
-
function parseModelCommandArgs(args: string):
|
|
183
|
+
function parseModelCommandArgs(args: string): ParsedModelCommandArgs {
|
|
150
184
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
151
185
|
const first = tokens[0]?.toLowerCase();
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
186
|
+
if (first === "roles" || first === "assignments") return { kind: "summary" };
|
|
187
|
+
|
|
188
|
+
const parseTarget = (
|
|
189
|
+
token: string | undefined,
|
|
190
|
+
): GjcModelAssignmentTargetId | GjcModelBatchAssignmentTargetId | undefined => {
|
|
191
|
+
const normalized = token?.toLowerCase();
|
|
192
|
+
if (GJC_MODEL_ASSIGNMENT_TARGET_IDS.includes(normalized as GjcModelAssignmentTargetId)) {
|
|
193
|
+
return normalized as GjcModelAssignmentTargetId;
|
|
194
|
+
}
|
|
195
|
+
if (normalized === "all-role-agents" || normalized === "all-targets") return normalized;
|
|
196
|
+
return undefined;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
if (first === "assign") {
|
|
200
|
+
const targetId = parseTarget(tokens[1]);
|
|
201
|
+
if (targetId) return { kind: "assign", targetId, selector: tokens.slice(2).join(" ") };
|
|
202
|
+
return { kind: "assign", targetId: "default", selector: tokens.slice(1).join(" ") };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const explicitTarget = parseTarget(first);
|
|
155
206
|
if (explicitTarget) {
|
|
156
|
-
return { targetId: explicitTarget, selector: tokens.slice(1).join(" ") };
|
|
207
|
+
return { kind: "assign", targetId: explicitTarget, selector: tokens.slice(1).join(" ") };
|
|
157
208
|
}
|
|
158
209
|
if (first === "set") {
|
|
159
|
-
const
|
|
160
|
-
if (
|
|
161
|
-
return { targetId: second as GjcModelAssignmentTargetId, selector: tokens.slice(2).join(" ") };
|
|
162
|
-
}
|
|
210
|
+
const targetId = parseTarget(tokens[1]);
|
|
211
|
+
if (targetId) return { kind: "assign", targetId, selector: tokens.slice(2).join(" ") };
|
|
163
212
|
}
|
|
164
|
-
return { targetId: "default", selector: args.trim() };
|
|
213
|
+
return { kind: "assign", targetId: "default", selector: args.trim() };
|
|
165
214
|
}
|
|
166
215
|
|
|
167
216
|
function splitExplicitThinkingSelector(selector: string): { baseSelector: string; thinkingLevel?: ThinkingLevel } {
|
|
@@ -296,11 +345,33 @@ async function resolveModelCommandSelection(
|
|
|
296
345
|
};
|
|
297
346
|
}
|
|
298
347
|
|
|
348
|
+
function getModelAssignmentTargetIds(
|
|
349
|
+
targetId: GjcModelAssignmentTargetId | GjcModelBatchAssignmentTargetId,
|
|
350
|
+
): GjcModelAssignmentTargetId[] {
|
|
351
|
+
if (targetId === "all-role-agents") return [...GJC_MODEL_ROLE_AGENT_TARGET_IDS];
|
|
352
|
+
if (targetId === "all-targets") return [...GJC_MODEL_ASSIGNMENT_TARGET_IDS];
|
|
353
|
+
return [targetId];
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function formatModelAssignmentSuccess(
|
|
357
|
+
targetId: GjcModelAssignmentTargetId | GjcModelBatchAssignmentTargetId,
|
|
358
|
+
selector: string,
|
|
359
|
+
): string {
|
|
360
|
+
if (targetId === "all-role-agents") {
|
|
361
|
+
return `Role-agent models set to ${selector} for EXECUTOR, ARCHITECT, PLANNER, CRITIC.`;
|
|
362
|
+
}
|
|
363
|
+
if (targetId === "all-targets") {
|
|
364
|
+
return `All model targets set to ${selector} for DEFAULT, EXECUTOR, ARCHITECT, PLANNER, CRITIC.`;
|
|
365
|
+
}
|
|
366
|
+
if (targetId === "default") return `Default model set to ${selector}.`;
|
|
367
|
+
return `${targetId} agent model set to ${selector}.`;
|
|
368
|
+
}
|
|
369
|
+
|
|
299
370
|
function modelSelectionUsage(runtime: SlashCommandRuntime, currentModelLine?: string): string {
|
|
300
371
|
return [
|
|
301
372
|
currentModelLine,
|
|
302
373
|
formatModelAssignmentSummary(runtime),
|
|
303
|
-
"
|
|
374
|
+
"Use /model <model> for DEFAULT, or /model <target> <model[:effort]> for EXECUTOR, ARCHITECT, PLANNER, or CRITIC.",
|
|
304
375
|
formatModelOnboardingGuidance(),
|
|
305
376
|
]
|
|
306
377
|
.filter((line): line is string => Boolean(line))
|
|
@@ -372,9 +443,16 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
372
443
|
acpDescription: "Show current model selection",
|
|
373
444
|
inlineHint: "[target] <model>",
|
|
374
445
|
acpInputHint: "[target] <model>",
|
|
446
|
+
allowArgs: true,
|
|
375
447
|
handle: async (command, runtime) => {
|
|
376
448
|
if (command.args) {
|
|
377
449
|
const parsedArgs = parseModelCommandArgs(command.args);
|
|
450
|
+
if (parsedArgs.kind === "summary") {
|
|
451
|
+
await runtime.output(formatModelAssignmentSummary(runtime));
|
|
452
|
+
return commandConsumed();
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const targetIds = getModelAssignmentTargetIds(parsedArgs.targetId);
|
|
378
456
|
const modelId = parsedArgs.selector;
|
|
379
457
|
if (!modelId) {
|
|
380
458
|
return usage(
|
|
@@ -388,24 +466,9 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
388
466
|
}
|
|
389
467
|
const { selection } = resolution;
|
|
390
468
|
try {
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
selector: selection.selector,
|
|
395
|
-
thinkingLevel: selection.thinkingLevel,
|
|
396
|
-
});
|
|
397
|
-
materializeActiveModelProfileAssignment({
|
|
398
|
-
session: runtime.session,
|
|
399
|
-
settings: runtime.settings,
|
|
400
|
-
role: parsedArgs.targetId,
|
|
401
|
-
selector: persistedSelector,
|
|
402
|
-
});
|
|
403
|
-
if (selection.thinkingLevel) {
|
|
404
|
-
runtime.session.setThinkingLevel(selection.thinkingLevel);
|
|
405
|
-
}
|
|
406
|
-
await runtime.output(`Default model set to ${persistedSelector}.`);
|
|
407
|
-
await runtime.notifyTitleChanged?.();
|
|
408
|
-
} else {
|
|
469
|
+
const includesDefault = targetIds.includes("default");
|
|
470
|
+
const includesRoleAgent = targetIds.some(role => role !== "default");
|
|
471
|
+
if (includesRoleAgent) {
|
|
409
472
|
const apiKey = await runtime.session.modelRegistry.getApiKey(
|
|
410
473
|
selection.model,
|
|
411
474
|
runtime.session.sessionId,
|
|
@@ -413,31 +476,60 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
413
476
|
if (!apiKey) {
|
|
414
477
|
throw new Error(`No API key for ${selection.model.provider}/${selection.model.id}`);
|
|
415
478
|
}
|
|
416
|
-
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const overrides = runtime.settings.get("task.agentModelOverrides");
|
|
482
|
+
const assignments = new Map<GjcModelAssignmentTargetId, string>();
|
|
483
|
+
const existingDefaultThinkingLevel =
|
|
484
|
+
selection.thinkingLevel !== undefined
|
|
485
|
+
? selection.thinkingLevel
|
|
486
|
+
: runtime.session.getActiveModelProfile?.()
|
|
487
|
+
? undefined
|
|
488
|
+
: extractExplicitThinkingSelector(runtime.settings.getModelRole("default"), runtime.settings);
|
|
489
|
+
const persistedSelector = formatModelSelectorValue(selection.selector, existingDefaultThinkingLevel);
|
|
490
|
+
for (const targetId of targetIds) {
|
|
491
|
+
if (targetId === "default") {
|
|
492
|
+
assignments.set(targetId, persistedSelector);
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
417
495
|
const thinkingLevel =
|
|
418
|
-
selection.thinkingLevel ??
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
496
|
+
selection.thinkingLevel ?? extractExplicitThinkingSelector(overrides[targetId], runtime.settings);
|
|
497
|
+
assignments.set(targetId, formatModelSelectorValue(selection.selector, thinkingLevel));
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (includesDefault) {
|
|
501
|
+
await runtime.session.setModel(selection.model, "default", {
|
|
502
|
+
selector: selection.selector,
|
|
503
|
+
thinkingLevel: existingDefaultThinkingLevel,
|
|
426
504
|
});
|
|
427
|
-
if (
|
|
428
|
-
|
|
505
|
+
if (existingDefaultThinkingLevel) {
|
|
506
|
+
runtime.session.setThinkingLevel(existingDefaultThinkingLevel);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const materializedProfile = materializeActiveModelProfileAssignments({
|
|
511
|
+
session: runtime.session,
|
|
512
|
+
settings: runtime.settings,
|
|
513
|
+
assignments,
|
|
514
|
+
});
|
|
515
|
+
if (!materializedProfile) {
|
|
516
|
+
for (const [targetId, selector] of assignments) {
|
|
517
|
+
const target = GJC_MODEL_ASSIGNMENT_TARGETS[targetId];
|
|
429
518
|
if (target.settingsPath === "modelRoles") {
|
|
430
|
-
runtime.settings.setModelRole(
|
|
519
|
+
runtime.settings.setModelRole(targetId, selector);
|
|
431
520
|
} else {
|
|
432
|
-
runtime.settings.
|
|
433
|
-
...overrides,
|
|
434
|
-
[parsedArgs.targetId]: roleSelector,
|
|
435
|
-
});
|
|
521
|
+
runtime.settings.setAgentModelOverride(targetId, selector);
|
|
436
522
|
}
|
|
437
523
|
}
|
|
438
|
-
runtime.settings.getStorage()?.recordModelUsage(`${selection.model.provider}/${selection.model.id}`);
|
|
439
|
-
await runtime.output(`${parsedArgs.targetId} agent model set to ${roleSelector}.`);
|
|
440
524
|
}
|
|
525
|
+
runtime.settings.getStorage()?.recordModelUsage(`${selection.model.provider}/${selection.model.id}`);
|
|
526
|
+
await runtime.output(
|
|
527
|
+
formatModelAssignmentSuccess(
|
|
528
|
+
parsedArgs.targetId,
|
|
529
|
+
assignments.get(targetIds[0] ?? "default") ?? persistedSelector,
|
|
530
|
+
),
|
|
531
|
+
);
|
|
532
|
+
if (includesDefault) await runtime.notifyTitleChanged?.();
|
|
441
533
|
await runtime.notifyConfigChanged?.();
|
|
442
534
|
return commandConsumed();
|
|
443
535
|
} catch (err) {
|
|
@@ -454,7 +546,18 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
454
546
|
);
|
|
455
547
|
return commandConsumed();
|
|
456
548
|
},
|
|
457
|
-
handleTui: (
|
|
549
|
+
handleTui: async (command, runtime) => {
|
|
550
|
+
if (command.args.trim()) {
|
|
551
|
+
const result = await BUILTIN_SLASH_COMMAND_LOOKUP.get(command.name)?.handle?.(
|
|
552
|
+
command,
|
|
553
|
+
toSlashCommandRuntime(runtime),
|
|
554
|
+
);
|
|
555
|
+
runtime.ctx.statusLine.invalidate();
|
|
556
|
+
runtime.ctx.updateEditorBorderColor();
|
|
557
|
+
runtime.ctx.editor.setText("");
|
|
558
|
+
runtime.ctx.ui.requestRender();
|
|
559
|
+
return result;
|
|
560
|
+
}
|
|
458
561
|
runtime.ctx.showModelSelector();
|
|
459
562
|
runtime.ctx.editor.setText("");
|
|
460
563
|
},
|
|
@@ -1303,29 +1406,8 @@ export async function executeBuiltinSlashCommand(
|
|
|
1303
1406
|
return true;
|
|
1304
1407
|
}
|
|
1305
1408
|
if (command.handle) {
|
|
1306
|
-
// No TUI-specific override → adapt the ACP/text-mode `handle` to the
|
|
1307
|
-
// TUI by routing `runtime.output` through `ctx.showStatus`, clearing
|
|
1308
|
-
// the editor after the call, and reusing the active session's plugin
|
|
1309
|
-
// reload pipeline. Spec authors get a single body usable from either
|
|
1310
|
-
// dispatcher without forcing every TUI test to construct the full
|
|
1311
|
-
// `SlashCommandRuntime` shape.
|
|
1312
1409
|
const ctx = runtime.ctx;
|
|
1313
|
-
const adapted
|
|
1314
|
-
session: ctx.session,
|
|
1315
|
-
sessionManager: ctx.sessionManager,
|
|
1316
|
-
settings: ctx.settings,
|
|
1317
|
-
cwd: ctx.sessionManager.getCwd(),
|
|
1318
|
-
output: (text: string) => {
|
|
1319
|
-
ctx.showStatus(text);
|
|
1320
|
-
},
|
|
1321
|
-
refreshCommands: () => ctx.refreshSlashCommandState(),
|
|
1322
|
-
reloadPlugins: async () => {
|
|
1323
|
-
const projectPath = await resolveActiveProjectRegistryPath(ctx.sessionManager.getCwd());
|
|
1324
|
-
clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
|
|
1325
|
-
await ctx.refreshSlashCommandState();
|
|
1326
|
-
await ctx.session.refreshSshTool({ activateIfAvailable: true });
|
|
1327
|
-
},
|
|
1328
|
-
};
|
|
1410
|
+
const adapted = toSlashCommandRuntime(runtime);
|
|
1329
1411
|
const result = await command.handle(parsed, adapted);
|
|
1330
1412
|
ctx.editor.setText("");
|
|
1331
1413
|
if (result && typeof result === "object" && "prompt" in result) return result.prompt;
|
package/src/task/executor.ts
CHANGED
|
@@ -139,6 +139,8 @@ export interface ExecutorOptions {
|
|
|
139
139
|
artifactsDir?: string;
|
|
140
140
|
/** Path to parent conversation context file */
|
|
141
141
|
contextFile?: string;
|
|
142
|
+
/** Whether the parent runtime actually exposes IRC coordination. */
|
|
143
|
+
ircAvailable?: boolean;
|
|
142
144
|
eventBus?: EventBus;
|
|
143
145
|
contextFiles?: ContextFileEntry[];
|
|
144
146
|
skills?: Skill[];
|
|
@@ -623,7 +625,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
623
625
|
: agent.spawns.join(",");
|
|
624
626
|
|
|
625
627
|
const lspEnabled = enableLsp ?? true;
|
|
626
|
-
const ircEnabled =
|
|
628
|
+
const ircEnabled = options.ircAvailable === true;
|
|
627
629
|
const contextFileForPrompt = ircEnabled ? undefined : options.contextFile;
|
|
628
630
|
const skipPythonPreflight = Array.isArray(toolNames) && !toolNames.includes("eval");
|
|
629
631
|
|
|
@@ -1231,9 +1233,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1231
1233
|
|
|
1232
1234
|
const { normalized: normalizedOutputSchema } = normalizeSchema(outputSchema);
|
|
1233
1235
|
|
|
1234
|
-
const forkContextNotice =
|
|
1235
|
-
|
|
1236
|
-
|
|
1236
|
+
const forkContextNotice =
|
|
1237
|
+
options.forkContextSeed && options.forkContextSeed.metadata.includedMessages > 0
|
|
1238
|
+
? `This subagent was started with a forked snapshot of the parent conversation. Included ${options.forkContextSeed.metadata.includedMessages} message(s), skipped ${options.forkContextSeed.metadata.skippedMessages}, approximately ${options.forkContextSeed.metadata.approximateTokens} tokens. The snapshot is not live.${ircEnabled ? " Use IRC for live coordination." : " Rely on the explicit assignment and supplied context for coordination."}`
|
|
1239
|
+
: "";
|
|
1237
1240
|
|
|
1238
1241
|
const agentSubskillBlock = await buildAgentSubskillInjection({
|
|
1239
1242
|
cwd,
|
package/src/task/index.ts
CHANGED
|
@@ -178,6 +178,10 @@ export {
|
|
|
178
178
|
/**
|
|
179
179
|
* Render the tool description from a cached agent list and current settings.
|
|
180
180
|
*/
|
|
181
|
+
function hasAvailableIrcTool(session: ToolSession): boolean {
|
|
182
|
+
return session.settings.get("irc.enabled") === true && session.getToolByName?.("irc") !== undefined;
|
|
183
|
+
}
|
|
184
|
+
|
|
181
185
|
function renderDescription(
|
|
182
186
|
agents: AgentDefinition[],
|
|
183
187
|
maxConcurrency: number,
|
|
@@ -381,7 +385,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
381
385
|
true,
|
|
382
386
|
disabledAgents,
|
|
383
387
|
this.#getTaskSimpleMode(),
|
|
384
|
-
this.session
|
|
388
|
+
hasAvailableIrcTool(this.session),
|
|
385
389
|
this.session.getSessionSpawns() ?? "*",
|
|
386
390
|
);
|
|
387
391
|
}
|
|
@@ -814,7 +818,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
814
818
|
? ` Failed to schedule ${failedSchedules.length} task${failedSchedules.length === 1 ? "" : "s"}.`
|
|
815
819
|
: "";
|
|
816
820
|
|
|
817
|
-
const ircEnabled = this.session
|
|
821
|
+
const ircEnabled = hasAvailableIrcTool(this.session);
|
|
818
822
|
const taskIdByItemId = new Map<string, string>();
|
|
819
823
|
for (let i = 0; i < taskItems.length; i++) {
|
|
820
824
|
taskIdByItemId.set(taskItems[i].id, uniqueIds[i]);
|
|
@@ -1171,7 +1175,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1171
1175
|
// Write parent conversation context for subagents. When IRC is available,
|
|
1172
1176
|
// subagents should ask live peers instead of reading a stale markdown dump.
|
|
1173
1177
|
await fs.mkdir(effectiveArtifactsDir, { recursive: true });
|
|
1174
|
-
const shouldWriteConversationContext = this.session
|
|
1178
|
+
const shouldWriteConversationContext = !hasAvailableIrcTool(this.session);
|
|
1175
1179
|
const compactContext = shouldWriteConversationContext ? this.session.getCompactContext?.() : undefined;
|
|
1176
1180
|
let contextFilePath: string | undefined;
|
|
1177
1181
|
if (compactContext) {
|
|
@@ -1298,6 +1302,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1298
1302
|
persistArtifacts: !!artifactsDir,
|
|
1299
1303
|
artifactsDir: effectiveArtifactsDir,
|
|
1300
1304
|
contextFile: contextFilePath,
|
|
1305
|
+
ircAvailable: hasAvailableIrcTool(this.session),
|
|
1301
1306
|
enableLsp: subagentLspEnabled,
|
|
1302
1307
|
signal,
|
|
1303
1308
|
eventBus: this.session.eventBus,
|
|
@@ -1360,6 +1365,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1360
1365
|
persistArtifacts: !!artifactsDir,
|
|
1361
1366
|
artifactsDir: effectiveArtifactsDir,
|
|
1362
1367
|
contextFile: contextFilePath,
|
|
1368
|
+
ircAvailable: hasAvailableIrcTool(this.session),
|
|
1363
1369
|
enableLsp: subagentLspEnabled,
|
|
1364
1370
|
signal,
|
|
1365
1371
|
eventBus: this.session.eventBus,
|
|
@@ -24,7 +24,7 @@ function escapeRegExp(value: string): string {
|
|
|
24
24
|
*/
|
|
25
25
|
export class AgentOutputManager {
|
|
26
26
|
#nextId = 0;
|
|
27
|
-
#
|
|
27
|
+
#initPromise: Promise<void> | undefined;
|
|
28
28
|
readonly #getArtifactsDir: () => string | null;
|
|
29
29
|
readonly #parentPrefix: string | undefined;
|
|
30
30
|
|
|
@@ -33,14 +33,24 @@ export class AgentOutputManager {
|
|
|
33
33
|
this.#parentPrefix = options?.parentPrefix;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Memoize the in-flight scan so concurrent `allocate*`/`peekNextIndex`
|
|
38
|
+
* calls await the SAME `readdir` before `#nextId` is derived. Assigning the
|
|
39
|
+
* promise synchronously (before any `await`) closes the TOCTOU window where a
|
|
40
|
+
* boolean "initialized" flag, flipped ahead of the awaited scan, let a second
|
|
41
|
+
* caller allocate at `#nextId === 0` while the first was still scanning —
|
|
42
|
+
* producing duplicate indices that overwrite prior outputs on resume.
|
|
43
|
+
*/
|
|
44
|
+
#ensureInitialized(): Promise<void> {
|
|
45
|
+
this.#initPromise ??= this.#scanExistingOutputs();
|
|
46
|
+
return this.#initPromise;
|
|
47
|
+
}
|
|
48
|
+
|
|
36
49
|
/**
|
|
37
50
|
* Scan existing agent output files to find the next available ID.
|
|
38
51
|
* This ensures we don't overwrite outputs when resuming a session.
|
|
39
52
|
*/
|
|
40
|
-
async #
|
|
41
|
-
if (this.#initialized) return;
|
|
42
|
-
this.#initialized = true;
|
|
43
|
-
|
|
53
|
+
async #scanExistingOutputs(): Promise<void> {
|
|
44
54
|
const dir = this.#getArtifactsDir();
|
|
45
55
|
if (!dir) return;
|
|
46
56
|
|
|
@@ -103,6 +113,6 @@ export class AgentOutputManager {
|
|
|
103
113
|
*/
|
|
104
114
|
reset(): void {
|
|
105
115
|
this.#nextId = 0;
|
|
106
|
-
this.#
|
|
116
|
+
this.#initPromise = undefined;
|
|
107
117
|
}
|
|
108
118
|
}
|
package/src/task/receipt.ts
CHANGED
|
@@ -77,6 +77,28 @@ function truncateText(value: string | undefined, maxChars: number): string | und
|
|
|
77
77
|
return value.length > maxChars ? value.slice(0, maxChars) : value;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
const SAFE_REVIEW_SEVERITIES = new Set(["blocker", "critical", "error", "high", "medium", "warning", "low", "info"]);
|
|
81
|
+
const SAFE_REVIEW_PRIORITIES = new Set(["P0", "P1", "P2", "P3"]);
|
|
82
|
+
|
|
83
|
+
function normalizeReviewFindingSeverity(severity: unknown, priority: unknown): string | undefined {
|
|
84
|
+
if (typeof severity === "string") {
|
|
85
|
+
const normalizedPriority = severity.toUpperCase();
|
|
86
|
+
if (SAFE_REVIEW_PRIORITIES.has(normalizedPriority)) return normalizedPriority;
|
|
87
|
+
const normalizedSeverity = severity.toLowerCase();
|
|
88
|
+
if (SAFE_REVIEW_SEVERITIES.has(normalizedSeverity)) return normalizedSeverity;
|
|
89
|
+
}
|
|
90
|
+
if (typeof priority === "string") {
|
|
91
|
+
const normalizedPriority = priority.toUpperCase();
|
|
92
|
+
if (SAFE_REVIEW_PRIORITIES.has(normalizedPriority)) return normalizedPriority;
|
|
93
|
+
const normalizedSeverity = priority.toLowerCase();
|
|
94
|
+
if (SAFE_REVIEW_SEVERITIES.has(normalizedSeverity)) return normalizedSeverity;
|
|
95
|
+
}
|
|
96
|
+
if (typeof priority === "number" && Number.isInteger(priority) && priority >= 0 && priority <= 3) {
|
|
97
|
+
return `P${priority}`;
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
80
102
|
function buildSafeSynopsis(raw: SingleResult, outputRef: TaskResultReceipt["outputRef"]): string {
|
|
81
103
|
const status = getStatus(raw);
|
|
82
104
|
if (raw.modelSubstitutionWarning) {
|
|
@@ -117,19 +139,16 @@ function buildReview(raw: SingleResult): TaskResultReceipt["review"] | undefined
|
|
|
117
139
|
const rawFindings = Array.isArray(data.report_finding) ? data.report_finding : [];
|
|
118
140
|
const findings = rawFindings.slice(0, 20).map(item => {
|
|
119
141
|
const value = item && typeof item === "object" ? (item as Record<string, unknown>) : {};
|
|
120
|
-
const severity =
|
|
121
|
-
typeof value.severity === "string"
|
|
122
|
-
? value.severity
|
|
123
|
-
: typeof value.priority === "string"
|
|
124
|
-
? value.priority
|
|
125
|
-
: undefined;
|
|
142
|
+
const severity = normalizeReviewFindingSeverity(value.severity, value.priority);
|
|
126
143
|
const summaryValue = value.summary ?? value.title ?? value.message ?? value.body ?? "finding";
|
|
127
144
|
return { severity, summary: truncateText(String(summaryValue), 200) ?? "finding" };
|
|
128
145
|
});
|
|
129
146
|
if (!reviewYield && findings.length === 0) return undefined;
|
|
130
147
|
return {
|
|
131
|
-
overallCorrectness:
|
|
148
|
+
overallCorrectness: truncateText(
|
|
132
149
|
typeof reviewYield?.overall_correctness === "string" ? reviewYield.overall_correctness : undefined,
|
|
150
|
+
200,
|
|
151
|
+
),
|
|
133
152
|
findingCount: rawFindings.length,
|
|
134
153
|
findings: findings.length > 0 ? findings : undefined,
|
|
135
154
|
};
|