@robota-sdk/agent-command 3.0.0-beta.79 → 3.0.0-beta.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/node/index.cjs +76 -38
- package/dist/node/index.d.cts +946 -0
- package/dist/node/index.d.cts.map +1 -0
- package/dist/node/index.d.ts +564 -51
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +76 -38
- package/dist/node/index.js.map +1 -1
- package/package.json +34 -16
- package/src/advisor/advisor-command-module.ts +47 -0
- package/src/advisor/advisor-command.test.ts +102 -0
- package/src/advisor/advisor-command.ts +55 -0
- package/src/advisor/advisor-cost-functional.test.ts +92 -0
- package/src/advisor/index.ts +6 -0
- package/src/agent/__tests__/agent-command.test.ts +39 -8
- package/src/agent/agent-command-module.ts +14 -7
- package/src/agent/agent-command-parser.ts +1 -1
- package/src/agent/agent-command.ts +3 -3
- package/src/background/__tests__/background-command-module.test.ts +59 -52
- package/src/background/background-command-module.ts +2 -1
- package/src/background/background-command.ts +3 -3
- package/src/command-module-utils.ts +33 -0
- package/src/compact/__tests__/compact-command-module.test.ts +31 -23
- package/src/compact/compact-command-module.ts +10 -1
- package/src/compact/compact-command.ts +6 -3
- package/src/context/__tests__/context-command-module.test.ts +214 -63
- package/src/context/auto-compact-format.ts +25 -0
- package/src/context/context-breakdown.ts +295 -0
- package/src/context/context-command-module.ts +45 -14
- package/src/context/context-command.ts +34 -257
- package/src/default/__tests__/__snapshots__/model-exposure.test.ts.snap +110 -0
- package/src/default/__tests__/default-command-modules.test.ts +59 -7
- package/src/default/__tests__/model-exposure.test.ts +241 -0
- package/src/default/__tests__/org-policy-forwarding.test.ts +98 -0
- package/src/default/default-command-modules.ts +130 -18
- package/src/default/index.ts +4 -1
- package/src/devices/__tests__/devices-command-module.test.ts +212 -0
- package/src/devices/devices-command-module.ts +224 -0
- package/src/devices/devices-command-port.ts +82 -0
- package/src/devices/index.ts +11 -0
- package/src/doctor/__tests__/doctor-command-module.test.ts +145 -0
- package/src/doctor/__tests__/doctor-display-vocabulary.test.ts +38 -0
- package/src/doctor/__tests__/doctor-fixture.ts +145 -0
- package/src/doctor/__tests__/doctor-redaction.test.ts +29 -0
- package/src/doctor/__tests__/doctor-runner.test.ts +464 -0
- package/src/doctor/doctor-command-module.ts +132 -0
- package/src/doctor/doctor-extensions-probe.ts +299 -0
- package/src/doctor/doctor-node-deps.ts +89 -0
- package/src/doctor/doctor-redaction.ts +99 -0
- package/src/doctor/doctor-render.ts +70 -0
- package/src/doctor/doctor-repair.ts +136 -0
- package/src/doctor/doctor-runner.ts +122 -0
- package/src/doctor/doctor-settings-probe.ts +239 -0
- package/src/doctor/doctor-storage-probe.ts +187 -0
- package/src/doctor/doctor-types.ts +109 -0
- package/src/doctor/index.ts +32 -0
- package/src/editor/__tests__/editor-command-functional.test.ts +49 -16
- package/src/editor/editor-command-module.ts +6 -5
- package/src/editor/editor-command.ts +10 -5
- package/src/effort/effort-command-module.ts +51 -0
- package/src/effort/effort-command.test.ts +140 -0
- package/src/effort/effort-command.ts +122 -0
- package/src/effort/index.ts +6 -0
- package/src/exit/__tests__/exit-command-module.test.ts +16 -9
- package/src/exit/exit-command-module.ts +2 -1
- package/src/exit/exit-command.ts +6 -6
- package/src/fork/__tests__/fork-command.test.ts +184 -0
- package/src/fork/fork-command-module.ts +61 -0
- package/src/fork/fork-command.ts +162 -0
- package/src/fork/index.ts +8 -0
- package/src/git/__tests__/fake-git-port.ts +28 -0
- package/src/git/__tests__/git-command-module.test.ts +103 -0
- package/src/git/__tests__/git-commit.test.ts +185 -0
- package/src/git/__tests__/git-diff.test.ts +125 -0
- package/src/git/__tests__/git-process.test.ts +221 -0
- package/src/git/__tests__/git-status.test.ts +117 -0
- package/src/git/git-command-module.ts +139 -0
- package/src/git/git-commit.ts +251 -0
- package/src/git/git-diff.ts +129 -0
- package/src/git/git-process.ts +169 -0
- package/src/git/git-status.ts +134 -0
- package/src/git/index.ts +17 -0
- package/src/goal/__tests__/goal-command.test.ts +9 -4
- package/src/goal/goal-command-module.ts +2 -1
- package/src/goal/goal-command.ts +6 -9
- package/src/handoff/__tests__/handoff-command.test.ts +218 -0
- package/src/handoff/handoff-command-module.ts +48 -0
- package/src/handoff/handoff-command.ts +187 -0
- package/src/handoff/index.ts +6 -0
- package/src/help/__tests__/help-command-module.test.ts +63 -40
- package/src/help/__tests__/help-command.test.ts +46 -37
- package/src/help/help-command-module.ts +2 -1
- package/src/help/help-command.ts +3 -3
- package/src/index.ts +124 -2
- package/src/keybindings/__tests__/keybindings-command-module.test.ts +57 -0
- package/src/keybindings/index.ts +6 -0
- package/src/keybindings/keybindings-command-module.ts +74 -0
- package/src/language/__tests__/language-command-module.test.ts +33 -28
- package/src/language/language-command-module.ts +2 -1
- package/src/language/language-command.ts +8 -6
- package/src/mcp-activation/__tests__/mcp-activation-command.test.ts +630 -0
- package/src/mcp-activation/__tests__/mcp-model-view.test.ts +211 -0
- package/src/mcp-activation/index.ts +12 -0
- package/src/mcp-activation/mcp-activation-command-module.ts +92 -0
- package/src/mcp-activation/mcp-activation-command.ts +448 -0
- package/src/mcp-activation/mcp-model-notice.ts +85 -0
- package/src/mcp-activation/mcp-model-status.ts +99 -0
- package/src/memory/__tests__/memory-command-module.test.ts +167 -74
- package/src/memory/memory-command-module.ts +18 -1
- package/src/memory/memory-command.ts +38 -38
- package/src/mode/__tests__/mode-command-module.test.ts +51 -38
- package/src/mode/mode-command-module.ts +2 -1
- package/src/mode/mode-command.ts +15 -7
- package/src/output-style/__tests__/output-style-command.test.ts +96 -0
- package/src/output-style/index.ts +6 -0
- package/src/output-style/output-style-command-module.ts +143 -0
- package/src/peers/__tests__/peers-command.test.ts +241 -0
- package/src/peers/index.ts +9 -0
- package/src/peers/peers-command-module.ts +47 -0
- package/src/peers/peers-command.ts +142 -0
- package/src/permissions/__tests__/permissions-command-module.test.ts +149 -37
- package/src/permissions/permissions-command-module.ts +2 -1
- package/src/permissions/permissions-command.ts +46 -4
- package/src/plan/__tests__/plan-command.test.ts +129 -0
- package/src/plan/index.ts +9 -0
- package/src/plan/plan-command-module.ts +49 -0
- package/src/plan/plan-command.ts +92 -0
- package/src/plugin/__tests__/plugin-command-module.test.ts +46 -40
- package/src/plugin/__tests__/plugin-host-boundary.test.ts +11 -0
- package/src/plugin/plugin-command-module.ts +3 -1
- package/src/plugin/plugin-command.ts +17 -16
- package/src/preset/__tests__/host-supplied-preset-registry.test.ts +98 -0
- package/src/preset/__tests__/preset-command-module.test.ts +102 -56
- package/src/preset/preset-command-module.ts +26 -8
- package/src/preset/preset-command.ts +85 -27
- package/src/provider/__tests__/org-policy.test.ts +4 -5
- package/src/provider/__tests__/provider-command-module.test.ts +11 -14
- package/src/provider/__tests__/provider-startup-host-settings.test.ts +16 -0
- package/src/provider/__tests__/scripted-interaction.ts +7 -5
- package/src/provider/provider-command-execution.ts +4 -4
- package/src/provider/provider-command-module.ts +2 -1
- package/src/provider/provider-command-profile-lifecycle.ts +4 -4
- package/src/provider/provider-command-profile-operations.ts +3 -3
- package/src/provider/provider-command-profile.ts +1 -1
- package/src/provider/provider-command-setup.ts +4 -4
- package/src/provider/provider-startup.ts +62 -35
- package/src/remote-control/__tests__/remote-control-command-module.test.ts +36 -0
- package/src/remote-control/__tests__/remote-control-command.test.ts +216 -0
- package/src/remote-control/index.ts +8 -0
- package/src/remote-control/remote-control-command-module.ts +61 -0
- package/src/remote-control/remote-control-command.ts +138 -0
- package/src/reset/__tests__/reset-command-module.test.ts +5 -5
- package/src/reset/reset-command-module.ts +2 -1
- package/src/reset/reset-command.ts +3 -4
- package/src/rewind/__tests__/rewind-command-module.test.ts +40 -0
- package/src/rewind/rewind-command-module.ts +2 -1
- package/src/rewind/rewind-command.ts +73 -6
- package/src/sandbox/__tests__/sandbox-command.test.ts +80 -0
- package/src/sandbox/index.ts +2 -0
- package/src/sandbox/sandbox-command-module.ts +56 -0
- package/src/sandbox/sandbox-command.ts +94 -0
- package/src/schedule/__tests__/loop-command-functional.test.ts +386 -0
- package/src/schedule/__tests__/loop-command.test.ts +360 -0
- package/src/schedule/__tests__/loop-jitter.test.ts +25 -0
- package/src/schedule/__tests__/monitor-model-permission-functional.test.ts +126 -0
- package/src/schedule/__tests__/schedule-command.test.ts +143 -3
- package/src/schedule/__tests__/schedule-redos.test.ts +81 -0
- package/src/schedule/index.ts +3 -0
- package/src/schedule/loop-command.ts +333 -0
- package/src/schedule/loop-jitter.ts +41 -0
- package/src/schedule/schedule-command-module.ts +92 -14
- package/src/schedule/schedule-command.ts +98 -9
- package/src/schedule/schedule-spec-parser.ts +5 -1
- package/src/session/__tests__/rename-host-persistence.test.ts +72 -0
- package/src/session/__tests__/session-command-module.test.ts +210 -45
- package/src/session/session-command-module.ts +68 -37
- package/src/session/session-command.ts +147 -80
- package/src/settings/settings-command-module.ts +3 -2
- package/src/shell/__tests__/resolve-shell-host.test.ts +11 -0
- package/src/shell/__tests__/shell-command-functional.test.ts +23 -1
- package/src/shell/resolve-shell.ts +2 -2
- package/src/shell/shell-command-module.ts +6 -5
- package/src/shell/shell-command.ts +9 -5
- package/src/skills/__tests__/skills-command-module.test.ts +69 -28
- package/src/skills/index.ts +1 -0
- package/src/skills/skills-command-module.ts +22 -5
- package/src/skills/skills-command.ts +14 -13
- package/src/statusline/__tests__/statusline-command-module.test.ts +5 -4
- package/src/statusline/statusline-command-module.ts +2 -1
- package/src/statusline/statusline-command.ts +4 -4
- package/src/theme/__tests__/theme-command.test.ts +204 -0
- package/src/theme/index.ts +12 -0
- package/src/theme/theme-command-module.ts +211 -0
- package/src/user-local/__tests__/user-local-command.test.ts +145 -110
- package/src/user-local/user-local-command-constants.ts +1 -1
- package/src/user-local/user-local-command-module.ts +6 -5
- package/src/user-local/user-local-command.ts +12 -6
- package/src/user-local/user-local-memory-command.ts +17 -7
- package/src/plugins/default-plugin-command-adapter.ts +0 -164
- package/src/plugins/default-plugin-command-source-loader.ts +0 -31
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
createAssistantMessage,
|
|
4
|
+
createToolMessage,
|
|
5
|
+
createUserMessage,
|
|
6
|
+
messageToHistoryEntry,
|
|
7
|
+
} from '@robota-sdk/agent-core';
|
|
8
|
+
import type { IHistoryEntry, IToolSchema } from '@robota-sdk/agent-core';
|
|
3
9
|
import type {
|
|
4
10
|
ICommandHostContext,
|
|
5
11
|
ICommandSessionRuntime,
|
|
@@ -8,11 +14,16 @@ import type {
|
|
|
8
14
|
IContextReferenceRemoveResult,
|
|
9
15
|
TAutoCompactThresholdSource,
|
|
10
16
|
} from '@robota-sdk/agent-framework';
|
|
11
|
-
import type { IContextReferenceItem } from '@robota-sdk/agent-interface-
|
|
12
|
-
import { SystemCommandExecutor } from '@robota-sdk/agent-framework';
|
|
17
|
+
import type { IContextReferenceItem } from '@robota-sdk/agent-interface-session';
|
|
18
|
+
import { CommandRegistry, SystemCommandExecutor } from '@robota-sdk/agent-framework';
|
|
13
19
|
import { createContextCommandModule } from '../context-command-module.js';
|
|
20
|
+
import type { ICommandHostContextWindow } from '@robota-sdk/agent-framework';
|
|
21
|
+
import {
|
|
22
|
+
createTestCommandHost,
|
|
23
|
+
createTestSessionRuntime,
|
|
24
|
+
} from '@robota-sdk/agent-framework/testing';
|
|
14
25
|
|
|
15
|
-
type TContextWindowState = ReturnType<
|
|
26
|
+
type TContextWindowState = ReturnType<ICommandHostContextWindow['getContextState']>;
|
|
16
27
|
type TPermissionMode = ReturnType<ICommandSessionRuntime['getPermissionMode']>;
|
|
17
28
|
|
|
18
29
|
const CONTEXT_STATE: TContextWindowState = {
|
|
@@ -58,12 +69,9 @@ const SYSTEM_REFERENCE: IContextReferenceItem = {
|
|
|
58
69
|
lastUsedAt: '2026-05-05T00:00:00.000Z',
|
|
59
70
|
};
|
|
60
71
|
|
|
61
|
-
function createRuntime(
|
|
62
|
-
state: { threshold: number | false },
|
|
63
|
-
history: IHistoryEntry[] = [],
|
|
64
|
-
): ICommandSessionRuntime {
|
|
72
|
+
function createRuntime(state: { threshold: number | false }, history: IHistoryEntry[] = []) {
|
|
65
73
|
let mode: TPermissionMode = 'default';
|
|
66
|
-
return {
|
|
74
|
+
return createTestSessionRuntime({
|
|
67
75
|
clearHistory: vi.fn(),
|
|
68
76
|
compact: vi.fn().mockResolvedValue(undefined),
|
|
69
77
|
getContextState: () => CONTEXT_STATE,
|
|
@@ -80,10 +88,12 @@ function createRuntime(
|
|
|
80
88
|
setAutoCompactThreshold: (threshold) => {
|
|
81
89
|
state.threshold = threshold;
|
|
82
90
|
},
|
|
83
|
-
};
|
|
91
|
+
});
|
|
84
92
|
}
|
|
85
93
|
|
|
86
|
-
function createCommandHostContext(threshold: number | false = 0.835):
|
|
94
|
+
function createCommandHostContext(threshold: number | false = 0.835): ReturnType<
|
|
95
|
+
typeof createTestCommandHost
|
|
96
|
+
> & {
|
|
87
97
|
settings: Record<string, number | false>;
|
|
88
98
|
source: TAutoCompactThresholdSource;
|
|
89
99
|
references: IContextReferenceItem[];
|
|
@@ -96,6 +106,63 @@ function createCommandHostContext(threshold: number | false = 0.835): ICommandHo
|
|
|
96
106
|
};
|
|
97
107
|
const runtime = createRuntime(state);
|
|
98
108
|
return {
|
|
109
|
+
...createTestCommandHost({
|
|
110
|
+
overrides: {
|
|
111
|
+
getSession: () => runtime,
|
|
112
|
+
getContextState: () => CONTEXT_STATE,
|
|
113
|
+
getAutoCompactThreshold: () => state.threshold,
|
|
114
|
+
getAutoCompactThresholdSource: () => state.source,
|
|
115
|
+
setAutoCompactThreshold: (nextThreshold, source = 'session') => {
|
|
116
|
+
state.threshold = nextThreshold;
|
|
117
|
+
state.source = source;
|
|
118
|
+
},
|
|
119
|
+
getCommandHostAdapters: () => ({
|
|
120
|
+
settings: {
|
|
121
|
+
read: () => state.settings,
|
|
122
|
+
write: (settings) => {
|
|
123
|
+
const value = settings.autoCompactThreshold;
|
|
124
|
+
state.settings =
|
|
125
|
+
typeof value === 'number' || value === false ? { autoCompactThreshold: value } : {};
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
}),
|
|
129
|
+
compactContext: vi.fn(),
|
|
130
|
+
listContextReferences: () => [...state.references],
|
|
131
|
+
addContextReference: async (path): Promise<IContextReferenceAddResult> => {
|
|
132
|
+
const reference = {
|
|
133
|
+
...MANUAL_REFERENCE,
|
|
134
|
+
relativePath: path,
|
|
135
|
+
sourcePath: `/workspace/${path}`,
|
|
136
|
+
originalReference: `@${path}`,
|
|
137
|
+
};
|
|
138
|
+
state.references = [...state.references, reference];
|
|
139
|
+
return { reference, evicted: [], diagnostics: [] };
|
|
140
|
+
},
|
|
141
|
+
removeContextReference: (path): IContextReferenceRemoveResult => {
|
|
142
|
+
const removed = state.references.find((reference) => reference.relativePath === path);
|
|
143
|
+
state.references = state.references.filter(
|
|
144
|
+
(reference) => reference.relativePath !== path,
|
|
145
|
+
);
|
|
146
|
+
return removed ? { removed } : {};
|
|
147
|
+
},
|
|
148
|
+
clearContextReferences: (): IContextReferenceClearResult => {
|
|
149
|
+
const removed = [...state.references];
|
|
150
|
+
state.references = [];
|
|
151
|
+
return { removed };
|
|
152
|
+
},
|
|
153
|
+
getCwd: () => '/workspace',
|
|
154
|
+
listCommands: () => [],
|
|
155
|
+
listEditCheckpoints: () => [],
|
|
156
|
+
restoreEditCheckpoint: vi.fn(),
|
|
157
|
+
rollbackEditCheckpoint: vi.fn(),
|
|
158
|
+
getUsedMemoryReferences: () => [],
|
|
159
|
+
recordMemoryEvent: vi.fn(),
|
|
160
|
+
listBackgroundTasks: () => [],
|
|
161
|
+
readBackgroundTaskLog: vi.fn().mockResolvedValue({ taskId: 'task_1', lines: [] }),
|
|
162
|
+
cancelBackgroundTask: vi.fn(),
|
|
163
|
+
closeBackgroundTask: vi.fn(),
|
|
164
|
+
},
|
|
165
|
+
}),
|
|
99
166
|
get settings() {
|
|
100
167
|
return state.settings;
|
|
101
168
|
},
|
|
@@ -105,57 +172,6 @@ function createCommandHostContext(threshold: number | false = 0.835): ICommandHo
|
|
|
105
172
|
get references() {
|
|
106
173
|
return state.references;
|
|
107
174
|
},
|
|
108
|
-
getSession: () => runtime,
|
|
109
|
-
getContextState: () => CONTEXT_STATE,
|
|
110
|
-
getAutoCompactThreshold: () => state.threshold,
|
|
111
|
-
getAutoCompactThresholdSource: () => state.source,
|
|
112
|
-
setAutoCompactThreshold: (nextThreshold, source = 'session') => {
|
|
113
|
-
state.threshold = nextThreshold;
|
|
114
|
-
state.source = source;
|
|
115
|
-
},
|
|
116
|
-
getCommandHostAdapters: () => ({
|
|
117
|
-
settings: {
|
|
118
|
-
read: () => state.settings,
|
|
119
|
-
write: (settings) => {
|
|
120
|
-
const value = settings.autoCompactThreshold;
|
|
121
|
-
state.settings =
|
|
122
|
-
typeof value === 'number' || value === false ? { autoCompactThreshold: value } : {};
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
}),
|
|
126
|
-
compactContext: vi.fn(),
|
|
127
|
-
listContextReferences: () => [...state.references],
|
|
128
|
-
addContextReference: async (path): Promise<IContextReferenceAddResult> => {
|
|
129
|
-
const reference = {
|
|
130
|
-
...MANUAL_REFERENCE,
|
|
131
|
-
relativePath: path,
|
|
132
|
-
sourcePath: `/workspace/${path}`,
|
|
133
|
-
originalReference: `@${path}`,
|
|
134
|
-
};
|
|
135
|
-
state.references = [...state.references, reference];
|
|
136
|
-
return { reference, evicted: [], diagnostics: [] };
|
|
137
|
-
},
|
|
138
|
-
removeContextReference: (path): IContextReferenceRemoveResult => {
|
|
139
|
-
const removed = state.references.find((reference) => reference.relativePath === path);
|
|
140
|
-
state.references = state.references.filter((reference) => reference.relativePath !== path);
|
|
141
|
-
return removed ? { removed } : {};
|
|
142
|
-
},
|
|
143
|
-
clearContextReferences: (): IContextReferenceClearResult => {
|
|
144
|
-
const removed = [...state.references];
|
|
145
|
-
state.references = [];
|
|
146
|
-
return { removed };
|
|
147
|
-
},
|
|
148
|
-
getCwd: () => '/workspace',
|
|
149
|
-
listCommands: () => [],
|
|
150
|
-
listEditCheckpoints: () => [],
|
|
151
|
-
restoreEditCheckpoint: vi.fn(),
|
|
152
|
-
rollbackEditCheckpoint: vi.fn(),
|
|
153
|
-
getUsedMemoryReferences: () => [],
|
|
154
|
-
recordMemoryEvent: vi.fn(),
|
|
155
|
-
listBackgroundTasks: () => [],
|
|
156
|
-
readBackgroundTaskLog: vi.fn().mockResolvedValue({ taskId: 'task_1', lines: [] }),
|
|
157
|
-
cancelBackgroundTask: vi.fn(),
|
|
158
|
-
closeBackgroundTask: vi.fn(),
|
|
159
175
|
};
|
|
160
176
|
}
|
|
161
177
|
|
|
@@ -164,6 +180,34 @@ function createExecutor(): SystemCommandExecutor {
|
|
|
164
180
|
}
|
|
165
181
|
|
|
166
182
|
describe('createContextCommandModule', () => {
|
|
183
|
+
it('preserves argument hints and specialized subcommands in both command projections', () => {
|
|
184
|
+
const module = createContextCommandModule();
|
|
185
|
+
const registry = new CommandRegistry();
|
|
186
|
+
registry.addModule(module);
|
|
187
|
+
const executor = new SystemCommandExecutor([...(module.systemCommands ?? [])]);
|
|
188
|
+
const entry = registry.getCommands().find((command) => command.name === 'context');
|
|
189
|
+
const command = executor.getCommand('context');
|
|
190
|
+
expect(entry?.argumentHint).toBe('list | add <path> | remove <path> | clear | auto ...');
|
|
191
|
+
expect(command?.argumentHint).toBe('list | add <path> | remove <path> | clear | auto ...');
|
|
192
|
+
expect(command?.subcommands?.map((subcommand) => subcommand.name)).toEqual([
|
|
193
|
+
'list',
|
|
194
|
+
'add',
|
|
195
|
+
'remove',
|
|
196
|
+
'clear',
|
|
197
|
+
'auto',
|
|
198
|
+
]);
|
|
199
|
+
expect(command?.subcommands).toEqual(registry.getSubcommands('context'));
|
|
200
|
+
expect(command).toMatchObject({
|
|
201
|
+
modelInvocable: true,
|
|
202
|
+
userInvocable: true,
|
|
203
|
+
requiresPermission: false,
|
|
204
|
+
});
|
|
205
|
+
expect(command?.lifecycle ?? 'inline').toBe('inline');
|
|
206
|
+
expect(command?.description).toBe(entry?.description);
|
|
207
|
+
expect(command?.modelDescription).toBe(entry?.modelDescription);
|
|
208
|
+
expect(executor.isModelInvocable('context')).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
|
|
167
211
|
it('provides context metadata and an executable command', () => {
|
|
168
212
|
const module = createContextCommandModule();
|
|
169
213
|
const command = module.systemCommands?.[0];
|
|
@@ -179,7 +223,7 @@ describe('createContextCommandModule', () => {
|
|
|
179
223
|
expect(command).toEqual(
|
|
180
224
|
expect.objectContaining({
|
|
181
225
|
name: 'context',
|
|
182
|
-
modelInvocable:
|
|
226
|
+
modelInvocable: true,
|
|
183
227
|
}),
|
|
184
228
|
);
|
|
185
229
|
});
|
|
@@ -335,6 +379,39 @@ describe('createContextCommandModule', () => {
|
|
|
335
379
|
expect(result?.message).toContain('References: 2 active, 0 observed');
|
|
336
380
|
});
|
|
337
381
|
|
|
382
|
+
it('lists only the tool results still in context, not turns compaction summarized away', async () => {
|
|
383
|
+
const call = (id: string, name: string, arg: string) =>
|
|
384
|
+
createAssistantMessage(null, {
|
|
385
|
+
toolCalls: [
|
|
386
|
+
{ id, type: 'function', function: { name, arguments: JSON.stringify({ path: arg }) } },
|
|
387
|
+
],
|
|
388
|
+
});
|
|
389
|
+
const compacted = [
|
|
390
|
+
createUserMessage('old task'),
|
|
391
|
+
call('old-1', 'Read', 'compacted-away.ts'),
|
|
392
|
+
createToolMessage('old result', { toolCallId: 'old-1' }),
|
|
393
|
+
];
|
|
394
|
+
const live = [
|
|
395
|
+
createUserMessage('new task'),
|
|
396
|
+
call('new-1', 'Read', 'still-live.ts'),
|
|
397
|
+
createToolMessage('new result', { toolCallId: 'new-1' }),
|
|
398
|
+
];
|
|
399
|
+
const runtime = createTestSessionRuntime({
|
|
400
|
+
getContextState: () => CONTEXT_STATE,
|
|
401
|
+
getFullHistory: () => [...compacted, ...live].map(messageToHistoryEntry),
|
|
402
|
+
getHistory: () => live,
|
|
403
|
+
});
|
|
404
|
+
const context = createTestCommandHost({
|
|
405
|
+
overrides: { getSession: () => runtime, getContextState: () => CONTEXT_STATE },
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const result = await createExecutor().execute('context', context, 'list');
|
|
409
|
+
|
|
410
|
+
expect(result?.message).toContain('still-live.ts');
|
|
411
|
+
expect(result?.message).not.toContain('compacted-away.ts');
|
|
412
|
+
expect(result?.message).toContain('Conversation history — 1 turn |');
|
|
413
|
+
});
|
|
414
|
+
|
|
338
415
|
it('shows full context breakdown with empty sections when no references', async () => {
|
|
339
416
|
const context = createCommandHostContext();
|
|
340
417
|
|
|
@@ -349,3 +426,77 @@ describe('createContextCommandModule', () => {
|
|
|
349
426
|
expect(result?.message).not.toContain('~');
|
|
350
427
|
});
|
|
351
428
|
});
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* CLI-1990 TC-12 — `/context` reports what the TOOL SCHEMAS cost.
|
|
432
|
+
*
|
|
433
|
+
* Before this, no surface separated the tool schemas from the system prompt they are billed
|
|
434
|
+
* alongside: `estimateSerializedContextTokens` covered messages only, and the tool line in the
|
|
435
|
+
* breakdown counted tool RESULTS. So the saving deferral exists to produce had no observable at all.
|
|
436
|
+
* These cases pin the figure and, crucially, that it FALLS when tools are deferred — a smaller
|
|
437
|
+
* offered set is a smaller request, which is the whole claim.
|
|
438
|
+
*/
|
|
439
|
+
function toolSchema(name: string): IToolSchema {
|
|
440
|
+
return {
|
|
441
|
+
name,
|
|
442
|
+
description: `the ${name} tool, described at a realistic length for a request`,
|
|
443
|
+
parameters: {
|
|
444
|
+
type: 'object',
|
|
445
|
+
properties: { path: { type: 'string', description: 'what to operate on' } },
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function createHostWithTools(schemas: IToolSchema[]) {
|
|
451
|
+
const runtime = createTestSessionRuntime({
|
|
452
|
+
getContextState: () => CONTEXT_STATE,
|
|
453
|
+
getOfferedToolSchemas: () => schemas,
|
|
454
|
+
});
|
|
455
|
+
return createTestCommandHost({
|
|
456
|
+
overrides: {
|
|
457
|
+
getSession: () => runtime,
|
|
458
|
+
getContextState: () => CONTEXT_STATE,
|
|
459
|
+
listContextReferences: () => [],
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
describe('CLI-1990 TC-12 — /context reports toolSchemaTokens', () => {
|
|
465
|
+
it('reports a figure greater than zero for a session with tools', async () => {
|
|
466
|
+
const context = createHostWithTools([toolSchema('Read'), toolSchema('Write')]);
|
|
467
|
+
|
|
468
|
+
const result = await createExecutor().execute('context', context, 'list');
|
|
469
|
+
|
|
470
|
+
expect(result?.success).toBe(true);
|
|
471
|
+
const tokens = (result?.data as { toolSchemaTokens: number }).toolSchemaTokens;
|
|
472
|
+
expect(tokens).toBeGreaterThan(0);
|
|
473
|
+
expect(result?.message).toContain('Tool schemas (sent every turn)');
|
|
474
|
+
expect(result?.message).toContain('Read');
|
|
475
|
+
expect(result?.message).toContain('Write');
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('reports a SMALLER figure when tools are deferred — the saving, made observable', async () => {
|
|
479
|
+
const all = [toolSchema('Read'), toolSchema('Write'), toolSchema('Grep')];
|
|
480
|
+
const full = await createExecutor().execute('context', createHostWithTools(all), 'list');
|
|
481
|
+
// With deferral engaged the withheld schemas are not in the offered set, so they are not sent.
|
|
482
|
+
const deferred = await createExecutor().execute(
|
|
483
|
+
'context',
|
|
484
|
+
createHostWithTools(all.slice(0, 1)),
|
|
485
|
+
'list',
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
const fullTokens = (full?.data as { toolSchemaTokens: number }).toolSchemaTokens;
|
|
489
|
+
const deferredTokens = (deferred?.data as { toolSchemaTokens: number }).toolSchemaTokens;
|
|
490
|
+
expect(deferredTokens).toBeGreaterThan(0);
|
|
491
|
+
expect(deferredTokens).toBeLessThan(fullTokens);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('reports zero, and no token label, for a session offering no tools', async () => {
|
|
495
|
+
const context = createHostWithTools([]);
|
|
496
|
+
|
|
497
|
+
const result = await createExecutor().execute('context', context, 'list');
|
|
498
|
+
|
|
499
|
+
expect((result?.data as { toolSchemaTokens: number }).toolSchemaTokens).toBe(0);
|
|
500
|
+
expect(result?.message).toContain('Tool schemas (sent every turn):\n (none)');
|
|
501
|
+
});
|
|
502
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leaf formatting helpers for the auto-compact threshold line. Split out of `context-command.ts`
|
|
3
|
+
* so `context-breakdown.ts` can use {@link formatAutoCompactLine} without importing back from
|
|
4
|
+
* `context-command.ts`, which previously created an import cycle between the two.
|
|
5
|
+
*/
|
|
6
|
+
import type { TAutoCompactThreshold, TAutoCompactThresholdSource } from '@robota-sdk/agent-framework';
|
|
7
|
+
|
|
8
|
+
const PERCENT = 100;
|
|
9
|
+
|
|
10
|
+
export function formatThreshold(threshold: TAutoCompactThreshold): string {
|
|
11
|
+
if (threshold === false) {
|
|
12
|
+
return 'disabled';
|
|
13
|
+
}
|
|
14
|
+
return `${Math.round(threshold * PERCENT)}%`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function formatAutoCompactLine(
|
|
18
|
+
threshold: TAutoCompactThreshold,
|
|
19
|
+
source: TAutoCompactThresholdSource,
|
|
20
|
+
): string {
|
|
21
|
+
if (threshold === false) {
|
|
22
|
+
return `Auto compact: disabled (${source})`;
|
|
23
|
+
}
|
|
24
|
+
return `Auto compact: ${formatThreshold(threshold)} (${source})`;
|
|
25
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/context` transcript breakdown: token accounting over the history plus its rendering.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `context-command.ts` when that file passed its size floor. The seam is a real one and
|
|
5
|
+
* the role ports named it: this half only READS (`TContextReadHost`), while the command surface it
|
|
6
|
+
* came from dispatches subcommands and writes thresholds and references.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { CONTEXT_ESTIMATE_CHARS_PER_TOKEN, estimateToolSchemaTokens } from '@robota-sdk/agent-core';
|
|
10
|
+
import {
|
|
11
|
+
listCommandContextReferences,
|
|
12
|
+
readAutoCompactThreshold,
|
|
13
|
+
readAutoCompactThresholdSource,
|
|
14
|
+
readCommandContextState,
|
|
15
|
+
} from '@robota-sdk/agent-framework';
|
|
16
|
+
|
|
17
|
+
import { formatAutoCompactLine } from './auto-compact-format.js';
|
|
18
|
+
|
|
19
|
+
import type { IHistoryEntry, TUniversalMessage } from '@robota-sdk/agent-core';
|
|
20
|
+
import type {
|
|
21
|
+
ICommandHostContextReferences,
|
|
22
|
+
ICommandHostContextWindow,
|
|
23
|
+
ICommandHostSessionAccess,
|
|
24
|
+
} from '@robota-sdk/agent-framework';
|
|
25
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
26
|
+
import type { IContextReferenceItem } from '@robota-sdk/agent-interface-session';
|
|
27
|
+
|
|
28
|
+
/** What the breakdown reads — no adapter role, no writes. */
|
|
29
|
+
type TContextReadHost = ICommandHostContextReferences &
|
|
30
|
+
ICommandHostContextWindow &
|
|
31
|
+
ICommandHostSessionAccess;
|
|
32
|
+
|
|
33
|
+
const TOOL_ARG_MAX_LEN = 60;
|
|
34
|
+
|
|
35
|
+
function estimateTokens(charLength: number): number {
|
|
36
|
+
return Math.ceil(charLength / CONTEXT_ESTIMATE_CHARS_PER_TOKEN);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function formatContextReferenceLine(reference: IContextReferenceItem): string {
|
|
40
|
+
return [
|
|
41
|
+
reference.relativePath,
|
|
42
|
+
`[${reference.loadType}, ${reference.status}]`,
|
|
43
|
+
`~${estimateTokens(reference.byteLength).toLocaleString()} tokens`,
|
|
44
|
+
].join(' ');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ── History analysis ────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
interface IToolResultSummary {
|
|
50
|
+
toolName: string;
|
|
51
|
+
displayArg: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface IMessageTokensByRole {
|
|
55
|
+
systemTokens: number;
|
|
56
|
+
userTokens: number;
|
|
57
|
+
userCount: number;
|
|
58
|
+
assistantTokens: number;
|
|
59
|
+
assistantCount: number;
|
|
60
|
+
toolTokens: number;
|
|
61
|
+
toolCallCount: number;
|
|
62
|
+
totalTokens: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface IToolDisplayList {
|
|
66
|
+
turnCount: number;
|
|
67
|
+
toolResults: IToolResultSummary[];
|
|
68
|
+
totalToolCallCount: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parseToolCallArgs(argsJson: string): Record<string, unknown> | null {
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(argsJson) as Record<string, unknown>;
|
|
74
|
+
} catch {
|
|
75
|
+
// allow-fallback: tool call arguments JSON may be malformed; display-only degradation, not a terminal path
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function extractFirstToolArg(argsJson: string): string {
|
|
81
|
+
const parsed = parseToolCallArgs(argsJson);
|
|
82
|
+
if (parsed === null) return '';
|
|
83
|
+
const first = Object.values(parsed)[0];
|
|
84
|
+
const raw = typeof first === 'string' ? first : JSON.stringify(first);
|
|
85
|
+
return raw.length > TOOL_ARG_MAX_LEN ? `${raw.slice(0, TOOL_ARG_MAX_LEN)}…` : raw;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function computeMessageTokensByRole(rawMessages: TUniversalMessage[]): IMessageTokensByRole {
|
|
89
|
+
let systemTokens = 0;
|
|
90
|
+
let userTokens = 0;
|
|
91
|
+
let userCount = 0;
|
|
92
|
+
let assistantTokens = 0;
|
|
93
|
+
let assistantCount = 0;
|
|
94
|
+
let toolTokens = 0;
|
|
95
|
+
let toolCallCount = 0;
|
|
96
|
+
|
|
97
|
+
for (const msg of rawMessages) {
|
|
98
|
+
const t = Math.ceil(JSON.stringify(msg).length / CONTEXT_ESTIMATE_CHARS_PER_TOKEN);
|
|
99
|
+
if (msg.role === 'system') {
|
|
100
|
+
systemTokens += t;
|
|
101
|
+
} else if (msg.role === 'user') {
|
|
102
|
+
userTokens += t;
|
|
103
|
+
userCount++;
|
|
104
|
+
} else if (msg.role === 'assistant') {
|
|
105
|
+
assistantTokens += t;
|
|
106
|
+
assistantCount++;
|
|
107
|
+
} else if (msg.role === 'tool') {
|
|
108
|
+
toolTokens += t;
|
|
109
|
+
toolCallCount++;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
systemTokens,
|
|
115
|
+
userTokens,
|
|
116
|
+
userCount,
|
|
117
|
+
assistantTokens,
|
|
118
|
+
assistantCount,
|
|
119
|
+
toolTokens,
|
|
120
|
+
toolCallCount,
|
|
121
|
+
totalTokens: systemTokens + userTokens + assistantTokens + toolTokens,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** One conversation item as the display reads it: its role and its message payload. */
|
|
126
|
+
interface IChatItem {
|
|
127
|
+
readonly type: string;
|
|
128
|
+
readonly data: unknown;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Every chat entry of the full history, including turns compaction has since summarized away. */
|
|
132
|
+
export function buildToolDisplayList(history: IHistoryEntry[]): IToolDisplayList {
|
|
133
|
+
return buildDisplayList(
|
|
134
|
+
history
|
|
135
|
+
.filter((entry) => entry.category === 'chat')
|
|
136
|
+
.map((entry) => ({ type: entry.type, data: entry.data })),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Only what the model still has in context — what `/context list` accounts for. */
|
|
141
|
+
export function buildLiveToolDisplayList(messages: readonly TUniversalMessage[]): IToolDisplayList {
|
|
142
|
+
return buildDisplayList(messages.map((message) => ({ type: message.role, data: message })));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function buildDisplayList(items: readonly IChatItem[]): IToolDisplayList {
|
|
146
|
+
type TToolCallEntry = { name: string; firstArg: string };
|
|
147
|
+
const toolCallMap = new Map<string, TToolCallEntry>();
|
|
148
|
+
|
|
149
|
+
for (const entry of items) {
|
|
150
|
+
if (entry.type !== 'assistant') continue;
|
|
151
|
+
const data = entry.data as {
|
|
152
|
+
toolCalls?: Array<{ id: string; function: { name: string; arguments: string } }>;
|
|
153
|
+
};
|
|
154
|
+
for (const tc of data.toolCalls ?? []) {
|
|
155
|
+
toolCallMap.set(tc.id, {
|
|
156
|
+
name: tc.function.name,
|
|
157
|
+
firstArg: extractFirstToolArg(tc.function.arguments),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let turnCount = 0;
|
|
163
|
+
let totalToolCallCount = 0;
|
|
164
|
+
const toolDisplayMap = new Map<string, IToolResultSummary>();
|
|
165
|
+
|
|
166
|
+
for (const entry of items) {
|
|
167
|
+
if (entry.type === 'user') {
|
|
168
|
+
turnCount++;
|
|
169
|
+
} else if (entry.type === 'tool') {
|
|
170
|
+
const data = entry.data as { toolCallId?: string };
|
|
171
|
+
const tc = toolCallMap.get(data.toolCallId ?? '');
|
|
172
|
+
const toolName = tc?.name ?? 'tool';
|
|
173
|
+
const displayArg = tc?.firstArg ?? '';
|
|
174
|
+
totalToolCallCount++;
|
|
175
|
+
toolDisplayMap.set(`${toolName}:${displayArg}`, { toolName, displayArg });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
turnCount,
|
|
181
|
+
toolResults: [...toolDisplayMap.values()],
|
|
182
|
+
totalToolCallCount,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ── Full context breakdown (for /context list) ──────────────────────────────
|
|
187
|
+
|
|
188
|
+
function formatSection(title: string, tokens: number, lines: string[]): string {
|
|
189
|
+
const tokenLabel = tokens > 0 ? ` — ~${tokens.toLocaleString()} tokens` : '';
|
|
190
|
+
const header = `${title}${tokenLabel}:`;
|
|
191
|
+
return lines.length === 0
|
|
192
|
+
? `${header}\n (none)`
|
|
193
|
+
: [header, ...lines.map((l) => ` ${l}`)].join('\n');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function formatFullContextBreakdown(context: TContextReadHost): ICommandResult {
|
|
197
|
+
const state = readCommandContextState(context);
|
|
198
|
+
const autoCompactThreshold = readAutoCompactThreshold(context);
|
|
199
|
+
const autoCompactThresholdSource = readAutoCompactThresholdSource(context);
|
|
200
|
+
const rawMessages = context.getSession().getHistory();
|
|
201
|
+
const msgTokens = computeMessageTokensByRole(rawMessages);
|
|
202
|
+
// CLI-1990: what the TOOL SCHEMAS cost on every request — the offered set, so a deferred tool that
|
|
203
|
+
// has not been loaded is not counted, because it is not sent. Until now nothing separated this
|
|
204
|
+
// from the system prompt it is billed alongside, so the saving deferral produces had no observable.
|
|
205
|
+
const offeredToolSchemas = context.getSession().getOfferedToolSchemas();
|
|
206
|
+
const toolSchemaTokens = estimateToolSchemaTokens(offeredToolSchemas);
|
|
207
|
+
// The live messages, like the token counts beside it: a turn compaction summarized away is no
|
|
208
|
+
// longer in context, so listing its tool results would describe a context the model does not have.
|
|
209
|
+
const display = buildLiveToolDisplayList(rawMessages);
|
|
210
|
+
const references = listCommandContextReferences(context);
|
|
211
|
+
|
|
212
|
+
const systemRefs = references.filter((r) => r.loadType === 'system');
|
|
213
|
+
const manualRefs = references.filter((r) => r.loadType === 'manual');
|
|
214
|
+
const promptRefs = references.filter((r) => r.loadType === 'prompt-reference');
|
|
215
|
+
|
|
216
|
+
const systemRefTokens = systemRefs.reduce((s, r) => s + estimateTokens(r.byteLength), 0);
|
|
217
|
+
const manualTokens = manualRefs.reduce((s, r) => s + estimateTokens(r.byteLength), 0);
|
|
218
|
+
const promptRefTokens = promptRefs.reduce((s, r) => s + estimateTokens(r.byteLength), 0);
|
|
219
|
+
|
|
220
|
+
const convTurnLabel =
|
|
221
|
+
display.turnCount === 0
|
|
222
|
+
? 'Conversation history — 0 turns'
|
|
223
|
+
: `Conversation history — ${display.turnCount} turn${display.turnCount !== 1 ? 's' : ''} | ~${msgTokens.totalTokens.toLocaleString()} tokens`;
|
|
224
|
+
|
|
225
|
+
const toolResultLines = display.toolResults.map(
|
|
226
|
+
(t) => `${t.toolName}${t.displayArg ? `: ${t.displayArg}` : ''}`,
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
const uniqueToolCount = display.toolResults.length;
|
|
230
|
+
const totalToolCallCount = display.totalToolCallCount;
|
|
231
|
+
const toolResultsLabel =
|
|
232
|
+
totalToolCallCount === 0
|
|
233
|
+
? 'Tool results (0): (none)'
|
|
234
|
+
: uniqueToolCount < totalToolCallCount
|
|
235
|
+
? `Tool results (${uniqueToolCount} unique / ${totalToolCallCount} calls, ~${msgTokens.toolTokens.toLocaleString()} tokens):`
|
|
236
|
+
: `Tool results (${uniqueToolCount}, ~${msgTokens.toolTokens.toLocaleString()} tokens):`;
|
|
237
|
+
|
|
238
|
+
const conversationLines: string[] =
|
|
239
|
+
display.turnCount === 0
|
|
240
|
+
? []
|
|
241
|
+
: [
|
|
242
|
+
`User (${msgTokens.userCount}): ~${msgTokens.userTokens.toLocaleString()} tokens`,
|
|
243
|
+
`Assistant (${msgTokens.assistantCount}): ~${msgTokens.assistantTokens.toLocaleString()} tokens`,
|
|
244
|
+
...(msgTokens.systemTokens > 0
|
|
245
|
+
? [`System messages: ~${msgTokens.systemTokens.toLocaleString()} tokens`]
|
|
246
|
+
: []),
|
|
247
|
+
totalToolCallCount > 0
|
|
248
|
+
? [toolResultsLabel, ...toolResultLines.map((l) => ` ${l}`)].join('\n')
|
|
249
|
+
: toolResultsLabel,
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
const convSection =
|
|
253
|
+
display.turnCount === 0
|
|
254
|
+
? `${convTurnLabel}:\n (none)`
|
|
255
|
+
: [`${convTurnLabel}:`, ...conversationLines.map((l) => ` ${l}`)].join('\n');
|
|
256
|
+
|
|
257
|
+
const message = [
|
|
258
|
+
`Context: ${state.usedTokens.toLocaleString()} / ${state.maxTokens.toLocaleString()} tokens (${Math.round(state.usedPercentage)}%)`,
|
|
259
|
+
formatAutoCompactLine(autoCompactThreshold, autoCompactThresholdSource),
|
|
260
|
+
'',
|
|
261
|
+
formatSection(
|
|
262
|
+
'System prompt (active every turn)',
|
|
263
|
+
systemRefTokens,
|
|
264
|
+
systemRefs.map(formatContextReferenceLine),
|
|
265
|
+
),
|
|
266
|
+
'',
|
|
267
|
+
formatSection(
|
|
268
|
+
'Tool schemas (sent every turn)',
|
|
269
|
+
toolSchemaTokens,
|
|
270
|
+
offeredToolSchemas.map((schema) => schema.name),
|
|
271
|
+
),
|
|
272
|
+
'',
|
|
273
|
+
convSection,
|
|
274
|
+
'',
|
|
275
|
+
formatSection('Manually added', manualTokens, manualRefs.map(formatContextReferenceLine)),
|
|
276
|
+
'',
|
|
277
|
+
formatSection(
|
|
278
|
+
'Prompt references (@-syntax)',
|
|
279
|
+
promptRefTokens,
|
|
280
|
+
promptRefs.map(formatContextReferenceLine),
|
|
281
|
+
),
|
|
282
|
+
].join('\n');
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
success: true,
|
|
286
|
+
message,
|
|
287
|
+
data: {
|
|
288
|
+
references,
|
|
289
|
+
history: { turnCount: display.turnCount, toolResults: display.toolResults },
|
|
290
|
+
// CLI-1990: machine-readable beside the rendered line, so a caller can compare the figure
|
|
291
|
+
// across configurations rather than parsing the message back out.
|
|
292
|
+
toolSchemaTokens,
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|