@duckmind/dm-darwin-x64 0.13.6 → 0.13.8
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/dm +0 -0
- package/extensions/.dm-extensions.json +26 -2
- package/extensions/dm-phone/README.md +23 -0
- package/extensions/dm-phone/index.ts +12 -0
- package/extensions/dm-phone/node_modules/.package-lock.json +29 -0
- package/extensions/dm-phone/node_modules/ws/LICENSE +20 -0
- package/extensions/dm-phone/node_modules/ws/README.md +548 -0
- package/extensions/dm-phone/node_modules/ws/browser.js +8 -0
- package/extensions/dm-phone/node_modules/ws/index.js +22 -0
- package/extensions/dm-phone/node_modules/ws/lib/buffer-util.js +131 -0
- package/extensions/dm-phone/node_modules/ws/lib/constants.js +19 -0
- package/extensions/dm-phone/node_modules/ws/lib/event-target.js +292 -0
- package/extensions/dm-phone/node_modules/ws/lib/extension.js +203 -0
- package/extensions/dm-phone/node_modules/ws/lib/limiter.js +55 -0
- package/extensions/dm-phone/node_modules/ws/lib/permessage-deflate.js +528 -0
- package/extensions/dm-phone/node_modules/ws/lib/receiver.js +706 -0
- package/extensions/dm-phone/node_modules/ws/lib/sender.js +602 -0
- package/extensions/dm-phone/node_modules/ws/lib/stream.js +161 -0
- package/extensions/dm-phone/node_modules/ws/lib/subprotocol.js +62 -0
- package/extensions/dm-phone/node_modules/ws/lib/validation.js +152 -0
- package/extensions/dm-phone/node_modules/ws/lib/websocket-server.js +554 -0
- package/extensions/dm-phone/node_modules/ws/lib/websocket.js +1393 -0
- package/extensions/dm-phone/node_modules/ws/package.json +70 -0
- package/extensions/dm-phone/node_modules/ws/wrapper.mjs +21 -0
- package/extensions/dm-phone/package-lock.json +66 -0
- package/extensions/dm-phone/package.json +35 -0
- package/extensions/dm-phone/phone-session-pool.ts +8 -0
- package/extensions/dm-phone/public/app/attachments.js +233 -0
- package/extensions/dm-phone/public/app/autocomplete-controller.js +81 -0
- package/extensions/dm-phone/public/app/autocomplete.js +135 -0
- package/extensions/dm-phone/public/app/bindings.js +178 -0
- package/extensions/dm-phone/public/app/command-catalog.js +76 -0
- package/extensions/dm-phone/public/app/commands.js +370 -0
- package/extensions/dm-phone/public/app/constants.js +60 -0
- package/extensions/dm-phone/public/app/formatters.js +131 -0
- package/extensions/dm-phone/public/app/handlers.js +442 -0
- package/extensions/dm-phone/public/app/main.js +6 -0
- package/extensions/dm-phone/public/app/markdown.js +105 -0
- package/extensions/dm-phone/public/app/messages.js +418 -0
- package/extensions/dm-phone/public/app/sheet-actions.js +113 -0
- package/extensions/dm-phone/public/app/sheet-navigation.js +19 -0
- package/extensions/dm-phone/public/app/sheets-view.js +272 -0
- package/extensions/dm-phone/public/app/state.js +95 -0
- package/extensions/dm-phone/public/app/tool-rendering.js +562 -0
- package/extensions/dm-phone/public/app/transport.js +176 -0
- package/extensions/dm-phone/public/app/ui.js +409 -0
- package/extensions/dm-phone/public/app.js +1 -0
- package/extensions/dm-phone/public/icon.svg +15 -0
- package/extensions/dm-phone/public/index.html +147 -0
- package/extensions/dm-phone/public/manifest.webmanifest +17 -0
- package/extensions/dm-phone/public/styles.css +1139 -0
- package/extensions/dm-phone/public/sw.js +78 -0
- package/extensions/dm-phone/src/extension/phone-args.ts +121 -0
- package/extensions/dm-phone/src/extension/phone-paths.ts +250 -0
- package/extensions/dm-phone/src/extension/phone-quota.ts +188 -0
- package/extensions/dm-phone/src/extension/phone-runtime.ts +154 -0
- package/extensions/dm-phone/src/extension/phone-server-runtime.ts +1217 -0
- package/extensions/dm-phone/src/extension/phone-sessions.ts +139 -0
- package/extensions/dm-phone/src/extension/phone-static.ts +30 -0
- package/extensions/dm-phone/src/extension/phone-tailscale.ts +148 -0
- package/extensions/dm-phone/src/extension/phone-theme.ts +85 -0
- package/extensions/dm-phone/src/extension/register-phone-child-extension.ts +112 -0
- package/extensions/dm-phone/src/extension/register-phone-extension.ts +106 -0
- package/extensions/dm-phone/src/extension/types.ts +73 -0
- package/extensions/dm-phone/src/session-pool/parent-session-worker.ts +881 -0
- package/extensions/dm-phone/src/session-pool/session-pool.ts +470 -0
- package/extensions/dm-phone/src/session-pool/session-worker.ts +734 -0
- package/extensions/dm-phone/src/session-pool/types.ts +105 -0
- package/extensions/dm-phone/src/session-pool/utils.ts +23 -0
- package/extensions/dm-subagents/agent-management.ts +15 -6
- package/extensions/dm-subagents/agent-manager-detail.ts +12 -2
- package/extensions/dm-subagents/agent-manager-edit.ts +75 -23
- package/extensions/dm-subagents/agent-manager-list.ts +9 -2
- package/extensions/dm-subagents/agent-manager.ts +199 -11
- package/extensions/dm-subagents/agents.ts +315 -20
- package/extensions/dm-ultrathink/README.md +5 -0
- package/extensions/dm-ultrathink/src/naming.ts +75 -3
- package/package.json +1 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { WebSocket } from "ws";
|
|
2
|
+
|
|
3
|
+
export type SessionKind = "parent" | "parallel";
|
|
4
|
+
|
|
5
|
+
export type SessionSummary = {
|
|
6
|
+
id: string;
|
|
7
|
+
kind: SessionKind;
|
|
8
|
+
sessionId: string | null;
|
|
9
|
+
sessionFile: string | null;
|
|
10
|
+
sessionName: string | null;
|
|
11
|
+
label: string;
|
|
12
|
+
secondaryLabel: string;
|
|
13
|
+
firstUserPreview: string | null;
|
|
14
|
+
lastUserPreview: string | null;
|
|
15
|
+
model: { id: string; name: string; provider: string } | null;
|
|
16
|
+
isRunning: boolean;
|
|
17
|
+
isStreaming: boolean;
|
|
18
|
+
isCompacting: boolean;
|
|
19
|
+
messageCount: number;
|
|
20
|
+
pendingMessageCount: number;
|
|
21
|
+
hasPendingUiRequest: boolean;
|
|
22
|
+
lastError: string;
|
|
23
|
+
lastActivityAt: number;
|
|
24
|
+
childPid: number | null;
|
|
25
|
+
cwd?: string | null;
|
|
26
|
+
mirrorsCli?: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type PendingRequest = {
|
|
30
|
+
resolve: (value: any) => void;
|
|
31
|
+
reject: (error: Error) => void;
|
|
32
|
+
timer: NodeJS.Timeout;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type PendingClientResponse = {
|
|
36
|
+
ws: WebSocket;
|
|
37
|
+
responseCommand?: string;
|
|
38
|
+
responseData?: Record<string, unknown>;
|
|
39
|
+
onSuccess?: (payload: any) => void;
|
|
40
|
+
onError?: (payload: any) => void;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type SessionSnapshot = {
|
|
44
|
+
state: any;
|
|
45
|
+
messages: any[];
|
|
46
|
+
commands: any[];
|
|
47
|
+
liveAssistantMessage: any;
|
|
48
|
+
liveTools: any[];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type ClientState = {
|
|
52
|
+
activeSessionId: string | null;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type SessionWorkerOptions<TWorker> = {
|
|
56
|
+
cwd: string;
|
|
57
|
+
send: (ws: WebSocket, payload: unknown) => void;
|
|
58
|
+
onActivity: () => void;
|
|
59
|
+
onStateChange: () => void;
|
|
60
|
+
onEnvelope: (worker: TWorker, envelope: any) => void;
|
|
61
|
+
shouldAutoRestart: (worker: TWorker) => boolean;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type SessionStatus = {
|
|
65
|
+
childRunning: boolean;
|
|
66
|
+
cwd: string;
|
|
67
|
+
previousCwd: string | null;
|
|
68
|
+
isStreaming: boolean;
|
|
69
|
+
isCompacting: boolean;
|
|
70
|
+
lastError: string;
|
|
71
|
+
childPid: number | null;
|
|
72
|
+
sessionWorkerId: string;
|
|
73
|
+
sessionKind: SessionKind;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export interface SessionController {
|
|
77
|
+
id: string;
|
|
78
|
+
kind: SessionKind;
|
|
79
|
+
cwd: string;
|
|
80
|
+
previousCwd: string | null;
|
|
81
|
+
currentSessionFile: string | null;
|
|
82
|
+
lastError: string;
|
|
83
|
+
lastActivityAt: number;
|
|
84
|
+
pendingUiRequest: any;
|
|
85
|
+
ensureStarted(startOptions?: { sessionFile?: string | null }): Promise<void>;
|
|
86
|
+
request(command: Record<string, unknown>, timeoutMs?: number): Promise<any>;
|
|
87
|
+
refreshCachedSnapshot(timeoutMs?: number): Promise<SessionSnapshot>;
|
|
88
|
+
getSnapshot(): Promise<SessionSnapshot>;
|
|
89
|
+
sendClientCommand(command: Record<string, unknown>, meta?: PendingClientResponse): Promise<string | undefined>;
|
|
90
|
+
reload(): Promise<void>;
|
|
91
|
+
dispose(): Promise<void>;
|
|
92
|
+
getStatus(): SessionStatus;
|
|
93
|
+
getSummary(): SessionSummary;
|
|
94
|
+
getCachedSnapshot(): SessionSnapshot;
|
|
95
|
+
setTrackedCwd?(cwd: string, previousCwd?: string | null): void;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type PhoneSessionPoolOptions = {
|
|
99
|
+
cwd: string;
|
|
100
|
+
send: (ws: WebSocket, payload: unknown) => void;
|
|
101
|
+
onActivity: () => void;
|
|
102
|
+
buildStatusMeta: () => Record<string, unknown>;
|
|
103
|
+
createDefaultSession: () => SessionController;
|
|
104
|
+
createParallelSession: (sessionFile?: string | null) => SessionController;
|
|
105
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function contentToPreviewText(content: unknown): string {
|
|
2
|
+
if (typeof content === "string") {
|
|
3
|
+
return content.replace(/\s+/g, " ").trim();
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
if (!Array.isArray(content)) {
|
|
7
|
+
return "";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return content
|
|
11
|
+
.map((part: any) => {
|
|
12
|
+
if (part?.type === "text") return part.text || "";
|
|
13
|
+
if (part?.type === "image") return "[image]";
|
|
14
|
+
return "";
|
|
15
|
+
})
|
|
16
|
+
.join(" ")
|
|
17
|
+
.replace(/\s+/g, " ")
|
|
18
|
+
.trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function shortId(value: unknown): string {
|
|
22
|
+
return String(value || "").trim().slice(0, 8);
|
|
23
|
+
}
|
|
@@ -35,13 +35,18 @@ function parseCsv(value: string): string[] {
|
|
|
35
35
|
return [...new Set(value.split(",").map((v) => v.trim()).filter(Boolean))];
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function configObject(config: unknown): Record<string, unknown
|
|
38
|
+
function configObject(config: unknown): { value?: Record<string, unknown>; error?: string } {
|
|
39
39
|
let val = config;
|
|
40
40
|
if (typeof val === "string") {
|
|
41
|
-
try {
|
|
41
|
+
try {
|
|
42
|
+
val = JSON.parse(val);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
return { error: `config must be valid JSON: ${message}` };
|
|
46
|
+
}
|
|
42
47
|
}
|
|
43
|
-
if (!val || typeof val !== "object" || Array.isArray(val)) return
|
|
44
|
-
return val as Record<string, unknown
|
|
48
|
+
if (!val || typeof val !== "object" || Array.isArray(val)) return {};
|
|
49
|
+
return { value: val as Record<string, unknown> };
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
function hasKey(obj: Record<string, unknown>, key: string): boolean {
|
|
@@ -383,7 +388,9 @@ export function handleGet(params: ManagementParams, ctx: ManagementContext): Age
|
|
|
383
388
|
}
|
|
384
389
|
|
|
385
390
|
export function handleCreate(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
386
|
-
const
|
|
391
|
+
const parsedConfig = configObject(params.config);
|
|
392
|
+
if (parsedConfig.error) return result(parsedConfig.error, true);
|
|
393
|
+
const cfg = parsedConfig.value;
|
|
387
394
|
if (!cfg) return result("config required for create.", true);
|
|
388
395
|
if (typeof cfg.name !== "string" || !cfg.name.trim()) return result("config.name is required and must be a non-empty string.", true);
|
|
389
396
|
if (typeof cfg.description !== "string" || !cfg.description.trim()) return result("config.description is required and must be a non-empty string.", true);
|
|
@@ -427,7 +434,9 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
427
434
|
export function handleUpdate(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
428
435
|
if (!params.agent && !params.chainName) return result("Specify 'agent' or 'chainName' for update.", true);
|
|
429
436
|
if (params.agent && params.chainName) return result("Specify either 'agent' or 'chainName', not both.", true);
|
|
430
|
-
const
|
|
437
|
+
const parsedConfig = configObject(params.config);
|
|
438
|
+
if (parsedConfig.error) return result(parsedConfig.error, true);
|
|
439
|
+
const cfg = parsedConfig.value;
|
|
431
440
|
if (!cfg) return result("config required for update.", true);
|
|
432
441
|
const warnings: string[] = [];
|
|
433
442
|
if (params.agent) {
|
|
@@ -61,6 +61,10 @@ function buildDetailLines(
|
|
|
61
61
|
const maxSubagentDepth = agent.maxSubagentDepth !== undefined ? String(agent.maxSubagentDepth) : "(default)";
|
|
62
62
|
|
|
63
63
|
lines.push(renderFieldLine("Model:", agent.model ?? "default", contentWidth, theme));
|
|
64
|
+
if (agent.override) {
|
|
65
|
+
const overrideLabel = `${agent.override.scope} · ${formatPath(agent.override.path)}`;
|
|
66
|
+
lines.push(renderFieldLine("Override:", overrideLabel, contentWidth, theme));
|
|
67
|
+
}
|
|
64
68
|
lines.push(renderFieldLine("Thinking:", agent.thinking ?? "off", contentWidth, theme));
|
|
65
69
|
lines.push(renderFieldLine("Tools:", tools, contentWidth, theme));
|
|
66
70
|
lines.push(renderFieldLine("MCP:", mcp, contentWidth, theme));
|
|
@@ -131,7 +135,11 @@ export function renderDetail(
|
|
|
131
135
|
theme: Theme,
|
|
132
136
|
): string[] {
|
|
133
137
|
const lines: string[] = [];
|
|
134
|
-
const scopeBadge = agent.source === "builtin"
|
|
138
|
+
const scopeBadge = agent.source === "builtin"
|
|
139
|
+
? (agent.override ? `[builtin+${agent.override.scope}]` : "[builtin]")
|
|
140
|
+
: agent.source === "project"
|
|
141
|
+
? "[proj]"
|
|
142
|
+
: "[user]";
|
|
135
143
|
const headerText = ` ${agent.name} ${scopeBadge} ${formatPath(agent.filePath)} `;
|
|
136
144
|
lines.push(renderHeader(headerText, width, theme));
|
|
137
145
|
lines.push(row("", width, theme));
|
|
@@ -152,7 +160,9 @@ export function renderDetail(
|
|
|
152
160
|
lines.push(row(scrollInfo ? ` ${theme.fg("dim", scrollInfo)}` : "", width, theme));
|
|
153
161
|
|
|
154
162
|
const footer = agent.source === "builtin"
|
|
155
|
-
?
|
|
163
|
+
? agent.override
|
|
164
|
+
? " [l]aunch [e]dit override [v] raw/resolved [↑↓] scroll [esc] back "
|
|
165
|
+
: " [l]aunch [e]create override [v] raw/resolved [↑↓] scroll [esc] back "
|
|
156
166
|
: " [l]aunch [e]dit [v] raw/resolved [↑↓] scroll [esc] back ";
|
|
157
167
|
lines.push(renderFooter(footer, width, theme));
|
|
158
168
|
return lines;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
|
|
3
|
-
import type { AgentConfig } from "./agents.ts";
|
|
3
|
+
import type { AgentConfig, BuiltinAgentOverrideBase } from "./agents.ts";
|
|
4
4
|
import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "./text-editor.ts";
|
|
5
5
|
import type { TextEditorState } from "./text-editor.ts";
|
|
6
6
|
import { pad, row, renderHeader, renderFooter, formatScrollInfo } from "./render-helpers.ts";
|
|
@@ -8,30 +8,70 @@ import { pad, row, renderHeader, renderFooter, formatScrollInfo } from "./render
|
|
|
8
8
|
export interface ModelInfo { provider: string; id: string; fullId: string; }
|
|
9
9
|
export interface SkillInfo { name: string; source: string; description?: string; }
|
|
10
10
|
export type EditScreen = "edit" | "edit-field" | "edit-prompt";
|
|
11
|
+
export type EditField = typeof FIELD_ORDER[number];
|
|
12
|
+
|
|
11
13
|
export interface EditState {
|
|
12
14
|
draft: AgentConfig; isNew: boolean; fieldIndex: number; fieldMode: "text" | "model" | "thinking" | "skills" | null;
|
|
13
15
|
fieldEditor: TextEditorState; promptEditor: TextEditorState; modelSearchQuery: string; modelCursor: number; filteredModels: ModelInfo[];
|
|
14
16
|
thinkingCursor: number; skillSearchQuery: string; skillCursor: number; filteredSkills: SkillInfo[]; skillSelected: Set<string>; error?: string;
|
|
17
|
+
fields: EditField[];
|
|
18
|
+
title?: string;
|
|
19
|
+
overrideBase?: BuiltinAgentOverrideBase;
|
|
20
|
+
}
|
|
21
|
+
export interface EditInputResult { action?: "save" | "discard" | "delete"; nextScreen?: EditScreen; }
|
|
22
|
+
export interface CreateEditStateOptions {
|
|
23
|
+
fields?: EditField[];
|
|
24
|
+
title?: string;
|
|
25
|
+
overrideBase?: BuiltinAgentOverrideBase;
|
|
15
26
|
}
|
|
16
|
-
export interface EditInputResult { action?: "save" | "discard"; nextScreen?: EditScreen; }
|
|
17
27
|
|
|
18
28
|
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
|
|
19
29
|
const FIELD_ORDER = ["name", "description", "model", "fallbackModels", "thinking", "tools", "extensions", "skills", "output", "reads", "progress", "interactive", "prompt"] as const;
|
|
20
|
-
type EditField = typeof FIELD_ORDER[number];
|
|
21
30
|
type ThinkingLevel = typeof THINKING_LEVELS[number];
|
|
22
31
|
const PROMPT_VIEWPORT_HEIGHT = 16;
|
|
23
32
|
const MODEL_SELECTOR_HEIGHT = 10;
|
|
24
33
|
const SKILL_SELECTOR_HEIGHT = 10;
|
|
25
34
|
|
|
26
|
-
function formatTools(draft: AgentConfig): string { const tools = [...(draft.tools ?? []), ...(draft.mcpDirectTools ?? []).map((tool) => `mcp:${tool}`)]; return tools.length > 0 ? tools.join(", ") : ""; }
|
|
35
|
+
function formatTools(draft: Pick<AgentConfig, "tools" | "mcpDirectTools">): string { const tools = [...(draft.tools ?? []), ...(draft.mcpDirectTools ?? []).map((tool) => `mcp:${tool}`)]; return tools.length > 0 ? tools.join(", ") : ""; }
|
|
36
|
+
function toolList(draft: Pick<AgentConfig, "tools" | "mcpDirectTools">): string[] | undefined { const tools = [...(draft.tools ?? []), ...(draft.mcpDirectTools ?? []).map((tool) => `mcp:${tool}`)]; return tools.length > 0 ? tools : undefined; }
|
|
27
37
|
function parseTools(value: string): { tools?: string[]; mcp?: string[] } { const items = value.split(",").map((item) => item.trim()).filter((item) => item.length > 0); const tools: string[] = []; const mcp: string[] = []; for (const item of items) { if (item.startsWith("mcp:")) { const name = item.slice(4).trim(); if (name) mcp.push(name); } else { tools.push(item); } } return { tools: tools.length > 0 ? tools : undefined, mcp: mcp.length > 0 ? mcp : undefined }; }
|
|
28
38
|
function parseCommaList(value: string): string[] | undefined { const items = value.split(",").map((item) => item.trim()).filter((item) => item.length > 0); return items.length > 0 ? items : undefined; }
|
|
39
|
+
function arraysEqual(a: string[] | undefined, b: string[] | undefined): boolean { if (!a && !b) return true; if (!a || !b || a.length !== b.length) return false; for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false; return true; }
|
|
40
|
+
|
|
41
|
+
function fieldValueMatchesBase(field: EditField, state: EditState): boolean {
|
|
42
|
+
const base = state.overrideBase;
|
|
43
|
+
if (!base) return false;
|
|
44
|
+
switch (field) {
|
|
45
|
+
case "model": return state.draft.model === base.model;
|
|
46
|
+
case "fallbackModels": return arraysEqual(state.draft.fallbackModels, base.fallbackModels);
|
|
47
|
+
case "thinking": return state.draft.thinking === base.thinking;
|
|
48
|
+
case "tools": return arraysEqual(toolList(state.draft), toolList(base));
|
|
49
|
+
case "skills": return arraysEqual(state.draft.skills, base.skills);
|
|
50
|
+
case "prompt": return state.draft.systemPrompt === base.systemPrompt;
|
|
51
|
+
default: return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
29
54
|
|
|
30
|
-
|
|
55
|
+
function resetFieldToBase(field: EditField, state: EditState): void {
|
|
56
|
+
const base = state.overrideBase;
|
|
57
|
+
if (!base) return;
|
|
58
|
+
switch (field) {
|
|
59
|
+
case "model": state.draft.model = base.model; break;
|
|
60
|
+
case "fallbackModels": state.draft.fallbackModels = base.fallbackModels ? [...base.fallbackModels] : undefined; break;
|
|
61
|
+
case "thinking": state.draft.thinking = base.thinking; break;
|
|
62
|
+
case "tools": state.draft.tools = base.tools ? [...base.tools] : undefined; state.draft.mcpDirectTools = base.mcpDirectTools ? [...base.mcpDirectTools] : undefined; break;
|
|
63
|
+
case "skills": state.draft.skills = base.skills ? [...base.skills] : undefined; break;
|
|
64
|
+
case "prompt": state.draft.systemPrompt = base.systemPrompt; state.promptEditor = createEditorState(base.systemPrompt); break;
|
|
65
|
+
default: break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function createEditState(draft: AgentConfig, isNew: boolean, models: ModelInfo[], skills: SkillInfo[], options: CreateEditStateOptions = {}): EditState {
|
|
31
70
|
return {
|
|
32
71
|
draft: { ...draft, tools: draft.tools ? [...draft.tools] : undefined, mcpDirectTools: draft.mcpDirectTools ? [...draft.mcpDirectTools] : undefined, skills: draft.skills ? [...draft.skills] : undefined, fallbackModels: draft.fallbackModels ? [...draft.fallbackModels] : undefined, extensions: draft.extensions ? [...draft.extensions] : draft.extensions, defaultReads: draft.defaultReads ? [...draft.defaultReads] : undefined, extraFields: draft.extraFields ? { ...draft.extraFields } : undefined },
|
|
33
72
|
isNew, fieldIndex: 0, fieldMode: null, fieldEditor: createEditorState(), promptEditor: createEditorState(draft.systemPrompt ?? ""),
|
|
34
73
|
modelSearchQuery: "", modelCursor: 0, filteredModels: [...models], thinkingCursor: 0, skillSearchQuery: "", skillCursor: 0, filteredSkills: [...skills], skillSelected: new Set(draft.skills ?? []),
|
|
74
|
+
fields: options.fields ?? [...FIELD_ORDER], title: options.title, overrideBase: options.overrideBase,
|
|
35
75
|
};
|
|
36
76
|
}
|
|
37
77
|
|
|
@@ -71,15 +111,15 @@ function applyFieldValue(field: EditField, state: EditState, value: string): voi
|
|
|
71
111
|
}
|
|
72
112
|
|
|
73
113
|
function openModelPicker(state: EditState, models: ModelInfo[]): void {
|
|
74
|
-
state.fieldIndex =
|
|
114
|
+
state.fieldIndex = state.fields.indexOf("model"); state.fieldMode = "model"; state.modelSearchQuery = ""; state.filteredModels = [...models];
|
|
75
115
|
const idx = state.filteredModels.findIndex((m) => m.fullId === state.draft.model || m.id === state.draft.model); state.modelCursor = idx >= 0 ? idx : 0;
|
|
76
116
|
}
|
|
77
117
|
function openThinkingPicker(state: EditState): void {
|
|
78
|
-
state.fieldIndex =
|
|
118
|
+
state.fieldIndex = state.fields.indexOf("thinking"); state.fieldMode = "thinking";
|
|
79
119
|
const idx = THINKING_LEVELS.indexOf((state.draft.thinking ?? "off") as ThinkingLevel); state.thinkingCursor = idx >= 0 ? idx : 0;
|
|
80
120
|
}
|
|
81
121
|
function openSkillPicker(state: EditState, skills: SkillInfo[]): void {
|
|
82
|
-
state.fieldIndex =
|
|
122
|
+
state.fieldIndex = state.fields.indexOf("skills"); state.fieldMode = "skills"; state.skillSearchQuery = ""; state.filteredSkills = [...skills]; state.skillSelected = new Set(state.draft.skills ?? []); state.skillCursor = 0;
|
|
83
123
|
}
|
|
84
124
|
|
|
85
125
|
function renderModelPicker(state: EditState, width: number, theme: Theme): string[] {
|
|
@@ -183,9 +223,11 @@ export function handleEditInput(screen: EditScreen, state: EditState, data: stri
|
|
|
183
223
|
if (screen === "edit") {
|
|
184
224
|
if (matchesKey(data, "ctrl+s")) return { action: "save" };
|
|
185
225
|
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) return { action: "discard" };
|
|
226
|
+
if (data === "D" && state.overrideBase) return { action: "delete" };
|
|
186
227
|
if (matchesKey(data, "up")) { state.fieldIndex = Math.max(0, state.fieldIndex - 1); return; }
|
|
187
|
-
if (matchesKey(data, "down")) { state.fieldIndex = Math.min(
|
|
188
|
-
const field =
|
|
228
|
+
if (matchesKey(data, "down")) { state.fieldIndex = Math.min(state.fields.length - 1, state.fieldIndex + 1); return; }
|
|
229
|
+
const field = state.fields[state.fieldIndex]!;
|
|
230
|
+
if (data === "r" && state.overrideBase) { resetFieldToBase(field, state); return; }
|
|
189
231
|
if (data === "m") { openModelPicker(state, models); return { nextScreen: "edit-field" }; }
|
|
190
232
|
if (data === "t") { openThinkingPicker(state); return { nextScreen: "edit-field" }; }
|
|
191
233
|
if (data === "s") { openSkillPicker(state, skills); return { nextScreen: "edit-field" }; }
|
|
@@ -234,7 +276,7 @@ export function handleEditInput(screen: EditScreen, state: EditState, data: stri
|
|
|
234
276
|
return;
|
|
235
277
|
}
|
|
236
278
|
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) { state.fieldMode = null; return { nextScreen: "edit" }; }
|
|
237
|
-
if (matchesKey(data, "return")) { const field =
|
|
279
|
+
if (matchesKey(data, "return")) { const field = state.fields[state.fieldIndex]!; applyFieldValue(field, state, state.fieldEditor.buffer); state.fieldMode = null; return { nextScreen: "edit" }; }
|
|
238
280
|
if (matchesKey(data, "tab")) return;
|
|
239
281
|
const innerW = width - 2; const labelWidth = 12; const textWidth = Math.max(10, innerW - labelWidth - 6);
|
|
240
282
|
const nextState = handleEditorInput(state.fieldEditor, data, textWidth); if (nextState) state.fieldEditor = nextState; return;
|
|
@@ -256,13 +298,14 @@ export function renderEdit(screen: EditScreen, state: EditState, width: number,
|
|
|
256
298
|
if (screen === "edit-prompt") return renderPromptEditor(state, width, theme);
|
|
257
299
|
const lines: string[] = [];
|
|
258
300
|
const scopeBadge = state.draft.source === "user" ? "[user]" : "[proj]"; const label = state.isNew ? " [new]" : "";
|
|
259
|
-
lines.push(renderHeader(` Editing: ${state.draft.name} ${scopeBadge}${label} `, width, theme));
|
|
301
|
+
lines.push(renderHeader(` ${state.title ?? `Editing: ${state.draft.name} ${scopeBadge}${label}`} `, width, theme));
|
|
260
302
|
lines.push(row("", width, theme));
|
|
261
303
|
const innerW = width - 2; const labelWidth = 12; const valueWidth = Math.max(10, innerW - labelWidth - 6);
|
|
262
|
-
for (let i = 0; i <
|
|
263
|
-
const field =
|
|
304
|
+
for (let i = 0; i < state.fields.length; i++) {
|
|
305
|
+
const field = state.fields[i]!; if (field === "prompt") break;
|
|
264
306
|
const isFocused = i === state.fieldIndex; const prefix = isFocused ? theme.fg("accent", "▸ ") : " ";
|
|
265
|
-
const
|
|
307
|
+
const rawLabel = pad(`${field[0]!.toUpperCase()}${field.slice(1)}:`, labelWidth);
|
|
308
|
+
const labelText = state.overrideBase && !fieldValueMatchesBase(field, state) ? theme.fg("accent", rawLabel) : rawLabel; let valueText = renderFieldValue(field, state);
|
|
266
309
|
if (field === "progress") { const toggle = state.draft.defaultProgress ? theme.fg("success", "[x]") : "[ ]"; valueText = `${toggle} ${state.draft.defaultProgress ? "on" : "off"}`; lines.push(row(` ${prefix}${labelText} ${pad(truncateToWidth(valueText, valueWidth), valueWidth)}`, width, theme)); continue; }
|
|
267
310
|
if (field === "interactive") { const toggle = state.draft.interactive ? theme.fg("success", "[x]") : "[ ]"; valueText = `${toggle} ${state.draft.interactive ? "on" : "off"}`; lines.push(row(` ${prefix}${labelText} ${pad(truncateToWidth(valueText, valueWidth), valueWidth)}`, width, theme)); continue; }
|
|
268
311
|
let displayValue = truncateToWidth(valueText, valueWidth);
|
|
@@ -275,14 +318,23 @@ export function renderEdit(screen: EditScreen, state: EditState, width: number,
|
|
|
275
318
|
}
|
|
276
319
|
lines.push(row(` ${prefix}${labelText} [${displayValue}]`, width, theme));
|
|
277
320
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
321
|
+
if (state.fields.includes("prompt")) {
|
|
322
|
+
lines.push(row("", width, theme));
|
|
323
|
+
const promptIndex = state.fields.indexOf("prompt");
|
|
324
|
+
const promptFocused = state.fieldIndex === promptIndex;
|
|
325
|
+
const promptPrefix = promptFocused ? theme.fg("accent", "▸ ") : " ";
|
|
326
|
+
const promptTitle = state.overrideBase && !fieldValueMatchesBase("prompt", state)
|
|
327
|
+
? theme.fg("accent", "── System Prompt ──")
|
|
328
|
+
: theme.fg("dim", "── System Prompt ──");
|
|
329
|
+
lines.push(row(` ${promptPrefix}${promptTitle}`, width, theme));
|
|
330
|
+
const previewWidth = innerW - 2; const wrapped = wrapText(state.draft.systemPrompt ?? "", previewWidth); const previewLines = wrapped.lines.slice(0, 4);
|
|
331
|
+
for (const line of previewLines) lines.push(row(` ${line}`, width, theme));
|
|
332
|
+
for (let i = previewLines.length; i < 4; i++) lines.push(row("", width, theme));
|
|
333
|
+
}
|
|
285
334
|
if (state.error) lines.push(row(` ${theme.fg("error", state.error)}`, width, theme)); else lines.push(row("", width, theme));
|
|
286
|
-
|
|
335
|
+
const footer = state.overrideBase
|
|
336
|
+
? " [ctrl+s] save [r] reset field [D] remove override [esc] back "
|
|
337
|
+
: " [ctrl+s] save [esc] back ";
|
|
338
|
+
lines.push(renderFooter(footer, width, theme));
|
|
287
339
|
return lines;
|
|
288
340
|
}
|
|
@@ -9,6 +9,7 @@ export interface ListAgent {
|
|
|
9
9
|
description: string;
|
|
10
10
|
model?: string;
|
|
11
11
|
source: AgentSource;
|
|
12
|
+
overrideScope?: "user" | "project";
|
|
12
13
|
kind: "agent" | "chain";
|
|
13
14
|
stepCount?: number;
|
|
14
15
|
}
|
|
@@ -190,7 +191,7 @@ export function renderList(
|
|
|
190
191
|
const innerW = width - 2;
|
|
191
192
|
const nameWidth = 16;
|
|
192
193
|
const modelWidth = 12;
|
|
193
|
-
const scopeWidth =
|
|
194
|
+
const scopeWidth = 17;
|
|
194
195
|
|
|
195
196
|
for (let i = 0; i < visible.length; i++) {
|
|
196
197
|
const agent = visible[i]!;
|
|
@@ -208,7 +209,13 @@ export function renderList(
|
|
|
208
209
|
const modelDisplay = modelRaw.includes("/") ? modelRaw.split("/").pop() ?? modelRaw : modelRaw;
|
|
209
210
|
const nameText = isCursor ? theme.fg("accent", agent.name) : agent.name;
|
|
210
211
|
const modelText = theme.fg("dim", modelDisplay);
|
|
211
|
-
const scopeLabel = agent.kind === "chain"
|
|
212
|
+
const scopeLabel = agent.kind === "chain"
|
|
213
|
+
? "[chain]"
|
|
214
|
+
: agent.source === "builtin"
|
|
215
|
+
? (agent.overrideScope ? `[builtin+${agent.overrideScope}]` : "[builtin]")
|
|
216
|
+
: agent.source === "project"
|
|
217
|
+
? "[proj]"
|
|
218
|
+
: "[user]";
|
|
212
219
|
const scopeBadge = theme.fg("dim", scopeLabel);
|
|
213
220
|
const descText = theme.fg("dim", agent.description);
|
|
214
221
|
|