@sayknow-cli/coding-agent 0.3.7 → 0.3.9
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/dist/types/append-only-mode.d.ts +31 -0
- package/dist/types/capability/rule.d.ts +8 -0
- package/dist/types/cli/completion-cli.d.ts +41 -0
- package/dist/types/cli/config-cli.d.ts +1 -0
- package/dist/types/cli/fixture-report.d.ts +20 -0
- package/dist/types/cli/fixture-report.test.d.ts +1 -0
- package/dist/types/cli/nofile-limit.d.ts +11 -0
- package/dist/types/cli/notify-cli.d.ts +7 -0
- package/dist/types/cli/update-cli.d.ts +22 -0
- package/dist/types/cli.d.ts +124 -1
- package/dist/types/commands/completion.d.ts +29 -0
- package/dist/types/commands/config.d.ts +3 -0
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +7 -6
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +6 -0
- package/dist/types/config/model-profiles.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +119 -1
- package/dist/types/config/settings.d.ts +8 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/export/ttsr.d.ts +14 -3
- package/dist/types/extensibility/extensions/runner.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/goals/runtime.d.ts +1 -0
- package/dist/types/hooks/native-skill-hook.d.ts +12 -0
- package/dist/types/main.d.ts +4 -1
- package/dist/types/modes/components/assistant-message.d.ts +5 -1
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +9 -0
- package/dist/types/modes/components/thinking-selector.d.ts +2 -2
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +5 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +4 -0
- package/dist/types/modes/print-mode.d.ts +13 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +1 -3
- package/dist/types/modes/rpc/rpc-socket-security.d.ts +3 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/modes/utils/injected-user-submission.d.ts +52 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/notifications/config-commands.d.ts +9 -0
- package/dist/types/notifications/config.d.ts +30 -1
- package/dist/types/notifications/daemon-paths.d.ts +1 -0
- package/dist/types/notifications/index.d.ts +8 -3
- package/dist/types/notifications/lifecycle-commands.d.ts +12 -3
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +2 -1
- package/dist/types/notifications/reply-sent-store.d.ts +53 -0
- package/dist/types/notifications/rich-draft.d.ts +68 -0
- package/dist/types/notifications/rich-render.d.ts +90 -0
- package/dist/types/notifications/telegram-daemon.d.ts +45 -3
- package/dist/types/notifications/telegram-reference.d.ts +7 -5
- package/dist/types/notifications/threaded-render.d.ts +14 -0
- package/dist/types/runtime-api-key.d.ts +7 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +61 -6
- package/dist/types/session/agent-storage.d.ts +1 -0
- package/dist/types/session/file-mention-pruning.d.ts +8 -0
- package/dist/types/session/messages.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +17 -1
- package/dist/types/skc-runtime/deep-interview-ambiguity.d.ts +39 -0
- package/dist/types/skc-runtime/deep-interview-recorder.d.ts +10 -1
- package/dist/types/skc-runtime/deep-interview-state.d.ts +17 -2
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +7 -0
- package/dist/types/skc-runtime/state-writer.d.ts +3 -0
- package/dist/types/skc-runtime/tmux-common.d.ts +1 -0
- package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +120 -13
- package/dist/types/skill-state/active-state.d.ts +18 -1
- package/dist/types/system-prompt.d.ts +18 -2
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/fork-context-budget.d.ts +7 -0
- package/dist/types/task/token-log.d.ts +30 -0
- package/dist/types/task/token-log.test.d.ts +1 -0
- package/dist/types/tools/bisect.d.ts +81 -0
- package/dist/types/tools/computer.d.ts +2 -2
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/search.d.ts +2 -2
- package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
- package/dist/types/utils/file-mentions.d.ts +14 -0
- package/dist/types/utils/git.d.ts +32 -0
- package/dist/types/utils/pasted-image-path.d.ts +30 -0
- package/dist/types/web/scrapers/youtube.d.ts +8 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +8 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
- package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
- package/package.json +8 -8
- package/src/append-only-mode.ts +58 -0
- package/src/autoresearch/prompt.md +1 -1
- package/src/autoresearch/tools/log-experiment.ts +1 -1
- package/src/capability/rule.ts +9 -0
- package/src/cli/completion-cli.ts +234 -0
- package/src/cli/config-cli.ts +61 -9
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/fixture-report.test.ts +230 -0
- package/src/cli/fixture-report.ts +171 -0
- package/src/cli/nofile-limit.ts +48 -0
- package/src/cli/notify-cli.ts +103 -17
- package/src/cli/telegram-cli.ts +1 -1
- package/src/cli/update-cli.ts +148 -33
- package/src/cli.ts +29 -1
- package/src/commands/completion.ts +84 -0
- package/src/commands/config.ts +2 -0
- package/src/commands/contribution-prep.ts +1 -1
- package/src/commands/deep-interview.ts +2 -1
- package/src/config/file-lock.ts +73 -34
- package/src/config/keybindings.ts +3 -3
- package/src/config/model-profile-activation.ts +65 -0
- package/src/config/model-profiles.ts +31 -2
- package/src/config/settings-schema.ts +76 -1
- package/src/config/settings.ts +22 -0
- package/src/defaults/skc/skills/team/SKILL.md +17 -14
- package/src/defaults/skc/skills/ultragoal/SKILL.md +59 -4
- package/src/defaults/skc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
- package/src/defaults/skc-defaults.ts +9 -0
- package/src/discovery/helpers.ts +10 -0
- package/src/discovery/index.ts +1 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +60 -2
- package/src/export/ttsr.ts +96 -18
- package/src/extensibility/extensions/runner.ts +183 -219
- package/src/extensibility/extensions/types.ts +9 -0
- package/src/extensibility/skc-plugins/runtime-adapters.ts +156 -44
- package/src/goals/runtime.ts +10 -2
- package/src/hindsight/backend.ts +1 -3
- package/src/hooks/native-skill-hook.ts +124 -4
- package/src/internal-urls/docs-index.generated.ts +17 -8
- package/src/lsp/client.ts +1 -0
- package/src/main.ts +95 -43
- package/src/memories/index.ts +12 -1
- package/src/modes/components/assistant-message.ts +241 -82
- package/src/modes/components/custom-editor.ts +1 -0
- package/src/modes/components/footer.ts +7 -2
- package/src/modes/components/model-selector.ts +54 -11
- package/src/modes/components/queued-message-selector.ts +92 -0
- package/src/modes/components/settings-selector.ts +4 -0
- package/src/modes/components/status-line.ts +167 -54
- package/src/modes/components/thinking-selector.ts +10 -8
- package/src/modes/components/tool-execution.ts +44 -19
- package/src/modes/components/welcome.ts +181 -44
- package/src/modes/controllers/command-controller.ts +40 -4
- package/src/modes/controllers/event-controller.ts +77 -49
- package/src/modes/controllers/extension-ui-controller.ts +12 -11
- package/src/modes/controllers/input-controller.ts +192 -35
- package/src/modes/controllers/selector-controller.ts +168 -24
- package/src/modes/interactive-mode.ts +56 -16
- package/src/modes/print-mode.ts +43 -4
- package/src/modes/prompt-action-autocomplete.ts +21 -20
- package/src/modes/rpc/rpc-mode.ts +32 -12
- package/src/modes/rpc/rpc-socket-security.ts +13 -1
- package/src/modes/rpc/rpc-types.ts +9 -0
- package/src/modes/shared/agent-wire/command-validation.ts +4 -0
- package/src/modes/shared/agent-wire/event-contract.ts +22 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/types.ts +3 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/injected-user-submission.ts +94 -0
- package/src/modes/utils/ui-helpers.ts +20 -7
- package/src/notifications/config-commands.ts +20 -0
- package/src/notifications/config.ts +52 -5
- package/src/notifications/daemon-paths.ts +2 -0
- package/src/notifications/index.ts +144 -18
- package/src/notifications/lifecycle-commands.ts +74 -23
- package/src/notifications/lifecycle-control-runtime.ts +8 -3
- package/src/notifications/lifecycle-orchestrator.ts +19 -6
- package/src/notifications/reply-sent-store.ts +134 -0
- package/src/notifications/rich-draft.ts +107 -0
- package/src/notifications/rich-render.ts +142 -0
- package/src/notifications/telegram-daemon-control.ts +5 -3
- package/src/notifications/telegram-daemon.ts +606 -98
- package/src/notifications/telegram-reference.ts +17 -16
- package/src/notifications/threaded-render.ts +45 -2
- package/src/prompts/agents/architect.md +19 -10
- package/src/prompts/agents/critic.md +16 -18
- package/src/prompts/agents/executor.md +14 -11
- package/src/prompts/agents/planner.md +18 -13
- package/src/prompts/ci-green-request.md +3 -8
- package/src/prompts/goals/goal-continuation.md +0 -5
- package/src/prompts/goals/goal-mode-active.md +0 -5
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/system/plan-mode-subagent.md +3 -3
- package/src/prompts/system/project-prompt.md +0 -13
- package/src/prompts/system/subagent-system-prompt.md +3 -2
- package/src/prompts/system/system-prompt.md +29 -17
- package/src/prompts/system/volatile-project-context.md +10 -0
- package/src/prompts/system/web-search.md +4 -7
- package/src/prompts/tools/apply-patch.md +6 -0
- package/src/prompts/tools/ast-edit.md +1 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/bisect.md +22 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/computer.md +2 -2
- package/src/prompts/tools/cron.md +0 -2
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/find.md +1 -1
- package/src/prompts/tools/github.md +6 -6
- package/src/prompts/tools/goal.md +1 -3
- package/src/prompts/tools/image-gen.md +12 -5
- package/src/prompts/tools/irc.md +5 -4
- package/src/prompts/tools/job.md +2 -2
- package/src/prompts/tools/lsp.md +1 -1
- package/src/prompts/tools/monitor.md +2 -2
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/replace.md +2 -21
- package/src/prompts/tools/rewind.md +1 -1
- package/src/prompts/tools/search-tool-bm25.md +6 -11
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/skill.md +3 -2
- package/src/prompts/tools/ssh.md +2 -1
- package/src/prompts/tools/subagent.md +2 -2
- package/src/prompts/tools/task.md +4 -5
- package/src/prompts/tools/todo-write.md +1 -1
- package/src/prompts/tools/vim.md +1 -0
- package/src/prompts/tools/web-search.md +2 -0
- package/src/prompts/tools/write.md +8 -2
- package/src/runtime-api-key.ts +16 -0
- package/src/sdk.ts +88 -36
- package/src/session/agent-session.ts +1013 -141
- package/src/session/agent-storage.ts +4 -0
- package/src/session/file-mention-pruning.ts +64 -0
- package/src/session/messages.ts +4 -0
- package/src/session/session-manager.ts +103 -4
- package/src/skc-runtime/deep-interview-ambiguity.ts +206 -0
- package/src/skc-runtime/deep-interview-recorder.ts +62 -8
- package/src/skc-runtime/deep-interview-state.ts +44 -2
- package/src/skc-runtime/session-state-sidecar.ts +263 -20
- package/src/skc-runtime/state-runtime.ts +12 -2
- package/src/skc-runtime/state-writer.ts +15 -0
- package/src/skc-runtime/tmux-common.ts +1 -0
- package/src/skc-runtime/ultragoal-guard.ts +55 -66
- package/src/skc-runtime/ultragoal-receipt-freshness.ts +315 -0
- package/src/skc-runtime/ultragoal-runtime.ts +1672 -159
- package/src/skc-runtime/workflow-manifest.generated.json +113 -2
- package/src/skc-runtime/workflow-manifest.ts +74 -1
- package/src/skill-state/active-state.ts +141 -5
- package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
- package/src/slash-commands/builtin-registry.ts +296 -72
- package/src/system-prompt.ts +57 -16
- package/src/task/executor.ts +132 -24
- package/src/task/fork-context-advisory.ts +2 -7
- package/src/task/fork-context-budget.ts +9 -0
- package/src/task/index.ts +13 -6
- package/src/task/output-manager.ts +20 -8
- package/src/task/receipt.ts +26 -7
- package/src/task/token-log.test.ts +188 -0
- package/src/task/token-log.ts +179 -0
- package/src/tools/bisect.ts +367 -0
- package/src/tools/computer.ts +246 -43
- package/src/tools/index.ts +3 -0
- package/src/tools/output-meta.ts +116 -4
- package/src/tools/search.ts +8 -2
- package/src/tools/skill.ts +15 -0
- package/src/tools/telegram-send.ts +6 -0
- package/src/tools/ultragoal-ask-guard.ts +39 -5
- package/src/utils/file-mentions.ts +39 -7
- package/src/utils/git.ts +73 -0
- package/src/utils/pasted-image-path.ts +170 -0
- package/src/web/scrapers/youtube.ts +24 -12
- package/src/workflow/workflow-intent-diff.ts +29 -6
- package/src/workflow/workflow-intent-report-consumer.ts +221 -0
- package/src/workflow/workflow-intent-report-schema.ts +129 -0
- package/src/workflow/workflow-intent-report.ts +135 -0
|
@@ -22,6 +22,25 @@ export interface WelcomeComponentOptions {
|
|
|
22
22
|
collapseChangelog?: boolean;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
const WELCOME_STATIC_RIGHT_ROWS_EXCLUDING_DYNAMIC_SECTIONS = 15;
|
|
26
|
+
const DEFAULT_WHATS_NEW_ROWS = 3;
|
|
27
|
+
const MAX_WHATS_NEW_ROWS = 12;
|
|
28
|
+
const NEWLINE_FLOW_KEY =
|
|
29
|
+
process.platform === "win32" ? { key: "alt+enter", label: "newline" } : { key: "ctrl+j", label: "newline" };
|
|
30
|
+
|
|
31
|
+
const FLOW_KEY_ITEMS: ReadonlyArray<{ key: string; label: string }> = [
|
|
32
|
+
{ key: "/", label: "commands" },
|
|
33
|
+
{ key: "#", label: "actions" },
|
|
34
|
+
{ key: "!", label: "shell" },
|
|
35
|
+
{ key: "$", label: "python" },
|
|
36
|
+
{ key: "?", label: "keymap" },
|
|
37
|
+
{ key: "ctrl+l", label: "model" },
|
|
38
|
+
{ key: "shift+tab", label: "reasoning" },
|
|
39
|
+
{ key: "tab", label: "complete" },
|
|
40
|
+
NEWLINE_FLOW_KEY,
|
|
41
|
+
{ key: "ctrl+c", label: "clear" },
|
|
42
|
+
];
|
|
43
|
+
|
|
25
44
|
/**
|
|
26
45
|
* Sayknow-CLI launch surface: a blue-gradient SAYKNOW wordmark, compact
|
|
27
46
|
* command affordances, and project signals — a distinct identity, not a
|
|
@@ -99,7 +118,6 @@ export class WelcomeComponent implements Component {
|
|
|
99
118
|
}
|
|
100
119
|
const targetContentRows = targetRows === undefined ? undefined : Math.max(0, targetRows - 2);
|
|
101
120
|
const dualContentWidth = boxWidth - 3; // 3 = │ + │ + │
|
|
102
|
-
const preferredLeftCol = 40;
|
|
103
121
|
const minLeftCol = 20; // wordmark plus Sayknow identity labels
|
|
104
122
|
const minRightCol = 24;
|
|
105
123
|
const modelPill = this.#pill(theme.icon.model || "model", this.modelName, "statusLineModel");
|
|
@@ -113,10 +131,12 @@ export class WelcomeComponent implements Component {
|
|
|
113
131
|
visibleWidth(modelPill),
|
|
114
132
|
visibleWidth(providerPill),
|
|
115
133
|
);
|
|
116
|
-
const
|
|
134
|
+
const evenLeftCol = Math.floor(dualContentWidth / 2);
|
|
135
|
+
const maxLeftColWithRightMinimum = Math.max(1, dualContentWidth - minRightCol);
|
|
136
|
+
const desiredLeftCol = Math.max(leftMinContentWidth, evenLeftCol);
|
|
117
137
|
const dualLeftCol =
|
|
118
138
|
dualContentWidth >= minRightCol + 1
|
|
119
|
-
? Math.min(desiredLeftCol,
|
|
139
|
+
? Math.min(desiredLeftCol, maxLeftColWithRightMinimum)
|
|
120
140
|
: Math.max(1, dualContentWidth - 1);
|
|
121
141
|
const dualRightCol = Math.max(1, dualContentWidth - dualLeftCol);
|
|
122
142
|
const showRightColumn = dualLeftCol >= leftMinContentWidth && dualRightCol >= minRightCol;
|
|
@@ -152,26 +172,6 @@ export class WelcomeComponent implements Component {
|
|
|
152
172
|
|
|
153
173
|
const rightColumnWidth = showRightColumn ? rightCol : leftCol;
|
|
154
174
|
const separator = buildSeparator(rightColumnWidth);
|
|
155
|
-
const changelogBudget = Math.max(1, Math.min(6, Math.floor((targetContentRows ?? 18) * 0.3)));
|
|
156
|
-
|
|
157
|
-
const sessionLines: string[] = [];
|
|
158
|
-
if (this.recentSessions.length === 0) {
|
|
159
|
-
sessionLines.push(` ${theme.fg("dim", t("welcome.noSessions"))}`);
|
|
160
|
-
} else {
|
|
161
|
-
const bulletPrefix = ` ${theme.md.bullet} `;
|
|
162
|
-
const prefixWidth = visibleWidth(bulletPrefix);
|
|
163
|
-
for (const session of this.recentSessions.slice(0, 3)) {
|
|
164
|
-
const timeSuffixRaw = ` (${session.timeAgo})`;
|
|
165
|
-
const timeWidth = visibleWidth(timeSuffixRaw);
|
|
166
|
-
const nameBudget = Math.max(1, rightColumnWidth - prefixWidth - timeWidth);
|
|
167
|
-
const nameVis = visibleWidth(session.name);
|
|
168
|
-
const name = nameVis > nameBudget ? truncateToWidth(session.name, nameBudget) : session.name;
|
|
169
|
-
sessionLines.push(
|
|
170
|
-
`${theme.fg("dim", bulletPrefix)}${theme.fg("muted", name)}${theme.fg("dim", timeSuffixRaw)}`,
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
175
|
const lspLines: string[] = [];
|
|
176
176
|
if (this.lspServers.length === 0) {
|
|
177
177
|
lspLines.push(` ${theme.fg("dim", t("welcome.noLsp"))}`);
|
|
@@ -188,6 +188,24 @@ export class WelcomeComponent implements Component {
|
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
const flowPreferredRows = this.#flowKeyRows(rightColumnWidth).length;
|
|
192
|
+
const changelogRowLimit = this.#whatsNewRowLimit(targetContentRows, lspLines.length, flowPreferredRows);
|
|
193
|
+
const changelogLines = this.#whatsNewLines(rightColumnWidth, changelogRowLimit);
|
|
194
|
+
const flowRowLimit = this.#flowKeyRowLimit(
|
|
195
|
+
targetContentRows,
|
|
196
|
+
changelogLines.length,
|
|
197
|
+
lspLines.length,
|
|
198
|
+
rightColumnWidth,
|
|
199
|
+
);
|
|
200
|
+
const flowLines = this.#flowKeyLines(rightColumnWidth, flowRowLimit);
|
|
201
|
+
const sessionLimit = this.#sessionTrailLimit(
|
|
202
|
+
targetContentRows,
|
|
203
|
+
changelogLines.length,
|
|
204
|
+
lspLines.length,
|
|
205
|
+
flowLines.length,
|
|
206
|
+
);
|
|
207
|
+
const sessionLines = this.#sessionTrailLines(rightColumnWidth, sessionLimit);
|
|
208
|
+
|
|
191
209
|
// Workflow affordances: pad the command so descriptions align.
|
|
192
210
|
const wf = (cmd: string, desc: string): string => {
|
|
193
211
|
const pad = " ".repeat(Math.max(1, 17 - visibleWidth(cmd)));
|
|
@@ -197,7 +215,7 @@ export class WelcomeComponent implements Component {
|
|
|
197
215
|
const rightLines = [
|
|
198
216
|
"",
|
|
199
217
|
` ${theme.bold(theme.fg("accent", "What's New"))}`,
|
|
200
|
-
...
|
|
218
|
+
...changelogLines,
|
|
201
219
|
separator,
|
|
202
220
|
` ${theme.bold(theme.fg("accent", t("welcome.workflows")))}`,
|
|
203
221
|
wf("/deep-interview", t("welcome.wf.deepInterview")),
|
|
@@ -206,19 +224,7 @@ export class WelcomeComponent implements Component {
|
|
|
206
224
|
wf("/team", t("welcome.wf.team")),
|
|
207
225
|
separator,
|
|
208
226
|
` ${theme.bold(theme.fg("accent", t("welcome.flowKeys")))}`,
|
|
209
|
-
|
|
210
|
-
"dim",
|
|
211
|
-
"#",
|
|
212
|
-
)}${theme.fg("muted", ` ${t("welcome.actions")}`)}`,
|
|
213
|
-
` ${theme.fg("dim", "!")}${theme.fg("muted", ` ${t("welcome.shell")}`)} ${theme.fg("dim", "·")} ${theme.fg("dim", "$")}${theme.fg(
|
|
214
|
-
"muted",
|
|
215
|
-
` ${t("welcome.python")}`,
|
|
216
|
-
)}`,
|
|
217
|
-
` ${theme.fg("dim", "?")}${theme.fg("muted", ` ${t("welcome.keymap")}`)} ${theme.fg("dim", "·")} ${theme.fg(
|
|
218
|
-
"dim",
|
|
219
|
-
"ctrl+l",
|
|
220
|
-
)}${theme.fg("muted", ` ${t("welcome.model")}`)}`,
|
|
221
|
-
` ${theme.fg("dim", "shift+tab")}${theme.fg("muted", ` ${t("welcome.reasoning")}`)}`,
|
|
227
|
+
...flowLines,
|
|
222
228
|
separator,
|
|
223
229
|
` ${theme.bold(theme.fg("accent", t("welcome.projectPulse")))}`,
|
|
224
230
|
...lspLines,
|
|
@@ -337,7 +343,139 @@ export class WelcomeComponent implements Component {
|
|
|
337
343
|
return [...Array.from({ length: topPad }, () => ""), ...clipped, ...Array.from({ length: bottomPad }, () => "")];
|
|
338
344
|
}
|
|
339
345
|
|
|
340
|
-
#
|
|
346
|
+
#flowKeyItemText(item: { key: string; label: string }): string {
|
|
347
|
+
return `${theme.fg("dim", item.key)}${theme.fg("muted", ` ${this.#flowKeyLabel(item.label)}`)}`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
#flowKeyLabel(label: string): string {
|
|
351
|
+
switch (label) {
|
|
352
|
+
case "commands":
|
|
353
|
+
return t("welcome.commands");
|
|
354
|
+
case "actions":
|
|
355
|
+
return t("welcome.actions");
|
|
356
|
+
case "shell":
|
|
357
|
+
return t("welcome.shell");
|
|
358
|
+
case "python":
|
|
359
|
+
return t("welcome.python");
|
|
360
|
+
case "keymap":
|
|
361
|
+
return t("welcome.keymap");
|
|
362
|
+
case "model":
|
|
363
|
+
return t("welcome.model");
|
|
364
|
+
case "reasoning":
|
|
365
|
+
return t("welcome.reasoning");
|
|
366
|
+
default:
|
|
367
|
+
return label;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
#flowKeyRows(width: number): string[] {
|
|
372
|
+
const contentWidth = Math.max(1, width - 1);
|
|
373
|
+
const separator = ` ${theme.fg("dim", "·")} `;
|
|
374
|
+
const rows: string[] = [];
|
|
375
|
+
let current = "";
|
|
376
|
+
for (const item of FLOW_KEY_ITEMS) {
|
|
377
|
+
const segment = this.#flowKeyItemText(item);
|
|
378
|
+
const next = current ? `${current}${separator}${segment}` : segment;
|
|
379
|
+
if (current && visibleWidth(next) > contentWidth) {
|
|
380
|
+
rows.push(` ${current}`);
|
|
381
|
+
current = segment;
|
|
382
|
+
} else {
|
|
383
|
+
current = next;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (current) rows.push(` ${current}`);
|
|
387
|
+
return rows.length > 0 ? rows : [` ${theme.fg("dim", "No flow keys")}`];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
#flowKeyLines(width: number, maxRows: number): string[] {
|
|
391
|
+
const rows = this.#flowKeyRows(width);
|
|
392
|
+
const rowLimit = Math.max(1, Math.floor(maxRows));
|
|
393
|
+
if (rows.length <= rowLimit) return rows;
|
|
394
|
+
if (rowLimit === 1) {
|
|
395
|
+
const firstItem = FLOW_KEY_ITEMS[0];
|
|
396
|
+
const firstSegment = firstItem ? this.#flowKeyItemText(firstItem) : theme.fg("dim", "keys");
|
|
397
|
+
return [this.#fitToWidth(` ${firstSegment} ${theme.fg("dim", "· … ")}${theme.bold("/help")}`, width)];
|
|
398
|
+
}
|
|
399
|
+
return [...rows.slice(0, rowLimit - 1), ` ${theme.fg("dim", `… ${theme.bold("/help")} for more`)}`];
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
#flowKeyRowLimit(
|
|
403
|
+
targetContentRows: number | undefined,
|
|
404
|
+
changelogLineCount: number,
|
|
405
|
+
lspLineCount: number,
|
|
406
|
+
rightColumnWidth: number,
|
|
407
|
+
): number {
|
|
408
|
+
const preferredRows = this.#flowKeyRows(rightColumnWidth).length;
|
|
409
|
+
if (targetContentRows === undefined) return preferredRows;
|
|
410
|
+
|
|
411
|
+
const sessionBaselineRows = this.recentSessions.length === 0 ? 1 : Math.min(3, this.recentSessions.length);
|
|
412
|
+
const availableRows =
|
|
413
|
+
targetContentRows -
|
|
414
|
+
WELCOME_STATIC_RIGHT_ROWS_EXCLUDING_DYNAMIC_SECTIONS -
|
|
415
|
+
changelogLineCount -
|
|
416
|
+
lspLineCount -
|
|
417
|
+
sessionBaselineRows;
|
|
418
|
+
return Math.max(1, Math.min(preferredRows, availableRows));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
#whatsNewRowLimit(targetContentRows: number | undefined, lspLineCount: number, flowLineCount: number): number {
|
|
422
|
+
if (targetContentRows === undefined) return 5;
|
|
423
|
+
|
|
424
|
+
const sessionBaselineRows = this.recentSessions.length === 0 ? 1 : Math.min(3, this.recentSessions.length);
|
|
425
|
+
const dynamicRows = Math.max(
|
|
426
|
+
1,
|
|
427
|
+
targetContentRows - WELCOME_STATIC_RIGHT_ROWS_EXCLUDING_DYNAMIC_SECTIONS - flowLineCount - lspLineCount,
|
|
428
|
+
);
|
|
429
|
+
const rowsAfterBaselineSessions = Math.max(1, dynamicRows - sessionBaselineRows);
|
|
430
|
+
const spareRows = Math.max(0, rowsAfterBaselineSessions - DEFAULT_WHATS_NEW_ROWS);
|
|
431
|
+
return Math.max(
|
|
432
|
+
1,
|
|
433
|
+
Math.min(MAX_WHATS_NEW_ROWS, rowsAfterBaselineSessions, DEFAULT_WHATS_NEW_ROWS + Math.floor(spareRows / 2)),
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
#sessionTrailLimit(
|
|
438
|
+
targetContentRows: number | undefined,
|
|
439
|
+
changelogLineCount: number,
|
|
440
|
+
lspLineCount: number,
|
|
441
|
+
flowLineCount: number,
|
|
442
|
+
): number {
|
|
443
|
+
if (this.recentSessions.length === 0) return 0;
|
|
444
|
+
|
|
445
|
+
const defaultLimit = Math.min(3, this.recentSessions.length);
|
|
446
|
+
if (targetContentRows === undefined) return defaultLimit;
|
|
447
|
+
|
|
448
|
+
const rowsWithDefaultTrail =
|
|
449
|
+
WELCOME_STATIC_RIGHT_ROWS_EXCLUDING_DYNAMIC_SECTIONS +
|
|
450
|
+
changelogLineCount +
|
|
451
|
+
flowLineCount +
|
|
452
|
+
lspLineCount +
|
|
453
|
+
defaultLimit;
|
|
454
|
+
const extraRows = Math.max(0, targetContentRows - rowsWithDefaultTrail);
|
|
455
|
+
return Math.min(this.recentSessions.length, defaultLimit + extraRows);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
#sessionTrailLines(rightColumnWidth: number, limit: number): string[] {
|
|
459
|
+
if (this.recentSessions.length === 0) {
|
|
460
|
+
return [` ${theme.fg("dim", t("welcome.noSessions"))}`];
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const bulletPrefix = ` ${theme.md.bullet} `;
|
|
464
|
+
const prefixWidth = visibleWidth(bulletPrefix);
|
|
465
|
+
const lines: string[] = [];
|
|
466
|
+
for (const session of this.recentSessions.slice(0, limit)) {
|
|
467
|
+
const timeSuffixRaw = ` (${session.timeAgo})`;
|
|
468
|
+
const timeWidth = visibleWidth(timeSuffixRaw);
|
|
469
|
+
const nameBudget = Math.max(1, rightColumnWidth - prefixWidth - timeWidth);
|
|
470
|
+
const nameVis = visibleWidth(session.name);
|
|
471
|
+
const name = nameVis > nameBudget ? truncateToWidth(session.name, nameBudget) : session.name;
|
|
472
|
+
lines.push(`${theme.fg("dim", bulletPrefix)}${theme.fg("muted", name)}${theme.fg("dim", timeSuffixRaw)}`);
|
|
473
|
+
}
|
|
474
|
+
return lines;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
#whatsNewLines(width: number, maxRows: number): string[] {
|
|
478
|
+
const rowLimit = Math.max(1, Math.floor(maxRows));
|
|
341
479
|
const changelog = this.options.changelogMarkdown?.trim();
|
|
342
480
|
if (!changelog) {
|
|
343
481
|
return [` ${theme.fg("dim", "Ready for your next prompt")}`];
|
|
@@ -348,25 +486,25 @@ export class WelcomeComponent implements Component {
|
|
|
348
486
|
return [
|
|
349
487
|
` ${theme.fg("muted", `Updated to v${version}`)}`,
|
|
350
488
|
` ${theme.fg("dim", `Use ${theme.bold("/changelog")} for details`)}`,
|
|
351
|
-
];
|
|
489
|
+
].slice(0, rowLimit);
|
|
352
490
|
}
|
|
353
491
|
|
|
354
|
-
const itemLimit = Math.max(1, Math.floor(maxItems));
|
|
355
492
|
const items = this.#changelogItems(changelog);
|
|
356
493
|
if (items.length === 0) {
|
|
357
494
|
return [
|
|
358
495
|
` ${theme.fg("muted", `Updated to v${version}`)}`,
|
|
359
496
|
` ${theme.fg("dim", `Use ${theme.bold("/changelog")} for details`)}`,
|
|
360
|
-
];
|
|
497
|
+
].slice(0, rowLimit);
|
|
361
498
|
}
|
|
362
499
|
|
|
363
500
|
const prefix = ` ${theme.md.bullet} `;
|
|
364
501
|
const textWidth = Math.max(1, width - visibleWidth(prefix));
|
|
365
|
-
const
|
|
502
|
+
const visibleItemCount = items.length > rowLimit ? Math.max(1, rowLimit - 1) : rowLimit;
|
|
503
|
+
const visibleItems = items.slice(0, visibleItemCount).map(item => {
|
|
366
504
|
const text = visibleWidth(item) > textWidth ? truncateToWidth(item, textWidth) : item;
|
|
367
505
|
return `${theme.fg("dim", prefix)}${theme.fg("muted", text)}`;
|
|
368
506
|
});
|
|
369
|
-
if (items.length > visibleItems.length) {
|
|
507
|
+
if (items.length > visibleItems.length && visibleItems.length < rowLimit) {
|
|
370
508
|
visibleItems.push(` ${theme.fg("dim", `… ${theme.bold("/changelog")} for full notes`)}`);
|
|
371
509
|
}
|
|
372
510
|
return visibleItems;
|
|
@@ -469,7 +607,6 @@ interface ShineConfig {
|
|
|
469
607
|
/** Center of the shine band along the diagonal, in [0, 1]. */
|
|
470
608
|
pos: number;
|
|
471
609
|
}
|
|
472
|
-
|
|
473
610
|
/**
|
|
474
611
|
* Apply a multi-stop diagonal gradient (bottom-left → top-right) plus an
|
|
475
612
|
* optional sliding shine band across multi-line art. `phase` (0..1) shifts the
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { Loader, Markdown, padding, Spacer, Text, visibleWidth } from "@sayknow-cli/tui";
|
|
14
14
|
import { formatDuration, Snowflake, setProjectDir } from "@sayknow-cli/utils";
|
|
15
15
|
import { $ } from "bun";
|
|
16
|
+
import { resolveAppendOnlyMode } from "../../append-only-mode";
|
|
16
17
|
import { jobElapsedMs } from "../../async";
|
|
17
18
|
import { reset as resetCapabilities } from "../../capability";
|
|
18
19
|
import { clearClaudePluginRootsCache } from "../../discovery/helpers";
|
|
@@ -400,7 +401,7 @@ export class CommandController {
|
|
|
400
401
|
{
|
|
401
402
|
const setting = this.ctx.settings.get("provider.appendOnlyContext") ?? "auto";
|
|
402
403
|
const provider = this.ctx.session.model?.provider;
|
|
403
|
-
const mode = setting
|
|
404
|
+
const mode = resolveAppendOnlyMode(setting, provider ?? "");
|
|
404
405
|
const activeLabel = mode ? theme.fg("success", "active") : theme.fg("dim", "inactive");
|
|
405
406
|
const settingLabel = setting === "auto" ? `${setting} (${provider ?? "?"})` : setting;
|
|
406
407
|
info += `${theme.fg("dim", "Append-Only:")} ${activeLabel} (setting: ${settingLabel})\n`;
|
|
@@ -540,7 +541,7 @@ export class CommandController {
|
|
|
540
541
|
const title = showFull ? "Full Changelog" : "Recent Changes";
|
|
541
542
|
const hint = showFull
|
|
542
543
|
? ""
|
|
543
|
-
: `\n\n${theme.fg("dim", "Use")} ${theme.bold("/changelog full")} ${theme.fg("dim", "to view the complete changelog.")}`;
|
|
544
|
+
: `\n\n${theme.fg("dim", "Use")} ${theme.bold("/changelog --full")} ${theme.fg("dim", "to view the complete changelog.")}`;
|
|
544
545
|
|
|
545
546
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
546
547
|
this.ctx.chatContainer.addChild(new DynamicBorder());
|
|
@@ -926,8 +927,7 @@ export class CommandController {
|
|
|
926
927
|
this.ctx.streamingMessage = undefined;
|
|
927
928
|
this.ctx.pendingTools.clear();
|
|
928
929
|
|
|
929
|
-
this.ctx.chatContainer.addChild(new
|
|
930
|
-
this.ctx.chatContainer.addChild(new Text(`${theme.fg("accent", `${theme.status.success} ${label}`)}`, 1, 1));
|
|
930
|
+
this.ctx.chatContainer.addChild(new Text(`${theme.fg("accent", `${theme.status.success} ${label}`)}`, 1, 0));
|
|
931
931
|
await this.ctx.reloadTodos();
|
|
932
932
|
this.ctx.ui.requestRender();
|
|
933
933
|
}
|
|
@@ -936,6 +936,42 @@ export class CommandController {
|
|
|
936
936
|
await this.#runNewSessionFlow();
|
|
937
937
|
}
|
|
938
938
|
|
|
939
|
+
async handleContextClearCommand(): Promise<void> {
|
|
940
|
+
if (this.ctx.loadingAnimation) {
|
|
941
|
+
this.ctx.loadingAnimation.stop();
|
|
942
|
+
this.ctx.loadingAnimation = undefined;
|
|
943
|
+
}
|
|
944
|
+
this.ctx.statusContainer.clear();
|
|
945
|
+
|
|
946
|
+
if (this.ctx.session.isCompacting) {
|
|
947
|
+
this.ctx.session.abortCompaction();
|
|
948
|
+
while (this.ctx.session.isCompacting) {
|
|
949
|
+
await Bun.sleep(10);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
if (!(await this.ctx.session.clearContext())) return;
|
|
953
|
+
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
|
|
954
|
+
|
|
955
|
+
this.ctx.statusLine.invalidate();
|
|
956
|
+
this.ctx.statusLine.setSessionStartTime(Date.now());
|
|
957
|
+
this.ctx.updateEditorTopBorder();
|
|
958
|
+
this.ctx.updateEditorBorderColor();
|
|
959
|
+
this.ctx.ui.requestRender();
|
|
960
|
+
|
|
961
|
+
this.ctx.chatContainer.clear();
|
|
962
|
+
this.ctx.pendingMessagesContainer.clear();
|
|
963
|
+
this.ctx.compactionQueuedMessages = [];
|
|
964
|
+
this.ctx.streamingComponent = undefined;
|
|
965
|
+
this.ctx.streamingMessage = undefined;
|
|
966
|
+
this.ctx.pendingTools.clear();
|
|
967
|
+
|
|
968
|
+
this.ctx.chatContainer.addChild(
|
|
969
|
+
new Text(`${theme.fg("accent", `${theme.status.success} Context cleared`)}`, 1, 0),
|
|
970
|
+
);
|
|
971
|
+
await this.ctx.reloadTodos();
|
|
972
|
+
this.ctx.ui.requestRender();
|
|
973
|
+
}
|
|
974
|
+
|
|
939
975
|
async handleDropCommand(): Promise<void> {
|
|
940
976
|
if (!this.ctx.sessionManager.getSessionFile()) {
|
|
941
977
|
this.ctx.showError("Nothing to drop (in-memory session)");
|
|
@@ -16,6 +16,7 @@ import { ToolExecutionComponent } from "../../modes/components/tool-execution";
|
|
|
16
16
|
import { TtsrNotificationComponent } from "../../modes/components/ttsr-notification";
|
|
17
17
|
import { getSymbolTheme, theme } from "../../modes/theme/theme";
|
|
18
18
|
import type { InteractiveModeContext, TodoPhase } from "../../modes/types";
|
|
19
|
+
import { completionNotifyDisabledByEnv } from "../../notifications/config";
|
|
19
20
|
import { summaryFromMessage } from "../../notifications/helpers";
|
|
20
21
|
import type { PlanApprovalDetails } from "../../plan-mode/approved-plan";
|
|
21
22
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
@@ -23,11 +24,22 @@ import { isSilentAbort, readPendingDisplayTag } from "../../session/messages";
|
|
|
23
24
|
import type { ResolveToolDetails } from "../../tools/resolve";
|
|
24
25
|
import { interruptHint } from "../shared";
|
|
25
26
|
import { buildAbortDisplayMessage } from "../utils/abort-message";
|
|
27
|
+
import { consumeInjectedOptimisticSignature } from "../utils/injected-user-submission";
|
|
26
28
|
import { ringTerminalBell } from "../utils/terminal-bell";
|
|
29
|
+
import { argsWithPartialJson } from "../utils/ui-helpers";
|
|
27
30
|
|
|
28
31
|
type AgentSessionEventKind = AgentSessionEvent["type"];
|
|
29
32
|
|
|
30
33
|
const IRC_MESSAGE_VISIBLE_TTL_MS = 10_000;
|
|
34
|
+
|
|
35
|
+
/** Test-only performance counters for advisory baseline tests. */
|
|
36
|
+
export const __eventControllerPerfCounters = {
|
|
37
|
+
messageUpdateContentVisits: 0,
|
|
38
|
+
reset(): void {
|
|
39
|
+
this.messageUpdateContentVisits = 0;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
31
43
|
const COMPLETION_NOTIFY_COMMAND_TIMEOUT_MS = 10_000;
|
|
32
44
|
|
|
33
45
|
interface CompletionNotifyPayload {
|
|
@@ -100,6 +112,8 @@ export class EventController {
|
|
|
100
112
|
#lastAssistantComponent: AssistantMessageComponent | undefined = undefined;
|
|
101
113
|
#idleCompactionTimer?: NodeJS.Timeout;
|
|
102
114
|
#ircExpiryTimers = new Map<string, NodeJS.Timeout>();
|
|
115
|
+
#toolIntentCache = new Map<string, { args: unknown; intent: string | undefined }>();
|
|
116
|
+
#thinkingContentIndices = new Set<number>();
|
|
103
117
|
#handlers: AgentSessionEventHandlers;
|
|
104
118
|
|
|
105
119
|
constructor(private ctx: InteractiveModeContext) {
|
|
@@ -279,12 +293,17 @@ export class EventController {
|
|
|
279
293
|
const signature = `${textContent}\u0000${imageCount}`;
|
|
280
294
|
|
|
281
295
|
this.#resetReadGroup();
|
|
282
|
-
const
|
|
296
|
+
const wasSingleOptimistic = this.ctx.optimisticUserMessageSignature === signature;
|
|
297
|
+
const wasInjectedOptimistic = !wasSingleOptimistic && consumeInjectedOptimisticSignature(this.ctx, signature);
|
|
298
|
+
const wasOptimistic = wasSingleOptimistic || wasInjectedOptimistic;
|
|
283
299
|
const wasLocallySubmitted = this.ctx.locallySubmittedUserSignatures.delete(signature) || wasOptimistic;
|
|
284
300
|
if (!wasOptimistic) {
|
|
285
301
|
this.ctx.addMessageToChat(event.message);
|
|
286
302
|
}
|
|
287
|
-
|
|
303
|
+
// Only clear the single local slot when IT matched; an injected match is
|
|
304
|
+
// consumed from the counting Map above and must not clobber a coexisting
|
|
305
|
+
// pending local optimistic signature.
|
|
306
|
+
if (wasSingleOptimistic) {
|
|
288
307
|
this.ctx.optimisticUserMessageSignature = undefined;
|
|
289
308
|
}
|
|
290
309
|
|
|
@@ -307,12 +326,14 @@ export class EventController {
|
|
|
307
326
|
} else if (event.message.role === "assistant") {
|
|
308
327
|
this.#lastThinkingCount = 0;
|
|
309
328
|
this.#resetReadGroup();
|
|
329
|
+
this.#toolIntentCache.clear();
|
|
330
|
+
this.#thinkingContentIndices.clear();
|
|
310
331
|
this.ctx.streamingComponent = new AssistantMessageComponent(undefined, this.ctx.hideThinkingBlock, () =>
|
|
311
332
|
this.ctx.ui.requestRender(),
|
|
312
333
|
);
|
|
313
334
|
this.ctx.streamingMessage = event.message;
|
|
314
335
|
this.ctx.chatContainer.addChild(this.ctx.streamingComponent);
|
|
315
|
-
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
|
|
336
|
+
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
316
337
|
this.ctx.ui.requestRender();
|
|
317
338
|
}
|
|
318
339
|
}
|
|
@@ -375,46 +396,53 @@ export class EventController {
|
|
|
375
396
|
async #handleMessageUpdate(event: Extract<AgentSessionEvent, { type: "message_update" }>): Promise<void> {
|
|
376
397
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
377
398
|
this.ctx.streamingMessage = event.message;
|
|
378
|
-
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
|
|
379
|
-
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
399
|
+
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
400
|
+
const contentIndex = event.assistantMessageEvent?.contentIndex;
|
|
401
|
+
const changedContent =
|
|
402
|
+
typeof contentIndex === "number" && contentIndex >= 0
|
|
403
|
+
? this.ctx.streamingMessage.content[contentIndex]
|
|
404
|
+
: undefined;
|
|
405
|
+
const contentsToProcess = changedContent ? [changedContent] : this.ctx.streamingMessage.content;
|
|
406
|
+
|
|
407
|
+
if (changedContent?.type === "thinking" && changedContent.thinking.trim()) {
|
|
408
|
+
if (typeof contentIndex === "number") this.#thinkingContentIndices.add(contentIndex);
|
|
409
|
+
const thinkingCount = this.#thinkingContentIndices.size;
|
|
410
|
+
if (thinkingCount > this.#lastThinkingCount) {
|
|
411
|
+
this.#resetReadGroup();
|
|
412
|
+
this.#lastThinkingCount = thinkingCount;
|
|
413
|
+
}
|
|
414
|
+
} else if (!changedContent) {
|
|
415
|
+
const thinkingCount = this.ctx.streamingMessage.content.filter(
|
|
416
|
+
content => content.type === "thinking" && content.thinking.trim(),
|
|
417
|
+
).length;
|
|
418
|
+
if (thinkingCount > this.#lastThinkingCount) {
|
|
419
|
+
this.#resetReadGroup();
|
|
420
|
+
this.#lastThinkingCount = thinkingCount;
|
|
421
|
+
}
|
|
386
422
|
}
|
|
387
423
|
|
|
388
|
-
for (const content of
|
|
424
|
+
for (const content of contentsToProcess) {
|
|
425
|
+
__eventControllerPerfCounters.messageUpdateContentVisits += 1;
|
|
389
426
|
if (content.type !== "toolCall") continue;
|
|
390
427
|
if (content.name === "read") {
|
|
391
|
-
if (!readArgsHaveTarget(content.arguments))
|
|
392
|
-
// Args still streaming — defer until path is parseable so we can route to the
|
|
393
|
-
// read group (regular files) vs ToolExecutionComponent (internal URLs).
|
|
394
|
-
// Creating either component now would lock the read into the wrong shape.
|
|
395
|
-
continue;
|
|
396
|
-
}
|
|
428
|
+
if (!readArgsHaveTarget(content.arguments)) continue;
|
|
397
429
|
if (!readArgsTargetInternalUrl(content.arguments)) {
|
|
398
430
|
this.#trackReadToolCall(content.id, content.arguments);
|
|
399
431
|
const component = this.ctx.pendingTools.get(content.id);
|
|
400
|
-
if (component)
|
|
401
|
-
|
|
402
|
-
} else {
|
|
432
|
+
if (component) component.updateArgs(content.arguments, content.id);
|
|
433
|
+
else {
|
|
403
434
|
const group = this.#getReadGroup();
|
|
404
435
|
group.updateArgs(content.arguments, content.id);
|
|
405
436
|
this.ctx.pendingTools.set(content.id, group);
|
|
406
437
|
}
|
|
407
438
|
continue;
|
|
408
439
|
}
|
|
409
|
-
// Internal URL read falls through to ToolExecutionComponent below.
|
|
410
440
|
}
|
|
411
441
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
? { ...content.arguments, __partialJson: content.partialJson }
|
|
417
|
-
: content.arguments;
|
|
442
|
+
const renderArgs = argsWithPartialJson(
|
|
443
|
+
content.arguments,
|
|
444
|
+
"partialJson" in content ? content.partialJson : undefined,
|
|
445
|
+
);
|
|
418
446
|
if (!this.ctx.pendingTools.has(content.id)) {
|
|
419
447
|
this.#resetReadGroup();
|
|
420
448
|
this.ctx.chatContainer.addChild(new Text("", 0, 0));
|
|
@@ -437,32 +465,30 @@ export class EventController {
|
|
|
437
465
|
this.ctx.chatContainer.addChild(component);
|
|
438
466
|
this.ctx.pendingTools.set(content.id, component);
|
|
439
467
|
} else {
|
|
440
|
-
|
|
441
|
-
if (component) {
|
|
442
|
-
component.updateArgs(renderArgs, content.id);
|
|
443
|
-
}
|
|
468
|
+
this.ctx.pendingTools.get(content.id)?.updateArgs(renderArgs, content.id);
|
|
444
469
|
}
|
|
445
|
-
}
|
|
446
470
|
|
|
447
|
-
// Update working message with intent from streamed tool arguments
|
|
448
|
-
for (const content of this.ctx.streamingMessage.content) {
|
|
449
|
-
if (content.type !== "toolCall") continue;
|
|
450
471
|
const args = content.arguments;
|
|
451
472
|
if (!args || typeof args !== "object") continue;
|
|
473
|
+
let intent: string | undefined;
|
|
452
474
|
if (INTENT_FIELD in args) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
475
|
+
intent = String(args[INTENT_FIELD]);
|
|
476
|
+
} else {
|
|
477
|
+
const cached = this.#toolIntentCache.get(content.id);
|
|
478
|
+
if (cached?.args === args) intent = cached.intent;
|
|
479
|
+
else {
|
|
480
|
+
const tool = this.ctx.session.getToolByName(content.name);
|
|
481
|
+
if (typeof tool?.intent === "function") {
|
|
482
|
+
try {
|
|
483
|
+
intent = tool.intent(args as never)?.trim();
|
|
484
|
+
} catch {
|
|
485
|
+
// intent function must never break the UI
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
this.#toolIntentCache.set(content.id, { args, intent });
|
|
462
489
|
}
|
|
463
|
-
} catch {
|
|
464
|
-
// intent function must never break the UI
|
|
465
490
|
}
|
|
491
|
+
if (intent) this.#updateWorkingMessageFromIntent(intent);
|
|
466
492
|
}
|
|
467
493
|
|
|
468
494
|
this.ctx.ui.requestRender();
|
|
@@ -495,9 +521,9 @@ export class EventController {
|
|
|
495
521
|
// display only — does NOT mutate the persisted message's stopReason
|
|
496
522
|
// (the marker on errorMessage drives replay-side suppression).
|
|
497
523
|
const msgWithoutAbort = { ...this.ctx.streamingMessage, stopReason: "stop" as const };
|
|
498
|
-
this.ctx.streamingComponent.updateContent(msgWithoutAbort);
|
|
524
|
+
this.ctx.streamingComponent.updateContent(msgWithoutAbort, { streaming: false });
|
|
499
525
|
} else {
|
|
500
|
-
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
|
|
526
|
+
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: false });
|
|
501
527
|
}
|
|
502
528
|
|
|
503
529
|
if (this.ctx.streamingMessage.stopReason !== "aborted" && this.ctx.streamingMessage.stopReason !== "error") {
|
|
@@ -941,6 +967,8 @@ export class EventController {
|
|
|
941
967
|
}
|
|
942
968
|
|
|
943
969
|
sendCompletionNotification(): void {
|
|
970
|
+
// Per-run hard opt-out (env, config-untouched, child-inheritable): SKC_NOTIFY=off.
|
|
971
|
+
if (completionNotifyDisabledByEnv(process.env)) return;
|
|
944
972
|
const isBackgrounded = this.ctx.isBackgrounded !== false;
|
|
945
973
|
const notify = settings.get("completion.notify");
|
|
946
974
|
if (notify === "off") return;
|