mixdog 0.9.85 → 0.9.87
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 +7 -1
- package/package.json +4 -3
- package/src/defaults/agents.json +12 -0
- package/src/defaults/skills/setup/SKILL.md +44 -12
- package/src/help.mjs +40 -9
- package/src/lib/keychain-cjs.cjs +11 -1
- package/src/rules/agent/43-title-agent.md +22 -0
- package/src/rules/shared/01-tool.md +3 -3
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +4 -2
- package/src/runtime/agent/orchestrator/mcp/client.mjs +24 -0
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +84 -3
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +125 -16
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +73 -45
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +15 -18
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +135 -9
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +31 -12
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +170 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +45 -6
- package/src/runtime/agent/orchestrator/session/manager.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/token-bpe.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/token-native.mjs +186 -0
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +15 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +20 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +9 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +24 -0
- package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +286 -0
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +152 -18
- package/src/runtime/agent/orchestrator/tools/token-binary-fetcher.mjs +201 -0
- package/src/runtime/agent/orchestrator/tools/token-manifest.json +26 -0
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +3 -3
- package/src/runtime/media/adapters/gemini-video.mjs +1 -1
- package/src/runtime/media/renditions.mjs +1 -1
- package/src/runtime/media/store.mjs +1 -1
- package/src/runtime/memory/lib/memory-action-handlers.mjs +4 -1
- package/src/runtime/memory/tool-defs.mjs +19 -3
- package/src/runtime/shared/atomic-file.mjs +25 -14
- package/src/runtime/shared/automation-attachments.mjs +3 -3
- package/src/runtime/shared/child-guardian.mjs +15 -5
- package/src/runtime/shared/child-spawn-gate.mjs +6 -12
- package/src/runtime/shared/resource-admission.mjs +7 -3
- package/src/runtime/shared/turn-snapshot.mjs +0 -3
- package/src/session-runtime/lifecycle-api.mjs +49 -9
- package/src/session-runtime/prewarm.mjs +35 -0
- package/src/session-runtime/provider-auth-api.mjs +8 -0
- package/src/session-runtime/provider-usage.mjs +30 -5
- package/src/session-runtime/remote-control.mjs +8 -47
- package/src/session-runtime/remote-transcript.mjs +3 -7
- package/src/session-runtime/route-preparation.mjs +24 -0
- package/src/session-runtime/runtime-core.mjs +286 -548
- package/src/session-runtime/session-lifecycle.mjs +378 -0
- package/src/session-runtime/session-turn-api.mjs +9 -2
- package/src/session-runtime/workflow-agents-api.mjs +3 -0
- package/src/standalone/agent-shard/shard-child.mjs +300 -0
- package/src/standalone/agent-shard/shard-pool.mjs +443 -0
- package/src/standalone/agent-tool/job-views.mjs +329 -0
- package/src/standalone/agent-tool/spawn-flow.mjs +629 -0
- package/src/standalone/agent-tool/tag-registry.mjs +340 -0
- package/src/standalone/agent-tool.mjs +112 -941
- package/src/standalone/channel-daemon-transport.mjs +126 -185
- package/src/standalone/channel-daemon.mjs +9 -10
- package/src/standalone/usage-dashboard.mjs +23 -1
- package/src/tui/App.jsx +362 -2569
- package/src/tui/app/app-view.jsx +504 -0
- package/src/tui/app/channel-pickers.mjs +3 -8
- package/src/tui/app/create-app-pickers.mjs +313 -0
- package/src/tui/app/prompt-submit.mjs +501 -0
- package/src/tui/app/route-pickers.mjs +117 -152
- package/src/tui/app/settings-picker.mjs +90 -89
- package/src/tui/app/shell-layout.mjs +563 -0
- package/src/tui/app/slash-commands.mjs +7 -7
- package/src/tui/app/slash-dispatch.mjs +0 -45
- package/src/tui/app/usage-context-panels.mjs +288 -0
- package/src/tui/app/use-copy-selection.mjs +56 -0
- package/src/tui/app/use-global-key-input.mjs +151 -0
- package/src/tui/app/use-pasted-buffers.mjs +115 -0
- package/src/tui/app/use-prompt-draft-flow.mjs +167 -0
- package/src/tui/app/use-prompt-hint.mjs +58 -0
- package/src/tui/app/use-prompt-queue-history.mjs +100 -0
- package/src/tui/app/use-terminal-chrome.mjs +72 -0
- package/src/tui/app/use-transcript-activity.mjs +131 -0
- package/src/tui/app/use-welcome-prompt-hint.mjs +96 -0
- package/src/tui/components/StatusLine.jsx +1 -1
- package/src/tui/components/tool-execution/ResultBody.jsx +1 -1
- package/src/tui/dist/index.mjs +9857 -9159
- package/src/tui/engine/session-api-ext.mjs +47 -9
- package/src/tui/engine.mjs +7 -1
- package/src/tui/figures.mjs +0 -1
- package/src/ui/statusline-format.mjs +0 -1
- package/src/hooks/lib/permission-evaluator.cjs +0 -24
- package/src/lib/config-cjs.cjs +0 -61
- package/src/runtime/shared/launcher-control.mjs +0 -258
- package/src/runtime/shared/workspace-router.mjs +0 -259
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
// Prompt submit dispatcher, extracted from App.jsx. One entry point for
|
|
2
|
+
// everything the prompt box can accept: provider/channel/hook/settings
|
|
3
|
+
// text-entry prompts, slash commands, and the normal chat submit with
|
|
4
|
+
// pasted image/text token expansion. Factory pattern (like the pickers):
|
|
5
|
+
// re-created per render so it closes over the CURRENT prompt/panel state.
|
|
6
|
+
import {
|
|
7
|
+
memoryCoreResultErrorText,
|
|
8
|
+
parseHookRuleInput,
|
|
9
|
+
parseMcpServerInput,
|
|
10
|
+
parseSkillInput,
|
|
11
|
+
} from './input-parsers.mjs';
|
|
12
|
+
import { projectNameFromPath } from './app-format.mjs';
|
|
13
|
+
import {
|
|
14
|
+
buildPromptContentWithImages,
|
|
15
|
+
expandPastedTextTokens,
|
|
16
|
+
imageReferenceIds,
|
|
17
|
+
pastedTextReferenceIds,
|
|
18
|
+
} from '../paste-attachments.mjs';
|
|
19
|
+
import {
|
|
20
|
+
ensureDir,
|
|
21
|
+
isDirectory,
|
|
22
|
+
pathExists,
|
|
23
|
+
renameProject,
|
|
24
|
+
resolveProjectPath,
|
|
25
|
+
} from '../../standalone/projects.mjs';
|
|
26
|
+
|
|
27
|
+
export function createPromptSubmit({
|
|
28
|
+
store,
|
|
29
|
+
state,
|
|
30
|
+
providerPrompt,
|
|
31
|
+
channelPrompt,
|
|
32
|
+
hookPrompt,
|
|
33
|
+
settingsPrompt,
|
|
34
|
+
setProviderPrompt,
|
|
35
|
+
setChannelPrompt,
|
|
36
|
+
setHookPrompt,
|
|
37
|
+
setSettingsPrompt,
|
|
38
|
+
oauthSubmitRef,
|
|
39
|
+
clearModelCaches,
|
|
40
|
+
openProviderSetupPicker,
|
|
41
|
+
openChannelSetupPicker,
|
|
42
|
+
openHooksPicker,
|
|
43
|
+
openSettingsPicker,
|
|
44
|
+
openProjectPicker,
|
|
45
|
+
openAutoClearPicker,
|
|
46
|
+
openProfilePicker,
|
|
47
|
+
openPluginsPicker,
|
|
48
|
+
openMcpServersPicker,
|
|
49
|
+
openProjectSkillsPicker,
|
|
50
|
+
openMemoryCorePicker,
|
|
51
|
+
registerProject,
|
|
52
|
+
runSlashCommand,
|
|
53
|
+
armTranscriptFollow,
|
|
54
|
+
clearPastedImagesSnapshot,
|
|
55
|
+
clearPastedTextsSnapshot,
|
|
56
|
+
pastedImagesRef,
|
|
57
|
+
pastedTextsRef,
|
|
58
|
+
}) {
|
|
59
|
+
const onSubmit = (raw) => {
|
|
60
|
+
const text = String(raw ?? '');
|
|
61
|
+
const commandText = text.trim();
|
|
62
|
+
if (providerPrompt) {
|
|
63
|
+
if (state.commandBusy) {
|
|
64
|
+
store.pushNotice('wait for the current command to finish', 'warn');
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (providerPrompt.kind === 'api-key') {
|
|
68
|
+
if (!commandText) {
|
|
69
|
+
store.pushNotice(`API key is required for ${providerPrompt.providerId}`, 'warn');
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
store.saveProviderApiKey(providerPrompt.providerId, commandText);
|
|
74
|
+
clearModelCaches('all');
|
|
75
|
+
const afterSave = providerPrompt.afterSave;
|
|
76
|
+
setProviderPrompt(null);
|
|
77
|
+
if (afterSave) afterSave();
|
|
78
|
+
else void openProviderSetupPicker();
|
|
79
|
+
return true;
|
|
80
|
+
} catch (e) {
|
|
81
|
+
store.pushNotice(`api key save failed: ${e?.message || e}`, 'error');
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (providerPrompt.kind === 'openai-usage-session') {
|
|
86
|
+
if (!commandText) {
|
|
87
|
+
store.pushNotice('OpenAI usage session key is required for credit lookup', 'warn');
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
store.saveOpenAIUsageSessionKey(commandText);
|
|
92
|
+
const afterSave = providerPrompt.afterSave;
|
|
93
|
+
setProviderPrompt(null);
|
|
94
|
+
if (afterSave) afterSave();
|
|
95
|
+
else void openProviderSetupPicker();
|
|
96
|
+
return true;
|
|
97
|
+
} catch (e) {
|
|
98
|
+
store.pushNotice(`OpenAI usage auth save failed: ${e?.message || e}`, 'error');
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (providerPrompt.kind === 'local-url') {
|
|
103
|
+
try {
|
|
104
|
+
store.setLocalProvider(providerPrompt.providerId, {
|
|
105
|
+
enabled: true,
|
|
106
|
+
baseURL: commandText || providerPrompt.defaultURL,
|
|
107
|
+
});
|
|
108
|
+
clearModelCaches('all');
|
|
109
|
+
const afterSave = providerPrompt.afterSave;
|
|
110
|
+
setProviderPrompt(null);
|
|
111
|
+
if (afterSave) afterSave();
|
|
112
|
+
else void openProviderSetupPicker();
|
|
113
|
+
return true;
|
|
114
|
+
} catch (e) {
|
|
115
|
+
store.pushNotice(`local provider update failed: ${e?.message || e}`, 'error');
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (providerPrompt.kind === 'oauth-code') {
|
|
120
|
+
if (!commandText) {
|
|
121
|
+
store.pushNotice('OAuth code is required', 'warn');
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
if (oauthSubmitRef.current || providerPrompt.submitting) {
|
|
125
|
+
store.pushNotice('OAuth code is already being submitted', 'warn');
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
oauthSubmitRef.current = true;
|
|
129
|
+
setProviderPrompt((prompt) => prompt === providerPrompt ? { ...prompt, submitting: true } : prompt);
|
|
130
|
+
void providerPrompt.login?.completeCode(commandText)
|
|
131
|
+
.then(() => {
|
|
132
|
+
const successReturn = providerPrompt.successReturn;
|
|
133
|
+
const afterSave = providerPrompt.afterSave;
|
|
134
|
+
oauthSubmitRef.current = false;
|
|
135
|
+
clearModelCaches('all');
|
|
136
|
+
setProviderPrompt(null);
|
|
137
|
+
store.pushNotice(`${providerPrompt.providerName || 'OAuth'} login complete`, 'info');
|
|
138
|
+
if (successReturn) successReturn();
|
|
139
|
+
else if (afterSave) afterSave();
|
|
140
|
+
else void openProviderSetupPicker();
|
|
141
|
+
})
|
|
142
|
+
.catch((e) => {
|
|
143
|
+
oauthSubmitRef.current = false;
|
|
144
|
+
store.pushNotice(`oauth code failed: ${e?.message || e}`, 'error');
|
|
145
|
+
setProviderPrompt(null);
|
|
146
|
+
providerPrompt.failureReturn?.(e);
|
|
147
|
+
});
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (channelPrompt) {
|
|
152
|
+
if (state.commandBusy) {
|
|
153
|
+
store.pushNotice('wait for the current command to finish', 'warn');
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const resumeAfterChannelPrompt = (prompt) => {
|
|
158
|
+
const afterSave = prompt?.afterSave;
|
|
159
|
+
setChannelPrompt(null);
|
|
160
|
+
if (typeof afterSave === 'function') afterSave();
|
|
161
|
+
else void openChannelSetupPicker('all');
|
|
162
|
+
};
|
|
163
|
+
if (channelPrompt.kind === 'discord-token') {
|
|
164
|
+
if (!commandText) return false;
|
|
165
|
+
store.saveDiscordToken(commandText);
|
|
166
|
+
resumeAfterChannelPrompt(channelPrompt);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
if (channelPrompt.kind === 'telegram-token') {
|
|
170
|
+
if (!commandText) return false;
|
|
171
|
+
store.saveTelegramToken(commandText);
|
|
172
|
+
resumeAfterChannelPrompt(channelPrompt);
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
const parts = commandText.split('|').map((part) => part.trim());
|
|
176
|
+
if (channelPrompt.kind === 'channel-add') {
|
|
177
|
+
// Single-channel: the UI asks only for the channel id. Legacy
|
|
178
|
+
// `name | id | ...` pipe input still parses (the id is the second
|
|
179
|
+
// field) so old muscle memory does not break.
|
|
180
|
+
const isPipe = parts.length > 1;
|
|
181
|
+
const channelId = isPipe ? parts[1] : parts[0];
|
|
182
|
+
Promise.resolve(store.setChannel({
|
|
183
|
+
channelId,
|
|
184
|
+
backend: channelPrompt.backend,
|
|
185
|
+
}))
|
|
186
|
+
.then(() => resumeAfterChannelPrompt(channelPrompt))
|
|
187
|
+
.catch((e) => {
|
|
188
|
+
store.pushNotice(`channel save failed: ${e?.message || e}`, 'error');
|
|
189
|
+
});
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
// schedule-add / webhook-add prompt kinds retired: schedules and
|
|
193
|
+
// webhooks are managed in the desktop app (user decision).
|
|
194
|
+
} catch (e) {
|
|
195
|
+
store.pushNotice(`channels update failed: ${e?.message || e}`, 'error');
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (hookPrompt) {
|
|
200
|
+
if (state.commandBusy) {
|
|
201
|
+
store.pushNotice('wait for the current command to finish', 'warn');
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
if (hookPrompt.kind === 'rule-add') {
|
|
206
|
+
const parsed = parseHookRuleInput(commandText);
|
|
207
|
+
if (parsed.error) {
|
|
208
|
+
store.pushNotice(parsed.error, 'warn');
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
store.addHookRule?.(parsed.rule);
|
|
212
|
+
setHookPrompt(null);
|
|
213
|
+
void openHooksPicker();
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
} catch (e) {
|
|
217
|
+
store.pushNotice(`hook update failed: ${e?.message || e}`, 'error');
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (settingsPrompt) {
|
|
222
|
+
if (state.commandBusy) {
|
|
223
|
+
store.pushNotice('wait for the current command to finish', 'warn');
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
if (settingsPrompt.kind === 'cwd') {
|
|
228
|
+
if (!commandText) {
|
|
229
|
+
store.pushNotice('working directory path is required', 'warn');
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
store.setCwd?.(commandText, { message: `Project set: ${projectNameFromPath(commandText)}` });
|
|
233
|
+
setSettingsPrompt(null);
|
|
234
|
+
void openSettingsPicker();
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
if (settingsPrompt.kind === 'project-new') {
|
|
238
|
+
if (!commandText) {
|
|
239
|
+
store.pushNotice('project path is required', 'warn');
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
const path = resolveProjectPath(commandText);
|
|
243
|
+
if (isDirectory(path)) {
|
|
244
|
+
setSettingsPrompt(null);
|
|
245
|
+
registerProject(path);
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
// A path that exists but is a regular file is not a valid project dir.
|
|
249
|
+
if (pathExists(path)) {
|
|
250
|
+
store.pushNotice(`${path} is not a directory`, 'warn');
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
// Missing folder: confirm creation before registering.
|
|
254
|
+
setSettingsPrompt({
|
|
255
|
+
kind: 'project-create-confirm',
|
|
256
|
+
label: 'New project · Create folder?',
|
|
257
|
+
hint: `${path} does not exist. Type "y" to create it, or anything else to cancel.`,
|
|
258
|
+
pendingPath: path,
|
|
259
|
+
});
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
if (settingsPrompt.kind === 'project-create-confirm') {
|
|
263
|
+
const pendingPath = String(settingsPrompt.pendingPath || '');
|
|
264
|
+
const answer = String(commandText || '').trim().toLowerCase();
|
|
265
|
+
if (answer === 'y' || answer === 'yes') {
|
|
266
|
+
const created = ensureDir(pendingPath);
|
|
267
|
+
if (!created) {
|
|
268
|
+
store.pushNotice(`could not create folder: ${pendingPath}`, 'error');
|
|
269
|
+
setSettingsPrompt(null);
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
setSettingsPrompt(null);
|
|
273
|
+
registerProject(pendingPath);
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
setSettingsPrompt(null);
|
|
277
|
+
store.pushNotice('project creation canceled', 'info');
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
if (settingsPrompt.kind === 'project-rename') {
|
|
281
|
+
const targetPath = String(settingsPrompt.projectPath || '');
|
|
282
|
+
try {
|
|
283
|
+
const updated = renameProject(targetPath, commandText);
|
|
284
|
+
if (updated) {
|
|
285
|
+
store.pushNotice(`project renamed to "${updated.name}"`, 'info');
|
|
286
|
+
}
|
|
287
|
+
} catch (e) {
|
|
288
|
+
store.pushNotice(`rename failed: ${e?.message || e}`, 'error');
|
|
289
|
+
}
|
|
290
|
+
setSettingsPrompt(null);
|
|
291
|
+
openProjectPicker();
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
if (settingsPrompt.kind === 'system-shell') {
|
|
295
|
+
store.setSystemShell?.(commandText);
|
|
296
|
+
setSettingsPrompt(null);
|
|
297
|
+
void openSettingsPicker();
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
if (settingsPrompt.kind === 'autoclear-provider') {
|
|
301
|
+
const provider = String(settingsPrompt.provider || '').trim();
|
|
302
|
+
if (!provider) {
|
|
303
|
+
store.pushNotice('auto-clear provider is missing', 'warn');
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
const text = String(commandText || '').trim();
|
|
307
|
+
try {
|
|
308
|
+
if (text) store.setAutoClear?.({ provider, duration: text });
|
|
309
|
+
else store.setAutoClear?.({ provider, resetProvider: true });
|
|
310
|
+
store.pushNotice(text ? `Auto-clear ${provider} default set to ${text}` : `Auto-clear ${provider} default reset`, 'info');
|
|
311
|
+
} catch (e) {
|
|
312
|
+
store.pushNotice(`autoclear failed: ${e?.message || e}`, 'error');
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
setSettingsPrompt(null);
|
|
316
|
+
openAutoClearPicker({ advanced: true, returnTo: settingsPrompt.returnTo });
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
if (settingsPrompt.kind === 'profile-title') {
|
|
320
|
+
try {
|
|
321
|
+
store.setProfile?.({ title: commandText });
|
|
322
|
+
store.pushNotice(commandText ? `Title set to "${commandText.trim()}"` : 'Title cleared', 'info');
|
|
323
|
+
} catch (e) {
|
|
324
|
+
store.pushNotice(`profile update failed: ${e?.message || e}`, 'error');
|
|
325
|
+
}
|
|
326
|
+
setSettingsPrompt(null);
|
|
327
|
+
openProfilePicker();
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
if (settingsPrompt.kind === 'plugin-add') {
|
|
331
|
+
if (!commandText) {
|
|
332
|
+
store.pushNotice('plugin URL/path is required', 'warn');
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
void store.addPlugin?.(commandText)
|
|
336
|
+
.then(() => openPluginsPicker())
|
|
337
|
+
.catch((e) => store.pushNotice(`plugin add failed: ${e?.message || e}`, 'error'));
|
|
338
|
+
setSettingsPrompt(null);
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
if (settingsPrompt.kind === 'mcp-add') {
|
|
342
|
+
const parsed = parseMcpServerInput(commandText);
|
|
343
|
+
if (parsed.error) {
|
|
344
|
+
store.pushNotice(parsed.error, 'warn');
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
void store.addMcpServer?.(parsed.server)
|
|
348
|
+
.then(() => openMcpServersPicker())
|
|
349
|
+
.catch((e) => store.pushNotice(`mcp add failed: ${e?.message || e}`, 'error'));
|
|
350
|
+
setSettingsPrompt(null);
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
if (settingsPrompt.kind === 'skill-add') {
|
|
354
|
+
const parsed = parseSkillInput(commandText);
|
|
355
|
+
if (parsed.error) {
|
|
356
|
+
store.pushNotice(parsed.error, 'warn');
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
void store.addSkill?.(parsed.skill)
|
|
360
|
+
.then(() => openProjectSkillsPicker())
|
|
361
|
+
.catch((e) => store.pushNotice(`skill add failed: ${e?.message || e}`, 'error'));
|
|
362
|
+
setSettingsPrompt(null);
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
if (settingsPrompt.kind === 'skill-use') {
|
|
366
|
+
const skillName = String(settingsPrompt.skillName || '').trim();
|
|
367
|
+
if (!skillName) {
|
|
368
|
+
store.pushNotice('skill name is missing', 'warn');
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
const prompt = `$${skillName}${commandText ? ` ${commandText}` : ''}`;
|
|
372
|
+
setSettingsPrompt(null);
|
|
373
|
+
const accepted = store.submit(prompt);
|
|
374
|
+
if (accepted) armTranscriptFollow();
|
|
375
|
+
return accepted;
|
|
376
|
+
}
|
|
377
|
+
if (settingsPrompt.kind === 'core-add') {
|
|
378
|
+
const sentence = commandText.trim();
|
|
379
|
+
if (!sentence) {
|
|
380
|
+
store.pushNotice('memory sentence is required', 'warn');
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
setSettingsPrompt(null);
|
|
384
|
+
void store.memoryControl?.({ action: 'core', op: 'add', project_id: 'common', element: sentence, summary: sentence }, { silent: true })
|
|
385
|
+
.then((result) => {
|
|
386
|
+
const errText = memoryCoreResultErrorText(result);
|
|
387
|
+
store.pushNotice(errText || 'core memory added', errText ? 'error' : 'info');
|
|
388
|
+
openMemoryCorePicker();
|
|
389
|
+
})
|
|
390
|
+
.catch((e) => {
|
|
391
|
+
store.pushNotice(`core add failed: ${e?.message || e}`, 'error');
|
|
392
|
+
openMemoryCorePicker();
|
|
393
|
+
});
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
if (settingsPrompt.kind === 'core-edit') {
|
|
397
|
+
const sentence = commandText.trim();
|
|
398
|
+
const id = settingsPrompt._id;
|
|
399
|
+
const projectId = settingsPrompt._projectId ?? 'common';
|
|
400
|
+
if (!sentence) {
|
|
401
|
+
store.pushNotice('memory sentence is required', 'warn');
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
setSettingsPrompt(null);
|
|
405
|
+
// Single-sentence semantics only rewrite `element` when the row was
|
|
406
|
+
// already element===summary at load (see beginEditCoreMemory's
|
|
407
|
+
// _singleSentence flag). A distinct legacy element carries meaning
|
|
408
|
+
// this text prompt never captured -- clobbering it on every edit
|
|
409
|
+
// would corrupt the entry (and re-embed/dedupe on the clobbered
|
|
410
|
+
// value). Otherwise only `summary` is sent.
|
|
411
|
+
const editArgs = settingsPrompt._singleSentence
|
|
412
|
+
? { action: 'core', op: 'edit', id, project_id: projectId, element: sentence, summary: sentence }
|
|
413
|
+
: { action: 'core', op: 'edit', id, project_id: projectId, summary: sentence };
|
|
414
|
+
void store.memoryControl?.(editArgs, { silent: true })
|
|
415
|
+
.then((result) => {
|
|
416
|
+
const errText = memoryCoreResultErrorText(result);
|
|
417
|
+
store.pushNotice(errText || 'core memory updated', errText ? 'error' : 'info');
|
|
418
|
+
openMemoryCorePicker();
|
|
419
|
+
})
|
|
420
|
+
.catch((e) => {
|
|
421
|
+
store.pushNotice(`core edit failed: ${e?.message || e}`, 'error');
|
|
422
|
+
openMemoryCorePicker();
|
|
423
|
+
});
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
if (settingsPrompt.kind === 'core-delete-confirm') {
|
|
427
|
+
const id = settingsPrompt._id;
|
|
428
|
+
const projectId = settingsPrompt._projectId ?? 'common';
|
|
429
|
+
const answer = String(commandText || '').trim().toLowerCase();
|
|
430
|
+
setSettingsPrompt(null);
|
|
431
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
432
|
+
store.pushNotice('delete canceled', 'info');
|
|
433
|
+
openMemoryCorePicker();
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
void store.memoryControl?.({ action: 'core', op: 'delete', id, project_id: projectId }, { silent: true })
|
|
437
|
+
.then((result) => {
|
|
438
|
+
const errText = memoryCoreResultErrorText(result);
|
|
439
|
+
store.pushNotice(errText || 'core memory deleted', errText ? 'error' : 'info');
|
|
440
|
+
openMemoryCorePicker();
|
|
441
|
+
})
|
|
442
|
+
.catch((e) => {
|
|
443
|
+
store.pushNotice(`core delete failed: ${e?.message || e}`, 'error');
|
|
444
|
+
openMemoryCorePicker();
|
|
445
|
+
});
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
} catch (e) {
|
|
449
|
+
store.pushNotice(`settings update failed: ${e?.message || e}`, 'error');
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (!commandText) return false;
|
|
454
|
+
|
|
455
|
+
if (commandText.startsWith('/')) {
|
|
456
|
+
if (state.commandBusy) {
|
|
457
|
+
store.pushNotice('wait for the current command to finish', 'warn');
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
const [cmd, ...rest] = commandText.slice(1).split(/\s+/);
|
|
461
|
+
const accepted = runSlashCommand(cmd, rest.join(' ').trim());
|
|
462
|
+
if (accepted !== false) clearPastedImagesSnapshot();
|
|
463
|
+
return accepted;
|
|
464
|
+
}
|
|
465
|
+
const imageRefs = imageReferenceIds(text);
|
|
466
|
+
const imageSnapshot = Object.fromEntries(Object.entries(pastedImagesRef.current || {})
|
|
467
|
+
.filter(([id]) => imageRefs.has(Number(id))));
|
|
468
|
+
const hasImageSnapshot = Object.keys(imageSnapshot).length > 0;
|
|
469
|
+
// Expand folded [Pasted text #N +M lines] tokens back to their original
|
|
470
|
+
// text at the same point buildPromptContentWithImages runs. Broken /
|
|
471
|
+
// partially-deleted tokens do not match and are left as-is.
|
|
472
|
+
const textRefs = pastedTextReferenceIds(text);
|
|
473
|
+
const textSnapshot = Object.fromEntries(Object.entries(pastedTextsRef.current || {})
|
|
474
|
+
.filter(([id]) => textRefs.has(Number(id))));
|
|
475
|
+
const hasTextSnapshot = Object.keys(textSnapshot).length > 0;
|
|
476
|
+
const expandedText = hasTextSnapshot ? expandPastedTextTokens(text, textSnapshot) : text;
|
|
477
|
+
const content = buildPromptContentWithImages(expandedText, imageSnapshot);
|
|
478
|
+
const accepted = store.submit(content, {
|
|
479
|
+
// Store the EXPANDED text in the transcript/history so a later prompt-
|
|
480
|
+
// history recall resubmits the real content, not the literal token
|
|
481
|
+
// (pastedTexts entries are cleared on accept). History recall therefore
|
|
482
|
+
// shows the full original text instead of the token — acceptable.
|
|
483
|
+
displayText: expandedText,
|
|
484
|
+
pastedImages: imageSnapshot,
|
|
485
|
+
pastedTexts: textSnapshot,
|
|
486
|
+
onCommitted: (hasImageSnapshot || hasTextSnapshot)
|
|
487
|
+
? () => { clearPastedImagesSnapshot(imageSnapshot); clearPastedTextsSnapshot(textSnapshot); }
|
|
488
|
+
: null,
|
|
489
|
+
});
|
|
490
|
+
if (accepted) {
|
|
491
|
+
armTranscriptFollow();
|
|
492
|
+
if (imageRefs.size === 0 || (!hasImageSnapshot && !state.busy)) clearPastedImagesSnapshot();
|
|
493
|
+
else if (state.busy && hasImageSnapshot) clearPastedImagesSnapshot(imageSnapshot);
|
|
494
|
+
if (textRefs.size === 0 || (!hasTextSnapshot && !state.busy)) clearPastedTextsSnapshot();
|
|
495
|
+
else if (state.busy && hasTextSnapshot) clearPastedTextsSnapshot(textSnapshot);
|
|
496
|
+
}
|
|
497
|
+
return accepted;
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
return { onSubmit };
|
|
501
|
+
}
|