@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
package/src/modes/types.ts
CHANGED
|
@@ -26,11 +26,15 @@ import type { EvalExecutionComponent } from "./components/eval-execution";
|
|
|
26
26
|
import type { HookEditorComponent } from "./components/hook-editor";
|
|
27
27
|
import type { HookInputComponent } from "./components/hook-input";
|
|
28
28
|
import type { HookSelectorComponent } from "./components/hook-selector";
|
|
29
|
+
import type { PetMode } from "./components/sayknow-pet-widget";
|
|
29
30
|
import type { StatusLineComponent } from "./components/status-line";
|
|
30
31
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
32
|
+
import type { IrcObservationLedger } from "./irc-observation-ledger";
|
|
31
33
|
import type { OAuthManualInputManager } from "./oauth-manual-input";
|
|
32
34
|
import type { Theme } from "./theme/theme";
|
|
35
|
+
import type { ParsedIrcMessage } from "./utils/irc-message";
|
|
33
36
|
|
|
37
|
+
export type TranscriptRebuildPolicy = "replace-identity" | "reconcile-same-transcript";
|
|
34
38
|
export type CompactionQueuedMessage = {
|
|
35
39
|
text: string;
|
|
36
40
|
mode: "steer" | "followUp";
|
|
@@ -60,6 +64,14 @@ export type TodoPhase = {
|
|
|
60
64
|
tasks: TodoItem[];
|
|
61
65
|
};
|
|
62
66
|
|
|
67
|
+
export type IrcArrivalSnapshot = Readonly<{
|
|
68
|
+
panelVisible: boolean;
|
|
69
|
+
/** User-requested open state; may be true while the panel yields at narrow widths. */
|
|
70
|
+
panelRequestedVisible: boolean;
|
|
71
|
+
sidebarAvailable: boolean;
|
|
72
|
+
resolvedToggleKey: string | null;
|
|
73
|
+
}>;
|
|
74
|
+
|
|
63
75
|
export interface InteractiveModeContext {
|
|
64
76
|
// UI access
|
|
65
77
|
ui: TUI;
|
|
@@ -84,6 +96,7 @@ export interface InteractiveModeContext {
|
|
|
84
96
|
mcpManager?: MCPManager;
|
|
85
97
|
lspServers?: LspStartupServerInfo[];
|
|
86
98
|
|
|
99
|
+
readonly ircLedger: IrcObservationLedger;
|
|
87
100
|
// State
|
|
88
101
|
isInitialized: boolean;
|
|
89
102
|
isBackgrounded: boolean;
|
|
@@ -163,6 +176,11 @@ export interface InteractiveModeContext {
|
|
|
163
176
|
setWorkingMessage(message?: string): void;
|
|
164
177
|
applyPendingWorkingMessage(): void;
|
|
165
178
|
ensureLoadingAnimation(): void;
|
|
179
|
+
setPetMode(mode: PetMode): void;
|
|
180
|
+
/** Live-preview a pet skin during a selector without persisting. */
|
|
181
|
+
previewPetMode(mode: PetMode): void;
|
|
182
|
+
/** Re-mount the composer (pet-aware) after an overlay/selector closes. */
|
|
183
|
+
restoreComposer(): void;
|
|
166
184
|
startPendingSubmission(input: {
|
|
167
185
|
text: string;
|
|
168
186
|
images?: ImageContent[];
|
|
@@ -187,21 +205,34 @@ export interface InteractiveModeContext {
|
|
|
187
205
|
withLocalSubmission<T>(text: string, fn: () => Promise<T>, options?: { imageCount?: number }): Promise<T>;
|
|
188
206
|
isKnownSlashCommand(text: string): boolean;
|
|
189
207
|
addMessageToChat(message: AgentMessage, options?: { populateHistory?: boolean }): Component[];
|
|
208
|
+
addLiveIrcObservationToChat(message: ParsedIrcMessage, arrival: IrcArrivalSnapshot): Component[];
|
|
209
|
+
removeRenderedIrcInlineComponents(observationId: string): readonly Component[] | undefined;
|
|
210
|
+
resetRenderedIrcInlineComponents(): readonly (readonly Component[])[];
|
|
190
211
|
renderSessionContext(
|
|
191
212
|
sessionContext: SessionContext,
|
|
192
213
|
options?: { updateFooter?: boolean; populateHistory?: boolean },
|
|
193
214
|
): void;
|
|
194
|
-
|
|
215
|
+
rebuildInitialMessages(
|
|
216
|
+
policy: TranscriptRebuildPolicy,
|
|
217
|
+
prebuiltContext?: SessionContext,
|
|
218
|
+
options?: { preserveExistingChat?: boolean },
|
|
219
|
+
): void;
|
|
195
220
|
getUserMessageText(message: Message): string;
|
|
221
|
+
getAssistantViewportAnchorId?(message: AssistantMessage): string;
|
|
196
222
|
findLastAssistantMessage(): AssistantMessage | undefined;
|
|
197
223
|
extractAssistantText(message: AssistantMessage): string;
|
|
198
224
|
updateEditorTopBorder(): void;
|
|
199
225
|
updateEditorBorderColor(): void;
|
|
200
|
-
rebuildChatFromMessages(): void;
|
|
226
|
+
rebuildChatFromMessages(policy: TranscriptRebuildPolicy): void;
|
|
201
227
|
setTodos(todos: TodoItem[] | TodoPhase[]): void;
|
|
202
228
|
reloadTodos(): Promise<void>;
|
|
203
229
|
toggleTodoExpansion(): void;
|
|
204
230
|
|
|
231
|
+
// IRC sidebar
|
|
232
|
+
toggleIrcSidebar(): void;
|
|
233
|
+
captureIrcArrivalSnapshot(): IrcArrivalSnapshot;
|
|
234
|
+
applyIrcSidebarAvailability(enabled: boolean): void;
|
|
235
|
+
resetIrcSidebarSession(): void;
|
|
205
236
|
// Command handling
|
|
206
237
|
handleExportCommand(text: string): Promise<void>;
|
|
207
238
|
handleShareCommand(): Promise<void>;
|
|
@@ -242,6 +273,7 @@ export interface InteractiveModeContext {
|
|
|
242
273
|
// Selector handling
|
|
243
274
|
showSettingsSelector(): void;
|
|
244
275
|
showThemeSelector(): void;
|
|
276
|
+
showPetSelector(): void;
|
|
245
277
|
showHistorySearch(): void;
|
|
246
278
|
showExtensionsDashboard(): void;
|
|
247
279
|
showAgentsDashboard(): void;
|
|
@@ -3,16 +3,20 @@ import type { CompactionSettings } from "@sayknow-cli/agent-core/compaction";
|
|
|
3
3
|
import {
|
|
4
4
|
effectiveReserveTokens,
|
|
5
5
|
estimateMessageTokensHeuristic,
|
|
6
|
-
estimateTextTokensHeuristic,
|
|
7
6
|
resolveThresholdTokens,
|
|
8
7
|
} from "@sayknow-cli/agent-core/compaction";
|
|
9
8
|
import type { Model } from "@sayknow-cli/ai";
|
|
10
9
|
import { formatNumber } from "@sayknow-cli/utils";
|
|
11
|
-
import type { Skill } from "../../extensibility/skills";
|
|
12
10
|
import type { AgentSession } from "../../session/agent-session";
|
|
13
|
-
import
|
|
11
|
+
import { computeNonMessageBreakdown } from "../../session/context-estimation";
|
|
14
12
|
import type { theme as Theme } from "../theme/theme";
|
|
15
13
|
|
|
14
|
+
export {
|
|
15
|
+
computeNonMessageTokens,
|
|
16
|
+
estimateSkillsTokens,
|
|
17
|
+
estimateToolSchemaTokens,
|
|
18
|
+
} from "../../session/context-estimation";
|
|
19
|
+
|
|
16
20
|
const GRID_COLS = 20;
|
|
17
21
|
const GRID_ROWS = 10;
|
|
18
22
|
const GRID_CELLS = GRID_COLS * GRID_ROWS;
|
|
@@ -38,90 +42,13 @@ export interface ContextBreakdown {
|
|
|
38
42
|
contextWindow: number;
|
|
39
43
|
categories: CategoryInfo[];
|
|
40
44
|
lastUserTurnTokens: number;
|
|
41
|
-
|
|
45
|
+
estimatedCategoryTotal: number;
|
|
46
|
+
usedTokens: number | null;
|
|
47
|
+
source: "provider_anchor" | "heuristic" | "unknown";
|
|
42
48
|
autoCompactBufferTokens: number;
|
|
43
49
|
freeTokens: number;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
export function estimateSkillsTokens(skills: readonly Skill[]): number {
|
|
47
|
-
const fragments: string[] = [];
|
|
48
|
-
for (const skill of skills) {
|
|
49
|
-
// "- name: description\n" wire framing tokenizes ~identically to the
|
|
50
|
-
// concatenated form, so encode each piece separately and sum.
|
|
51
|
-
fragments.push(skill.name, skill.description);
|
|
52
|
-
}
|
|
53
|
-
return estimateTextTokensHeuristic(fragments);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function estimateToolSchemaTokens(
|
|
57
|
-
tools: ReadonlyArray<Pick<Tool, "name" | "description" | "parameters">>,
|
|
58
|
-
): number {
|
|
59
|
-
const fragments: string[] = [];
|
|
60
|
-
for (const tool of tools) {
|
|
61
|
-
fragments.push(tool.name, tool.description);
|
|
62
|
-
try {
|
|
63
|
-
fragments.push(JSON.stringify(tool.parameters ?? {}));
|
|
64
|
-
} catch {
|
|
65
|
-
// Schema may contain functions or cycles; ignore.
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return estimateTextTokensHeuristic(fragments);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Compute just the NON-MESSAGE token total: system prompt (with its skills
|
|
73
|
-
* section subtracted, since skills are tokenized separately) + system context
|
|
74
|
-
* (the rest of the system-prompt array) + tools + skills.
|
|
75
|
-
*
|
|
76
|
-
* Exposed so callers like `StatusLineComponent` can cache the non-message
|
|
77
|
-
* total separately from the message total. Non-message inputs (skills,
|
|
78
|
-
* tools, system prompt) change rarely; the message list grows on every
|
|
79
|
-
* streaming turn. Splitting the two lets the caller refresh each on its own
|
|
80
|
-
* cadence — non-message recomputed only when the inputs identity changes,
|
|
81
|
-
* messages walked incrementally as new entries append.
|
|
82
|
-
*/
|
|
83
|
-
export function computeNonMessageTokens(session: AgentSession): number {
|
|
84
|
-
const parts = computeNonMessageBreakdown(session);
|
|
85
|
-
return (
|
|
86
|
-
parts.systemPromptTokens + parts.systemContextTokens + parts.rulesTokens + parts.toolsTokens + parts.skillsTokens
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Shared helper for the four non-message token totals. Single source of truth
|
|
92
|
-
* for both `computeNonMessageTokens` (status-line incremental cache) and
|
|
93
|
-
* `computeContextBreakdown` (/context panel). The split avoids drift between
|
|
94
|
-
* the two surfaces — they MUST report the same numbers.
|
|
95
|
-
*/
|
|
96
|
-
function computeNonMessageBreakdown(session: AgentSession): {
|
|
97
|
-
rulesTokens: number;
|
|
98
|
-
skillsTokens: number;
|
|
99
|
-
toolsTokens: number;
|
|
100
|
-
systemContextTokens: number;
|
|
101
|
-
systemPromptTokens: number;
|
|
102
|
-
} {
|
|
103
|
-
const skillsTokens = estimateSkillsTokens(session.skills ?? []);
|
|
104
|
-
const toolsTokens = estimateToolSchemaTokens(session.agent?.state?.tools ?? []);
|
|
105
|
-
const systemPromptParts = session.systemPrompt ?? [];
|
|
106
|
-
const rulesTokens = estimateRulesTokens(systemPromptParts);
|
|
107
|
-
const systemContextTokens = estimateTextTokensHeuristic(systemPromptParts.slice(1));
|
|
108
|
-
const systemPromptTokens = Math.max(
|
|
109
|
-
0,
|
|
110
|
-
estimateTextTokensHeuristic(systemPromptParts[0] ?? "") - skillsTokens - rulesTokens,
|
|
111
|
-
);
|
|
112
|
-
return { rulesTokens, skillsTokens, toolsTokens, systemContextTokens, systemPromptTokens };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function estimateRulesTokens(systemPromptParts: readonly string[]): number {
|
|
116
|
-
const fragments: string[] = [];
|
|
117
|
-
for (const part of systemPromptParts) {
|
|
118
|
-
for (const match of part.matchAll(/<rules>[\s\S]*?<\/rules>/g)) {
|
|
119
|
-
fragments.push(match[0]);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return fragments.length === 0 ? 0 : estimateTextTokensHeuristic(fragments);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
52
|
function splitLastUserTurn(messages: readonly AgentMessage[]): {
|
|
126
53
|
regularMessagesTokens: number;
|
|
127
54
|
lastUserTurnTokens: number;
|
|
@@ -198,7 +125,11 @@ export function computeContextBreakdown(
|
|
|
198
125
|
},
|
|
199
126
|
];
|
|
200
127
|
|
|
201
|
-
const
|
|
128
|
+
const estimatedCategoryTotal = categories.reduce((sum, c) => sum + c.tokens, 0);
|
|
129
|
+
const contextUsage = session.getContextUsage?.();
|
|
130
|
+
const source = contextUsage?.source ?? "heuristic";
|
|
131
|
+
const usedTokens = source === "unknown" ? null : (contextUsage?.tokens ?? estimatedCategoryTotal);
|
|
132
|
+
const tokensForFreeSpace = usedTokens ?? estimatedCategoryTotal;
|
|
202
133
|
|
|
203
134
|
let autoCompactBufferTokens = 0;
|
|
204
135
|
if (contextWindow > 0) {
|
|
@@ -214,16 +145,18 @@ export function computeContextBreakdown(
|
|
|
214
145
|
autoCompactBufferTokens = effectiveReserveTokens(contextWindow, compactionSettings);
|
|
215
146
|
}
|
|
216
147
|
}
|
|
217
|
-
autoCompactBufferTokens = Math.min(autoCompactBufferTokens, Math.max(0, contextWindow -
|
|
148
|
+
autoCompactBufferTokens = Math.min(autoCompactBufferTokens, Math.max(0, contextWindow - tokensForFreeSpace));
|
|
218
149
|
|
|
219
|
-
const freeTokens = Math.max(0, contextWindow -
|
|
150
|
+
const freeTokens = Math.max(0, contextWindow - tokensForFreeSpace - autoCompactBufferTokens);
|
|
220
151
|
|
|
221
152
|
return {
|
|
222
153
|
model,
|
|
223
154
|
contextWindow,
|
|
224
155
|
categories,
|
|
225
156
|
lastUserTurnTokens,
|
|
157
|
+
estimatedCategoryTotal,
|
|
226
158
|
usedTokens,
|
|
159
|
+
source,
|
|
227
160
|
autoCompactBufferTokens,
|
|
228
161
|
freeTokens,
|
|
229
162
|
};
|
|
@@ -310,18 +243,48 @@ function percentString(part: number, whole: number, fractionDigits = 1): string
|
|
|
310
243
|
|
|
311
244
|
function buildLegendLines(breakdown: ContextBreakdown, theme: typeof Theme): string[] {
|
|
312
245
|
const lines: string[] = [];
|
|
313
|
-
const {
|
|
246
|
+
const {
|
|
247
|
+
model,
|
|
248
|
+
contextWindow,
|
|
249
|
+
categories,
|
|
250
|
+
estimatedCategoryTotal,
|
|
251
|
+
usedTokens,
|
|
252
|
+
source,
|
|
253
|
+
autoCompactBufferTokens,
|
|
254
|
+
freeTokens,
|
|
255
|
+
} = breakdown;
|
|
314
256
|
|
|
315
257
|
const modelName = model?.name ?? model?.id ?? "no model";
|
|
316
258
|
const modelId = model?.id ?? "unknown";
|
|
317
259
|
const windowLabel = formatNumber(contextWindow).toLowerCase();
|
|
260
|
+
const totalSourceLabel =
|
|
261
|
+
source === "provider_anchor"
|
|
262
|
+
? "provider-reported"
|
|
263
|
+
: source === "unknown"
|
|
264
|
+
? "estimated; exact count unknown until next response"
|
|
265
|
+
: "estimated";
|
|
318
266
|
|
|
319
267
|
lines.push(theme.bold(`${modelName}`) + theme.fg("dim", ` (${windowLabel} context)`));
|
|
320
268
|
lines.push(theme.fg("muted", `${modelId}[${windowLabel}]`));
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
theme.
|
|
324
|
-
|
|
269
|
+
if (usedTokens === null) {
|
|
270
|
+
lines.push(
|
|
271
|
+
`${theme.bold("unknown")}${theme.fg("dim", `/${windowLabel} tokens`)}` +
|
|
272
|
+
theme.fg("muted", " (exact count unknown until next response)"),
|
|
273
|
+
);
|
|
274
|
+
} else {
|
|
275
|
+
lines.push(
|
|
276
|
+
`${theme.bold(formatNumber(usedTokens))}${theme.fg("dim", `/${windowLabel} tokens`)}` +
|
|
277
|
+
theme.fg("muted", ` (${percentString(usedTokens, contextWindow)}) (${totalSourceLabel})`),
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
if (source !== "unknown" && usedTokens !== null && estimatedCategoryTotal !== usedTokens) {
|
|
281
|
+
lines.push(
|
|
282
|
+
theme.fg(
|
|
283
|
+
"muted",
|
|
284
|
+
`Estimated category total: ${formatNumber(estimatedCategoryTotal)} tokens (composition below is estimated)`,
|
|
285
|
+
),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
325
288
|
lines.push("");
|
|
326
289
|
lines.push(theme.fg("muted", "Estimated usage by category"));
|
|
327
290
|
|
|
@@ -334,8 +297,9 @@ function buildLegendLines(breakdown: ContextBreakdown, theme: typeof Theme): str
|
|
|
334
297
|
}
|
|
335
298
|
|
|
336
299
|
const freeDot = theme.fg("dim", CELL_FREE);
|
|
300
|
+
const freeLabel = usedTokens === null ? "Free space (estimated)" : "Free space";
|
|
337
301
|
lines.push(
|
|
338
|
-
`${freeDot}
|
|
302
|
+
`${freeDot} ${freeLabel}: ${theme.bold(formatNumber(freeTokens))} ${theme.fg("dim", `(${percentString(freeTokens, contextWindow)})`)}`,
|
|
339
303
|
);
|
|
340
304
|
|
|
341
305
|
if (autoCompactBufferTokens > 0) {
|
|
@@ -54,6 +54,7 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
|
|
|
54
54
|
`| \`${appKey(bindings, "app.editor.external")}\` | Edit message in external editor |`,
|
|
55
55
|
`| \`${appKey(bindings, "app.clipboard.pasteImage")}\` | Paste image from clipboard |`,
|
|
56
56
|
`| \`${appKey(bindings, "app.stt.toggle")}\` | Toggle speech-to-text recording |`,
|
|
57
|
+
`| \`${appKey(bindings, "app.irc.sidebar.toggle")}\` | Toggle IRC sidebar |`,
|
|
57
58
|
"| `?` | Show help / shortcuts |",
|
|
58
59
|
"| `#` | Prompt actions (command-palette style actions) |",
|
|
59
60
|
"| `/` | Slash commands (try `/help` or `/new`) |",
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
import type { AgentMessage } from "@sayknow-cli/agent-core";
|
|
3
|
+
import { getSegmenter } from "@sayknow-cli/tui";
|
|
4
|
+
import { sanitizeText } from "@sayknow-cli/utils";
|
|
5
|
+
import { associateSessionMessageObservationId, getSessionMessageObservationId } from "../../session/session-manager";
|
|
6
|
+
|
|
7
|
+
const graphemeSegmenter = getSegmenter();
|
|
8
|
+
const IRC_IDENTITY_SOURCE_MAX_UTF8_BYTES = 4 * 1_024;
|
|
9
|
+
const IRC_IDENTITY_DISPLAY_MAX_UTF8_BYTES = 256;
|
|
10
|
+
export type IrcMessageKind = "incoming" | "autoreply" | "relay";
|
|
11
|
+
|
|
12
|
+
export type ParsedIrcMessage = {
|
|
13
|
+
observationId: string;
|
|
14
|
+
from: string;
|
|
15
|
+
to: string;
|
|
16
|
+
text: string;
|
|
17
|
+
timestamp: number;
|
|
18
|
+
kind: IrcMessageKind;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export interface IrcMessageBlock {
|
|
22
|
+
readonly sender: string;
|
|
23
|
+
readonly recipient: string;
|
|
24
|
+
readonly kind: "incoming" | "autoreply" | "outgoing";
|
|
25
|
+
readonly time: string;
|
|
26
|
+
readonly bodyLines: readonly string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function projectIrcText(
|
|
30
|
+
text: string,
|
|
31
|
+
maxUtf8Bytes: number,
|
|
32
|
+
): { text: string; truncated: boolean; utf8Bytes: number } {
|
|
33
|
+
let end = 0;
|
|
34
|
+
let utf8Bytes = 0;
|
|
35
|
+
for (const part of graphemeSegmenter.segment(text)) {
|
|
36
|
+
const segmentBytes = Buffer.byteLength(part.segment, "utf8");
|
|
37
|
+
if (utf8Bytes + segmentBytes > maxUtf8Bytes) break;
|
|
38
|
+
utf8Bytes += segmentBytes;
|
|
39
|
+
end = part.index + part.segment.length;
|
|
40
|
+
}
|
|
41
|
+
return { text: text.slice(0, end), truncated: end < text.length, utf8Bytes };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Normalizes and bounds untrusted IRC identity fields without splitting visible graphemes. */
|
|
45
|
+
export function normalizeIrcIdentity(identity: string): string {
|
|
46
|
+
const source = projectIrcText(identity, IRC_IDENTITY_SOURCE_MAX_UTF8_BYTES);
|
|
47
|
+
const sanitized = sanitizeText(source.text.replace(/[\r\n\t\u2028\u2029]+/g, " ")).replace(
|
|
48
|
+
/[\u061C\u200E-\u200F\u202A-\u202E\u2066-\u2069]/g,
|
|
49
|
+
"",
|
|
50
|
+
);
|
|
51
|
+
const display = projectIrcText(sanitized, IRC_IDENTITY_DISPLAY_MAX_UTF8_BYTES);
|
|
52
|
+
return source.truncated || display.truncated ? `${display.text}…` : display.text;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Formats IRC observations into display-neutral semantic blocks for both IRC surfaces. */
|
|
56
|
+
export function formatIrcMessageBlock(message: ParsedIrcMessage & { timestamp: number }): IrcMessageBlock {
|
|
57
|
+
const date = new Date(message.timestamp);
|
|
58
|
+
// IRC timestamps are human-facing, so use local time rather than UTC serialization.
|
|
59
|
+
const time = Number.isFinite(date.getTime())
|
|
60
|
+
? `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`
|
|
61
|
+
: "--:--";
|
|
62
|
+
const bodyLines = message.text === "" ? [] : message.text.replaceAll("\t", " ").split("\n");
|
|
63
|
+
return {
|
|
64
|
+
sender: normalizeIrcIdentity(message.from),
|
|
65
|
+
recipient: normalizeIrcIdentity(message.to),
|
|
66
|
+
kind: message.kind === "relay" ? "outgoing" : message.kind,
|
|
67
|
+
time,
|
|
68
|
+
bodyLines,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type IrcCustomMessage = Extract<AgentMessage, { role: "custom" }>;
|
|
73
|
+
|
|
74
|
+
export function isIrcCustomType(customType: string | undefined): customType is `irc:${IrcMessageKind}` {
|
|
75
|
+
return customType === "irc:incoming" || customType === "irc:autoreply" || customType === "irc:relay";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function stringDetail(details: unknown, key: string): string {
|
|
79
|
+
if (!details || typeof details !== "object") return "";
|
|
80
|
+
const value = (details as Record<string, unknown>)[key];
|
|
81
|
+
return typeof value === "string" ? value : "";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function legacyObservationId(fields: readonly string[]): string {
|
|
85
|
+
const hash = crypto.createHash("sha256");
|
|
86
|
+
hash.update("skc:irc:legacy-observation:v1\0");
|
|
87
|
+
for (const field of fields) {
|
|
88
|
+
hash.update(String(Buffer.byteLength(field, "utf8")));
|
|
89
|
+
hash.update(":");
|
|
90
|
+
hash.update(field);
|
|
91
|
+
}
|
|
92
|
+
return `legacy:sha256:${hash.digest("hex")}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Normalizes every supported IRC custom message into its UI observation shape.
|
|
97
|
+
* Legacy records have no UUID, so their immutable payload is used as a stable key.
|
|
98
|
+
*/
|
|
99
|
+
export function parseIrcMessage(message: IrcCustomMessage): ParsedIrcMessage | undefined {
|
|
100
|
+
if (!isIrcCustomType(message.customType)) return undefined;
|
|
101
|
+
|
|
102
|
+
const kind = message.customType.slice(4) as IrcMessageKind;
|
|
103
|
+
const sourceTimestamp =
|
|
104
|
+
typeof message.timestamp === "number" && Number.isFinite(message.timestamp) ? message.timestamp : undefined;
|
|
105
|
+
const timestamp = sourceTimestamp ?? Date.now();
|
|
106
|
+
const rawFrom = kind === "autoreply" ? "you" : stringDetail(message.details, "from") || "?";
|
|
107
|
+
const rawTo = kind === "incoming" ? "you" : stringDetail(message.details, "to") || "?";
|
|
108
|
+
const rawText =
|
|
109
|
+
kind === "incoming"
|
|
110
|
+
? stringDetail(message.details, "message")
|
|
111
|
+
: kind === "autoreply"
|
|
112
|
+
? stringDetail(message.details, "reply")
|
|
113
|
+
: stringDetail(message.details, "body");
|
|
114
|
+
const from = normalizeIrcIdentity(rawFrom);
|
|
115
|
+
const to = normalizeIrcIdentity(rawTo);
|
|
116
|
+
const text = sanitizeText(rawText);
|
|
117
|
+
const explicitObservationId = stringDetail(message.details, "observationId");
|
|
118
|
+
const observationId = associateSessionMessageObservationId(
|
|
119
|
+
message,
|
|
120
|
+
explicitObservationId ||
|
|
121
|
+
getSessionMessageObservationId(message) ||
|
|
122
|
+
legacyObservationId([
|
|
123
|
+
kind,
|
|
124
|
+
sourceTimestamp === undefined ? "null" : String(sourceTimestamp),
|
|
125
|
+
rawFrom,
|
|
126
|
+
rawTo,
|
|
127
|
+
rawText,
|
|
128
|
+
]),
|
|
129
|
+
);
|
|
130
|
+
return {
|
|
131
|
+
observationId,
|
|
132
|
+
from,
|
|
133
|
+
to,
|
|
134
|
+
text,
|
|
135
|
+
timestamp,
|
|
136
|
+
kind,
|
|
137
|
+
};
|
|
138
|
+
}
|