@sayknow-cli/coding-agent 0.3.12 → 0.3.15
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/autoresearch/tools/init-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/log-experiment.d.ts +2 -2
- package/dist/types/cli/classify-install-target.d.ts +0 -10
- package/dist/types/cli/daemon-cli.d.ts +2 -0
- package/dist/types/cli/initial-message.d.ts +1 -1
- package/dist/types/cli/malloc-env-guard.d.ts +65 -0
- package/dist/types/cli/malloc-env-guard.test.d.ts +1 -0
- package/dist/types/cli/notify-cli.d.ts +3 -1
- package/dist/types/cli/session-picker.d.ts +4 -3
- package/dist/types/cli/update-cli.d.ts +22 -3
- package/dist/types/cli.d.ts +5 -0
- package/dist/types/commands/daemon.d.ts +6 -2
- package/dist/types/commands/launch.d.ts +1 -0
- package/dist/types/commands/notify.d.ts +6 -0
- package/dist/types/commands/web-search.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +4 -4
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +9 -9
- package/dist/types/commit/agentic/tools/schemas.d.ts +9 -9
- package/dist/types/commit/agentic/tools/split-commit.d.ts +9 -9
- package/dist/types/commit/analysis/conventional.d.ts +1 -1
- package/dist/types/commit/analysis/summary.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +3 -3
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
- package/dist/types/commit/shared-llm.d.ts +9 -9
- package/dist/types/config/keybindings.d.ts +134 -129
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +35 -35
- package/dist/types/config/models-config-schema.d.ts +111 -111
- package/dist/types/config/settings-schema.d.ts +56 -16
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/model-preset.d.ts +45 -0
- package/dist/types/coordinator-mcp/policy.d.ts +3 -0
- package/dist/types/coordinator-mcp/server.d.ts +79 -6
- package/dist/types/coordinator-mcp/session-reaper.d.ts +53 -0
- package/dist/types/daemon/control-types.d.ts +10 -0
- package/dist/types/daemon/operator-contract.d.ts +47 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.d.ts +4 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/errors.d.ts +12 -12
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/edit/modes/patch.d.ts +2 -2
- package/dist/types/extensibility/extensions/types.d.ts +3 -1
- package/dist/types/goals/tools/goal-tool.d.ts +6 -6
- package/dist/types/i18n/messages/en.d.ts +8 -8
- package/dist/types/internal-urls/local-protocol.d.ts +13 -9
- package/dist/types/lsp/index.d.ts +9 -9
- package/dist/types/lsp/types.d.ts +9 -9
- package/dist/types/main.d.ts +66 -2
- package/dist/types/modes/acp/acp-agent.d.ts +19 -2
- package/dist/types/modes/acp/acp-mode.d.ts +12 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -1
- package/dist/types/modes/components/custom-editor.d.ts +2 -0
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +32 -0
- package/dist/types/modes/components/pet-selector.d.ts +11 -0
- package/dist/types/modes/components/sayknow-pet-widget.d.ts +59 -0
- package/dist/types/modes/components/session-selector.d.ts +22 -9
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line/types.d.ts +1 -1
- package/dist/types/modes/components/status-line.d.ts +0 -10
- package/dist/types/modes/components/tool-execution.d.ts +1 -0
- package/dist/types/modes/components/user-message.d.ts +2 -1
- package/dist/types/modes/controllers/event-controller.d.ts +4 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +24 -2
- package/dist/types/modes/irc-observation-ledger.d.ts +22 -0
- package/dist/types/modes/print-mode.d.ts +3 -4
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +2 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +17 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +8 -0
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +5 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +45 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +42 -2
- package/dist/types/modes/theme/defaults/index.d.ts +568 -568
- package/dist/types/modes/types.d.ts +28 -2
- package/dist/types/modes/utils/abort-message.d.ts +1 -1
- package/dist/types/modes/utils/context-usage.d.ts +4 -19
- package/dist/types/modes/utils/irc-message.d.ts +38 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +13 -4
- package/dist/types/notifications/config.d.ts +34 -0
- package/dist/types/notifications/daemon-paths.d.ts +7 -0
- package/dist/types/notifications/index.d.ts +27 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +12 -4
- package/dist/types/notifications/notification-service.d.ts +144 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +27 -9
- package/dist/types/notifications/telegram-daemon-control.d.ts +6 -0
- package/dist/types/notifications/telegram-daemon.d.ts +130 -3
- package/dist/types/notifications/telegram-reference.d.ts +19 -2
- package/dist/types/notifications/topic-registry.d.ts +24 -4
- package/dist/types/registry/agent-registry.d.ts +2 -0
- package/dist/types/rlm/index.d.ts +1 -1
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +40 -3
- package/dist/types/session/cache-economics.d.ts +79 -0
- package/dist/types/session/context-estimation.d.ts +31 -0
- package/dist/types/session/provider-safety-stop.d.ts +5 -0
- package/dist/types/session/session-manager.d.ts +124 -4
- package/dist/types/session/session-storage.d.ts +155 -13
- package/dist/types/setup/credential-auto-import.d.ts +2 -2
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +34 -3
- package/dist/types/skc-runtime/state-schema.d.ts +19 -19
- package/dist/types/skc-runtime/tmux-common.d.ts +6 -0
- package/dist/types/skc-runtime/tmux-owner-isolation-cli.d.ts +5 -0
- package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +309 -0
- package/dist/types/skc-runtime/tmux-sessions.d.ts +49 -17
- package/dist/types/task/agents.d.ts +2 -2
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/receipt.d.ts +3 -1
- package/dist/types/task/types.d.ts +35 -27
- package/dist/types/tools/ask-answer-registry.d.ts +5 -0
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/browser/tab-supervisor.d.ts +6 -0
- package/dist/types/tools/browser.d.ts +28 -28
- package/dist/types/tools/computer.d.ts +20 -20
- package/dist/types/tools/cron.d.ts +2 -2
- package/dist/types/tools/debug.d.ts +40 -40
- package/dist/types/tools/gh.d.ts +10 -10
- package/dist/types/tools/image-gen.d.ts +2 -2
- package/dist/types/tools/index.d.ts +58 -6
- package/dist/types/tools/monitor.d.ts +6 -6
- package/dist/types/tools/skill-discovery.d.ts +4 -4
- package/dist/types/tools/subagent.d.ts +12 -12
- package/dist/types/tools/telegram-send.d.ts +7 -0
- package/dist/types/tools/todo-write.d.ts +8 -8
- package/dist/types/utils/external-editor.d.ts +2 -0
- package/dist/types/web/insane/bridge.d.ts +9 -9
- package/dist/types/web/search/index.d.ts +4 -4
- package/package.json +8 -8
- package/scripts/g004-tmux-smoke.ts +196 -36
- package/src/cli/daemon-cli.ts +19 -29
- package/src/cli/malloc-env-guard.test.ts +104 -0
- package/src/cli/malloc-env-guard.ts +149 -0
- package/src/cli/notify-cli.ts +78 -14
- package/src/cli/session-picker.ts +23 -40
- package/src/cli/update-cli.ts +56 -37
- package/src/cli.ts +51 -10
- package/src/commands/daemon.ts +19 -5
- package/src/commands/harness.ts +515 -47
- package/src/commands/launch.ts +11 -3
- package/src/commands/notify.ts +5 -1
- package/src/commands/session.ts +6 -1
- package/src/config/keybindings.ts +5 -0
- package/src/config/model-profile-activation.ts +1 -1
- package/src/config/model-profiles.ts +14 -14
- package/src/config/settings-schema.ts +41 -1
- package/src/config/settings.ts +96 -45
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/model-preset.ts +100 -0
- package/src/coordinator-mcp/policy.ts +30 -0
- package/src/coordinator-mcp/server.ts +2958 -430
- package/src/coordinator-mcp/session-reaper.ts +129 -0
- package/src/daemon/control-types.ts +11 -0
- package/src/daemon/operator-contract.ts +121 -0
- package/src/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.ts +57 -17
- package/src/defaults/skc/extensions/grok-cli-vendor/src/payload/sanitize.ts +15 -2
- package/src/defaults/skc/extensions/grok-cli-vendor/src/provider/register.ts +5 -1
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +10 -9
- package/src/internal-urls/local-protocol.ts +29 -10
- package/src/main.ts +301 -80
- package/src/modes/acp/acp-agent.ts +730 -100
- package/src/modes/acp/acp-mode.ts +29 -2
- package/src/modes/components/assistant-message.ts +32 -3
- package/src/modes/components/bash-execution.ts +43 -13
- package/src/modes/components/custom-editor.ts +5 -0
- package/src/modes/components/footer.ts +3 -3
- package/src/modes/components/hook-selector.ts +12 -0
- package/src/modes/components/irc-sidebar.ts +206 -0
- package/src/modes/components/pet-selector.ts +55 -0
- package/src/modes/components/sayknow-pet-widget.ts +360 -0
- package/src/modes/components/session-selector.ts +222 -221
- package/src/modes/components/settings-selector.ts +10 -0
- package/src/modes/components/status-line/segments.ts +14 -7
- package/src/modes/components/status-line/types.ts +1 -1
- package/src/modes/components/status-line.ts +4 -171
- package/src/modes/components/tool-execution.ts +47 -6
- package/src/modes/components/user-message.ts +26 -9
- package/src/modes/controllers/command-controller.ts +28 -2
- package/src/modes/controllers/event-controller.ts +159 -36
- package/src/modes/controllers/extension-ui-controller.ts +37 -18
- package/src/modes/controllers/input-controller.ts +8 -2
- package/src/modes/controllers/selector-controller.ts +62 -15
- package/src/modes/interactive-mode.ts +199 -7
- package/src/modes/irc-observation-ledger.ts +127 -0
- package/src/modes/print-mode.ts +228 -92
- package/src/modes/rpc/rpc-client.ts +58 -1
- package/src/modes/rpc/rpc-mode.ts +286 -85
- package/src/modes/rpc/rpc-types.ts +15 -1
- package/src/modes/shared/agent-wire/command-dispatch.ts +33 -1
- package/src/modes/shared/agent-wire/command-validation.ts +10 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +45 -2
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +288 -1
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +130 -20
- package/src/modes/types.ts +34 -2
- package/src/modes/utils/context-usage.ts +56 -92
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/irc-message.ts +138 -0
- package/src/modes/utils/ui-helpers.ts +216 -50
- package/src/notifications/config.ts +44 -0
- package/src/notifications/daemon-paths.ts +8 -0
- package/src/notifications/index.ts +535 -75
- package/src/notifications/lifecycle-control-runtime.ts +930 -77
- package/src/notifications/notification-service.ts +710 -0
- package/src/notifications/rate-limit-pool.ts +46 -12
- package/src/notifications/telegram-daemon-control.ts +27 -2
- package/src/notifications/telegram-daemon.ts +635 -94
- package/src/notifications/telegram-reference.ts +73 -13
- package/src/notifications/topic-registry.ts +86 -9
- package/src/prompts/system/irc-peer-roster.md +1 -0
- package/src/prompts/system/subagent-system-prompt.md +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/irc.md +1 -1
- package/src/registry/agent-registry.ts +3 -0
- package/src/sdk.ts +52 -1
- package/src/secrets/obfuscator.ts +4 -2
- package/src/session/agent-session.ts +670 -217
- package/src/session/cache-economics.ts +275 -0
- package/src/session/context-estimation.ts +91 -0
- package/src/session/provider-safety-stop.ts +9 -0
- package/src/session/session-dump-format.ts +6 -0
- package/src/session/session-manager.ts +809 -39
- package/src/session/session-storage.ts +582 -38
- package/src/skc-runtime/session-state-sidecar.ts +756 -135
- package/src/skc-runtime/tmux-common.ts +26 -7
- package/src/skc-runtime/tmux-gc.ts +52 -9
- package/src/skc-runtime/tmux-owner-isolation-cli.ts +185 -0
- package/src/skc-runtime/tmux-owner-isolation.ts +2060 -0
- package/src/skc-runtime/tmux-sessions.ts +871 -68
- package/src/slash-commands/builtin-registry.ts +153 -9
- package/src/slash-commands/helpers/context-report.ts +26 -4
- package/src/task/executor.ts +12 -3
- package/src/task/index.ts +8 -0
- package/src/task/receipt.ts +7 -2
- package/src/task/types.ts +26 -0
- package/src/tools/ask-answer-registry.ts +25 -0
- package/src/tools/browser/tab-supervisor.ts +63 -6
- package/src/tools/browser.ts +8 -3
- package/src/tools/index.ts +81 -6
- package/src/tools/sqlite-reader.ts +8 -4
- package/src/tui/output-block.ts +18 -6
- package/src/utils/external-editor.ts +6 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
|
|
3
|
-
import { estimateMessageTokensHeuristic } from "@sayknow-cli/agent-core/compaction";
|
|
2
|
+
|
|
4
3
|
import { type Component, truncateToWidth, visibleWidth } from "@sayknow-cli/tui";
|
|
5
4
|
import { formatCount, getProjectDir } from "@sayknow-cli/utils";
|
|
6
5
|
import { $ } from "bun";
|
|
@@ -13,7 +12,6 @@ import * as git from "../../utils/git";
|
|
|
13
12
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../../utils/session-color";
|
|
14
13
|
import { EMPTY_JOBS_SNAPSHOT, type JobsSnapshot } from "../jobs-observer";
|
|
15
14
|
import { sanitizeStatusText } from "../shared";
|
|
16
|
-
import { computeNonMessageTokens } from "../utils/context-usage";
|
|
17
15
|
import { renderSkillHudBar } from "./skill-hud/render";
|
|
18
16
|
import {
|
|
19
17
|
canReuseCachedPr,
|
|
@@ -70,104 +68,6 @@ interface CollectedStatusSegments {
|
|
|
70
68
|
rightCapWidth: number;
|
|
71
69
|
}
|
|
72
70
|
|
|
73
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
74
|
-
// Per-message token cache
|
|
75
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Symbol-keyed sidecar tagged onto each `AgentMessage` to memoize its
|
|
79
|
-
* `estimateMessageTokensHeuristic` result. Keyed by message identity (the object itself);
|
|
80
|
-
* a cheap content fingerprint detects in-place mutations (post-hoc error
|
|
81
|
-
* attachment, retry-truncated branch rebuild, etc.) and forces recompute.
|
|
82
|
-
*
|
|
83
|
-
* Cache lives on the message — multiple `StatusLineComponent` instances
|
|
84
|
-
* share it for free, and entries collect with the message itself when the
|
|
85
|
-
* conversation is replaced or compacted.
|
|
86
|
-
*/
|
|
87
|
-
const kTokenCache = Symbol("statusLine.tokenCache");
|
|
88
|
-
interface TaggedMessage {
|
|
89
|
-
[kTokenCache]?: { fingerprint: string; tokens: number };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Cheap structural fingerprint mirroring `estimateMessageTokensHeuristic`'s content walk.
|
|
94
|
-
* O(blocks) — only reads string `.length` and primitives, never copies or
|
|
95
|
-
* serializes content. Any in-place mutation that alters total tokenized
|
|
96
|
-
* content also alters one of the byte-length sums or block counts captured
|
|
97
|
-
* here, forcing the cached heuristic token value to be recomputed.
|
|
98
|
-
*/
|
|
99
|
-
function messageFingerprint(msg: AgentMessage): string {
|
|
100
|
-
const role = (msg as { role?: string }).role ?? "";
|
|
101
|
-
const ts = (msg as { timestamp?: number }).timestamp ?? 0;
|
|
102
|
-
let textLen = 0;
|
|
103
|
-
let blocks = 0;
|
|
104
|
-
let images = 0;
|
|
105
|
-
if (role === "bashExecution") {
|
|
106
|
-
const b = msg as { command?: unknown; output?: unknown };
|
|
107
|
-
if (typeof b.command === "string") textLen += b.command.length;
|
|
108
|
-
if (typeof b.output === "string") textLen += b.output.length;
|
|
109
|
-
} else if (role === "user") {
|
|
110
|
-
const content = (msg as { content?: unknown }).content;
|
|
111
|
-
if (typeof content === "string") {
|
|
112
|
-
textLen += content.length;
|
|
113
|
-
} else if (Array.isArray(content)) {
|
|
114
|
-
blocks = content.length;
|
|
115
|
-
for (const block of content) {
|
|
116
|
-
if (block?.type === "text" && typeof block.text === "string") textLen += block.text.length;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
} else if (role === "assistant") {
|
|
120
|
-
const content = (msg as { content?: unknown }).content;
|
|
121
|
-
if (Array.isArray(content)) {
|
|
122
|
-
blocks = content.length;
|
|
123
|
-
for (const block of content) {
|
|
124
|
-
if (!block || typeof block !== "object") continue;
|
|
125
|
-
const b = block as { type?: string; text?: string; thinking?: string; name?: string; arguments?: unknown };
|
|
126
|
-
if (b.type === "text" && typeof b.text === "string") textLen += b.text.length;
|
|
127
|
-
else if (b.type === "thinking" && typeof b.thinking === "string") textLen += b.thinking.length;
|
|
128
|
-
else if (b.type === "toolCall") {
|
|
129
|
-
if (typeof b.name === "string") textLen += b.name.length;
|
|
130
|
-
// Argument bytes vary; a length proxy is enough to detect in-place edits.
|
|
131
|
-
textLen += b.arguments === undefined ? 0 : JSON.stringify(b.arguments).length;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
} else if (role === "toolResult" || role === "hookMessage") {
|
|
136
|
-
const content = (msg as { content?: unknown }).content;
|
|
137
|
-
if (typeof content === "string") {
|
|
138
|
-
textLen += content.length;
|
|
139
|
-
} else if (Array.isArray(content)) {
|
|
140
|
-
blocks = content.length;
|
|
141
|
-
for (const block of content) {
|
|
142
|
-
if (!block || typeof block !== "object") continue;
|
|
143
|
-
const b = block as { type?: string; text?: string };
|
|
144
|
-
if (b.type === "text" && typeof b.text === "string") textLen += b.text.length;
|
|
145
|
-
else if (b.type === "image") images++;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
} else if (role === "branchSummary" || role === "compactionSummary") {
|
|
149
|
-
const s = (msg as { summary?: unknown }).summary;
|
|
150
|
-
if (typeof s === "string") textLen += s.length;
|
|
151
|
-
}
|
|
152
|
-
return `${role}:${ts}:${textLen}:${blocks}:${images}`;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Token count for a single message, using the per-message sidecar cache.
|
|
157
|
-
* The caller MUST skip caching for the last message during streaming —
|
|
158
|
-
* it may still be growing and its tokens belong recomputed each refresh.
|
|
159
|
-
*/
|
|
160
|
-
function tokensForMessage(msg: AgentMessage): number {
|
|
161
|
-
const fp = messageFingerprint(msg);
|
|
162
|
-
const tagged = msg as TaggedMessage;
|
|
163
|
-
const cached = tagged[kTokenCache];
|
|
164
|
-
if (cached && cached.fingerprint === fp) return cached.tokens;
|
|
165
|
-
const tokens = estimateMessageTokensHeuristic(msg);
|
|
166
|
-
tagged[kTokenCache] = { fingerprint: fp, tokens };
|
|
167
|
-
return tokens;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
171
71
|
// StatusLineComponent
|
|
172
72
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
173
73
|
|
|
@@ -217,20 +117,6 @@ export class StatusLineComponent implements Component {
|
|
|
217
117
|
#cachedUsage: SegmentContext["usage"] = null;
|
|
218
118
|
#usageFetchedAt = 0;
|
|
219
119
|
#usageInFlight = false;
|
|
220
|
-
// Context breakdown — incremental cache. Replaces the previous 2-second
|
|
221
|
-
// TTL design (which re-walked every message on each refresh and produced
|
|
222
|
-
// ~1.1 s sync freezes on 2,000+ message sessions because `updateEditorTopBorder`
|
|
223
|
-
// is called on every agent event in event-controller). The new scheme
|
|
224
|
-
// caches by message-object identity (a Symbol-keyed sidecar on each
|
|
225
|
-
// message) plus a cheap content fingerprint, so in-place mutations of
|
|
226
|
-
// an existing message (post-hoc error attachment, retry-truncated
|
|
227
|
-
// branch rebuild, replaceMessages with the same length) are detected
|
|
228
|
-
// and recomputed.
|
|
229
|
-
// Cached non-message total (system prompt + tools + skills). Invalidated
|
|
230
|
-
// when the inputs-identity fingerprint changes (model swap, skill toggle,
|
|
231
|
-
// tool registration).
|
|
232
|
-
#nonMessageTokensCache: number | undefined;
|
|
233
|
-
#nonMessageInputsKey: string | undefined;
|
|
234
120
|
|
|
235
121
|
constructor(
|
|
236
122
|
private readonly session: AgentSession,
|
|
@@ -618,57 +504,6 @@ export class StatusLineComponent implements Component {
|
|
|
618
504
|
return windows.length > 0 ? { windows } : null;
|
|
619
505
|
}
|
|
620
506
|
|
|
621
|
-
/**
|
|
622
|
-
* Compute the (cached) used-tokens / context-window totals for the
|
|
623
|
-
* status-line context% segment. Exposed (non-private) so unit tests can
|
|
624
|
-
* verify the incremental-cache invariants; not part of any external
|
|
625
|
-
* API.
|
|
626
|
-
*/
|
|
627
|
-
getCachedContextBreakdown(): { usedTokens: number; contextWindow: number } {
|
|
628
|
-
const messages = this.session.messages ?? [];
|
|
629
|
-
const contextWindow = this.session.model?.contextWindow ?? 0;
|
|
630
|
-
|
|
631
|
-
// 1) Non-message tokens (system prompt + tools + skills). Refresh only
|
|
632
|
-
// when the inputs identity fingerprint changes — usually never
|
|
633
|
-
// during a streaming turn. ~10-30 ms when it does refresh.
|
|
634
|
-
const inputsKey = this.#computeNonMessageInputsKey();
|
|
635
|
-
if (this.#nonMessageTokensCache === undefined || this.#nonMessageInputsKey !== inputsKey) {
|
|
636
|
-
this.#nonMessageTokensCache = computeNonMessageTokens(this.session);
|
|
637
|
-
this.#nonMessageInputsKey = inputsKey;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
// 2) Message tokens — incremental. The sidecar cache lives on the
|
|
641
|
-
// message object itself (Symbol-keyed), keyed by identity and
|
|
642
|
-
// validated by a cheap content fingerprint. Mutations that
|
|
643
|
-
// replace messages (replaceMessages, branch rebuild, compaction)
|
|
644
|
-
// yield fresh objects → cache miss → recompute. In-place
|
|
645
|
-
// mutations on the same object are caught by fingerprint
|
|
646
|
-
// mismatch. The LAST message is always recomputed because it
|
|
647
|
-
// may still be growing during streaming.
|
|
648
|
-
let messagesTokens = 0;
|
|
649
|
-
const lastIdx = messages.length - 1;
|
|
650
|
-
for (let i = 0; i < messages.length; i++) {
|
|
651
|
-
messagesTokens += i === lastIdx ? estimateMessageTokensHeuristic(messages[i]) : tokensForMessage(messages[i]);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
const usedTokens = this.#nonMessageTokensCache + messagesTokens;
|
|
655
|
-
return { usedTokens, contextWindow };
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* Build an identity fingerprint for the non-message inputs (system prompt,
|
|
660
|
-
* tools, skills). When this changes, the non-message token cache must be
|
|
661
|
-
* recomputed. Cheap: just lengths + first-string-length. Doesn't need to
|
|
662
|
-
* be cryptographically unique — only stable for the same inputs.
|
|
663
|
-
*/
|
|
664
|
-
#computeNonMessageInputsKey(): string {
|
|
665
|
-
const sp = this.session.systemPrompt ?? [];
|
|
666
|
-
const tools = this.session.agent?.state?.tools ?? [];
|
|
667
|
-
const skills = this.session.skills ?? [];
|
|
668
|
-
const modelId = this.session.model?.id ?? "";
|
|
669
|
-
return `${modelId}|${sp.length}:${sp[0]?.length ?? 0}|${tools.length}|${skills.length}`;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
507
|
#buildSegmentContext(
|
|
673
508
|
width: number,
|
|
674
509
|
effectiveSettings: Required<
|
|
@@ -695,11 +530,9 @@ export class StatusLineComponent implements Component {
|
|
|
695
530
|
tokensPerSecond: this.#getTokensPerSecond(),
|
|
696
531
|
};
|
|
697
532
|
|
|
698
|
-
|
|
699
|
-
const
|
|
700
|
-
const
|
|
701
|
-
const contextWindow = breakdown.contextWindow || state.model?.contextWindow || 0;
|
|
702
|
-
const contextPercent = contextWindow > 0 ? (contextTokens / contextWindow) * 100 : 0;
|
|
533
|
+
const contextUsage = this.session.getContextUsage?.();
|
|
534
|
+
const contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;
|
|
535
|
+
const contextPercent = contextUsage?.percent ?? null;
|
|
703
536
|
// Suppress the inline model percentage when a standalone context_pct
|
|
704
537
|
// segment is also rendered, so the value is not shown twice.
|
|
705
538
|
const contextPctSegmentActive =
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
Image,
|
|
9
9
|
ImageProtocol,
|
|
10
10
|
imageFallback,
|
|
11
|
+
isTerminalGraphicsFallbackActive,
|
|
11
12
|
registerAnimationCallback,
|
|
12
13
|
Spacer,
|
|
13
14
|
TERMINAL,
|
|
@@ -33,7 +34,7 @@ import {
|
|
|
33
34
|
import { formatExpandHint, replaceTabs, resolveImageOptions, truncateToWidth } from "../../tools/render-utils";
|
|
34
35
|
import { toolRenderers } from "../../tools/renderers";
|
|
35
36
|
import { renderStatusLine } from "../../tui";
|
|
36
|
-
import { sanitizeWithOptionalSixelPassthrough } from "../../utils/sixel";
|
|
37
|
+
import { containsSixelSequence, getSixelLineMask, sanitizeWithOptionalSixelPassthrough } from "../../utils/sixel";
|
|
37
38
|
import { renderDiff } from "./diff";
|
|
38
39
|
|
|
39
40
|
function ensureInvalidate(component: unknown): Component {
|
|
@@ -44,6 +45,23 @@ function ensureInvalidate(component: unknown): Component {
|
|
|
44
45
|
return c as Component;
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
const SIXEL_FALLBACK_PLACEHOLDER = "[SIXEL image hidden while IRC sidebar is visible]";
|
|
49
|
+
|
|
50
|
+
function replaceSixelOutputForGraphicsFallback(text: string): string {
|
|
51
|
+
const lines = text.split("\n");
|
|
52
|
+
const sixelLineMask = getSixelLineMask(lines);
|
|
53
|
+
if (!sixelLineMask.some(Boolean)) return text;
|
|
54
|
+
|
|
55
|
+
return lines
|
|
56
|
+
.flatMap((line, index) => {
|
|
57
|
+
if (!sixelLineMask[index]) return [line];
|
|
58
|
+
return index === 0 || !sixelLineMask[index - 1] || containsSixelSequence(line)
|
|
59
|
+
? [SIXEL_FALLBACK_PLACEHOLDER]
|
|
60
|
+
: [];
|
|
61
|
+
})
|
|
62
|
+
.join("\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
47
65
|
function cloneToolArgs<T>(args: T): T {
|
|
48
66
|
if (args === null || args === undefined) return args;
|
|
49
67
|
try {
|
|
@@ -169,7 +187,14 @@ export class ToolExecutionComponent extends Container {
|
|
|
169
187
|
isError?: boolean;
|
|
170
188
|
details?: any;
|
|
171
189
|
};
|
|
172
|
-
#textOutputCache?: {
|
|
190
|
+
#textOutputCache?: {
|
|
191
|
+
content: unknown;
|
|
192
|
+
showImages: boolean;
|
|
193
|
+
terminalImageProtocol: unknown;
|
|
194
|
+
graphicsFallbackActive: boolean;
|
|
195
|
+
output: string;
|
|
196
|
+
};
|
|
197
|
+
#displayBuiltWithGraphicsFallback: boolean | undefined;
|
|
173
198
|
// Edit preview state
|
|
174
199
|
#editMode?: EditMode;
|
|
175
200
|
#editDiffPreview?: PerFileDiffPreview[];
|
|
@@ -441,6 +466,13 @@ export class ToolExecutionComponent extends Container {
|
|
|
441
466
|
this.#updateDisplay();
|
|
442
467
|
}
|
|
443
468
|
|
|
469
|
+
override render(width: number): string[] {
|
|
470
|
+
if (this.#displayBuiltWithGraphicsFallback !== isTerminalGraphicsFallbackActive()) {
|
|
471
|
+
this.#updateDisplay();
|
|
472
|
+
}
|
|
473
|
+
return super.render(width);
|
|
474
|
+
}
|
|
475
|
+
|
|
444
476
|
#updateDisplay(): void {
|
|
445
477
|
// Set background based on state
|
|
446
478
|
const bgFn = this.#isPartial
|
|
@@ -685,6 +717,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
685
717
|
}
|
|
686
718
|
}
|
|
687
719
|
}
|
|
720
|
+
this.#displayBuiltWithGraphicsFallback = isTerminalGraphicsFallbackActive();
|
|
688
721
|
}
|
|
689
722
|
|
|
690
723
|
#getCallArgsForRender(): any {
|
|
@@ -761,11 +794,13 @@ export class ToolExecutionComponent extends Container {
|
|
|
761
794
|
|
|
762
795
|
const content = this.#result.content;
|
|
763
796
|
const terminalImageProtocol = TERMINAL.imageProtocol;
|
|
797
|
+
const graphicsFallbackActive = isTerminalGraphicsFallbackActive();
|
|
764
798
|
const cached = this.#textOutputCache;
|
|
765
799
|
if (
|
|
766
800
|
cached?.content === content &&
|
|
767
801
|
cached.showImages === this.#showImages &&
|
|
768
|
-
cached.terminalImageProtocol === terminalImageProtocol
|
|
802
|
+
cached.terminalImageProtocol === terminalImageProtocol &&
|
|
803
|
+
cached.graphicsFallbackActive === graphicsFallbackActive
|
|
769
804
|
) {
|
|
770
805
|
return cached.output;
|
|
771
806
|
}
|
|
@@ -773,8 +808,8 @@ export class ToolExecutionComponent extends Container {
|
|
|
773
808
|
const textParts: string[] = [];
|
|
774
809
|
for (const block of content ?? []) {
|
|
775
810
|
if (block.type !== "text") continue;
|
|
776
|
-
const text = block.text || "";
|
|
777
|
-
textParts.push(
|
|
811
|
+
const text = sanitizeWithOptionalSixelPassthrough(block.text || "", sanitizeText);
|
|
812
|
+
textParts.push(graphicsFallbackActive ? replaceSixelOutputForGraphicsFallback(text) : text);
|
|
778
813
|
}
|
|
779
814
|
let output = textParts.join("\n");
|
|
780
815
|
|
|
@@ -789,7 +824,13 @@ export class ToolExecutionComponent extends Container {
|
|
|
789
824
|
output = output ? `${output}\n${imageIndicators}` : imageIndicators;
|
|
790
825
|
}
|
|
791
826
|
|
|
792
|
-
this.#textOutputCache = {
|
|
827
|
+
this.#textOutputCache = {
|
|
828
|
+
content,
|
|
829
|
+
showImages: this.#showImages,
|
|
830
|
+
terminalImageProtocol,
|
|
831
|
+
graphicsFallbackActive,
|
|
832
|
+
output,
|
|
833
|
+
};
|
|
793
834
|
return output;
|
|
794
835
|
}
|
|
795
836
|
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type Component,
|
|
3
|
+
Container,
|
|
4
|
+
Markdown,
|
|
5
|
+
Spacer,
|
|
6
|
+
Text,
|
|
7
|
+
type ViewportAnchorRender,
|
|
8
|
+
type ViewportAnchorSource,
|
|
9
|
+
} from "@sayknow-cli/tui";
|
|
2
10
|
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
3
11
|
|
|
4
12
|
// OSC 133 shell integration: marks prompt zones for terminal multiplexers
|
|
@@ -10,8 +18,11 @@ const OSC133_ZONE_FINAL = "\x1b]133;C\x07";
|
|
|
10
18
|
* Component that renders a user message
|
|
11
19
|
*/
|
|
12
20
|
export class UserMessageComponent extends Container {
|
|
13
|
-
|
|
21
|
+
#viewportAnchorSource?: ViewportAnchorSource;
|
|
22
|
+
|
|
23
|
+
constructor(text: string, synthetic = false, viewportAnchorId?: string) {
|
|
14
24
|
super();
|
|
25
|
+
if (viewportAnchorId) this.#viewportAnchorSource = { id: viewportAnchorId };
|
|
15
26
|
const bgColor = (value: string) => theme.bg("userMessageBg", value);
|
|
16
27
|
const color = synthetic
|
|
17
28
|
? (value: string) => theme.fg("dim", value)
|
|
@@ -19,7 +30,9 @@ export class UserMessageComponent extends Container {
|
|
|
19
30
|
this.addChild(new Spacer(1));
|
|
20
31
|
const label = synthetic ? "replay" : "user";
|
|
21
32
|
this.addChild(new Text(theme.bold(theme.fg("accent", label)), 1, 0));
|
|
22
|
-
|
|
33
|
+
const prompt = new PromptZoneMarkdown(text, bgColor, color);
|
|
34
|
+
this.addChild(prompt);
|
|
35
|
+
if (this.#viewportAnchorSource) this.setViewportAnchorSource(prompt, this.#viewportAnchorSource);
|
|
23
36
|
}
|
|
24
37
|
}
|
|
25
38
|
|
|
@@ -37,15 +50,19 @@ class PromptZoneMarkdown implements Component {
|
|
|
37
50
|
this.#markdown.invalidate();
|
|
38
51
|
}
|
|
39
52
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (lines.length === 0) {
|
|
43
|
-
return lines;
|
|
44
|
-
}
|
|
45
|
-
|
|
53
|
+
#withPromptZone(lines: string[]): string[] {
|
|
54
|
+
if (lines.length === 0) return lines;
|
|
46
55
|
const zoned = [...lines];
|
|
47
56
|
zoned[0] = OSC133_ZONE_START + zoned[0];
|
|
48
57
|
zoned[zoned.length - 1] = `${zoned[zoned.length - 1]}${OSC133_ZONE_END}${OSC133_ZONE_FINAL}`;
|
|
49
58
|
return zoned;
|
|
50
59
|
}
|
|
60
|
+
|
|
61
|
+
renderWithViewportAnchorSource(width: number, source: ViewportAnchorSource): ViewportAnchorRender {
|
|
62
|
+
const rendered = this.#markdown.renderWithViewportAnchorSource(width, source);
|
|
63
|
+
return { lines: this.#withPromptZone(rendered.lines), anchors: rendered.anchors };
|
|
64
|
+
}
|
|
65
|
+
render(width: number): string[] {
|
|
66
|
+
return this.#withPromptZone(this.#markdown.render(width));
|
|
67
|
+
}
|
|
51
68
|
}
|
|
@@ -40,6 +40,7 @@ import { buildHotkeysMarkdown } from "../../modes/utils/hotkeys-markdown";
|
|
|
40
40
|
import { buildToolsMarkdown } from "../../modes/utils/tools-markdown";
|
|
41
41
|
import type { AsyncJobSnapshotItem } from "../../session/agent-session";
|
|
42
42
|
import type { AuthStorage } from "../../session/auth-storage";
|
|
43
|
+
import { computeCacheMissCostSummary, formatCacheMissSummaryLines } from "../../session/cache-economics";
|
|
43
44
|
import type { NewSessionOptions } from "../../session/session-manager";
|
|
44
45
|
import { outputMeta } from "../../tools/output-meta";
|
|
45
46
|
import { resolveToCwd, stripOuterDoubleQuotes } from "../../tools/path-utils";
|
|
@@ -48,6 +49,7 @@ import { getDisplayChangelogEntries } from "../../utils/changelog";
|
|
|
48
49
|
import { copyToClipboard } from "../../utils/clipboard";
|
|
49
50
|
import { openPath } from "../../utils/open";
|
|
50
51
|
import { setSessionTerminalTitle } from "../../utils/title-generator";
|
|
52
|
+
import { prepareTranscriptRebuild } from "../utils/ui-helpers";
|
|
51
53
|
|
|
52
54
|
function showMarkdownPanel(ctx: InteractiveModeContext, title: string, markdown: string): void {
|
|
53
55
|
ctx.chatContainer.addChild(new Spacer(1));
|
|
@@ -426,6 +428,24 @@ export class CommandController {
|
|
|
426
428
|
info += `${theme.fg("dim", "Premium Requests:")} ${normalizedPremiumRequests.toLocaleString()}\n`;
|
|
427
429
|
}
|
|
428
430
|
}
|
|
431
|
+
const cacheMissSummary = stats.costBreakdown
|
|
432
|
+
? computeCacheMissCostSummary(stats.tokens, {
|
|
433
|
+
kind: "persisted-aggregate",
|
|
434
|
+
costBreakdown: stats.costBreakdown,
|
|
435
|
+
})
|
|
436
|
+
: undefined;
|
|
437
|
+
if (cacheMissSummary) {
|
|
438
|
+
info += `\n${theme.bold("Cache Miss Cost")}`;
|
|
439
|
+
for (const line of formatCacheMissSummaryLines(cacheMissSummary)) {
|
|
440
|
+
const separator = line.indexOf(":");
|
|
441
|
+
if (separator === -1) {
|
|
442
|
+
info += `\n${line}`;
|
|
443
|
+
} else {
|
|
444
|
+
info += `\n${theme.fg("dim", `${line.slice(0, separator)}:`)}${line.slice(separator + 1)}`;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
info += `\n`;
|
|
448
|
+
}
|
|
429
449
|
|
|
430
450
|
if (this.ctx.lspServers && this.ctx.lspServers.length > 0) {
|
|
431
451
|
info += `\n${theme.bold("LSP Servers")}\n`;
|
|
@@ -911,6 +931,8 @@ export class CommandController {
|
|
|
911
931
|
}
|
|
912
932
|
}
|
|
913
933
|
if (!(await this.ctx.session.newSession(options))) return;
|
|
934
|
+
this.ctx.resetIrcSidebarSession();
|
|
935
|
+
|
|
914
936
|
this.ctx.resetObserverRegistry();
|
|
915
937
|
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
|
|
916
938
|
|
|
@@ -920,6 +942,7 @@ export class CommandController {
|
|
|
920
942
|
this.ctx.updateEditorBorderColor();
|
|
921
943
|
this.ctx.ui.requestRender();
|
|
922
944
|
|
|
945
|
+
prepareTranscriptRebuild(this.ctx.ui, "replace-identity");
|
|
923
946
|
this.ctx.chatContainer.clear();
|
|
924
947
|
this.ctx.pendingMessagesContainer.clear();
|
|
925
948
|
this.ctx.compactionQueuedMessages = [];
|
|
@@ -958,6 +981,7 @@ export class CommandController {
|
|
|
958
981
|
this.ctx.updateEditorBorderColor();
|
|
959
982
|
this.ctx.ui.requestRender();
|
|
960
983
|
|
|
984
|
+
prepareTranscriptRebuild(this.ctx.ui, "replace-identity");
|
|
961
985
|
this.ctx.chatContainer.clear();
|
|
962
986
|
this.ctx.pendingMessagesContainer.clear();
|
|
963
987
|
this.ctx.compactionQueuedMessages = [];
|
|
@@ -996,6 +1020,7 @@ export class CommandController {
|
|
|
996
1020
|
this.ctx.showError("Fork failed (session not persisted or cancelled)");
|
|
997
1021
|
return;
|
|
998
1022
|
}
|
|
1023
|
+
this.ctx.resetIrcSidebarSession();
|
|
999
1024
|
|
|
1000
1025
|
this.ctx.statusLine.invalidate();
|
|
1001
1026
|
this.ctx.updateEditorTopBorder();
|
|
@@ -1219,7 +1244,7 @@ export class CommandController {
|
|
|
1219
1244
|
: undefined;
|
|
1220
1245
|
await this.ctx.session.compact(instructions, options);
|
|
1221
1246
|
|
|
1222
|
-
this.ctx.rebuildChatFromMessages();
|
|
1247
|
+
this.ctx.rebuildChatFromMessages("reconcile-same-transcript");
|
|
1223
1248
|
|
|
1224
1249
|
this.ctx.statusLine.invalidate();
|
|
1225
1250
|
this.ctx.updateEditorTopBorder();
|
|
@@ -1279,9 +1304,10 @@ export class CommandController {
|
|
|
1279
1304
|
this.ctx.showError("Handoff cancelled");
|
|
1280
1305
|
return;
|
|
1281
1306
|
}
|
|
1307
|
+
this.ctx.resetIrcSidebarSession();
|
|
1282
1308
|
|
|
1283
1309
|
// Rebuild chat from the new session (which now contains the handoff document)
|
|
1284
|
-
this.ctx.rebuildChatFromMessages();
|
|
1310
|
+
this.ctx.rebuildChatFromMessages("replace-identity");
|
|
1285
1311
|
|
|
1286
1312
|
this.ctx.statusLine.invalidate();
|
|
1287
1313
|
this.ctx.updateEditorTopBorder();
|