@robota-sdk/agent-command 3.0.0-beta.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1351 -0
- package/LICENSE +661 -0
- package/README.md +100 -0
- package/dist/node/index.cjs +121 -0
- package/dist/node/index.d.cts +1053 -0
- package/dist/node/index.d.cts.map +1 -0
- package/dist/node/index.d.ts +1053 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +122 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +83 -0
- 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 +597 -0
- package/src/agent/agent-command-module.ts +89 -0
- package/src/agent/agent-command-parser.ts +180 -0
- package/src/agent/agent-command.ts +255 -0
- package/src/agent/index.ts +7 -0
- package/src/background/__tests__/background-command-module.test.ts +261 -0
- package/src/background/background-command-module.ts +52 -0
- package/src/background/background-command.ts +65 -0
- package/src/background/index.ts +6 -0
- package/src/command-module-utils.test.ts +37 -0
- package/src/command-module-utils.ts +35 -0
- package/src/compact/__tests__/compact-command-module.test.ts +181 -0
- package/src/compact/compact-command-module.ts +59 -0
- package/src/compact/compact-command.ts +33 -0
- package/src/compact/index.ts +6 -0
- package/src/context/__tests__/context-command-module.test.ts +502 -0
- package/src/context/auto-compact-format.ts +25 -0
- package/src/context/context-breakdown.ts +295 -0
- package/src/context/context-command-module.ts +82 -0
- package/src/context/context-command.ts +291 -0
- package/src/context/index.ts +6 -0
- package/src/default/__tests__/__snapshots__/model-exposure.test.ts.snap +121 -0
- package/src/default/__tests__/default-command-modules.test.ts +192 -0
- package/src/default/__tests__/model-exposure.test.ts +269 -0
- package/src/default/__tests__/org-policy-forwarding.test.ts +106 -0
- package/src/default/__tests__/provider-environment-forwarding.test.ts +98 -0
- package/src/default/default-command-modules.ts +247 -0
- package/src/default/index.ts +5 -0
- package/src/devices/__tests__/devices-command-module.test.ts +299 -0
- package/src/devices/devices-command-module.ts +308 -0
- package/src/devices/devices-command-port.ts +143 -0
- package/src/devices/index.ts +14 -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 +146 -0
- package/src/doctor/__tests__/doctor-redaction.test.ts +29 -0
- package/src/doctor/__tests__/doctor-runner.test.ts +484 -0
- package/src/doctor/doctor-command-module.ts +132 -0
- package/src/doctor/doctor-extensions-probe.ts +323 -0
- package/src/doctor/doctor-node-deps.ts +88 -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 +124 -0
- package/src/editor/editor-command-module.ts +53 -0
- package/src/editor/editor-command.ts +58 -0
- package/src/editor/index.ts +7 -0
- package/src/editor/resolve-editor.ts +21 -0
- package/src/effort/effort-command-module.ts +51 -0
- package/src/effort/effort-command.test.ts +203 -0
- package/src/effort/effort-command.ts +136 -0
- package/src/effort/index.ts +6 -0
- package/src/events/__tests__/events-command.test.ts +73 -0
- package/src/events/events-command-module.ts +49 -0
- package/src/events/events-command.ts +69 -0
- package/src/events/index.ts +2 -0
- package/src/exit/__tests__/exit-command-module.test.ts +61 -0
- package/src/exit/exit-command-module.ts +47 -0
- package/src/exit/exit-command.ts +26 -0
- package/src/exit/index.ts +6 -0
- 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 +35 -0
- package/src/git/__tests__/git-command-module.test.ts +111 -0
- package/src/git/__tests__/git-commit.test.ts +185 -0
- package/src/git/git-command-module.ts +138 -0
- package/src/git/git-commit.ts +254 -0
- package/src/git/index.ts +20 -0
- package/src/goal/__tests__/goal-command.test.ts +80 -0
- package/src/goal/goal-command-module.ts +49 -0
- package/src/goal/goal-command.ts +69 -0
- package/src/goal/index.ts +6 -0
- package/src/handoff/__tests__/handoff-command-module.test.ts +25 -0
- package/src/handoff/__tests__/handoff-command.test.ts +218 -0
- package/src/handoff/handoff-command-module.ts +49 -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 +134 -0
- package/src/help/__tests__/help-command.test.ts +130 -0
- package/src/help/help-command-module.ts +47 -0
- package/src/help/help-command.ts +11 -0
- package/src/help/index.ts +6 -0
- package/src/index.ts +163 -0
- 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 +88 -0
- package/src/language/__tests__/language-command-module.test.ts +126 -0
- package/src/language/index.ts +6 -0
- package/src/language/language-command-module.ts +55 -0
- package/src/language/language-command.ts +52 -0
- package/src/mcp-activation/__tests__/mcp-activation-command.test.ts +895 -0
- package/src/mcp-activation/__tests__/mcp-model-view.test.ts +242 -0
- package/src/mcp-activation/__tests__/mcp-skill-command.test.ts +141 -0
- package/src/mcp-activation/index.ts +12 -0
- package/src/mcp-activation/mcp-activation-command-module.ts +123 -0
- package/src/mcp-activation/mcp-activation-command.ts +662 -0
- package/src/mcp-activation/mcp-model-notice.ts +99 -0
- package/src/mcp-activation/mcp-model-status.ts +104 -0
- package/src/memory/__tests__/memory-command-module.test.ts +476 -0
- package/src/memory/__tests__/memory-command-unavailable.test.ts +63 -0
- package/src/memory/index.ts +6 -0
- package/src/memory/memory-command-module.ts +77 -0
- package/src/memory/memory-command.ts +326 -0
- package/src/mode/__tests__/mode-command-module.test.ts +230 -0
- package/src/mode/index.ts +6 -0
- package/src/mode/mode-command-module.ts +55 -0
- package/src/mode/mode-command.ts +67 -0
- package/src/model/__tests__/model-command-module.test.ts +80 -0
- package/src/model/__tests__/model-command.test.ts +265 -0
- package/src/model/index.ts +6 -0
- package/src/model/model-command-module.ts +56 -0
- package/src/model/model-command.ts +249 -0
- 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-module.test.ts +24 -0
- package/src/peers/__tests__/peers-command.test.ts +399 -0
- package/src/peers/index.ts +9 -0
- package/src/peers/peers-command-module.ts +49 -0
- package/src/peers/peers-command.ts +220 -0
- package/src/permissions/__tests__/permissions-command-module.test.ts +276 -0
- package/src/permissions/index.ts +6 -0
- package/src/permissions/permissions-command-module.ts +55 -0
- package/src/permissions/permissions-command.ts +89 -0
- 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-active-turn.test.ts +100 -0
- package/src/plugin/__tests__/plugin-command-module.test.ts +300 -0
- package/src/plugin/__tests__/plugin-host-boundary.test.ts +11 -0
- package/src/plugin/index.ts +7 -0
- package/src/plugin/plugin-command-module.ts +85 -0
- package/src/plugin/plugin-command.ts +271 -0
- package/src/preset/__tests__/host-supplied-preset-registry.test.ts +98 -0
- package/src/preset/__tests__/preset-command-module.test.ts +220 -0
- package/src/preset/index.ts +6 -0
- package/src/preset/preset-command-module.ts +80 -0
- package/src/preset/preset-command.ts +148 -0
- package/src/provider/__tests__/optional-provider-credentials.test.ts +130 -0
- package/src/provider/__tests__/org-policy.test.ts +174 -0
- package/src/provider/__tests__/provider-command-module.test.ts +884 -0
- package/src/provider/__tests__/provider-connection-setup.test.ts +567 -0
- package/src/provider/__tests__/provider-reference-switch.test.ts +113 -0
- package/src/provider/__tests__/provider-setup-flow.test.ts +244 -0
- package/src/provider/__tests__/provider-startup-host-settings.test.ts +16 -0
- package/src/provider/__tests__/scripted-interaction.ts +40 -0
- package/src/provider/index.ts +32 -0
- package/src/provider/provider-command-execution.ts +235 -0
- package/src/provider/provider-command-module.ts +82 -0
- package/src/provider/provider-command-profile-lifecycle.ts +239 -0
- package/src/provider/provider-command-profile-operations.ts +218 -0
- package/src/provider/provider-command-profile.ts +116 -0
- package/src/provider/provider-command-setup.ts +79 -0
- package/src/provider/provider-connection-setup.ts +287 -0
- package/src/provider/provider-connection-validation.ts +38 -0
- package/src/provider/provider-onboarding.ts +73 -0
- package/src/provider/provider-prompt-interaction.ts +28 -0
- package/src/provider/provider-setup-ask.ts +67 -0
- package/src/provider/provider-setup-flow.ts +326 -0
- package/src/provider/provider-startup.ts +225 -0
- 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 +63 -0
- package/src/reset/index.ts +2 -0
- package/src/reset/reset-command-module.ts +47 -0
- package/src/reset/reset-command.ts +10 -0
- package/src/rewind/__tests__/rewind-command-module.test.ts +287 -0
- package/src/rewind/index.ts +2 -0
- package/src/rewind/rewind-command-module.ts +56 -0
- package/src/rewind/rewind-command.ts +279 -0
- 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 +399 -0
- package/src/schedule/__tests__/loop-command.test.ts +594 -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 +247 -0
- package/src/schedule/__tests__/schedule-redos.test.ts +114 -0
- package/src/schedule/index.ts +12 -0
- package/src/schedule/loop-command.ts +427 -0
- package/src/schedule/loop-jitter.ts +41 -0
- package/src/schedule/schedule-command-module.ts +184 -0
- package/src/schedule/schedule-command.ts +161 -0
- package/src/schedule/schedule-spec-parser.ts +70 -0
- package/src/session/__tests__/model-pricing.test.ts +66 -0
- package/src/session/__tests__/rename-host-persistence.test.ts +72 -0
- package/src/session/__tests__/session-command-module.test.ts +589 -0
- package/src/session/index.ts +17 -0
- package/src/session/model-pricing.ts +26 -0
- package/src/session/session-command-module.ts +197 -0
- package/src/session/session-command.ts +272 -0
- package/src/settings/index.ts +7 -0
- package/src/settings/settings-command-module.ts +47 -0
- package/src/shell/__tests__/resolve-shell-host.test.ts +11 -0
- package/src/shell/__tests__/shell-command-functional.test.ts +118 -0
- package/src/shell/index.ts +8 -0
- package/src/shell/resolve-shell.ts +25 -0
- package/src/shell/shell-command-module.ts +53 -0
- package/src/shell/shell-command.ts +48 -0
- package/src/shell/spawn-inherited.ts +32 -0
- package/src/skills/__tests__/skill-read-command.test.ts +64 -0
- package/src/skills/__tests__/skills-command-module.test.ts +200 -0
- package/src/skills/index.ts +7 -0
- package/src/skills/skill-read-command.ts +60 -0
- package/src/skills/skills-command-module.ts +76 -0
- package/src/skills/skills-command.ts +108 -0
- package/src/statusline/__tests__/statusline-command-module.test.ts +96 -0
- package/src/statusline/index.ts +6 -0
- package/src/statusline/statusline-command-module.ts +55 -0
- package/src/statusline/statusline-command.ts +80 -0
- package/src/terminal-client/__tests__/terminal-client-commands.test.ts +216 -0
- package/src/terminal-client/index.ts +5 -0
- package/src/terminal-client/terminal-client-commands.ts +109 -0
- package/src/theme/__tests__/theme-command.test.ts +204 -0
- package/src/theme/index.ts +12 -0
- package/src/theme/theme-command-module.ts +221 -0
- package/src/user-local/__tests__/user-local-command.test.ts +180 -0
- package/src/user-local/index.ts +13 -0
- package/src/user-local/user-local-command-constants.ts +5 -0
- package/src/user-local/user-local-command-module.ts +65 -0
- package/src/user-local/user-local-command.ts +214 -0
- package/src/user-local/user-local-memory-command.ts +160 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
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';
|
|
9
|
+
import type {
|
|
10
|
+
ICommandHostContext,
|
|
11
|
+
ICommandSessionRuntime,
|
|
12
|
+
IContextReferenceAddResult,
|
|
13
|
+
IContextReferenceClearResult,
|
|
14
|
+
IContextReferenceRemoveResult,
|
|
15
|
+
TAutoCompactThresholdSource,
|
|
16
|
+
} from '@robota-sdk/agent-framework';
|
|
17
|
+
import type { IContextReferenceItem } from '@robota-sdk/agent-interface-session';
|
|
18
|
+
import { CommandRegistry, SystemCommandExecutor } from '@robota-sdk/agent-framework';
|
|
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';
|
|
25
|
+
|
|
26
|
+
type TContextWindowState = ReturnType<ICommandHostContextWindow['getContextState']>;
|
|
27
|
+
type TPermissionMode = ReturnType<ICommandSessionRuntime['getPermissionMode']>;
|
|
28
|
+
|
|
29
|
+
const CONTEXT_STATE: TContextWindowState = {
|
|
30
|
+
usedTokens: 5000,
|
|
31
|
+
maxTokens: 200000,
|
|
32
|
+
usedPercentage: 2.5,
|
|
33
|
+
remainingPercentage: 97.5,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const MANUAL_REFERENCE: IContextReferenceItem = {
|
|
37
|
+
id: 'manual:AGENTS.md',
|
|
38
|
+
sourcePath: '/workspace/AGENTS.md',
|
|
39
|
+
relativePath: 'AGENTS.md',
|
|
40
|
+
originalReference: '@AGENTS.md',
|
|
41
|
+
loadType: 'manual',
|
|
42
|
+
status: 'active',
|
|
43
|
+
byteLength: 42,
|
|
44
|
+
loadedAt: '2026-05-05T00:00:00.000Z',
|
|
45
|
+
lastUsedAt: '2026-05-05T00:00:00.000Z',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const PROMPT_REFERENCE: IContextReferenceItem = {
|
|
49
|
+
id: 'prompt-reference:packages/agent-sdk/docs/SPEC.md',
|
|
50
|
+
sourcePath: '/workspace/packages/agent-sdk/docs/SPEC.md',
|
|
51
|
+
relativePath: 'packages/agent-sdk/docs/SPEC.md',
|
|
52
|
+
originalReference: '@packages/agent-sdk/docs/SPEC.md',
|
|
53
|
+
loadType: 'prompt-reference',
|
|
54
|
+
status: 'observed',
|
|
55
|
+
byteLength: 200,
|
|
56
|
+
loadedAt: '2026-05-05T00:00:01.000Z',
|
|
57
|
+
lastUsedAt: '2026-05-05T00:00:01.000Z',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const SYSTEM_REFERENCE: IContextReferenceItem = {
|
|
61
|
+
id: 'system:AGENTS.md',
|
|
62
|
+
sourcePath: '/workspace/AGENTS.md',
|
|
63
|
+
relativePath: 'AGENTS.md',
|
|
64
|
+
originalReference: 'AGENTS.md',
|
|
65
|
+
loadType: 'system',
|
|
66
|
+
status: 'active',
|
|
67
|
+
byteLength: 1024,
|
|
68
|
+
loadedAt: '2026-05-05T00:00:00.000Z',
|
|
69
|
+
lastUsedAt: '2026-05-05T00:00:00.000Z',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
function createRuntime(state: { threshold: number | false }, history: IHistoryEntry[] = []) {
|
|
73
|
+
let mode: TPermissionMode = 'default';
|
|
74
|
+
return createTestSessionRuntime({
|
|
75
|
+
clearHistory: vi.fn(),
|
|
76
|
+
compact: vi.fn().mockResolvedValue(undefined),
|
|
77
|
+
getContextState: () => CONTEXT_STATE,
|
|
78
|
+
getPermissionMode: () => mode,
|
|
79
|
+
setPermissionMode: (nextMode) => {
|
|
80
|
+
mode = nextMode;
|
|
81
|
+
},
|
|
82
|
+
getSessionId: () => 'session_1',
|
|
83
|
+
getMessageCount: () => 1,
|
|
84
|
+
getSessionAllowedTools: () => [],
|
|
85
|
+
getAutoCompactThreshold: () => state.threshold,
|
|
86
|
+
getFullHistory: () => history,
|
|
87
|
+
getHistory: () => [],
|
|
88
|
+
setAutoCompactThreshold: (threshold) => {
|
|
89
|
+
state.threshold = threshold;
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function createCommandHostContext(threshold: number | false = 0.835): ReturnType<
|
|
95
|
+
typeof createTestCommandHost
|
|
96
|
+
> & {
|
|
97
|
+
settings: Record<string, number | false>;
|
|
98
|
+
source: TAutoCompactThresholdSource;
|
|
99
|
+
references: IContextReferenceItem[];
|
|
100
|
+
} {
|
|
101
|
+
const state = {
|
|
102
|
+
threshold,
|
|
103
|
+
source: 'settings' as TAutoCompactThresholdSource,
|
|
104
|
+
settings: {} as Record<string, number | false>,
|
|
105
|
+
references: [] as IContextReferenceItem[],
|
|
106
|
+
};
|
|
107
|
+
const runtime = createRuntime(state);
|
|
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
|
+
}),
|
|
166
|
+
get settings() {
|
|
167
|
+
return state.settings;
|
|
168
|
+
},
|
|
169
|
+
get source() {
|
|
170
|
+
return state.source;
|
|
171
|
+
},
|
|
172
|
+
get references() {
|
|
173
|
+
return state.references;
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function createExecutor(): SystemCommandExecutor {
|
|
179
|
+
return new SystemCommandExecutor([...(createContextCommandModule().systemCommands ?? [])]);
|
|
180
|
+
}
|
|
181
|
+
|
|
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
|
+
|
|
211
|
+
it('provides context metadata and an executable command', () => {
|
|
212
|
+
const module = createContextCommandModule();
|
|
213
|
+
const command = module.systemCommands?.[0];
|
|
214
|
+
const entry = module.commandSources?.[0]?.getCommands()[0];
|
|
215
|
+
|
|
216
|
+
expect(module.name).toBe('agent-command-context');
|
|
217
|
+
expect(entry).toEqual(
|
|
218
|
+
expect.objectContaining({
|
|
219
|
+
name: 'context',
|
|
220
|
+
description: 'Context window info, reference inventory, and auto-compact controls',
|
|
221
|
+
}),
|
|
222
|
+
);
|
|
223
|
+
expect(command).toEqual(
|
|
224
|
+
expect.objectContaining({
|
|
225
|
+
name: 'context',
|
|
226
|
+
modelInvocable: true,
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('formats context usage and enabled auto compact policy', async () => {
|
|
232
|
+
const result = await createExecutor().execute('context', createCommandHostContext(0.75), '');
|
|
233
|
+
|
|
234
|
+
expect(result?.success).toBe(true);
|
|
235
|
+
expect(result?.message).toContain('Context: 5,000 / 200,000 tokens (3%)');
|
|
236
|
+
expect(result?.message).toContain('Auto compact: 75% (settings)');
|
|
237
|
+
expect(result?.message).toContain('References: 0 active, 0 observed');
|
|
238
|
+
expect(result?.data).toEqual({
|
|
239
|
+
usedTokens: 5000,
|
|
240
|
+
maxTokens: 200000,
|
|
241
|
+
percentage: 2.5,
|
|
242
|
+
autoCompactThreshold: 0.75,
|
|
243
|
+
autoCompactThresholdSource: 'settings',
|
|
244
|
+
references: [],
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('lists active and observed context references', async () => {
|
|
249
|
+
const context = createCommandHostContext();
|
|
250
|
+
context.references.push(MANUAL_REFERENCE, PROMPT_REFERENCE);
|
|
251
|
+
|
|
252
|
+
const result = await createExecutor().execute('context', context, 'list');
|
|
253
|
+
|
|
254
|
+
expect(result?.success).toBe(true);
|
|
255
|
+
expect(result?.message).toContain('AGENTS.md [manual, active] ~11 tokens');
|
|
256
|
+
expect(result?.message).toContain(
|
|
257
|
+
'packages/agent-sdk/docs/SPEC.md [prompt-reference, observed] ~50 tokens',
|
|
258
|
+
);
|
|
259
|
+
expect(result?.data?.references).toEqual([MANUAL_REFERENCE, PROMPT_REFERENCE]);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('adds manual context references through the SDK command API', async () => {
|
|
263
|
+
const context = createCommandHostContext();
|
|
264
|
+
|
|
265
|
+
const result = await createExecutor().execute('context', context, 'add AGENTS.md');
|
|
266
|
+
|
|
267
|
+
expect(result?.success).toBe(true);
|
|
268
|
+
expect(result?.message).toContain(
|
|
269
|
+
'Context reference added: AGENTS.md [manual, active] ~11 tokens.',
|
|
270
|
+
);
|
|
271
|
+
expect(context.references[0]?.relativePath).toBe('AGENTS.md');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('removes context references through the SDK command API', async () => {
|
|
275
|
+
const context = createCommandHostContext();
|
|
276
|
+
context.references.push(MANUAL_REFERENCE);
|
|
277
|
+
|
|
278
|
+
const result = await createExecutor().execute('context', context, 'remove AGENTS.md');
|
|
279
|
+
|
|
280
|
+
expect(result?.success).toBe(true);
|
|
281
|
+
expect(result?.message).toBe(
|
|
282
|
+
'Context reference removed: AGENTS.md [manual, active] ~11 tokens.',
|
|
283
|
+
);
|
|
284
|
+
expect(context.references).toEqual([]);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('clears context references through the SDK command API', async () => {
|
|
288
|
+
const context = createCommandHostContext();
|
|
289
|
+
context.references.push(MANUAL_REFERENCE, PROMPT_REFERENCE);
|
|
290
|
+
|
|
291
|
+
const result = await createExecutor().execute('context', context, 'clear');
|
|
292
|
+
|
|
293
|
+
expect(result?.success).toBe(true);
|
|
294
|
+
expect(result?.message).toBe('Context references cleared: 2 removed.');
|
|
295
|
+
expect(context.references).toEqual([]);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('formats disabled auto compact policy', async () => {
|
|
299
|
+
const result = await createExecutor().execute('context', createCommandHostContext(false), '');
|
|
300
|
+
|
|
301
|
+
expect(result?.success).toBe(true);
|
|
302
|
+
expect(result?.message).toContain('Auto compact: disabled');
|
|
303
|
+
expect(result?.data?.autoCompactThreshold).toBe(false);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('sets auto compact threshold and persists the setting', async () => {
|
|
307
|
+
const context = createCommandHostContext();
|
|
308
|
+
const result = await createExecutor().execute('context', context, 'auto 85%');
|
|
309
|
+
|
|
310
|
+
expect(result?.success).toBe(true);
|
|
311
|
+
expect(result?.message).toBe('Auto compact threshold set to 85% (settings).');
|
|
312
|
+
expect(context.settings.autoCompactThreshold).toBe(0.85);
|
|
313
|
+
expect(context.source).toBe('settings');
|
|
314
|
+
expect(context.getAutoCompactThreshold()).toBe(0.85);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('disables auto compact and persists false', async () => {
|
|
318
|
+
const context = createCommandHostContext();
|
|
319
|
+
const result = await createExecutor().execute('context', context, 'auto off');
|
|
320
|
+
|
|
321
|
+
expect(result?.success).toBe(true);
|
|
322
|
+
expect(result?.message).toBe('Auto compact disabled (settings).');
|
|
323
|
+
expect(context.settings.autoCompactThreshold).toBe(false);
|
|
324
|
+
expect(context.getAutoCompactThreshold()).toBe(false);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('enables auto compact at the documented default', async () => {
|
|
328
|
+
const context = createCommandHostContext(false);
|
|
329
|
+
const result = await createExecutor().execute('context', context, 'auto on');
|
|
330
|
+
|
|
331
|
+
expect(result?.success).toBe(true);
|
|
332
|
+
expect(result?.message).toBe('Auto compact enabled at 84% (settings).');
|
|
333
|
+
expect(context.settings.autoCompactThreshold).toBe(0.835);
|
|
334
|
+
expect(context.getAutoCompactThreshold()).toBe(0.835);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('resets auto compact to the documented default and removes persisted override', async () => {
|
|
338
|
+
const context = createCommandHostContext(0.5);
|
|
339
|
+
await createExecutor().execute('context', context, 'auto 70%');
|
|
340
|
+
|
|
341
|
+
const result = await createExecutor().execute('context', context, 'auto reset');
|
|
342
|
+
|
|
343
|
+
expect(result?.success).toBe(true);
|
|
344
|
+
expect(result?.message).toBe('Auto compact reset to default: 84% (settings).');
|
|
345
|
+
expect(context.settings).toEqual({});
|
|
346
|
+
expect(context.source).toBe('default');
|
|
347
|
+
expect(context.getAutoCompactThreshold()).toBe(0.835);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('rejects invalid auto compact thresholds', async () => {
|
|
351
|
+
const result = await createExecutor().execute(
|
|
352
|
+
'context',
|
|
353
|
+
createCommandHostContext(),
|
|
354
|
+
'auto 150%',
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
expect(result?.success).toBe(false);
|
|
358
|
+
expect(result?.message).toContain('greater than 0% and at most 100%');
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('lists system context references with [system, active] label', async () => {
|
|
362
|
+
const context = createCommandHostContext();
|
|
363
|
+
context.references.push(SYSTEM_REFERENCE);
|
|
364
|
+
|
|
365
|
+
const result = await createExecutor().execute('context', context, 'list');
|
|
366
|
+
|
|
367
|
+
expect(result?.success).toBe(true);
|
|
368
|
+
expect(result?.message).toContain('AGENTS.md [system, active] ~256 tokens');
|
|
369
|
+
expect(result?.data?.references).toEqual([SYSTEM_REFERENCE]);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('includes system references in context summary active count', async () => {
|
|
373
|
+
const context = createCommandHostContext();
|
|
374
|
+
context.references.push(SYSTEM_REFERENCE, MANUAL_REFERENCE);
|
|
375
|
+
|
|
376
|
+
const result = await createExecutor().execute('context', context, '');
|
|
377
|
+
|
|
378
|
+
expect(result?.success).toBe(true);
|
|
379
|
+
expect(result?.message).toContain('References: 2 active, 0 observed');
|
|
380
|
+
});
|
|
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
|
+
|
|
415
|
+
it('shows full context breakdown with empty sections when no references', async () => {
|
|
416
|
+
const context = createCommandHostContext();
|
|
417
|
+
|
|
418
|
+
const result = await createExecutor().execute('context', context, 'list');
|
|
419
|
+
|
|
420
|
+
expect(result?.success).toBe(true);
|
|
421
|
+
expect(result?.message).toContain('System prompt (active every turn):');
|
|
422
|
+
expect(result?.message).toContain('Conversation history — 0 turns:');
|
|
423
|
+
expect(result?.message).toContain('Manually added:');
|
|
424
|
+
expect(result?.message).toContain('Prompt references (@-syntax):');
|
|
425
|
+
// All sections empty
|
|
426
|
+
expect(result?.message).not.toContain('~');
|
|
427
|
+
});
|
|
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
|
+
}
|