@oh-my-pi/pi-coding-agent 16.2.2 → 16.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/dist/cli.js +3682 -3615
- package/dist/types/advisor/__tests__/config.test.d.ts +1 -0
- package/dist/types/advisor/advise-tool.d.ts +8 -4
- package/dist/types/advisor/config.d.ts +88 -0
- package/dist/types/advisor/index.d.ts +1 -0
- package/dist/types/advisor/transcript-recorder.d.ts +13 -2
- package/dist/types/advisor/watchdog.d.ts +20 -0
- package/dist/types/cli/update-cli.d.ts +15 -0
- package/dist/types/collab/guest.d.ts +29 -0
- package/dist/types/collab/replication-shrink.d.ts +39 -0
- package/dist/types/config/provider-globals.d.ts +7 -0
- package/dist/types/config/settings-schema.d.ts +81 -0
- package/dist/types/debug/log-viewer.d.ts +1 -0
- package/dist/types/debug/raw-sse.d.ts +1 -0
- package/dist/types/edit/hashline/diff.d.ts +0 -11
- package/dist/types/extensibility/tool-event-input.d.ts +7 -0
- package/dist/types/extensibility/utils.d.ts +12 -0
- package/dist/types/mcp/transports/index.d.ts +1 -0
- package/dist/types/mcp/transports/sse.d.ts +20 -0
- package/dist/types/memories/index.d.ts +20 -1
- package/dist/types/modes/components/advisor-config.d.ts +59 -0
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +9 -1
- package/dist/types/modes/components/settings-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/component.d.ts +35 -1
- package/dist/types/modes/components/status-line/types.d.ts +13 -1
- package/dist/types/modes/controllers/command-controller.d.ts +3 -3
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +11 -4
- package/dist/types/modes/skill-command.d.ts +32 -0
- package/dist/types/modes/types.d.ts +9 -3
- package/dist/types/session/agent-session.d.ts +58 -10
- package/dist/types/session/indexed-session-storage.d.ts +7 -1
- package/dist/types/session/messages.d.ts +26 -0
- package/dist/types/session/messages.test.d.ts +1 -0
- package/dist/types/session/session-entries.d.ts +31 -3
- package/dist/types/session/session-history-format.d.ts +6 -0
- package/dist/types/session/session-loader.d.ts +9 -1
- package/dist/types/session/session-manager.d.ts +8 -7
- package/dist/types/session/session-storage.d.ts +11 -0
- package/dist/types/session/session-title-slot.d.ts +19 -0
- package/dist/types/ssh/connection-manager.d.ts +47 -0
- package/dist/types/ssh/utils.d.ts +16 -0
- package/dist/types/task/executor.d.ts +3 -16
- package/dist/types/task/render.d.ts +0 -5
- package/dist/types/task/renderer.d.ts +13 -0
- package/dist/types/task/types.d.ts +16 -0
- package/dist/types/task/yield-assembly.d.ts +28 -0
- package/dist/types/tiny/text.d.ts +8 -0
- package/dist/types/tools/render-utils.d.ts +2 -0
- package/dist/types/tools/review.d.ts +6 -4
- package/dist/types/tools/ssh.d.ts +1 -1
- package/dist/types/tools/todo.d.ts +6 -0
- package/dist/types/tools/yield.d.ts +8 -3
- package/dist/types/utils/thinking-display.d.ts +4 -0
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +242 -10
- package/src/advisor/__tests__/config.test.ts +173 -0
- package/src/advisor/advise-tool.ts +11 -6
- package/src/advisor/config.ts +256 -0
- package/src/advisor/index.ts +1 -0
- package/src/advisor/runtime.ts +12 -2
- package/src/advisor/transcript-recorder.ts +25 -2
- package/src/advisor/watchdog.ts +57 -31
- package/src/autolearn/controller.ts +13 -22
- package/src/autoresearch/index.ts +7 -2
- package/src/cli/gc-cli.ts +17 -10
- package/src/cli/update-cli.ts +254 -0
- package/src/collab/guest.ts +43 -7
- package/src/collab/host.ts +13 -8
- package/src/collab/replication-shrink.ts +111 -0
- package/src/config/model-registry.ts +80 -18
- package/src/config/provider-globals.ts +25 -0
- package/src/config/settings-schema.ts +77 -0
- package/src/debug/index.ts +32 -7
- package/src/debug/log-viewer.ts +111 -53
- package/src/debug/raw-sse.ts +68 -48
- package/src/discovery/codex.ts +13 -5
- package/src/edit/hashline/diff.ts +57 -4
- package/src/eval/__tests__/julia-prelude.test.ts +2 -2
- package/src/eval/js/shared/local-module-loader.ts +23 -1
- package/src/export/html/template.js +13 -7
- package/src/extensibility/extensions/loader.ts +5 -3
- package/src/extensibility/extensions/wrapper.ts +9 -3
- package/src/extensibility/hooks/loader.ts +3 -3
- package/src/extensibility/hooks/tool-wrapper.ts +13 -4
- package/src/extensibility/plugins/manager.ts +2 -1
- package/src/extensibility/tool-event-input.ts +23 -0
- package/src/extensibility/utils.ts +74 -0
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/mcp/client.ts +3 -1
- package/src/mcp/manager.ts +12 -5
- package/src/mcp/transports/index.ts +1 -0
- package/src/mcp/transports/sse.ts +377 -0
- package/src/memories/index.ts +130 -15
- package/src/memory-backend/local-backend.ts +5 -3
- package/src/modes/components/advisor-config.ts +555 -0
- package/src/modes/components/advisor-message.ts +9 -2
- package/src/modes/components/agent-hub.ts +9 -4
- package/src/modes/components/index.ts +2 -0
- package/src/modes/components/model-selector.ts +79 -48
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/component.ts +150 -5
- package/src/modes/components/status-line/segments.ts +46 -21
- package/src/modes/components/status-line/types.ts +13 -1
- package/src/modes/components/tool-execution.ts +47 -6
- package/src/modes/controllers/command-controller.ts +27 -36
- package/src/modes/controllers/event-controller.ts +113 -1
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +61 -61
- package/src/modes/controllers/selector-controller.ts +100 -9
- package/src/modes/interactive-mode.ts +74 -25
- package/src/modes/skill-command.ts +116 -0
- package/src/modes/types.ts +9 -3
- package/src/modes/utils/ui-helpers.ts +41 -23
- package/src/prompts/agents/reviewer.md +11 -10
- package/src/prompts/goals/goal-todo-context.md +12 -0
- package/src/prompts/review-custom-request.md +1 -2
- package/src/prompts/review-request.md +1 -2
- package/src/prompts/system/interrupted-thinking.md +7 -0
- package/src/prompts/system/recap-user.md +9 -0
- package/src/prompts/system/subagent-system-prompt.md +8 -5
- package/src/prompts/system/subagent-yield-reminder.md +6 -5
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/irc.md +2 -2
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +28 -24
- package/src/session/agent-session.ts +899 -429
- package/src/session/indexed-session-storage.ts +86 -13
- package/src/session/messages.test.ts +125 -0
- package/src/session/messages.ts +172 -9
- package/src/session/redis-session-storage.ts +49 -2
- package/src/session/session-entries.ts +39 -2
- package/src/session/session-history-format.ts +29 -2
- package/src/session/session-listing.ts +54 -24
- package/src/session/session-loader.ts +66 -3
- package/src/session/session-manager.ts +115 -22
- package/src/session/session-persistence.ts +95 -1
- package/src/session/session-storage.ts +36 -0
- package/src/session/session-title-slot.ts +141 -0
- package/src/session/sql-session-storage.ts +71 -11
- package/src/slash-commands/builtin-registry.ts +23 -24
- package/src/ssh/__tests__/connection-manager-args.test.ts +123 -1
- package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +55 -18
- package/src/ssh/connection-manager.ts +139 -12
- package/src/ssh/file-transfer.ts +23 -18
- package/src/ssh/ssh-executor.ts +2 -13
- package/src/ssh/utils.ts +19 -0
- package/src/task/executor.ts +21 -23
- package/src/task/render.ts +162 -20
- package/src/task/renderer.ts +14 -0
- package/src/task/types.ts +17 -0
- package/src/task/yield-assembly.ts +207 -0
- package/src/tiny/text.ts +23 -0
- package/src/tools/ask.ts +55 -4
- package/src/tools/render-utils.ts +2 -0
- package/src/tools/renderers.ts +8 -2
- package/src/tools/review.ts +17 -7
- package/src/tools/ssh.ts +8 -4
- package/src/tools/todo.ts +17 -1
- package/src/tools/yield.ts +140 -31
- package/src/utils/thinking-display.ts +15 -0
- package/src/prompts/system/autolearn-nudge.md +0 -5
|
@@ -4,8 +4,15 @@ import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
|
|
|
4
4
|
import type { OAuthProvider } from "@oh-my-pi/pi-ai/oauth/types";
|
|
5
5
|
import type { Component, OverlayHandle } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { Input, Loader, Spacer, setTuiTight, Text } from "@oh-my-pi/pi-tui";
|
|
7
|
-
import { getAgentDbPath, getProjectDir, normalizePathForComparison } from "@oh-my-pi/pi-utils";
|
|
8
|
-
import {
|
|
7
|
+
import { getAgentDbPath, getAgentDir, getProjectDir, normalizePathForComparison } from "@oh-my-pi/pi-utils";
|
|
8
|
+
import {
|
|
9
|
+
type AdvisorConfigScope,
|
|
10
|
+
discoverAdvisorConfigs,
|
|
11
|
+
loadWatchdogConfigFile,
|
|
12
|
+
resolveAdvisorConfigEditPath,
|
|
13
|
+
saveWatchdogConfigFile,
|
|
14
|
+
} from "../../advisor";
|
|
15
|
+
import { formatModelSelectorValue, resolveAdvisorRoleSelection } from "../../config/model-resolver";
|
|
9
16
|
import { getRoleInfo } from "../../config/model-roles";
|
|
10
17
|
import { settings } from "../../config/settings";
|
|
11
18
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
@@ -49,7 +56,9 @@ import {
|
|
|
49
56
|
} from "../../tools";
|
|
50
57
|
import { shortenPath } from "../../tools/render-utils";
|
|
51
58
|
import { copyToClipboard } from "../../utils/clipboard";
|
|
59
|
+
import { repo } from "../../utils/git";
|
|
52
60
|
import { setSessionTerminalTitle } from "../../utils/title-generator";
|
|
61
|
+
import { type AdvisorConfigDeps, AdvisorConfigOverlayComponent } from "../components/advisor-config";
|
|
53
62
|
import { AgentDashboard } from "../components/agent-dashboard";
|
|
54
63
|
import { AgentHubOverlayComponent } from "../components/agent-hub";
|
|
55
64
|
import { AssistantMessageComponent } from "../components/assistant-message";
|
|
@@ -163,6 +172,7 @@ export class SelectorController {
|
|
|
163
172
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
164
173
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
165
174
|
transparent: settings.get("statusLine.transparent"),
|
|
175
|
+
compactThinkingLevel: settings.get("statusLine.compactThinkingLevel"),
|
|
166
176
|
...previewSettings,
|
|
167
177
|
});
|
|
168
178
|
this.ctx.updateEditorTopBorder();
|
|
@@ -191,6 +201,7 @@ export class SelectorController {
|
|
|
191
201
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
192
202
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
193
203
|
transparent: settings.get("statusLine.transparent"),
|
|
204
|
+
compactThinkingLevel: settings.get("statusLine.compactThinkingLevel"),
|
|
194
205
|
});
|
|
195
206
|
this.ctx.updateEditorTopBorder();
|
|
196
207
|
this.ctx.ui.requestRender();
|
|
@@ -209,6 +220,78 @@ export class SelectorController {
|
|
|
209
220
|
});
|
|
210
221
|
}
|
|
211
222
|
|
|
223
|
+
showAdvisorConfigure(): void {
|
|
224
|
+
const cwd = this.ctx.sessionManager.getCwd();
|
|
225
|
+
const agentDir = getAgentDir() ?? getProjectDir();
|
|
226
|
+
const initialScope: AdvisorConfigScope = "project";
|
|
227
|
+
void (async () => {
|
|
228
|
+
// "Project" scope edits the repo-root WATCHDOG.yml (the project-level file
|
|
229
|
+
// discovery walks), not the launch subdir — `getProjectDir()` is only cwd.
|
|
230
|
+
let projectDir = cwd;
|
|
231
|
+
try {
|
|
232
|
+
projectDir = (await repo.root(cwd)) ?? cwd;
|
|
233
|
+
} catch {
|
|
234
|
+
projectDir = cwd;
|
|
235
|
+
}
|
|
236
|
+
const dirs = { projectDir, agentDir };
|
|
237
|
+
const initialDoc = await loadWatchdogConfigFile(await resolveAdvisorConfigEditPath(initialScope, dirs));
|
|
238
|
+
// Fullscreen editor on the alternate screen (the /settings idiom): the
|
|
239
|
+
// overlay holds the alt buffer + mouse tracking; the transcript stays put.
|
|
240
|
+
let overlayHandle: OverlayHandle | undefined;
|
|
241
|
+
const done = () => {
|
|
242
|
+
overlayHandle?.hide();
|
|
243
|
+
this.focusActiveEditorArea();
|
|
244
|
+
this.ctx.ui.requestRender();
|
|
245
|
+
};
|
|
246
|
+
// Label the seeded implicit-default row with the actual advisor-role model
|
|
247
|
+
// (NOT the first live advisor, which may be a named advisor from another scope).
|
|
248
|
+
const advisorRoleSel = resolveAdvisorRoleSelection(
|
|
249
|
+
this.ctx.settings,
|
|
250
|
+
this.ctx.session.modelRegistry.getAvailable(),
|
|
251
|
+
this.ctx.session.modelRegistry,
|
|
252
|
+
);
|
|
253
|
+
const defaultAdvisorModel = advisorRoleSel?.model;
|
|
254
|
+
const deps: AdvisorConfigDeps = {
|
|
255
|
+
modelRegistry: this.ctx.session.modelRegistry,
|
|
256
|
+
settings: this.ctx.settings,
|
|
257
|
+
scopedModels: this.ctx.session.scopedModels,
|
|
258
|
+
availableToolNames: this.ctx.session.getAdvisorAvailableToolNames(),
|
|
259
|
+
defaultModelLabel: defaultAdvisorModel
|
|
260
|
+
? `${defaultAdvisorModel.provider}/${defaultAdvisorModel.id}`
|
|
261
|
+
: undefined,
|
|
262
|
+
};
|
|
263
|
+
const overlay = new AdvisorConfigOverlayComponent(this.ctx.ui, deps, initialScope, initialDoc, {
|
|
264
|
+
loadDoc: async scope => loadWatchdogConfigFile(await resolveAdvisorConfigEditPath(scope, dirs)),
|
|
265
|
+
save: async (scope, doc) => {
|
|
266
|
+
await saveWatchdogConfigFile(await resolveAdvisorConfigEditPath(scope, dirs), doc);
|
|
267
|
+
// Re-discover the merged roster (project + user) so the live advisors
|
|
268
|
+
// reflect cross-level precedence, not just the edited file.
|
|
269
|
+
const discovered = await discoverAdvisorConfigs(cwd, agentDir);
|
|
270
|
+
const count = this.ctx.session.applyAdvisorConfigs(discovered.advisors, discovered.sharedInstructions);
|
|
271
|
+
this.ctx.statusLine.invalidate();
|
|
272
|
+
this.ctx.showStatus(
|
|
273
|
+
count > 0
|
|
274
|
+
? `Saved ${scope} WATCHDOG.yml — ${count} advisor${count === 1 ? "" : "s"} active.`
|
|
275
|
+
: `Saved ${scope} WATCHDOG.yml. Run /advisor on to activate the configured advisors.`,
|
|
276
|
+
);
|
|
277
|
+
this.ctx.ui.requestRender();
|
|
278
|
+
},
|
|
279
|
+
close: done,
|
|
280
|
+
requestRender: () => this.ctx.ui.requestRender(),
|
|
281
|
+
notify: message => this.ctx.showStatus(message),
|
|
282
|
+
});
|
|
283
|
+
overlayHandle = this.ctx.ui.showOverlay(overlay, {
|
|
284
|
+
anchor: "bottom-center",
|
|
285
|
+
width: "100%",
|
|
286
|
+
maxHeight: "100%",
|
|
287
|
+
margin: 0,
|
|
288
|
+
fullscreen: true,
|
|
289
|
+
});
|
|
290
|
+
this.ctx.ui.setFocus(overlay);
|
|
291
|
+
this.ctx.ui.requestRender();
|
|
292
|
+
})();
|
|
293
|
+
}
|
|
294
|
+
|
|
212
295
|
showHistorySearch(): void {
|
|
213
296
|
const historyStorage = this.ctx.historyStorage;
|
|
214
297
|
if (!historyStorage) return;
|
|
@@ -451,6 +534,7 @@ export class SelectorController {
|
|
|
451
534
|
case "statusLine.showHookStatus":
|
|
452
535
|
case "statusLine.sessionAccent":
|
|
453
536
|
case "statusLine.transparent":
|
|
537
|
+
case "statusLine.compactThinkingLevel":
|
|
454
538
|
case "statusLineSegments":
|
|
455
539
|
case "statusLineModelThinking":
|
|
456
540
|
case "statusLinePathAbbreviate":
|
|
@@ -471,6 +555,7 @@ export class SelectorController {
|
|
|
471
555
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
472
556
|
transparent: settings.get("statusLine.transparent"),
|
|
473
557
|
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
558
|
+
compactThinkingLevel: settings.get("statusLine.compactThinkingLevel"),
|
|
474
559
|
};
|
|
475
560
|
this.ctx.statusLine.updateSettings(statusLineSettings);
|
|
476
561
|
this.ctx.updateEditorTopBorder();
|
|
@@ -536,19 +621,25 @@ export class SelectorController {
|
|
|
536
621
|
done();
|
|
537
622
|
this.ctx.ui.requestRender();
|
|
538
623
|
} else if (role === "default") {
|
|
539
|
-
|
|
540
|
-
await this.ctx.session.setModel(model, role, {
|
|
624
|
+
const { switched } = await this.ctx.session.setModel(model, role, {
|
|
541
625
|
selector,
|
|
542
626
|
thinkingLevel: concreteThinking,
|
|
543
627
|
persist: true,
|
|
628
|
+
currentContextTokens,
|
|
544
629
|
});
|
|
545
630
|
if (isAuto) {
|
|
546
|
-
|
|
547
|
-
|
|
631
|
+
if (switched) {
|
|
632
|
+
this.ctx.session.setThinkingLevel(AUTO_THINKING, true);
|
|
633
|
+
} else {
|
|
634
|
+
this.ctx.settings.set("defaultThinkingLevel", AUTO_THINKING);
|
|
635
|
+
}
|
|
636
|
+
} else if (switched && concreteThinking && concreteThinking !== ThinkingLevel.Inherit) {
|
|
548
637
|
this.ctx.session.setThinkingLevel(concreteThinking);
|
|
549
638
|
}
|
|
550
|
-
|
|
551
|
-
|
|
639
|
+
if (switched) {
|
|
640
|
+
this.ctx.statusLine.invalidate();
|
|
641
|
+
this.ctx.updateEditorBorderColor();
|
|
642
|
+
}
|
|
552
643
|
this.ctx.showStatus(`Default model: ${selector ?? model.id}`);
|
|
553
644
|
// Don't call done() - selector stays open for role assignment
|
|
554
645
|
} else {
|
|
@@ -933,7 +1024,7 @@ export class SelectorController {
|
|
|
933
1024
|
|
|
934
1025
|
this.ctx.clearTransientSessionUi();
|
|
935
1026
|
this.ctx.statusLine.invalidate();
|
|
936
|
-
this.ctx.statusLine.
|
|
1027
|
+
this.ctx.statusLine.resetActiveTime();
|
|
937
1028
|
this.ctx.updateEditorTopBorder();
|
|
938
1029
|
this.ctx.updateEditorBorderColor();
|
|
939
1030
|
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
@@ -53,6 +53,7 @@ import { reset as resetCapabilities } from "../capability";
|
|
|
53
53
|
import type { CollabGuestLink } from "../collab/guest";
|
|
54
54
|
import type { CollabHost } from "../collab/host";
|
|
55
55
|
import { KeybindingsManager } from "../config/keybindings";
|
|
56
|
+
import { applyProviderGlobalsFromSettings } from "../config/provider-globals";
|
|
56
57
|
import { isSettingsInitialized, onStatusLineSessionAccentChanged, Settings, settings } from "../config/settings";
|
|
57
58
|
import { clearClaudePluginRootsCache } from "../discovery/helpers";
|
|
58
59
|
import type {
|
|
@@ -100,7 +101,6 @@ import { STTController, type SttState } from "../stt";
|
|
|
100
101
|
import { discoverTitleSystemPromptFile, resolvePromptInput } from "../system-prompt";
|
|
101
102
|
import { formatTaskId } from "../task/render";
|
|
102
103
|
import type { LspStartupServerInfo } from "../tools";
|
|
103
|
-
import { isImageProviderPreference, setPreferredImageProvider } from "../tools/image-gen";
|
|
104
104
|
import { normalizeLocalScheme } from "../tools/path-utils";
|
|
105
105
|
import { replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../tools/render-utils";
|
|
106
106
|
import { setAutoQaConsentHandler } from "../tools/report-tool-issue";
|
|
@@ -112,13 +112,8 @@ import { renderTreeList } from "../tui/tree-list";
|
|
|
112
112
|
import type { EventBus } from "../utils/event-bus";
|
|
113
113
|
import { getEditorCommand, openInEditor } from "../utils/external-editor";
|
|
114
114
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-color";
|
|
115
|
+
import { messageHasDisplayableThinking } from "../utils/thinking-display";
|
|
115
116
|
import { popTerminalTitle, pushTerminalTitle, setSessionTerminalTitle } from "../utils/title-generator";
|
|
116
|
-
import {
|
|
117
|
-
isSearchProviderId,
|
|
118
|
-
isSearchProviderPreference,
|
|
119
|
-
setExcludedSearchProviders,
|
|
120
|
-
setPreferredSearchProvider,
|
|
121
|
-
} from "../web/search";
|
|
122
117
|
import type { AssistantMessageComponent } from "./components/assistant-message";
|
|
123
118
|
import type { BashExecutionComponent } from "./components/bash-execution";
|
|
124
119
|
import { ChatBlock, type ChatBlockHost } from "./components/chat-block";
|
|
@@ -383,6 +378,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
383
378
|
chatContainer: TranscriptContainer;
|
|
384
379
|
pendingMessagesContainer: Container;
|
|
385
380
|
statusContainer: Container;
|
|
381
|
+
todoReminderContainer: Container;
|
|
386
382
|
todoContainer: Container;
|
|
387
383
|
subagentContainer: Container;
|
|
388
384
|
btwContainer: Container;
|
|
@@ -413,14 +409,29 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
413
409
|
#modelCycleClearTimer: NodeJS.Timeout | undefined;
|
|
414
410
|
todoPhases: TodoPhase[] = [];
|
|
415
411
|
hideThinkingBlock = false;
|
|
412
|
+
#sessionsWithDisplayableThinkingContent = new WeakSet<AgentSession>();
|
|
413
|
+
/** Whether the visible session has produced thinking content the user can reveal. */
|
|
414
|
+
get hasDisplayableThinkingContent(): boolean {
|
|
415
|
+
return this.#sessionsWithDisplayableThinkingContent.has(this.viewSession);
|
|
416
|
+
}
|
|
417
|
+
/** Record received reasoning content so Ctrl+T can reveal it even when model metadata says thinking is off. */
|
|
418
|
+
noteDisplayableThinkingContent(message: AgentMessage): boolean {
|
|
419
|
+
if (this.hasDisplayableThinkingContent || !messageHasDisplayableThinking(message, this.proseOnlyThinking)) {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
this.#sessionsWithDisplayableThinkingContent.add(this.viewSession);
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
416
425
|
/**
|
|
417
|
-
* Effective thinking-block visibility: hidden when the user's setting is on
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
426
|
+
* Effective thinking-block visibility: hidden when the user's setting is on,
|
|
427
|
+
* or while thinking is "off" before the session has actually produced
|
|
428
|
+
* displayable thinking content. Some providers return thinking blocks without
|
|
429
|
+
* advertising reasoning support, so observed content unlocks the visibility
|
|
430
|
+
* toggle.
|
|
421
431
|
*/
|
|
422
432
|
get effectiveHideThinkingBlock(): boolean {
|
|
423
|
-
|
|
433
|
+
const thinkingOff = (this.viewSession?.thinkingLevel ?? ThinkingLevel.Off) === ThinkingLevel.Off;
|
|
434
|
+
return this.hideThinkingBlock || (thinkingOff && !this.hasDisplayableThinkingContent);
|
|
424
435
|
}
|
|
425
436
|
proseOnlyThinking = true;
|
|
426
437
|
compactionQueuedMessages: CompactionQueuedMessage[] = [];
|
|
@@ -536,6 +547,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
536
547
|
this.retryLoader = undefined;
|
|
537
548
|
}
|
|
538
549
|
this.statusContainer.clear();
|
|
550
|
+
this.todoReminderContainer.clear();
|
|
539
551
|
this.pendingMessagesContainer.clear();
|
|
540
552
|
this.#cancelModelCycleClearTimer();
|
|
541
553
|
this.modelCycleContainer.clear();
|
|
@@ -615,6 +627,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
615
627
|
this.chatContainer = new TranscriptContainer();
|
|
616
628
|
this.pendingMessagesContainer = new Container();
|
|
617
629
|
this.statusContainer = new AnchoredLiveContainer();
|
|
630
|
+
this.todoReminderContainer = new AnchoredLiveContainer();
|
|
618
631
|
this.todoContainer = new AnchoredLiveContainer();
|
|
619
632
|
this.subagentContainer = new AnchoredLiveContainer();
|
|
620
633
|
this.btwContainer = new AnchoredLiveContainer();
|
|
@@ -834,6 +847,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
834
847
|
|
|
835
848
|
this.ui.addChild(this.chatContainer);
|
|
836
849
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
850
|
+
this.ui.addChild(this.todoReminderContainer);
|
|
837
851
|
this.ui.addChild(this.todoContainer);
|
|
838
852
|
this.ui.addChild(this.subagentContainer);
|
|
839
853
|
this.ui.addChild(this.btwContainer);
|
|
@@ -1037,18 +1051,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1037
1051
|
// module-level search/image provider state reflects the destination
|
|
1038
1052
|
// project's configuration. Without this, the previous project's
|
|
1039
1053
|
// exclusions leak and newly-excluded providers are still used.
|
|
1040
|
-
|
|
1041
|
-
if (Array.isArray(excludedWebSearchProviders)) {
|
|
1042
|
-
setExcludedSearchProviders(excludedWebSearchProviders.filter(isSearchProviderId));
|
|
1043
|
-
}
|
|
1044
|
-
const webSearchProvider = settings.get("providers.webSearch");
|
|
1045
|
-
if (typeof webSearchProvider === "string" && isSearchProviderPreference(webSearchProvider)) {
|
|
1046
|
-
setPreferredSearchProvider(webSearchProvider);
|
|
1047
|
-
}
|
|
1048
|
-
const imageProvider = settings.get("providers.image");
|
|
1049
|
-
if (isImageProviderPreference(imageProvider)) {
|
|
1050
|
-
setPreferredImageProvider(imageProvider);
|
|
1051
|
-
}
|
|
1054
|
+
applyProviderGlobalsFromSettings(settings);
|
|
1052
1055
|
}
|
|
1053
1056
|
// Re-warm plugin roots, capabilities, slash commands, and the ssh tool so
|
|
1054
1057
|
// the next prompt sees everything scoped to the new project directory.
|
|
@@ -1417,6 +1420,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1417
1420
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
1418
1421
|
transparent: settings.get("statusLine.transparent"),
|
|
1419
1422
|
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
1423
|
+
compactThinkingLevel: settings.get("statusLine.compactThinkingLevel"),
|
|
1420
1424
|
});
|
|
1421
1425
|
}
|
|
1422
1426
|
|
|
@@ -1478,11 +1482,47 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1478
1482
|
}
|
|
1479
1483
|
|
|
1480
1484
|
rebuildChatFromMessages(): void {
|
|
1485
|
+
// Mid-stream rebuilds (e.g. `/shake`, theme/setting changes that touch the
|
|
1486
|
+
// transcript) replay only committed `state.messages`. The agent's in-flight
|
|
1487
|
+
// `streamMessage` and its still-pending tool calls live OUTSIDE
|
|
1488
|
+
// `state.messages` until `message_end`, so a plain clear+replay detaches
|
|
1489
|
+
// their UI components while keeping the `streamingComponent` / `pendingTools`
|
|
1490
|
+
// references — subsequent `message_update`/`message_end` events would then
|
|
1491
|
+
// update orphaned components that never re-render and the live LLM output
|
|
1492
|
+
// vanishes from the chat (#3656). Snapshot the in-flight components,
|
|
1493
|
+
// clear+replay, then re-append them in their original chat-container order
|
|
1494
|
+
// and restore the `pendingTools` map so streaming routes back into them.
|
|
1495
|
+
const liveComponents: Component[] = [];
|
|
1496
|
+
const livePendingTools = new Map<string, ToolExecutionHandle>();
|
|
1497
|
+
if (this.viewSession?.isStreaming) {
|
|
1498
|
+
const liveSet = new Set<Component>();
|
|
1499
|
+
if (this.streamingComponent) liveSet.add(this.streamingComponent);
|
|
1500
|
+
for (const [id, component] of this.pendingTools) {
|
|
1501
|
+
livePendingTools.set(id, component);
|
|
1502
|
+
liveSet.add(component as unknown as Component);
|
|
1503
|
+
}
|
|
1504
|
+
if (liveSet.size > 0) {
|
|
1505
|
+
for (const child of this.chatContainer.children) {
|
|
1506
|
+
if (liveSet.has(child)) liveComponents.push(child);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1481
1510
|
this.chatContainer.clear();
|
|
1482
1511
|
// Live display uses the compacted transcript tail; export/resume callers
|
|
1483
1512
|
// can still request the full inline compaction history.
|
|
1484
1513
|
const context = this.viewSession.buildTranscriptSessionContext({ collapseCompactedHistory: true });
|
|
1485
1514
|
this.renderSessionContext(context);
|
|
1515
|
+
for (const child of liveComponents) {
|
|
1516
|
+
this.chatContainer.addChild(child);
|
|
1517
|
+
}
|
|
1518
|
+
// `renderSessionContext` clears `pendingTools` at start AND end so the
|
|
1519
|
+
// reconstructed historical tool components don't leak into live tracking.
|
|
1520
|
+
// Restore the in-flight entries afterwards so the next streamed tool-call
|
|
1521
|
+
// delta is routed into the preserved component instead of stacking a
|
|
1522
|
+
// duplicate ToolExecutionComponent below it.
|
|
1523
|
+
for (const [id, component] of livePendingTools) {
|
|
1524
|
+
this.pendingTools.set(id, component);
|
|
1525
|
+
}
|
|
1486
1526
|
// During the pre-streaming window — after `startPendingSubmission` has
|
|
1487
1527
|
// optimistically rendered the user's message but before the user
|
|
1488
1528
|
// `message_start` event lands it in `session` entries — any rebuild
|
|
@@ -1571,8 +1611,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1571
1611
|
}),
|
|
1572
1612
|
}));
|
|
1573
1613
|
if (!mutated) return;
|
|
1574
|
-
this.todoPhases = next;
|
|
1575
1614
|
this.session.setTodoPhases(next);
|
|
1615
|
+
this.setTodos(next);
|
|
1576
1616
|
}
|
|
1577
1617
|
|
|
1578
1618
|
#cancelTodoAutoClearTimer(): void {
|
|
@@ -3583,6 +3623,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3583
3623
|
sessionContext: SessionContext,
|
|
3584
3624
|
options?: { updateFooter?: boolean; populateHistory?: boolean },
|
|
3585
3625
|
): void {
|
|
3626
|
+
for (const message of sessionContext.messages) {
|
|
3627
|
+
this.noteDisplayableThinkingContent(message);
|
|
3628
|
+
}
|
|
3586
3629
|
this.#uiHelpers.renderSessionContext(sessionContext, options);
|
|
3587
3630
|
}
|
|
3588
3631
|
|
|
@@ -3844,6 +3887,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3844
3887
|
this.#selectorController.showSettingsSelector();
|
|
3845
3888
|
}
|
|
3846
3889
|
|
|
3890
|
+
showAdvisorConfigure(): void {
|
|
3891
|
+
this.#selectorController.showAdvisorConfigure();
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3847
3894
|
showHistorySearch(): void {
|
|
3848
3895
|
this.#selectorController.showHistorySearch();
|
|
3849
3896
|
}
|
|
@@ -4029,12 +4076,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4029
4076
|
},
|
|
4030
4077
|
];
|
|
4031
4078
|
}
|
|
4079
|
+
this.todoReminderContainer.clear();
|
|
4032
4080
|
this.#syncTodoAutoClearTimer();
|
|
4033
4081
|
this.#renderTodoList();
|
|
4034
4082
|
this.ui.requestRender();
|
|
4035
4083
|
}
|
|
4036
4084
|
|
|
4037
4085
|
async reloadTodos(): Promise<void> {
|
|
4086
|
+
this.todoReminderContainer.clear();
|
|
4038
4087
|
await this.#loadTodoList();
|
|
4039
4088
|
this.ui.requestRender();
|
|
4040
4089
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import { type CustomMessage, SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails } from "../session/messages";
|
|
3
|
+
import type { InteractiveModeContext } from "./types";
|
|
4
|
+
|
|
5
|
+
type SkillCommandHost = Pick<InteractiveModeContext, "skillCommands" | "session" | "showError">;
|
|
6
|
+
|
|
7
|
+
type SkillPromptMessage = Pick<
|
|
8
|
+
CustomMessage<SkillPromptDetails>,
|
|
9
|
+
"customType" | "content" | "display" | "details" | "attribution"
|
|
10
|
+
> & {
|
|
11
|
+
customType: typeof SKILL_PROMPT_MESSAGE_TYPE;
|
|
12
|
+
content: string | (TextContent | ImageContent)[];
|
|
13
|
+
display: true;
|
|
14
|
+
details: SkillPromptDetails;
|
|
15
|
+
attribution: "user";
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type SkillPromptOptions = {
|
|
19
|
+
streamingBehavior: "steer" | "followUp";
|
|
20
|
+
queueChipText: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
interface ParsedSkillCommand {
|
|
24
|
+
commandName: string;
|
|
25
|
+
args: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface InvokeSkillCommandOptions {
|
|
29
|
+
propagateErrors?: boolean;
|
|
30
|
+
queueOnly?: boolean;
|
|
31
|
+
images?: ImageContent[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Built custom-message payload and delivery options for a `/skill:` command. */
|
|
35
|
+
export interface BuiltSkillCommandPrompt {
|
|
36
|
+
message: SkillPromptMessage;
|
|
37
|
+
options: SkillPromptOptions;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseSkillCommand(text: string): ParsedSkillCommand | undefined {
|
|
41
|
+
if (!text.startsWith("/skill:")) return undefined;
|
|
42
|
+
const spaceIndex = text.indexOf(" ");
|
|
43
|
+
const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
44
|
+
const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
|
|
45
|
+
return { commandName, args };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Return true when `text` names a registered `/skill:<name>` command. */
|
|
49
|
+
export function isKnownSkillCommand(ctx: SkillCommandHost, text: string): boolean {
|
|
50
|
+
const parsed = parseSkillCommand(text);
|
|
51
|
+
if (!parsed) return false;
|
|
52
|
+
return ctx.skillCommands.has(parsed.commandName);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Build the user-attributed custom message for a registered `/skill:<name>` command. */
|
|
56
|
+
export async function buildSkillCommandPrompt(
|
|
57
|
+
ctx: SkillCommandHost,
|
|
58
|
+
text: string,
|
|
59
|
+
streamingBehavior: "steer" | "followUp",
|
|
60
|
+
images?: ImageContent[],
|
|
61
|
+
): Promise<BuiltSkillCommandPrompt | undefined> {
|
|
62
|
+
const parsed = parseSkillCommand(text);
|
|
63
|
+
if (!parsed) return undefined;
|
|
64
|
+
const skillPath = ctx.skillCommands.get(parsed.commandName);
|
|
65
|
+
if (!skillPath) return undefined;
|
|
66
|
+
|
|
67
|
+
const content = await Bun.file(skillPath).text();
|
|
68
|
+
const body = content.replace(/^---\n[\s\S]*?\n---\n/, "").trim();
|
|
69
|
+
const metaLines = [`Skill: ${skillPath}`];
|
|
70
|
+
if (parsed.args) {
|
|
71
|
+
metaLines.push(`User: ${parsed.args}`);
|
|
72
|
+
}
|
|
73
|
+
const message = `${body}\n\n---\n\n${metaLines.join("\n")}`;
|
|
74
|
+
const textBlock: TextContent = { type: "text", text: message };
|
|
75
|
+
const promptContent = images && images.length > 0 ? [textBlock, ...images] : message;
|
|
76
|
+
const skillName = parsed.commandName.slice("skill:".length);
|
|
77
|
+
const details: SkillPromptDetails = {
|
|
78
|
+
name: skillName || parsed.commandName,
|
|
79
|
+
path: skillPath,
|
|
80
|
+
args: parsed.args || undefined,
|
|
81
|
+
lineCount: body ? body.split("\n").length : 0,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
message: {
|
|
86
|
+
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
87
|
+
content: promptContent,
|
|
88
|
+
display: true,
|
|
89
|
+
details,
|
|
90
|
+
attribution: "user",
|
|
91
|
+
},
|
|
92
|
+
options: { streamingBehavior, queueChipText: text },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Invoke a registered `/skill:<name>` command as a user-attributed custom message. */
|
|
97
|
+
export async function invokeSkillCommandFromText(
|
|
98
|
+
ctx: SkillCommandHost,
|
|
99
|
+
text: string,
|
|
100
|
+
streamingBehavior: "steer" | "followUp",
|
|
101
|
+
options?: InvokeSkillCommandOptions,
|
|
102
|
+
): Promise<boolean> {
|
|
103
|
+
try {
|
|
104
|
+
const built = await buildSkillCommandPrompt(ctx, text, streamingBehavior, options?.images);
|
|
105
|
+
if (!built) return false;
|
|
106
|
+
const promptOptions = options?.queueOnly ? { ...built.options, queueOnly: true } : built.options;
|
|
107
|
+
await ctx.session.promptCustomMessage(built.message, promptOptions);
|
|
108
|
+
return true;
|
|
109
|
+
} catch (err) {
|
|
110
|
+
if (options?.propagateErrors) {
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
ctx.showError(`Failed to load skill: ${err instanceof Error ? err.message : String(err)}`);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
}
|
package/src/modes/types.ts
CHANGED
|
@@ -96,6 +96,7 @@ export interface InteractiveModeContext {
|
|
|
96
96
|
chatContainer: TranscriptContainer;
|
|
97
97
|
pendingMessagesContainer: Container;
|
|
98
98
|
statusContainer: Container;
|
|
99
|
+
todoReminderContainer: Container;
|
|
99
100
|
todoContainer: Container;
|
|
100
101
|
subagentContainer: Container;
|
|
101
102
|
btwContainer: Container;
|
|
@@ -121,7 +122,7 @@ export interface InteractiveModeContext {
|
|
|
121
122
|
focusParentSession(): Promise<void>;
|
|
122
123
|
/** Return the view to the main session (delegates to SessionFocusController.unfocus). */
|
|
123
124
|
unfocusSession(): Promise<void>;
|
|
124
|
-
/** Clear loader,
|
|
125
|
+
/** Clear loader, transient HUD/pending containers, streaming state, and pending tools. */
|
|
125
126
|
clearTransientSessionUi(): void;
|
|
126
127
|
settings: Settings;
|
|
127
128
|
keybindings: KeybindingsManager;
|
|
@@ -161,10 +162,14 @@ export interface InteractiveModeContext {
|
|
|
161
162
|
hideThinkingBlock: boolean;
|
|
162
163
|
/**
|
|
163
164
|
* Effective thinking-block visibility: true when hidden by user setting OR
|
|
164
|
-
* thinking level is "off"
|
|
165
|
-
*
|
|
165
|
+
* thinking level is "off" before the session has produced displayable
|
|
166
|
+
* thinking content.
|
|
166
167
|
*/
|
|
167
168
|
readonly effectiveHideThinkingBlock: boolean;
|
|
169
|
+
/** Whether this visible session has produced thinking content the user can reveal. */
|
|
170
|
+
readonly hasDisplayableThinkingContent: boolean;
|
|
171
|
+
/** Record a message whose thinking content makes Ctrl+T meaningful even at thinking level "off"; returns true on first observation. */
|
|
172
|
+
noteDisplayableThinkingContent(message: AgentMessage): boolean;
|
|
168
173
|
proseOnlyThinking: boolean;
|
|
169
174
|
compactionQueuedMessages: CompactionQueuedMessage[];
|
|
170
175
|
pendingTools: Map<string, ToolExecutionHandle>;
|
|
@@ -342,6 +347,7 @@ export interface InteractiveModeContext {
|
|
|
342
347
|
|
|
343
348
|
// Selector handling
|
|
344
349
|
showSettingsSelector(): void;
|
|
350
|
+
showAdvisorConfigure(): void;
|
|
345
351
|
showHistorySearch(): void;
|
|
346
352
|
showExtensionsDashboard(): void;
|
|
347
353
|
showAgentsDashboard(): void;
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
type SkillPromptDetails,
|
|
46
46
|
} from "../../session/messages";
|
|
47
47
|
import type { SessionContext } from "../../session/session-context";
|
|
48
|
+
import { buildSkillCommandPrompt, invokeSkillCommandFromText, isKnownSkillCommand } from "../skill-command";
|
|
48
49
|
import { createAssistantMessageComponent } from "./interactive-context-helpers";
|
|
49
50
|
import {
|
|
50
51
|
assistantHasVisibleContent,
|
|
@@ -667,6 +668,15 @@ export class UiHelpers {
|
|
|
667
668
|
}
|
|
668
669
|
|
|
669
670
|
async #deliverQueuedMessage(message: CompactionQueuedMessage): Promise<void> {
|
|
671
|
+
if (
|
|
672
|
+
await invokeSkillCommandFromText(this.ctx, message.text, message.mode, {
|
|
673
|
+
propagateErrors: true,
|
|
674
|
+
queueOnly: true,
|
|
675
|
+
images: message.images,
|
|
676
|
+
})
|
|
677
|
+
) {
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
670
680
|
if (this.ctx.isKnownSlashCommand(message.text)) {
|
|
671
681
|
await this.ctx.session.prompt(message.text);
|
|
672
682
|
return;
|
|
@@ -754,29 +764,37 @@ export class UiHelpers {
|
|
|
754
764
|
await this.#deliverQueuedMessage(message);
|
|
755
765
|
}
|
|
756
766
|
|
|
757
|
-
//
|
|
758
|
-
//
|
|
759
|
-
//
|
|
760
|
-
//
|
|
761
|
-
//
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
767
|
+
// First prompt is fire-and-forget — its rejection is funneled through
|
|
768
|
+
// `restoreQueue` rather than rethrown. Plain prompts use primitive
|
|
769
|
+
// recordLocalSubmission and dispose manually in the catch. Skill prompts
|
|
770
|
+
// are rebuilt as user-attributed custom messages so queued `/skill:` text
|
|
771
|
+
// is not sent as a literal prompt after compaction.
|
|
772
|
+
let promptPromise: Promise<unknown>;
|
|
773
|
+
if (isKnownSkillCommand(this.ctx, firstPrompt.text)) {
|
|
774
|
+
const built = await buildSkillCommandPrompt(
|
|
775
|
+
this.ctx,
|
|
776
|
+
firstPrompt.text,
|
|
777
|
+
firstPrompt.mode,
|
|
778
|
+
firstPrompt.images,
|
|
779
|
+
);
|
|
780
|
+
promptPromise = built
|
|
781
|
+
? this.ctx.session.promptCustomMessage(built.message, built.options).catch(restoreQueue)
|
|
782
|
+
: Promise.resolve();
|
|
783
|
+
} else {
|
|
784
|
+
const disposeFirstPrompt = this.ctx.recordLocalSubmission(
|
|
785
|
+
firstPrompt.text,
|
|
786
|
+
firstPrompt.images?.length ?? 0,
|
|
787
|
+
);
|
|
788
|
+
promptPromise = this.ctx.session
|
|
789
|
+
.prompt(firstPrompt.text, {
|
|
790
|
+
streamingBehavior: firstPrompt.mode === "followUp" ? "followUp" : "steer",
|
|
791
|
+
images: firstPrompt.images,
|
|
792
|
+
})
|
|
793
|
+
.catch((error: unknown) => {
|
|
794
|
+
disposeFirstPrompt();
|
|
795
|
+
restoreQueue(error);
|
|
796
|
+
});
|
|
797
|
+
}
|
|
780
798
|
|
|
781
799
|
for (const message of rest) {
|
|
782
800
|
await this.#deliverQueuedMessage(message);
|