@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
|
@@ -7,15 +7,18 @@ import {
|
|
|
7
7
|
} from '@robota-sdk/agent-framework';
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
+
CD_COMMAND_DESCRIPTION,
|
|
11
|
+
executeCdCommand,
|
|
10
12
|
executeClearCommand,
|
|
11
13
|
executeCostCommand,
|
|
12
14
|
executeRenameCommand,
|
|
13
15
|
executeResumeCommand,
|
|
14
16
|
executeValidateSessionCommand,
|
|
15
17
|
} from './session-command.js';
|
|
18
|
+
import { createSystemCommandFromEntry } from '../command-module-utils.js';
|
|
16
19
|
|
|
17
20
|
import type { ICommandModule, ISystemCommand } from '@robota-sdk/agent-framework';
|
|
18
|
-
import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-
|
|
21
|
+
import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-command';
|
|
19
22
|
|
|
20
23
|
export function createClearCommandEntry(): ICommand {
|
|
21
24
|
return {
|
|
@@ -23,7 +26,9 @@ export function createClearCommandEntry(): ICommand {
|
|
|
23
26
|
displayName: 'Clear History',
|
|
24
27
|
description: CLEAR_COMMAND_DESCRIPTION,
|
|
25
28
|
source: 'session',
|
|
29
|
+
// User-only: wiping the conversation is the user's decision.
|
|
26
30
|
modelInvocable: false,
|
|
31
|
+
userInvocable: true,
|
|
27
32
|
};
|
|
28
33
|
}
|
|
29
34
|
|
|
@@ -32,8 +37,24 @@ export function createRenameCommandEntry(): ICommand {
|
|
|
32
37
|
name: 'rename',
|
|
33
38
|
displayName: 'Rename Session',
|
|
34
39
|
description: RENAME_COMMAND_DESCRIPTION,
|
|
40
|
+
argumentHint: '<name>',
|
|
41
|
+
source: 'session',
|
|
42
|
+
// User-only: the session's name is the user's label; UI-only.
|
|
43
|
+
modelInvocable: false,
|
|
44
|
+
userInvocable: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createCdCommandEntry(): ICommand {
|
|
49
|
+
return {
|
|
50
|
+
name: 'cd',
|
|
51
|
+
displayName: 'Change Directory',
|
|
52
|
+
description: CD_COMMAND_DESCRIPTION,
|
|
53
|
+
argumentHint: '<directory>',
|
|
35
54
|
source: 'session',
|
|
55
|
+
// A move re-roots what every later tool call may touch; only the user decides it.
|
|
36
56
|
modelInvocable: false,
|
|
57
|
+
userInvocable: true,
|
|
37
58
|
};
|
|
38
59
|
}
|
|
39
60
|
|
|
@@ -43,7 +64,9 @@ export function createResumeCommandEntry(): ICommand {
|
|
|
43
64
|
displayName: 'Resume Session',
|
|
44
65
|
description: RESUME_COMMAND_DESCRIPTION,
|
|
45
66
|
source: 'session',
|
|
67
|
+
// User-only: switching to another session is the user's decision.
|
|
46
68
|
modelInvocable: false,
|
|
69
|
+
userInvocable: true,
|
|
47
70
|
};
|
|
48
71
|
}
|
|
49
72
|
|
|
@@ -52,8 +75,28 @@ export function createCostCommandEntry(): ICommand {
|
|
|
52
75
|
name: 'cost',
|
|
53
76
|
displayName: 'Session Cost',
|
|
54
77
|
description: COST_COMMAND_DESCRIPTION,
|
|
78
|
+
// Model-invocable for the read-only report only: the model can weigh cost before an expensive
|
|
79
|
+
// step. The monthly budget is the user's spending decision, so `budget` stays user-only.
|
|
80
|
+
modelDescription:
|
|
81
|
+
'Report this session’s token usage and estimated cost. Use it before an expensive step (a ' +
|
|
82
|
+
'large refactor, many subagents) or when the user asks what the session has cost. Returns ' +
|
|
83
|
+
'the message count, input/output token totals and the estimated USD cost, with the remaining ' +
|
|
84
|
+
'monthly budget when one is set. Setting or clearing the budget is the user’s decision: suggest `/cost budget <amount>`.',
|
|
85
|
+
argumentHint: '[budget [<amount>|clear]]',
|
|
86
|
+
// The bare `/cost` is the report, so choosing it from a menu runs it.
|
|
87
|
+
runsBare: true,
|
|
55
88
|
source: 'session',
|
|
56
|
-
modelInvocable:
|
|
89
|
+
modelInvocable: true,
|
|
90
|
+
userInvocable: true,
|
|
91
|
+
subcommands: [
|
|
92
|
+
{
|
|
93
|
+
name: 'budget',
|
|
94
|
+
description: 'Show, set, or clear the monthly budget',
|
|
95
|
+
argumentHint: '[<amount>|clear]',
|
|
96
|
+
source: 'session',
|
|
97
|
+
modelInvocable: false,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
57
100
|
};
|
|
58
101
|
}
|
|
59
102
|
|
|
@@ -63,78 +106,64 @@ export function createValidateSessionCommandEntry(): ICommand {
|
|
|
63
106
|
displayName: 'Validate Session',
|
|
64
107
|
description: VALIDATE_SESSION_COMMAND_DESCRIPTION,
|
|
65
108
|
source: 'session',
|
|
109
|
+
// User-only: a diagnostic of the replay log for the user; nothing the model acts on.
|
|
66
110
|
modelInvocable: false,
|
|
111
|
+
userInvocable: true,
|
|
67
112
|
};
|
|
68
113
|
}
|
|
69
114
|
|
|
70
115
|
function createClearSystemCommand(): ISystemCommand {
|
|
71
116
|
const entry = createClearCommandEntry();
|
|
72
|
-
return {
|
|
73
|
-
name: entry.name,
|
|
74
|
-
displayName: entry.displayName,
|
|
75
|
-
description: entry.description,
|
|
117
|
+
return createSystemCommandFromEntry(entry, {
|
|
76
118
|
requiresPermission: false,
|
|
77
|
-
userInvocable: true,
|
|
78
|
-
modelInvocable: false,
|
|
79
119
|
lifecycle: 'inline',
|
|
80
120
|
execute: executeClearCommand,
|
|
81
|
-
};
|
|
121
|
+
});
|
|
82
122
|
}
|
|
83
123
|
|
|
84
124
|
function createRenameSystemCommand(): ISystemCommand {
|
|
85
125
|
const entry = createRenameCommandEntry();
|
|
86
|
-
return {
|
|
87
|
-
name: entry.name,
|
|
88
|
-
displayName: entry.displayName,
|
|
89
|
-
description: entry.description,
|
|
126
|
+
return createSystemCommandFromEntry(entry, {
|
|
90
127
|
requiresPermission: false,
|
|
91
|
-
userInvocable: true,
|
|
92
|
-
modelInvocable: false,
|
|
93
128
|
lifecycle: 'inline',
|
|
94
129
|
execute: executeRenameCommand,
|
|
95
|
-
};
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function createCdSystemCommand(): ISystemCommand {
|
|
134
|
+
const entry = createCdCommandEntry();
|
|
135
|
+
return createSystemCommandFromEntry(entry, {
|
|
136
|
+
requiresPermission: false,
|
|
137
|
+
lifecycle: 'inline',
|
|
138
|
+
execute: executeCdCommand,
|
|
139
|
+
});
|
|
96
140
|
}
|
|
97
141
|
|
|
98
142
|
function createResumeSystemCommand(): ISystemCommand {
|
|
99
143
|
const entry = createResumeCommandEntry();
|
|
100
|
-
return {
|
|
101
|
-
name: entry.name,
|
|
102
|
-
displayName: entry.displayName,
|
|
103
|
-
description: entry.description,
|
|
144
|
+
return createSystemCommandFromEntry(entry, {
|
|
104
145
|
requiresPermission: false,
|
|
105
|
-
userInvocable: true,
|
|
106
|
-
modelInvocable: false,
|
|
107
146
|
lifecycle: 'inline',
|
|
108
147
|
execute: executeResumeCommand,
|
|
109
|
-
};
|
|
148
|
+
});
|
|
110
149
|
}
|
|
111
150
|
|
|
112
151
|
function createCostSystemCommand(): ISystemCommand {
|
|
113
152
|
const entry = createCostCommandEntry();
|
|
114
|
-
return {
|
|
115
|
-
name: entry.name,
|
|
116
|
-
displayName: entry.displayName,
|
|
117
|
-
description: entry.description,
|
|
153
|
+
return createSystemCommandFromEntry(entry, {
|
|
118
154
|
requiresPermission: false,
|
|
119
|
-
userInvocable: true,
|
|
120
|
-
modelInvocable: false,
|
|
121
155
|
lifecycle: 'inline',
|
|
122
156
|
execute: executeCostCommand,
|
|
123
|
-
};
|
|
157
|
+
});
|
|
124
158
|
}
|
|
125
159
|
|
|
126
160
|
function createValidateSessionSystemCommand(): ISystemCommand {
|
|
127
161
|
const entry = createValidateSessionCommandEntry();
|
|
128
|
-
return {
|
|
129
|
-
name: entry.name,
|
|
130
|
-
displayName: entry.displayName,
|
|
131
|
-
description: entry.description,
|
|
162
|
+
return createSystemCommandFromEntry(entry, {
|
|
132
163
|
requiresPermission: false,
|
|
133
|
-
userInvocable: true,
|
|
134
|
-
modelInvocable: false,
|
|
135
164
|
lifecycle: 'inline',
|
|
136
165
|
execute: executeValidateSessionCommand,
|
|
137
|
-
};
|
|
166
|
+
});
|
|
138
167
|
}
|
|
139
168
|
|
|
140
169
|
export class SessionCommandSource implements ICommandSource {
|
|
@@ -144,6 +173,7 @@ export class SessionCommandSource implements ICommandSource {
|
|
|
144
173
|
return [
|
|
145
174
|
createClearCommandEntry(),
|
|
146
175
|
createRenameCommandEntry(),
|
|
176
|
+
createCdCommandEntry(),
|
|
147
177
|
createResumeCommandEntry(),
|
|
148
178
|
createCostCommandEntry(),
|
|
149
179
|
createValidateSessionCommandEntry(),
|
|
@@ -158,6 +188,7 @@ export function createSessionCommandModule(): ICommandModule {
|
|
|
158
188
|
systemCommands: [
|
|
159
189
|
createClearSystemCommand(),
|
|
160
190
|
createRenameSystemCommand(),
|
|
191
|
+
createCdSystemCommand(),
|
|
161
192
|
createResumeSystemCommand(),
|
|
162
193
|
createCostSystemCommand(),
|
|
163
194
|
createValidateSessionSystemCommand(),
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
|
|
4
1
|
import { confirmAction, isConfirmed } from '@robota-sdk/agent-core';
|
|
5
2
|
import {
|
|
6
3
|
RENAME_COMMAND_USAGE,
|
|
7
4
|
clearConversationHistory,
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
createShowSessionPickerIntent,
|
|
6
|
+
createSessionRenameHostAction,
|
|
10
7
|
formatCommandSessionReplayValidationReport,
|
|
11
8
|
parseSessionNameArgument,
|
|
12
9
|
readCommandSessionInfo,
|
|
@@ -15,17 +12,39 @@ import {
|
|
|
15
12
|
|
|
16
13
|
import { calculateCost, formatTokens, formatUsd } from './model-pricing.js';
|
|
17
14
|
|
|
18
|
-
import type {
|
|
19
|
-
|
|
15
|
+
import type {
|
|
16
|
+
ICommandCostBudgetAdapter,
|
|
17
|
+
ICommandHostAdapterAccess,
|
|
18
|
+
ICommandHostNoCapability,
|
|
19
|
+
ICommandHostSessionAccess,
|
|
20
|
+
ICommandHostUserInteraction,
|
|
21
|
+
ICommandHostWorkspace,
|
|
22
|
+
ISessionUsageRecord,
|
|
23
|
+
} from '@robota-sdk/agent-framework';
|
|
24
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
20
25
|
|
|
21
26
|
export const CLEAR_COMMAND_MESSAGE = 'Conversation cleared.';
|
|
22
27
|
|
|
28
|
+
export const CD_COMMAND_DESCRIPTION = 'Move this session to another working directory';
|
|
29
|
+
export const CD_COMMAND_USAGE = 'Usage: /cd <directory>';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `/cd <directory>` (issue #3081) requests the host-executed move. The checks — no turn running, no
|
|
33
|
+
* live background task, the target exists, no `Cd(...)` deny rule — run where the session state is,
|
|
34
|
+
* when the host applies the action.
|
|
35
|
+
*/
|
|
36
|
+
export function executeCdCommand(_context: ICommandHostNoCapability, args: string): ICommandResult {
|
|
37
|
+
const path = args.trim();
|
|
38
|
+
if (path === '') return { success: false, message: CD_COMMAND_USAGE };
|
|
39
|
+
return { success: true, message: '', hostActions: [{ type: 'workspace-move', path }] };
|
|
40
|
+
}
|
|
41
|
+
|
|
23
42
|
export async function executeClearCommand(
|
|
24
|
-
context:
|
|
43
|
+
context: ICommandHostSessionAccess & ICommandHostUserInteraction,
|
|
25
44
|
_args: string,
|
|
26
45
|
): Promise<ICommandResult> {
|
|
27
46
|
// Confirm only when an interactive renderer is attached; with no human the explicit /clear proceeds.
|
|
28
|
-
const ui = context.getUserInteraction
|
|
47
|
+
const ui = context.getUserInteraction();
|
|
29
48
|
if (ui) {
|
|
30
49
|
const response = await ui.ask(confirmAction('clear', 'Clear conversation history?'));
|
|
31
50
|
if (!isConfirmed(response)) {
|
|
@@ -33,15 +52,16 @@ export async function executeClearCommand(
|
|
|
33
52
|
}
|
|
34
53
|
}
|
|
35
54
|
|
|
55
|
+
// CMD-004 Stage E: the clear mutation runs host-side and the session broadcasts `history_cleared`
|
|
56
|
+
// to every attached surface — the result carries no separate notification.
|
|
36
57
|
clearConversationHistory(context);
|
|
37
|
-
return {
|
|
38
|
-
success: true,
|
|
39
|
-
message: CLEAR_COMMAND_MESSAGE,
|
|
40
|
-
effects: [{ type: 'conversation-history-cleared' }],
|
|
41
|
-
};
|
|
58
|
+
return { success: true, message: CLEAR_COMMAND_MESSAGE };
|
|
42
59
|
}
|
|
43
60
|
|
|
44
|
-
export function executeRenameCommand(
|
|
61
|
+
export function executeRenameCommand(
|
|
62
|
+
_context: ICommandHostNoCapability,
|
|
63
|
+
args: string,
|
|
64
|
+
): ICommandResult {
|
|
45
65
|
const name = parseSessionNameArgument(args);
|
|
46
66
|
if (name === undefined) {
|
|
47
67
|
return { success: false, message: RENAME_COMMAND_USAGE };
|
|
@@ -51,64 +71,95 @@ export function executeRenameCommand(_context: ICommandHostContext, args: string
|
|
|
51
71
|
success: true,
|
|
52
72
|
message: `Session renamed to "${name}".`,
|
|
53
73
|
data: { name },
|
|
54
|
-
|
|
74
|
+
hostActions: [createSessionRenameHostAction(name)],
|
|
55
75
|
};
|
|
56
76
|
}
|
|
57
77
|
|
|
58
|
-
export function executeResumeCommand(
|
|
78
|
+
export function executeResumeCommand(): ICommandResult {
|
|
59
79
|
return {
|
|
60
80
|
success: true,
|
|
61
81
|
message: 'Opening session picker...',
|
|
62
82
|
data: { triggerResumePicker: true },
|
|
63
|
-
|
|
83
|
+
uiIntents: [createShowSessionPickerIntent()],
|
|
64
84
|
};
|
|
65
85
|
}
|
|
66
86
|
|
|
67
|
-
|
|
87
|
+
type TCostCommandContext = ICommandHostSessionAccess &
|
|
88
|
+
ICommandHostWorkspace &
|
|
89
|
+
ICommandHostAdapterAccess;
|
|
68
90
|
|
|
69
|
-
|
|
70
|
-
|
|
91
|
+
/**
|
|
92
|
+
* CMD-007 (issue #2058): the budget lives behind `ICommandCostBudgetAdapter`, composed by the shell.
|
|
93
|
+
* This package owns no path literal and no filesystem call for it any more — atomic/symlink-safe
|
|
94
|
+
* writing is the adapter's policy (agent-cli's file adapter keeps the O_NOFOLLOW guard).
|
|
95
|
+
*/
|
|
96
|
+
function costBudgetAdapter(context: TCostCommandContext): ICommandCostBudgetAdapter | undefined {
|
|
97
|
+
return context.getCommandHostAdapters?.().costBudget;
|
|
71
98
|
}
|
|
72
99
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
} catch {
|
|
80
|
-
// allow-fallback: budget file read failure disables feature gracefully
|
|
81
|
-
return undefined;
|
|
82
|
-
}
|
|
83
|
-
try {
|
|
84
|
-
return JSON.parse(raw) as IBudgetConfig;
|
|
85
|
-
} catch {
|
|
86
|
-
// allow-fallback: malformed budget JSON treated as no budget set
|
|
87
|
-
return undefined;
|
|
88
|
-
}
|
|
100
|
+
const BUDGET_UNAVAILABLE_MESSAGE =
|
|
101
|
+
'Budget persistence is not available on this host (no cost-budget adapter was composed).';
|
|
102
|
+
|
|
103
|
+
/** What to tell the operator when the adapter refused the write; the adapter names the cause. */
|
|
104
|
+
function describeBudgetWriteFailure(error: unknown): string {
|
|
105
|
+
return `Could not write the budget: ${error instanceof Error ? error.message : String(error)}`;
|
|
89
106
|
}
|
|
90
107
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
108
|
+
interface ICostTotals {
|
|
109
|
+
inputTokens: number;
|
|
110
|
+
outputTokens: number;
|
|
111
|
+
/** Absent when nothing could be priced. */
|
|
112
|
+
costUsd?: number;
|
|
113
|
+
/** Some usage could not be priced, so the cost is a lower bound. */
|
|
114
|
+
partial: boolean;
|
|
115
|
+
/** The models the cost was priced on. */
|
|
116
|
+
models: string[];
|
|
95
117
|
}
|
|
96
118
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Total the session's usage records. A record priced on its own model (the advisor's) keeps that
|
|
121
|
+
* price; a main-thread record without one is priced on the session's model. A consulted model whose
|
|
122
|
+
* price is unknown is left unpriced rather than priced as the main model.
|
|
123
|
+
*/
|
|
124
|
+
function totalSessionUsage(
|
|
125
|
+
records: readonly ISessionUsageRecord[],
|
|
126
|
+
modelId: string | undefined,
|
|
127
|
+
): ICostTotals | undefined {
|
|
128
|
+
if (records.length === 0) return undefined;
|
|
129
|
+
const totals: ICostTotals = { inputTokens: 0, outputTokens: 0, partial: false, models: [] };
|
|
130
|
+
const models = new Set<string>();
|
|
131
|
+
let cost = 0;
|
|
132
|
+
let priced = false;
|
|
133
|
+
for (const record of records) {
|
|
134
|
+
totals.inputTokens += record.promptTokens;
|
|
135
|
+
totals.outputTokens += record.completionTokens;
|
|
136
|
+
const consulted = record.source?.scope === 'tool';
|
|
137
|
+
const recordCost =
|
|
138
|
+
record.costUsd ??
|
|
139
|
+
(!consulted && modelId
|
|
140
|
+
? calculateCost(modelId, record.promptTokens, record.completionTokens)
|
|
141
|
+
: undefined);
|
|
142
|
+
if (recordCost === undefined) {
|
|
143
|
+
totals.partial = true;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
cost += recordCost;
|
|
147
|
+
priced = true;
|
|
148
|
+
models.add(consulted ? (record.source?.label ?? 'another model') : (modelId ?? 'main model'));
|
|
101
149
|
}
|
|
150
|
+
if (priced) totals.costUsd = cost;
|
|
151
|
+
totals.models = [...models];
|
|
152
|
+
return totals;
|
|
102
153
|
}
|
|
103
154
|
|
|
104
|
-
function buildCostOutput(context:
|
|
155
|
+
function buildCostOutput(context: TCostCommandContext): {
|
|
105
156
|
lines: string[];
|
|
106
157
|
data: Record<string, unknown>;
|
|
107
158
|
} {
|
|
108
159
|
const session = context.getSession();
|
|
109
160
|
const sessionInfo = readCommandSessionInfo(context);
|
|
110
|
-
const
|
|
111
|
-
const
|
|
161
|
+
const modelId = session.getModelId();
|
|
162
|
+
const totals = totalSessionUsage(context.getSessionUsage(), modelId);
|
|
112
163
|
const lines: string[] = [
|
|
113
164
|
`Session: ${sessionInfo.sessionId}`,
|
|
114
165
|
`Messages: ${sessionInfo.messageCount}`,
|
|
@@ -118,29 +169,32 @@ function buildCostOutput(context: ICommandHostContext): {
|
|
|
118
169
|
messageCount: sessionInfo.messageCount,
|
|
119
170
|
};
|
|
120
171
|
|
|
121
|
-
if (
|
|
172
|
+
if (totals) {
|
|
122
173
|
lines.push(
|
|
123
|
-
`Tokens: ${formatTokens(
|
|
174
|
+
`Tokens: ${formatTokens(totals.inputTokens)} input / ${formatTokens(totals.outputTokens)} output`,
|
|
124
175
|
);
|
|
125
|
-
data.inputTokens =
|
|
126
|
-
data.outputTokens =
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
176
|
+
data.inputTokens = totals.inputTokens;
|
|
177
|
+
data.outputTokens = totals.outputTokens;
|
|
178
|
+
|
|
179
|
+
const cost = totals.costUsd;
|
|
180
|
+
if (cost !== undefined) {
|
|
181
|
+
const priced =
|
|
182
|
+
totals.models.length > 1 ? `mixed: ${totals.models.join(', ')}` : (totals.models[0] ?? '');
|
|
183
|
+
const note = totals.partial ? '; some usage could not be priced' : '';
|
|
184
|
+
lines.push(`Cost: ${formatUsd(cost)} (${priced}${note})`);
|
|
185
|
+
data.estimatedCostUsd = cost;
|
|
186
|
+
data.costModels = totals.models;
|
|
187
|
+
if (totals.partial) data.costPartial = true;
|
|
188
|
+
|
|
189
|
+
const budget = costBudgetAdapter(context)?.read();
|
|
190
|
+
if (budget?.monthly) {
|
|
191
|
+
const remaining = budget.monthly - cost;
|
|
192
|
+
const pct = Math.min(100, Math.round((cost / budget.monthly) * 100));
|
|
193
|
+
lines.push(
|
|
194
|
+
`Budget: ${formatUsd(remaining)} remaining of ${formatUsd(budget.monthly)}/mo (${pct}% used)`,
|
|
195
|
+
);
|
|
196
|
+
data.budgetMonthly = budget.monthly;
|
|
197
|
+
data.budgetRemainingUsd = remaining;
|
|
144
198
|
}
|
|
145
199
|
}
|
|
146
200
|
} else {
|
|
@@ -150,19 +204,34 @@ function buildCostOutput(context: ICommandHostContext): {
|
|
|
150
204
|
return { lines, data };
|
|
151
205
|
}
|
|
152
206
|
|
|
153
|
-
export function executeCostCommand(context:
|
|
207
|
+
export function executeCostCommand(context: TCostCommandContext, args: string): ICommandResult {
|
|
154
208
|
const trimmed = args.trim();
|
|
155
209
|
|
|
156
210
|
if (trimmed.startsWith('budget')) {
|
|
157
211
|
const budgetArg = trimmed.slice('budget'.length).trim();
|
|
212
|
+
// Argument shape is judged before the host is asked for persistence: a malformed amount is a
|
|
213
|
+
// usage error on every host, not a symptom of the adapter that would have stored it.
|
|
214
|
+
const amount = budgetArg === 'clear' || budgetArg === '' ? undefined : parseFloat(budgetArg);
|
|
215
|
+
if (amount !== undefined && (!Number.isFinite(amount) || amount <= 0)) {
|
|
216
|
+
return {
|
|
217
|
+
success: false,
|
|
218
|
+
message: 'Usage: /cost budget <amount> (e.g. /cost budget 5.00)',
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const adapter = costBudgetAdapter(context);
|
|
222
|
+
if (adapter === undefined) return { success: false, message: BUDGET_UNAVAILABLE_MESSAGE };
|
|
158
223
|
|
|
159
224
|
if (budgetArg === 'clear') {
|
|
160
|
-
|
|
225
|
+
try {
|
|
226
|
+
adapter.clear();
|
|
227
|
+
} catch (error) {
|
|
228
|
+
return { success: false, message: describeBudgetWriteFailure(error) };
|
|
229
|
+
}
|
|
161
230
|
return { success: true, message: 'Monthly budget cleared.' };
|
|
162
231
|
}
|
|
163
232
|
|
|
164
233
|
if (budgetArg === '') {
|
|
165
|
-
const current =
|
|
234
|
+
const current = adapter.read();
|
|
166
235
|
if (!current?.monthly) {
|
|
167
236
|
return {
|
|
168
237
|
success: true,
|
|
@@ -172,14 +241,12 @@ export function executeCostCommand(context: ICommandHostContext, args: string):
|
|
|
172
241
|
return { success: true, message: `Monthly budget: ${formatUsd(current.monthly)}` };
|
|
173
242
|
}
|
|
174
243
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
};
|
|
244
|
+
if (amount === undefined) return { success: false, message: BUDGET_UNAVAILABLE_MESSAGE };
|
|
245
|
+
try {
|
|
246
|
+
adapter.write({ monthly: amount });
|
|
247
|
+
} catch (error) {
|
|
248
|
+
return { success: false, message: describeBudgetWriteFailure(error) };
|
|
181
249
|
}
|
|
182
|
-
writeBudget(context.getCwd(), { monthly: amount });
|
|
183
250
|
return { success: true, message: `Monthly budget set to ${formatUsd(amount)}.` };
|
|
184
251
|
}
|
|
185
252
|
|
|
@@ -188,7 +255,7 @@ export function executeCostCommand(context: ICommandHostContext, args: string):
|
|
|
188
255
|
}
|
|
189
256
|
|
|
190
257
|
export function executeValidateSessionCommand(
|
|
191
|
-
context:
|
|
258
|
+
context: ICommandHostSessionAccess & ICommandHostWorkspace,
|
|
192
259
|
_args: string,
|
|
193
260
|
): ICommandResult {
|
|
194
261
|
const report = validateCommandSessionReplayLog(context);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ICommandModule, ISystemCommand } from '@robota-sdk/agent-framework';
|
|
2
|
-
import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-
|
|
2
|
+
import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-command';
|
|
3
3
|
|
|
4
4
|
export function createSettingsCommandEntry(): ICommand {
|
|
5
5
|
return {
|
|
@@ -7,6 +7,7 @@ export function createSettingsCommandEntry(): ICommand {
|
|
|
7
7
|
displayName: 'Settings',
|
|
8
8
|
description: 'Open transport settings — enable/disable transports and configure options',
|
|
9
9
|
source: 'settings',
|
|
10
|
+
// User-only: transport settings screen; UI-only.
|
|
10
11
|
modelInvocable: false,
|
|
11
12
|
};
|
|
12
13
|
}
|
|
@@ -24,7 +25,7 @@ function createSettingsSystemCommand(): ISystemCommand {
|
|
|
24
25
|
execute: async () => ({
|
|
25
26
|
success: true,
|
|
26
27
|
message: 'Opening settings...',
|
|
27
|
-
|
|
28
|
+
uiIntents: [{ type: 'show-settings' as const }],
|
|
28
29
|
}),
|
|
29
30
|
};
|
|
30
31
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { resolveShell } from '../resolve-shell.js';
|
|
4
|
+
|
|
5
|
+
describe('interactive shell host selection', () => {
|
|
6
|
+
it('keeps the executable and argument family together', () => {
|
|
7
|
+
const shell = resolveShell('C:\\Windows\\System32\\cmd.exe');
|
|
8
|
+
expect(shell.command).toBe('C:\\Windows\\System32\\cmd.exe');
|
|
9
|
+
expect(shell.commandArgs('echo ok')).toEqual(['/d', '/s', '/c', 'echo ok']);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -12,7 +12,7 @@ import { scriptedSession, type ScriptedSessionHarness } from '@robota-sdk/agent-
|
|
|
12
12
|
|
|
13
13
|
import { createShellCommandModule } from '../shell-command-module.js';
|
|
14
14
|
|
|
15
|
-
import type { ITerminalHandoff } from '@robota-sdk/agent-interface-
|
|
15
|
+
import type { ITerminalHandoff } from '@robota-sdk/agent-interface-session';
|
|
16
16
|
|
|
17
17
|
const TEST_TIMEOUT = 20_000;
|
|
18
18
|
|
|
@@ -39,6 +39,28 @@ afterEach(async () => {
|
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
describe('/shell command (framework functional)', () => {
|
|
42
|
+
it(
|
|
43
|
+
'uses the host-selected executable captured by its module',
|
|
44
|
+
async () => {
|
|
45
|
+
if (process.platform === 'win32') return;
|
|
46
|
+
const previousShell = process.env['SHELL'];
|
|
47
|
+
process.env['SHELL'] = '/bin/false';
|
|
48
|
+
try {
|
|
49
|
+
h = scriptedSession({
|
|
50
|
+
turns: [{ text: 'unused' }],
|
|
51
|
+
terminalHandoff: fakeHandoff(true),
|
|
52
|
+
commandModules: [createShellCommandModule('/bin/bash')],
|
|
53
|
+
});
|
|
54
|
+
const result = await h.command('shell', 'exit 0');
|
|
55
|
+
expect(result?.success).toBe(true);
|
|
56
|
+
} finally {
|
|
57
|
+
if (previousShell === undefined) delete process.env['SHELL'];
|
|
58
|
+
else process.env['SHELL'] = previousShell;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
TEST_TIMEOUT,
|
|
62
|
+
);
|
|
63
|
+
|
|
42
64
|
it(
|
|
43
65
|
'runs a one-shot command through the handoff and returns exit code 0',
|
|
44
66
|
async () => {
|
|
@@ -15,8 +15,8 @@ export interface IResolvedShell {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/** Resolve the interactive shell for the current platform via the agent-core SSOT resolver. */
|
|
18
|
-
export function resolveShell(): IResolvedShell {
|
|
19
|
-
const shell = resolvePlatformShell();
|
|
18
|
+
export function resolveShell(executable?: string): IResolvedShell {
|
|
19
|
+
const shell = resolvePlatformShell({ executable });
|
|
20
20
|
return {
|
|
21
21
|
command: shell.command,
|
|
22
22
|
interactiveArgs: shell.interactiveArgs,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { executeShellCommand, SHELL_COMMAND_DESCRIPTION } from './shell-command.js';
|
|
5
5
|
|
|
6
6
|
import type { ICommandModule, ISystemCommand } from '@robota-sdk/agent-framework';
|
|
7
|
-
import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-
|
|
7
|
+
import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-command';
|
|
8
8
|
|
|
9
9
|
export function createShellCommandEntry(): ICommand {
|
|
10
10
|
return {
|
|
@@ -12,11 +12,12 @@ export function createShellCommandEntry(): ICommand {
|
|
|
12
12
|
displayName: 'Shell',
|
|
13
13
|
description: SHELL_COMMAND_DESCRIPTION,
|
|
14
14
|
source: 'shell',
|
|
15
|
+
// User-only: the user's own shell passthrough; the model has its permission-gated shell tool.
|
|
15
16
|
modelInvocable: false,
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
function createShellSystemCommand(): ISystemCommand {
|
|
20
|
+
function createShellSystemCommand(executable?: string): ISystemCommand {
|
|
20
21
|
const entry = createShellCommandEntry();
|
|
21
22
|
return {
|
|
22
23
|
name: entry.name,
|
|
@@ -26,7 +27,7 @@ function createShellSystemCommand(): ISystemCommand {
|
|
|
26
27
|
userInvocable: true,
|
|
27
28
|
modelInvocable: false,
|
|
28
29
|
lifecycle: 'inline',
|
|
29
|
-
execute: executeShellCommand,
|
|
30
|
+
execute: (context, args) => executeShellCommand(context, args, executable),
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -38,10 +39,10 @@ export class ShellCommandSource implements ICommandSource {
|
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
export function createShellCommandModule(): ICommandModule {
|
|
42
|
+
export function createShellCommandModule(executable?: string): ICommandModule {
|
|
42
43
|
return {
|
|
43
44
|
name: 'agent-command-shell',
|
|
44
45
|
commandSources: [new ShellCommandSource()],
|
|
45
|
-
systemCommands: [createShellSystemCommand()],
|
|
46
|
+
systemCommands: [createShellSystemCommand(executable)],
|
|
46
47
|
};
|
|
47
48
|
}
|