one-code-extension 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +189 -94
- package/docs/guide/README.md +3 -2
- package/docs/guide/bring-your-claude-code-setup.md +7 -5
- package/docs/guide/configuration.md +7 -5
- package/docs/guide/differences-from-claude-code.md +25 -14
- package/docs/guide/doctor.md +5 -3
- package/docs/guide/hooks.md +20 -6
- package/docs/guide/installation.md +62 -12
- package/docs/guide/permissions-modes-and-auto-mode.md +46 -28
- package/docs/guide/providers-and-models.md +12 -10
- package/docs/guide/reference.md +9 -4
- package/docs/guide/sessions-and-context.md +23 -3
- package/docs/guide/skills-plugins-and-mcp.md +7 -5
- package/docs/guide/subagents-and-workflows.md +13 -11
- package/docs/guide/tasks-and-background-work.md +10 -8
- package/docs/guide/terminal-interface.md +7 -5
- package/docs/guide/tools.md +29 -5
- package/docs/guide/troubleshooting.md +26 -14
- package/docs/guide/windows.md +184 -0
- package/extensions/auto-mode/actions.ts +3 -1
- package/extensions/auto-mode/paths.ts +21 -23
- package/extensions/auto-mode/safety-floor.ts +44 -6
- package/extensions/auto-mode/shell-analysis.ts +25 -10
- package/extensions/auto-mode/transcript.ts +15 -5
- package/extensions/background/index.ts +83 -34
- package/extensions/background/monitor-batch.ts +7 -5
- package/extensions/background/wakeup.ts +18 -24
- package/extensions/bash/background.ts +19 -10
- package/extensions/bash/guards.ts +36 -16
- package/extensions/bash/index.ts +36 -166
- package/extensions/branding/index.ts +35 -4
- package/extensions/btw/index.ts +207 -0
- package/extensions/btw/panel.ts +179 -0
- package/extensions/btw/prompt.ts +45 -0
- package/extensions/clear/index.ts +15 -0
- package/extensions/compaction/fit.ts +9 -6
- package/extensions/doctor/build.ts +22 -2
- package/extensions/doctor/dependencies.ts +63 -31
- package/extensions/doctor/fix-prompt.ts +12 -4
- package/extensions/doctor/index.ts +1 -0
- package/extensions/doctor/models.ts +2 -1
- package/extensions/doctor/report.ts +2 -3
- package/extensions/doctor/update-lookup.ts +19 -11
- package/extensions/effort/index.ts +2 -1
- package/extensions/footer/footer-line.ts +19 -0
- package/extensions/footer/index.ts +25 -5
- package/extensions/hooks/executor.ts +84 -18
- package/extensions/hooks/index.ts +1 -0
- package/extensions/hooks/matcher.ts +23 -9
- package/extensions/hooks/settings.ts +15 -0
- package/extensions/lib/branch-restore.ts +12 -0
- package/extensions/lib/claude-context.ts +2 -3
- package/extensions/lib/claude-settings.ts +19 -0
- package/extensions/lib/command-launch.ts +71 -0
- package/extensions/lib/deferred.ts +173 -33
- package/extensions/lib/git.ts +16 -0
- package/extensions/lib/keys.ts +38 -0
- package/extensions/lib/local-command.ts +112 -0
- package/extensions/lib/model-facts.generated.json +263 -68
- package/extensions/lib/model-policy.ts +13 -0
- package/extensions/lib/model-tier.ts +16 -5
- package/extensions/lib/notifications.ts +547 -53
- package/extensions/lib/paths.ts +130 -2
- package/extensions/lib/pi-ai-estimate.ts +57 -26
- package/extensions/lib/pi-version.ts +1 -1
- package/extensions/lib/process-tree.ts +131 -5
- package/extensions/lib/reminders.ts +80 -27
- package/extensions/lib/scratchpad.ts +11 -10
- package/extensions/lib/shell-spawn.ts +298 -0
- package/extensions/lib/shell-tool.ts +252 -0
- package/extensions/lib/shell-tools.ts +13 -0
- package/extensions/lib/side-call.ts +76 -0
- package/extensions/lib/tui-render.ts +62 -11
- package/extensions/lib/update-check.mjs +155 -0
- package/extensions/lib/usage-bus.ts +1 -1
- package/extensions/lib/which.ts +62 -0
- package/extensions/lib/worktree-channel.ts +18 -0
- package/extensions/lib/worktree-isolation.ts +2 -1
- package/extensions/lsp/client.ts +14 -3
- package/extensions/lsp/index.ts +12 -5
- package/extensions/lsp/install-hints.ts +33 -3
- package/extensions/lsp/plugin-servers.ts +2 -2
- package/extensions/lsp/servers.ts +29 -7
- package/extensions/mcp/index.ts +3 -2
- package/extensions/memory/entries.ts +9 -6
- package/extensions/memory/index.ts +3 -2
- package/extensions/memory/open-external.ts +4 -1
- package/extensions/model-default/index.ts +21 -2
- package/extensions/permissions/index.ts +57 -28
- package/extensions/permissions/matcher.ts +120 -33
- package/extensions/permissions/modes.ts +14 -6
- package/extensions/permissions/powershell-rules.ts +549 -0
- package/extensions/permissions/session-grant.ts +9 -9
- package/extensions/plugins/index.ts +3 -2
- package/extensions/plugins/install/install.ts +1 -1
- package/extensions/powershell/description.ts +139 -0
- package/extensions/powershell/guards.ts +91 -0
- package/extensions/powershell/index.ts +89 -0
- package/extensions/powershell/policy.ts +91 -0
- package/extensions/recap/index.ts +10 -24
- package/extensions/recap/prompt.ts +3 -3
- package/extensions/session-title/index.ts +120 -0
- package/extensions/session-title/title.ts +111 -0
- package/extensions/skill/index.ts +40 -18
- package/extensions/skill/invoke.ts +1 -0
- package/extensions/subagents/hand-back-review.ts +10 -12
- package/extensions/subagents/index.ts +221 -78
- package/extensions/subagents/model-select.ts +75 -7
- package/extensions/system-prompt/environment.ts +14 -1
- package/extensions/system-reminder/index.ts +9 -0
- package/extensions/tasks/index.ts +2 -1
- package/extensions/tool-search/index.ts +41 -20
- package/extensions/workflow/agent-session.ts +6 -4
- package/extensions/workflow/index.ts +28 -5
- package/extensions/worktree/guards.ts +5 -4
- package/extensions/worktree/index.ts +15 -2
- package/extensions/worktree/rewrite.ts +17 -0
- package/package.json +7 -4
- package/types/app.d.ts +18 -2
|
@@ -26,7 +26,7 @@ import { getAgentDir, type ExtensionAPI, type ExtensionContext, type ToolDefinit
|
|
|
26
26
|
import { Type } from "typebox";
|
|
27
27
|
import { SUBAGENT_ACTIONS_CHANNEL, type SubagentActionsPayload } from "../auto-mode/actions.ts";
|
|
28
28
|
import { type AgentDefinition, type AgentSource, agentDirs, discoverAgents } from "./agents.ts";
|
|
29
|
-
import { modelIdentity, modelSpec } from "../lib/model-policy.ts";
|
|
29
|
+
import { modelIdentity, modelSpec, supportsImageInput } from "../lib/model-policy.ts";
|
|
30
30
|
import { MODEL_UNUSABLE_CHANNEL, type ModelUnusableEvent, withoutUnusable } from "../lib/model-unusable.ts";
|
|
31
31
|
import { applicableSubagentDefault, loadSubagentDefault, persistSubagentModel, type SubagentDefault } from "./default-model.ts";
|
|
32
32
|
import {
|
|
@@ -56,12 +56,25 @@ import { emptyUsage, formatStats, type UsageTotals } from "./usage.ts";
|
|
|
56
56
|
import { cleanupWorktree, createWorktree, isGitRepo, type Worktree } from "./worktree.ts";
|
|
57
57
|
import { findGitRoot } from "../lib/git.ts";
|
|
58
58
|
import { registerWorktreeIsolation } from "../lib/worktree-isolation.ts";
|
|
59
|
-
import {
|
|
59
|
+
import {
|
|
60
|
+
AGENT_NOTE,
|
|
61
|
+
agentMessage,
|
|
62
|
+
agentSummary,
|
|
63
|
+
createTaskNotifier,
|
|
64
|
+
type HandBackVerdict,
|
|
65
|
+
handBackPointer,
|
|
66
|
+
handBackWarning,
|
|
67
|
+
oneShotNote,
|
|
68
|
+
sessionOutlivesTurn,
|
|
69
|
+
taskNotification,
|
|
70
|
+
type TaskStatus,
|
|
71
|
+
withReview,
|
|
72
|
+
} from "../lib/notifications.ts";
|
|
60
73
|
import { persistIfLarge, sessionResultsDir } from "../lib/persisted-output.ts";
|
|
61
74
|
import { ccToolRenderers, customMessageText, liveUiCtx, notificationComponent, safeThemeBold, safeThemePaint, truncateLine } from "../lib/tui-render.ts";
|
|
62
75
|
import { deriveActivity, type FinishOutcome, LiveRunRegistry } from "./live-runs.ts";
|
|
63
76
|
import { DELEGATION_STEER } from "./delegation-steer.ts";
|
|
64
|
-
import { awaitHandBackReview
|
|
77
|
+
import { awaitHandBackReview } from "./hand-back-review.ts";
|
|
65
78
|
import type { LiveSink } from "./runner.ts";
|
|
66
79
|
import { recordUsage } from "../lib/usage-bus.ts";
|
|
67
80
|
import { SUBAGENT_DEFAULT_CHANGED_CHANNEL } from "../lib/settings-channels.ts";
|
|
@@ -71,6 +84,7 @@ import { decodeStripKey, type StripKey } from "./panel-keys.ts";
|
|
|
71
84
|
import { reduceShellKey } from "./shell-panel.ts";
|
|
72
85
|
import { trackShellTasks } from "../lib/shell-tasks.ts";
|
|
73
86
|
import { createMarkdownProse } from "./prose.ts";
|
|
87
|
+
import { registerLocalCommand } from "../lib/local-command.ts";
|
|
74
88
|
|
|
75
89
|
/** The catalog shipped in this package: <package>/agents. */
|
|
76
90
|
const BUNDLED_AGENTS_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "agents");
|
|
@@ -195,13 +209,15 @@ const RESIDENT_IDLE_MS = 15 * 60_000;
|
|
|
195
209
|
/** A background agent's process, kept alive after its run so it can be messaged. */
|
|
196
210
|
interface Resident {
|
|
197
211
|
handle: RpcChildHandle;
|
|
212
|
+
/** When the resident was started: its hand-backs report tokens and tool uses cumulatively, so the duration is too. */
|
|
213
|
+
startedAt: number;
|
|
198
214
|
/**
|
|
199
215
|
* FIFO — the head entry handles the next turn_end (initial task, then one per
|
|
200
216
|
* idle-time message). `review` is auto mode's rendered hand-back flag for
|
|
201
217
|
* that turn (undefined when clean or auto mode is off); the handler puts it
|
|
202
218
|
* ahead of the report in its notification.
|
|
203
219
|
*/
|
|
204
|
-
turnHandlers: Array<(outcome: ChildOutcome, review:
|
|
220
|
+
turnHandlers: Array<(outcome: ChildOutcome, review: HandBackVerdict | undefined, stopped: boolean) => void>;
|
|
205
221
|
}
|
|
206
222
|
|
|
207
223
|
export default function subagentsExtension(pi: ExtensionAPI) {
|
|
@@ -232,7 +248,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
232
248
|
/** taskId → kill handle: a resident's kill resolves when it has exited; a blocking run exposes `result`. */
|
|
233
249
|
const liveHandles = new Map<string, { kill(): void | Promise<void>; result?: Promise<unknown> }>();
|
|
234
250
|
/**
|
|
235
|
-
* Task ids the user asked to stop (x / ctrl+x ctrl+k / session teardown). A
|
|
251
|
+
* Task ids the user asked to stop (task_stop / x / ctrl+x ctrl+k / session teardown). A
|
|
236
252
|
* kill still surfaces as an exit, but it neither completed nor failed — so the
|
|
237
253
|
* strip and the notification read this to say "Stopped" instead of the
|
|
238
254
|
* misleading "Completed"/"failed" (TUI-REVIEW L1). Cleared when a run (re)starts.
|
|
@@ -458,7 +474,14 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
458
474
|
const session = sessionModel ? modelSpec(sessionModel) : "(none)";
|
|
459
475
|
const signature = `${session}|${configured?.spec ?? ""}|${configured?.setForContainment ?? ""}|${configured?.source ?? ""}|${unusableModels.size}`;
|
|
460
476
|
if (autoDefaultCache?.signature === signature) return autoDefaultCache.resolution;
|
|
461
|
-
|
|
477
|
+
// The image-modality gate keys off the session model, which is part of the
|
|
478
|
+
// cache signature above, so the cached resolution stays correct per session.
|
|
479
|
+
const resolution = resolveSubagentModel({
|
|
480
|
+
configuredDefault: configured,
|
|
481
|
+
sessionModel,
|
|
482
|
+
available,
|
|
483
|
+
requireImageInput: supportsImageInput(sessionModel),
|
|
484
|
+
});
|
|
462
485
|
autoDefaultCache = { signature, resolution };
|
|
463
486
|
return resolution;
|
|
464
487
|
};
|
|
@@ -481,6 +504,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
481
504
|
defaultModel: resolution.model,
|
|
482
505
|
defaultSource: resolution.source,
|
|
483
506
|
configured,
|
|
507
|
+
requireImageInput: supportsImageInput(sessionModel),
|
|
484
508
|
}),
|
|
485
509
|
scope: "every-turn",
|
|
486
510
|
key: "subagent-models",
|
|
@@ -534,8 +558,12 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
534
558
|
};
|
|
535
559
|
pi.on("agent_start", () => {
|
|
536
560
|
spawnedThisLoop.clear();
|
|
561
|
+
parentBusy = true;
|
|
537
562
|
return undefined;
|
|
538
563
|
});
|
|
564
|
+
pi.on("agent_settled", () => {
|
|
565
|
+
parentBusy = false;
|
|
566
|
+
});
|
|
539
567
|
pi.on("agent_end", (_event, ctx) => {
|
|
540
568
|
const spawned = [...spawnedThisLoop];
|
|
541
569
|
spawnedThisLoop.clear();
|
|
@@ -600,17 +628,18 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
600
628
|
|
|
601
629
|
// --- The subagent panel: strip soft focus + Enter-to-view transcript swap ---
|
|
602
630
|
|
|
603
|
-
/** Stop
|
|
604
|
-
const stopAgent = (taskId: string) => {
|
|
605
|
-
|
|
606
|
-
|
|
631
|
+
/** Stop an agent, recording its persistent id before kill can settle its turn. */
|
|
632
|
+
const stopAgent = (taskId: string, handle = liveHandles.get(taskId)) => {
|
|
633
|
+
// An old task can still await review after the agent has resumed. Its
|
|
634
|
+
// captured handle must not mark the replacement run as stopped.
|
|
635
|
+
if (handle && liveHandles.get(taskId) === handle) stoppedTaskIds.add(taskId);
|
|
636
|
+
return handle?.kill();
|
|
607
637
|
};
|
|
608
638
|
/** Stop every live agent; returns one settle promise per agent (a resident's exit, a blocking run's result). */
|
|
609
639
|
const stopAllAgents = (): Promise<unknown>[] => {
|
|
610
640
|
const exits: Promise<unknown>[] = [];
|
|
611
641
|
for (const [taskId, handle] of liveHandles) {
|
|
612
|
-
|
|
613
|
-
const killed = handle.kill();
|
|
642
|
+
const killed = stopAgent(taskId, handle);
|
|
614
643
|
const settled = handle.result ?? killed;
|
|
615
644
|
if (settled) exits.push(settled);
|
|
616
645
|
}
|
|
@@ -823,10 +852,14 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
823
852
|
return undefined; // typing resumes in the editor, byte included
|
|
824
853
|
}
|
|
825
854
|
if (result.effect === "toAgents") {
|
|
826
|
-
// No agent rows to land on → the byte passes through to the editor,
|
|
827
|
-
// consistent with the initial-entry branch.
|
|
828
855
|
panel.setShellFocus(undefined);
|
|
829
|
-
|
|
856
|
+
if (focusFirstAgentRow()) return { consume: true };
|
|
857
|
+
// No agent rows below the chip: stay on it. Dropping focus here handed
|
|
858
|
+
// the NEXT key to the editor, where ↑ recalls the last prompt — the
|
|
859
|
+
// "sometimes ↓ recalls history" seen on the Windows VM (2026-09-19):
|
|
860
|
+
// ↓ chip, ↓ (nothing below, focus silently gone), ↑ → history.
|
|
861
|
+
panel.setShellFocus({ stage: "chip" });
|
|
862
|
+
return { consume: true };
|
|
830
863
|
}
|
|
831
864
|
if (result.effect === "stopSelected") {
|
|
832
865
|
try {
|
|
@@ -972,7 +1005,10 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
972
1005
|
);
|
|
973
1006
|
}
|
|
974
1007
|
|
|
975
|
-
|
|
1008
|
+
// A hand-back the model already read through task_output (block: true on the
|
|
1009
|
+
// agent's task) is withdrawn — One Code's task_output returns the full report,
|
|
1010
|
+
// so the message and its pointer would only repeat it (see decisions).
|
|
1011
|
+
const notifier = createTaskNotifier(pi, { withdrawOnDelivery: true });
|
|
976
1012
|
/** Every subagent notification; silent during shutdown (a teardown kill is not news). */
|
|
977
1013
|
const notify: typeof notifier = (customType, text, details) => {
|
|
978
1014
|
if (shuttingDown) return;
|
|
@@ -998,10 +1034,10 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
998
1034
|
* the spawning child never saw the message and the main model was told about
|
|
999
1035
|
* work it did not delegate (SUBAGENT-REVIEW L4).
|
|
1000
1036
|
*/
|
|
1001
|
-
const relayToParent = (parentTaskId: string, name: string, message: string, summary?: string) => {
|
|
1037
|
+
const relayToParent = (parentTaskId: string, from: string, name: string, message: string, summary?: string) => {
|
|
1002
1038
|
// Bounded once: the fallback reuses the same body (and persisted file).
|
|
1003
1039
|
const body = boundedMessage(name, message);
|
|
1004
|
-
const toMain = () => announceAgentMessage(name, body
|
|
1040
|
+
const toMain = () => announceAgentMessage(from, name, body);
|
|
1005
1041
|
const parent = residents.get(parentTaskId);
|
|
1006
1042
|
if (!parent || parent.handle.exited()) {
|
|
1007
1043
|
toMain();
|
|
@@ -1011,10 +1047,78 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1011
1047
|
};
|
|
1012
1048
|
|
|
1013
1049
|
const boundedMessage = (name: string, message: string) => bounded(message, `message-${name}-${Date.now()}`, MESSAGE_TO_MAIN_CAP);
|
|
1014
|
-
|
|
1015
|
-
|
|
1050
|
+
/** True between agent_start and agent_settled: a child's mid-run message then lands mid-turn (CC's other opener). */
|
|
1051
|
+
let parentBusy = false;
|
|
1052
|
+
/**
|
|
1053
|
+
* A child's mid-run message as CC's `<agent-message>` (no hand-back preamble:
|
|
1054
|
+
* it is not a report), addressed from the child's task id — the address
|
|
1055
|
+
* SendMessage reaches it by. CC's frame has no slot for the child's own
|
|
1056
|
+
* one-line summary, so it is dropped here (it still labels the relay to a
|
|
1057
|
+
* parent child).
|
|
1058
|
+
*/
|
|
1059
|
+
const announceAgentMessage = (from: string, name: string, body: string) =>
|
|
1060
|
+
notify("subagent-message", agentMessage({ from, body, midTurn: parentBusy }), { taskId: from, name });
|
|
1016
1061
|
/** Relay a child's send_message {to: "main"} into this conversation. */
|
|
1017
|
-
const notifyAgentMessage = (
|
|
1062
|
+
const notifyAgentMessage = (from: string, name: string, message: string) => announceAgentMessage(from, name, boundedMessage(name, message));
|
|
1063
|
+
|
|
1064
|
+
/** The spool a resident's turns are written to (`task_output` and the notification's `<output-file>`). */
|
|
1065
|
+
const outputLogPath = (record: AgentRunRecord): string | undefined =>
|
|
1066
|
+
record.sessionSearchDir ? join(record.sessionSearchDir, "output.log") : undefined;
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* Claude Code's two-message hand-back for a resident agent's finished turn
|
|
1070
|
+
* (its first report, an update, a reply): the report goes out as an
|
|
1071
|
+
* `<agent-message>` from the agent — the preamble, every line indented, auto
|
|
1072
|
+
* mode's review flag above it — and the agent's own `<task-notification>`
|
|
1073
|
+
* follows, its `<result>` a pointer at that message, never the report again.
|
|
1074
|
+
* A turn that failed or was stopped delivered no report, so it is one
|
|
1075
|
+
* notification with the output (and any flag) inline in `<result>`. Both go
|
|
1076
|
+
* through the same notifier, in this order, so a coalesced round keeps the
|
|
1077
|
+
* report ahead of the pointer.
|
|
1078
|
+
*/
|
|
1079
|
+
const notifyHandBack = (opts: {
|
|
1080
|
+
/** The agent's persistent id: `from=` on the message, the SendMessage address. */
|
|
1081
|
+
from: string;
|
|
1082
|
+
/** The registered task this turn is (the agent's own id, or a SendMessage task's). */
|
|
1083
|
+
taskId: string;
|
|
1084
|
+
name: string;
|
|
1085
|
+
toolUseId?: string;
|
|
1086
|
+
outputFile?: string;
|
|
1087
|
+
/** The turn's outcome: its status, tool count and tokens come from here. */
|
|
1088
|
+
outcome: ChildOutcome;
|
|
1089
|
+
/** The turn ended because the user or the model stopped the agent (CC's `killed`). */
|
|
1090
|
+
stopped?: boolean;
|
|
1091
|
+
/** Start of the span `outcome`'s tokens and tool uses cover, for CC's `<duration_ms>`. */
|
|
1092
|
+
startedAt: number;
|
|
1093
|
+
/** The bounded report text (a failed turn's output rides `<result>` instead). */
|
|
1094
|
+
report: string;
|
|
1095
|
+
review: HandBackVerdict | undefined;
|
|
1096
|
+
details?: Record<string, unknown>;
|
|
1097
|
+
}) => {
|
|
1098
|
+
const status: TaskStatus = opts.stopped ? "killed" : opts.outcome.failed ? "failed" : "completed";
|
|
1099
|
+
const warning = opts.review ? handBackWarning(opts.review) : undefined;
|
|
1100
|
+
const details = { ...opts.details, taskId: opts.taskId, name: opts.name, failed: status === "failed", reviewed: opts.review !== undefined };
|
|
1101
|
+
const envelope = (result: string) =>
|
|
1102
|
+
taskNotification({
|
|
1103
|
+
kind: "agent",
|
|
1104
|
+
taskId: opts.taskId,
|
|
1105
|
+
toolUseId: opts.toolUseId,
|
|
1106
|
+
outputFile: opts.outputFile,
|
|
1107
|
+
status,
|
|
1108
|
+
summary: agentSummary(opts.name, status),
|
|
1109
|
+
note: AGENT_NOTE,
|
|
1110
|
+
result,
|
|
1111
|
+
usage: { subagentTokens: opts.outcome.usage.total, toolUses: opts.outcome.toolCalls, durationMs: Date.now() - opts.startedAt },
|
|
1112
|
+
});
|
|
1113
|
+
if (status !== "completed") {
|
|
1114
|
+
notify("subagent-result", envelope(withReview(opts.report, warning)), details);
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
// CC's mid-turn form applies to hand-backs too (observed live: a report
|
|
1118
|
+
// landing while the parent works takes the other opener and the hint).
|
|
1119
|
+
notify("subagent-message", agentMessage({ from: opts.from, body: opts.report, handBack: true, warning, midTurn: parentBusy }), details);
|
|
1120
|
+
notify("subagent-result", envelope(handBackPointer(opts.from, opts.review !== undefined)), details);
|
|
1121
|
+
};
|
|
1018
1122
|
|
|
1019
1123
|
/** A fork's system prompt is persisted beside its session so a later resume can restore it (review S6). */
|
|
1020
1124
|
const FORK_PROMPT_FILE = "system-prompt.md";
|
|
@@ -1135,6 +1239,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1135
1239
|
configuredDefault: applicableSubagentDefault(loadSubagentDefault(os.homedir()), ctx.model),
|
|
1136
1240
|
sessionModel: ctx.model,
|
|
1137
1241
|
available,
|
|
1242
|
+
requireImageInput: supportsImageInput(ctx.model),
|
|
1138
1243
|
});
|
|
1139
1244
|
for (const notice of resolution.notices) notifyModelOnce(ctx, notice);
|
|
1140
1245
|
const modelNotes = subagentModelNotes(resolution);
|
|
@@ -1297,7 +1402,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1297
1402
|
},
|
|
1298
1403
|
sink: live.sink,
|
|
1299
1404
|
onMessageToMain: (message, summary) =>
|
|
1300
|
-
parent ? relayToParent(parent.taskId, request.name, message, summary) : notifyAgentMessage(request.name, message
|
|
1405
|
+
parent ? relayToParent(parent.taskId, record.taskId, request.name, message, summary) : notifyAgentMessage(record.taskId, request.name, message),
|
|
1301
1406
|
extraTools: spawnToolsFor(record),
|
|
1302
1407
|
});
|
|
1303
1408
|
liveHandles.set(record.taskId, handle);
|
|
@@ -1354,7 +1459,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1354
1459
|
"For a single-fact lookup you already know how to run, search directly instead. Once you've delegated work, don't also run it yourself — wait for the result.\n" +
|
|
1355
1460
|
"\n" +
|
|
1356
1461
|
"## How agents run\n" +
|
|
1357
|
-
"Agents run in the background: the call returns immediately with a task id, and you'll be notified when one completes — the agent's report arrives as a
|
|
1462
|
+
"Agents run in the background: the call returns immediately with a task id, and you'll be notified when one completes — the agent's report arrives as a task notification while you keep working, or on its own if you are idle. Never fabricate or predict a pending agent's results — the notification is never something you write yourself; if the user asks before it arrives, say it's still running. Call task_output only if your next step cannot proceed without the result (block=true waits); stop a run with task_stop. Both are deferred — load them with tool_search first. (Exception: in a one-shot print session the call blocks and returns the report directly — no notification follows.)\n" +
|
|
1358
1463
|
"\n" +
|
|
1359
1464
|
"## Usage notes\n" +
|
|
1360
1465
|
"- Give a complete, self-contained task: the agent cannot ask follow-up questions.\n" +
|
|
@@ -1553,6 +1658,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1553
1658
|
configuredDefault: configuredDefault,
|
|
1554
1659
|
sessionModel: ctx.model,
|
|
1555
1660
|
available,
|
|
1661
|
+
requireImageInput: supportsImageInput(ctx.model),
|
|
1556
1662
|
})
|
|
1557
1663
|
: resolveAutoDefault(ctx.model, available, configuredDefault);
|
|
1558
1664
|
if (resolution.unresolved) {
|
|
@@ -1561,18 +1667,20 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1561
1667
|
configuredDefault: configuredDefault,
|
|
1562
1668
|
sessionModel: ctx.model,
|
|
1563
1669
|
available,
|
|
1670
|
+
requireImageInput: supportsImageInput(ctx.model),
|
|
1564
1671
|
});
|
|
1565
1672
|
return {
|
|
1566
1673
|
content: [
|
|
1567
1674
|
{
|
|
1568
1675
|
type: "text",
|
|
1569
1676
|
text:
|
|
1570
|
-
`Unknown model "${resolution.unresolved}" — no available model matches it
|
|
1677
|
+
`${resolution.unresolvedReason ?? `Unknown model "${resolution.unresolved}" — no available model matches it.`}\n\n` +
|
|
1571
1678
|
subagentModelsReminder({
|
|
1572
1679
|
available,
|
|
1573
1680
|
sessionModel: ctx.model,
|
|
1574
1681
|
defaultModel: fallback.model,
|
|
1575
1682
|
defaultSource: fallback.source,
|
|
1683
|
+
requireImageInput: supportsImageInput(ctx.model),
|
|
1576
1684
|
}),
|
|
1577
1685
|
},
|
|
1578
1686
|
],
|
|
@@ -1586,6 +1694,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1586
1694
|
configuredDefault: configuredDefault,
|
|
1587
1695
|
sessionModel: ctx.model,
|
|
1588
1696
|
available,
|
|
1697
|
+
requireImageInput: supportsImageInput(ctx.model),
|
|
1589
1698
|
});
|
|
1590
1699
|
return {
|
|
1591
1700
|
content: [
|
|
@@ -1600,6 +1709,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1600
1709
|
sessionModel: ctx.model,
|
|
1601
1710
|
defaultModel: fallback.model,
|
|
1602
1711
|
defaultSource: fallback.source,
|
|
1712
|
+
requireImageInput: supportsImageInput(ctx.model),
|
|
1603
1713
|
}),
|
|
1604
1714
|
},
|
|
1605
1715
|
],
|
|
@@ -1685,7 +1795,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1685
1795
|
}
|
|
1686
1796
|
}
|
|
1687
1797
|
|
|
1688
|
-
const logPath =
|
|
1798
|
+
const logPath = outputLogPath(p.record);
|
|
1689
1799
|
let lastLogWrite = 0;
|
|
1690
1800
|
let finish!: () => void;
|
|
1691
1801
|
const finished = new Promise<void>((resolve) => {
|
|
@@ -1703,28 +1813,27 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1703
1813
|
// the same way a SendMessage task returns one reply (review S12).
|
|
1704
1814
|
let firstTurnOutput: string | undefined;
|
|
1705
1815
|
const live = trackLiveRun(p.record, p.request);
|
|
1706
|
-
const resident: Resident = { handle: undefined as never, turnHandlers: [] };
|
|
1816
|
+
const resident: Resident = { handle: undefined as never, startedAt: Date.now(), turnHandlers: [] };
|
|
1707
1817
|
const worktreeNote = worktree
|
|
1708
1818
|
? `\n\n(Running in worktree ${worktree.path} — kept while the agent stays resident.)`
|
|
1709
1819
|
: "";
|
|
1710
|
-
resident.turnHandlers.push((outcome, review) => {
|
|
1711
|
-
const stopped = stoppedTaskIds.has(p.record.taskId);
|
|
1820
|
+
resident.turnHandlers.push((outcome, review, stopped) => {
|
|
1712
1821
|
task.status = stopped ? "stopped" : outcome.failed ? "failed" : "completed";
|
|
1713
1822
|
task.finishedAt = Date.now();
|
|
1714
1823
|
firstTurnOutput = outcome.output;
|
|
1715
1824
|
finish();
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
)
|
|
1726
|
-
|
|
1727
|
-
);
|
|
1825
|
+
notifyHandBack({
|
|
1826
|
+
from: p.record.taskId,
|
|
1827
|
+
taskId: p.record.taskId,
|
|
1828
|
+
name: p.record.name,
|
|
1829
|
+
toolUseId: toolCallId,
|
|
1830
|
+
outputFile: logPath,
|
|
1831
|
+
outcome,
|
|
1832
|
+
stopped,
|
|
1833
|
+
startedAt: task.startedAt,
|
|
1834
|
+
report: `${bounded(outcome.output, `${p.record.taskId}-report`, OUTPUT_CAP)}${worktreeNote}`,
|
|
1835
|
+
review,
|
|
1836
|
+
});
|
|
1728
1837
|
});
|
|
1729
1838
|
|
|
1730
1839
|
// Idle reaper: every run is a resident now, so without this a long
|
|
@@ -1778,7 +1887,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1778
1887
|
}
|
|
1779
1888
|
},
|
|
1780
1889
|
sink: live.sink,
|
|
1781
|
-
onMessageToMain: (message
|
|
1890
|
+
onMessageToMain: (message) => notifyAgentMessage(p.record.taskId, p.record.name, message),
|
|
1782
1891
|
extraTools: spawnToolsFor(p.record),
|
|
1783
1892
|
onTurnEnd: (outcome) => {
|
|
1784
1893
|
registry.sessionFileFor(p.record);
|
|
@@ -1792,18 +1901,28 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1792
1901
|
// trailing it. The wait is bounded (hand-back-review.ts) and
|
|
1793
1902
|
// answered synchronously when auto mode is off.
|
|
1794
1903
|
const handler = resident.turnHandlers.shift();
|
|
1904
|
+
// A stop or resume during review must not change this turn's outcome.
|
|
1905
|
+
const stopped = stoppedTaskIds.has(p.record.taskId);
|
|
1795
1906
|
armReaper();
|
|
1796
1907
|
void awaitHandBackReview(pi.events, p.record, outcome.actions).then((review) => {
|
|
1797
1908
|
if (handler) {
|
|
1798
|
-
handler(outcome, review);
|
|
1909
|
+
handler(outcome, review, stopped);
|
|
1799
1910
|
} else {
|
|
1800
1911
|
// A turn nobody is waiting on (e.g. a steer that raced past its
|
|
1801
1912
|
// target turn and ran on its own) must still surface.
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1913
|
+
notifyHandBack({
|
|
1914
|
+
from: p.record.taskId,
|
|
1915
|
+
taskId: p.record.taskId,
|
|
1916
|
+
name: p.record.name,
|
|
1917
|
+
toolUseId: toolCallId,
|
|
1918
|
+
outputFile: logPath,
|
|
1919
|
+
outcome,
|
|
1920
|
+
stopped,
|
|
1921
|
+
startedAt: resident.startedAt,
|
|
1922
|
+
report: bounded(outcome.output, `${p.record.taskId}-update-${Date.now()}`, OUTPUT_CAP),
|
|
1923
|
+
review,
|
|
1924
|
+
details: { update: true },
|
|
1925
|
+
});
|
|
1807
1926
|
}
|
|
1808
1927
|
});
|
|
1809
1928
|
},
|
|
@@ -1828,7 +1947,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1828
1947
|
startedAt: Date.now(),
|
|
1829
1948
|
logPath,
|
|
1830
1949
|
output: () => firstTurnOutput ?? (handle.snapshot().text || lastOutput),
|
|
1831
|
-
stop: () => handle
|
|
1950
|
+
stop: () => stopAgent(p.record.taskId, handle),
|
|
1832
1951
|
resident: () => !handle.exited(),
|
|
1833
1952
|
finished,
|
|
1834
1953
|
};
|
|
@@ -1844,7 +1963,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1844
1963
|
content: [
|
|
1845
1964
|
{
|
|
1846
1965
|
type: "text",
|
|
1847
|
-
text: `${lines.join("\n")}\n\nCompletion (with the agent's report) will arrive as a
|
|
1966
|
+
text: `${lines.join("\n")}\n\nCompletion (with the agent's report) will arrive as a task notification on its own — you do not need to wait for it or poll; keep working. ${PENDING_RESULT_PROHIBITION} Call task_output only if your next step cannot proceed without the result (block=true waits). Stop with task_stop; SendMessage reaches the agent even while it runs (the message is steered into its current turn).`,
|
|
1848
1967
|
},
|
|
1849
1968
|
],
|
|
1850
1969
|
details: { agentRuns: records, background: true },
|
|
@@ -1859,13 +1978,13 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1859
1978
|
title: (a) => (a?.to ? `to ${a.to}${a.summary ? `: ${a.summary}` : ""}` : undefined),
|
|
1860
1979
|
}),
|
|
1861
1980
|
description:
|
|
1862
|
-
'Send a message to a previously spawned agent, addressed by the name from its spawn result (or its task id). To discover the names of running or finished agents, use list_agents (deferred — load it with tool_search select:list_agents). A resident background agent is reached live (mid-turn the message is steered into its current work; when idle it starts a new turn); a finished agent is resumed from its session with full context. Replies arrive as
|
|
1981
|
+
'Send a message to a previously spawned agent, addressed by the name from its spawn result (or its task id). To discover the names of running or finished agents, use list_agents (deferred — load it with tool_search select:list_agents). A resident background agent is reached live (mid-turn the message is steered into its current work; when idle it starts a new turn); a finished agent is resumed from its session with full context. Replies arrive as task notifications. (A subagent reporting back to the main conversation uses its own SendMessage with to: "main".)',
|
|
1863
1982
|
parameters: Type.Object({
|
|
1864
1983
|
to: Type.String({ description: "Agent name (or task id) from a previous Agent run" }),
|
|
1865
1984
|
message: Type.String({ description: "Plain text message for the agent" }),
|
|
1866
1985
|
summary: Type.Optional(Type.String({ description: "5-10 word preview shown in the UI" })),
|
|
1867
1986
|
}),
|
|
1868
|
-
async execute(
|
|
1987
|
+
async execute(toolCallId, params, _signal, _onUpdate, ctx) {
|
|
1869
1988
|
if (params.to === "main") {
|
|
1870
1989
|
// The main conversation's SendMessage only addresses spawned agents; the
|
|
1871
1990
|
// "main" recipient exists only on a subagent's own injected SendMessage.
|
|
@@ -1922,7 +2041,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1922
2041
|
content: [
|
|
1923
2042
|
{
|
|
1924
2043
|
type: "text",
|
|
1925
|
-
text: `${record.name}'s turn had just finished; the message starts its next turn. The reply will arrive as a
|
|
2044
|
+
text: `${record.name}'s turn had just finished; the message starts its next turn. The reply will arrive as a task notification from ${record.name}. ${PENDING_RESULT_PROHIBITION}`,
|
|
1926
2045
|
},
|
|
1927
2046
|
],
|
|
1928
2047
|
details: { agentRuns: [record] },
|
|
@@ -1947,21 +2066,31 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1947
2066
|
status: "running",
|
|
1948
2067
|
startedAt: Date.now(),
|
|
1949
2068
|
output: () => replyOutput || resident.handle.snapshot().text,
|
|
1950
|
-
stop: () => resident.handle
|
|
2069
|
+
stop: () => stopAgent(record.taskId, resident.handle),
|
|
1951
2070
|
resident: () => !resident.handle.exited(),
|
|
1952
2071
|
finished,
|
|
1953
2072
|
};
|
|
1954
|
-
resident.turnHandlers.push((outcome, review) => {
|
|
1955
|
-
task.status = outcome.failed ? "failed" : "completed";
|
|
2073
|
+
resident.turnHandlers.push((outcome, review, stopped) => {
|
|
2074
|
+
task.status = stopped ? "stopped" : outcome.failed ? "failed" : "completed";
|
|
1956
2075
|
task.finishedAt = Date.now();
|
|
1957
2076
|
replyOutput = outcome.output;
|
|
1958
2077
|
finish();
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2078
|
+
// `<task-id>` is this message's task (what task_output resolves); `from=`
|
|
2079
|
+
// stays the agent's id, the address a further SendMessage uses. The
|
|
2080
|
+
// tracker's tokens and tool uses span the resident's life, so the
|
|
2081
|
+
// duration does too.
|
|
2082
|
+
notifyHandBack({
|
|
2083
|
+
from: record.taskId,
|
|
2084
|
+
taskId,
|
|
2085
|
+
name: record.name,
|
|
2086
|
+
toolUseId: toolCallId,
|
|
2087
|
+
outputFile: outputLogPath(record),
|
|
2088
|
+
outcome,
|
|
2089
|
+
stopped,
|
|
2090
|
+
startedAt: resident.startedAt,
|
|
2091
|
+
report: bounded(outcome.output, `${taskId}-reply`, OUTPUT_CAP),
|
|
2092
|
+
review,
|
|
2093
|
+
});
|
|
1965
2094
|
});
|
|
1966
2095
|
pi.events.emit(TASK_REGISTER_CHANNEL, task);
|
|
1967
2096
|
void resident.handle.send(params.message);
|
|
@@ -1973,7 +2102,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
1973
2102
|
content: [
|
|
1974
2103
|
{
|
|
1975
2104
|
type: "text",
|
|
1976
|
-
text: `Message sent to resident agent ${record.name} (task ${taskId}). The reply will arrive as a
|
|
2105
|
+
text: `Message sent to resident agent ${record.name} (task ${taskId}). The reply will arrive as a task notification on its own; inspect with task_output. ${PENDING_RESULT_PROHIBITION}`,
|
|
1977
2106
|
},
|
|
1978
2107
|
],
|
|
1979
2108
|
details: { agentRuns: [record], taskId },
|
|
@@ -2061,7 +2190,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
2061
2190
|
thinking: record.thinking,
|
|
2062
2191
|
onProgress: (toolCalls, _text, usage) => live.progress(toolCalls, usage),
|
|
2063
2192
|
sink: live.sink,
|
|
2064
|
-
onMessageToMain: (message
|
|
2193
|
+
onMessageToMain: (message) => notifyAgentMessage(record.taskId, record.name, message),
|
|
2065
2194
|
extraTools: spawnToolsFor(record),
|
|
2066
2195
|
});
|
|
2067
2196
|
liveHandles.set(record.taskId, handle);
|
|
@@ -2074,23 +2203,29 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
2074
2203
|
status: "running",
|
|
2075
2204
|
startedAt: Date.now(),
|
|
2076
2205
|
output: () => handle.snapshot().text,
|
|
2077
|
-
stop: () => handle
|
|
2206
|
+
stop: () => stopAgent(record.taskId, handle),
|
|
2078
2207
|
finished,
|
|
2079
2208
|
};
|
|
2080
2209
|
pi.events.emit(TASK_REGISTER_CHANNEL, task);
|
|
2081
2210
|
|
|
2082
2211
|
void handle.result.then((outcome) => {
|
|
2083
2212
|
runningIds.delete(record.taskId);
|
|
2084
|
-
|
|
2085
|
-
|
|
2213
|
+
const stopped = stoppedTaskIds.has(record.taskId);
|
|
2214
|
+
live.finish(stopped ? "stopped" : Boolean(outcome.failed));
|
|
2215
|
+
task.status = stopped ? "stopped" : outcome.failed ? "failed" : "completed";
|
|
2086
2216
|
task.finishedAt = Date.now();
|
|
2087
2217
|
finish();
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2218
|
+
notifyHandBack({
|
|
2219
|
+
from: record.taskId,
|
|
2220
|
+
taskId,
|
|
2221
|
+
name: record.name,
|
|
2222
|
+
toolUseId: toolCallId,
|
|
2223
|
+
outcome,
|
|
2224
|
+
stopped,
|
|
2225
|
+
startedAt: task.startedAt,
|
|
2226
|
+
report: `${relocationNote}${bounded(outcome.output, `${taskId}-reply`, OUTPUT_CAP)}`,
|
|
2227
|
+
review: undefined,
|
|
2228
|
+
});
|
|
2094
2229
|
});
|
|
2095
2230
|
|
|
2096
2231
|
spawnedThisLoop.add(record.taskId); // arm the anti-fabrication backstop (WEAK-MODEL-REVIEW H1)
|
|
@@ -2098,7 +2233,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
2098
2233
|
content: [
|
|
2099
2234
|
{
|
|
2100
2235
|
type: "text",
|
|
2101
|
-
text: `Message sent to ${record.name} (task ${taskId}). The reply will arrive as a
|
|
2236
|
+
text: `Message sent to ${record.name} (task ${taskId}). The reply will arrive as a task notification on its own; inspect with task_output. ${PENDING_RESULT_PROHIBITION}`,
|
|
2102
2237
|
},
|
|
2103
2238
|
],
|
|
2104
2239
|
details: { agentRuns: [record], taskId },
|
|
@@ -2146,9 +2281,9 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
2146
2281
|
});
|
|
2147
2282
|
pi.events.emit(DEFER_CHANNEL, { name: "list_agents", keywords: ["agents", "list", "running", "spawned", "subagents", "who"] });
|
|
2148
2283
|
|
|
2149
|
-
pi
|
|
2284
|
+
registerLocalCommand(pi, "agents", {
|
|
2150
2285
|
description: "Open the live subagent panel, or list available agents",
|
|
2151
|
-
handler: async (
|
|
2286
|
+
handler: async (args, ctx) => {
|
|
2152
2287
|
// With live children, focus the strip on the newest and open its view
|
|
2153
2288
|
// (Claude Code's agent panel); otherwise there is nothing running, so
|
|
2154
2289
|
// list the catalog the way the tool's action:"list" does.
|
|
@@ -2172,6 +2307,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
2172
2307
|
configuredDefault: applicable,
|
|
2173
2308
|
sessionModel: ctx.model,
|
|
2174
2309
|
available,
|
|
2310
|
+
requireImageInput: supportsImageInput(ctx.model),
|
|
2175
2311
|
});
|
|
2176
2312
|
ctx.ui.notify(
|
|
2177
2313
|
[
|
|
@@ -2208,20 +2344,27 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
2208
2344
|
}
|
|
2209
2345
|
|
|
2210
2346
|
const available = usableModels(ctx);
|
|
2211
|
-
const resolution = resolveSubagentModel({
|
|
2347
|
+
const resolution = resolveSubagentModel({
|
|
2348
|
+
requested: spec,
|
|
2349
|
+
sessionModel: ctx.model,
|
|
2350
|
+
available,
|
|
2351
|
+
requireImageInput: supportsImageInput(ctx.model),
|
|
2352
|
+
});
|
|
2212
2353
|
if (resolution.unresolved) {
|
|
2213
2354
|
const fallback = resolveSubagentModel({
|
|
2214
2355
|
configuredDefault: applicableSubagentDefault(loadSubagentDefault(os.homedir()), ctx.model),
|
|
2215
2356
|
sessionModel: ctx.model,
|
|
2216
2357
|
available,
|
|
2358
|
+
requireImageInput: supportsImageInput(ctx.model),
|
|
2217
2359
|
});
|
|
2218
2360
|
ctx.ui.notify(
|
|
2219
|
-
`No available model matches "${spec}"
|
|
2361
|
+
`${resolution.unresolvedReason ?? `No available model matches "${spec}".`}\n\n` +
|
|
2220
2362
|
subagentModelsReminder({
|
|
2221
2363
|
available,
|
|
2222
2364
|
sessionModel: ctx.model,
|
|
2223
2365
|
defaultModel: fallback.model,
|
|
2224
2366
|
defaultSource: fallback.source,
|
|
2367
|
+
requireImageInput: supportsImageInput(ctx.model),
|
|
2225
2368
|
}),
|
|
2226
2369
|
"error",
|
|
2227
2370
|
);
|
|
@@ -2249,7 +2392,7 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
2249
2392
|
ctx.ui.notify(`Subagent default set to "${spec}" (saved to ~/.onecode/settings.json).`, "info");
|
|
2250
2393
|
};
|
|
2251
2394
|
|
|
2252
|
-
pi
|
|
2395
|
+
registerLocalCommand(pi, "subagent", {
|
|
2253
2396
|
description: "Set the default model for subagent/workflow runs: /subagent [provider/model-id|inherit|status|clear]",
|
|
2254
2397
|
getArgumentCompletions: (prefix) =>
|
|
2255
2398
|
["inherit", "status", "clear"]
|