@robota-sdk/agent-command 3.0.0-beta.86 → 3.0.0-beta.87

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +53 -12
  2. package/README.md +6 -3
  3. package/dist/node/index.cjs +72 -70
  4. package/dist/node/index.d.cts +16 -8
  5. package/dist/node/index.d.cts.map +1 -1
  6. package/dist/node/index.d.ts +16 -8
  7. package/dist/node/index.d.ts.map +1 -1
  8. package/dist/node/index.js +51 -49
  9. package/dist/node/index.js.map +1 -1
  10. package/package.json +15 -13
  11. package/src/agent/__tests__/agent-command.test.ts +5 -5
  12. package/src/agent/agent-command-module.ts +1 -1
  13. package/src/background/__tests__/background-command-module.test.ts +6 -6
  14. package/src/default/__tests__/__snapshots__/model-exposure.test.ts.snap +15 -4
  15. package/src/default/__tests__/default-command-modules.test.ts +2 -2
  16. package/src/default/__tests__/model-exposure.test.ts +18 -5
  17. package/src/default/__tests__/org-policy-forwarding.test.ts +1 -1
  18. package/src/default/default-command-modules.ts +2 -2
  19. package/src/devices/devices-command-module.ts +1 -1
  20. package/src/doctor/__tests__/doctor-command-module.test.ts +10 -10
  21. package/src/doctor/__tests__/doctor-display-vocabulary.test.ts +1 -1
  22. package/src/doctor/__tests__/doctor-fixture.ts +14 -14
  23. package/src/doctor/__tests__/doctor-runner.test.ts +57 -57
  24. package/src/doctor/doctor-storage-probe.ts +1 -1
  25. package/src/doctor/doctor-types.ts +2 -2
  26. package/src/editor/__tests__/editor-command-functional.test.ts +1 -1
  27. package/src/fork/__tests__/fork-command.test.ts +1 -1
  28. package/src/fork/fork-command-module.ts +1 -1
  29. package/src/git/__tests__/git-command-module.test.ts +1 -1
  30. package/src/handoff/__tests__/handoff-command-module.test.ts +1 -1
  31. package/src/handoff/handoff-command-module.ts +1 -1
  32. package/src/keybindings/keybindings-command-module.ts +1 -1
  33. package/src/mcp-activation/__tests__/mcp-activation-command.test.ts +8 -8
  34. package/src/mcp-activation/__tests__/mcp-model-view.test.ts +43 -12
  35. package/src/mcp-activation/__tests__/mcp-skill-command.test.ts +141 -0
  36. package/src/mcp-activation/mcp-activation-command-module.ts +27 -2
  37. package/src/mcp-activation/mcp-activation-command.ts +149 -15
  38. package/src/mcp-activation/mcp-model-notice.ts +19 -5
  39. package/src/mcp-activation/mcp-model-status.ts +7 -2
  40. package/src/memory/__tests__/memory-command-module.test.ts +89 -16
  41. package/src/memory/memory-command-module.ts +4 -1
  42. package/src/memory/memory-command.ts +53 -1
  43. package/src/peers/__tests__/peers-command-module.test.ts +1 -1
  44. package/src/peers/peers-command-module.ts +1 -1
  45. package/src/permissions/__tests__/permissions-command-module.test.ts +4 -4
  46. package/src/plugin/__tests__/plugin-active-turn.test.ts +100 -0
  47. package/src/plugin/__tests__/plugin-command-module.test.ts +7 -7
  48. package/src/plugin/plugin-command-module.ts +4 -0
  49. package/src/provider/provider-startup.ts +4 -2
  50. package/src/remote-control/__tests__/remote-control-command.test.ts +2 -2
  51. package/src/rewind/__tests__/rewind-command-module.test.ts +4 -3
  52. package/src/rewind/rewind-command.ts +27 -21
  53. package/src/schedule/schedule-command-module.ts +1 -1
  54. package/src/session/__tests__/session-command-module.test.ts +2 -2
  55. package/src/skills/__tests__/skill-read-command.test.ts +64 -0
  56. package/src/skills/__tests__/skills-command-module.test.ts +2 -1
  57. package/src/skills/skill-read-command.ts +60 -0
  58. package/src/skills/skills-command-module.ts +3 -2
  59. package/src/terminal-client/__tests__/terminal-client-commands.test.ts +1 -1
  60. package/src/theme/__tests__/theme-command.test.ts +1 -1
  61. package/src/theme/theme-command-module.ts +1 -1
  62. package/src/user-local/__tests__/user-local-command.test.ts +16 -16
@@ -41,7 +41,7 @@ function byId(report: IDoctorReport, id: string) {
41
41
  const CLEAN_SETTINGS = {
42
42
  currentProvider: 'doctor-env',
43
43
  providers: {
44
- 'doctor-env': { type: 'fixture', model: 'fixture-model', apiKey: '$ENV:ROBOTA_DOCTOR_MARKER' },
44
+ 'doctor-env': { type: 'fixture', model: 'fixture-model', apiKey: '$ENV:FIXTURE_DOCTOR_MARKER' },
45
45
  'doctor-inactive': { type: 'fixture', model: 'fixture-model', apiKey: MARKERS.profile },
46
46
  },
47
47
  };
@@ -50,8 +50,8 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
50
50
  it('uses neutral provider and trust guidance unless the host supplies its own commands', async () => {
51
51
  const f = fixture({ env: {} });
52
52
  const neutral = await runDoctor(f.inputs, f.deps);
53
- expect(byId(neutral, 'provider.resolution').detail?.join(' ')).not.toMatch(/robota/i);
54
- expect(byId(neutral, 'workspace.trust').detail?.join(' ')).not.toMatch(/robota/i);
53
+ expect(byId(neutral, 'provider.resolution').detail?.join(' ')).not.toMatch(/fixture-agent/i);
54
+ expect(byId(neutral, 'workspace.trust').detail?.join(' ')).not.toMatch(/fixture-agent/i);
55
55
 
56
56
  const hosted = await runDoctor(
57
57
  {
@@ -70,7 +70,7 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
70
70
  it('shows each effective settings hook source without embedding payloads in provenance', async () => {
71
71
  const f = fixture({ env: {} });
72
72
  const userPath = f.write(
73
- '.robota/settings.json',
73
+ '.fixture-product/settings.json',
74
74
  JSON.stringify({
75
75
  disabledHooks: ['project-muted'],
76
76
  hooks: {
@@ -118,18 +118,18 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
118
118
  });
119
119
 
120
120
  it('TC-05/TC-02: names every failing path and cause in the broken HOME and exits 1', async () => {
121
- const f = fixture({ env: { ROBOTA_DOCTOR_MARKER: MARKERS.env } });
121
+ const f = fixture({ env: { FIXTURE_DOCTOR_MARKER: MARKERS.env } });
122
122
  installBrokenHome(f);
123
123
  const report = await runDoctor(f.inputs, f.deps);
124
124
 
125
125
  expect(report.exitCode).toBe(1);
126
126
  expect(byId(report, 'host.fixture').status).toBe('ok');
127
- const user = byId(report, 'settings.user.robota');
127
+ const user = byId(report, 'settings.user.fixture-product');
128
128
  expect(user).toMatchObject({
129
129
  status: 'fail',
130
130
  cause: 'empty',
131
- path: join(f.home, '.robota', 'settings.json'),
132
- repair: 'settings.user.robota',
131
+ path: join(f.home, '.fixture-product', 'settings.json'),
132
+ repair: 'settings.user.fixture-product',
133
133
  });
134
134
  const claude = byId(report, 'settings.user.claude');
135
135
  expect(claude.status).toBe('fail');
@@ -145,7 +145,7 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
145
145
  expect(broken.path).toContain(join('broken-plugin', '1.0.0', '.claude-plugin', 'plugin.json'));
146
146
  const ghost = byId(report, 'mcp.plugin.mcp-plugin@fixture-market.ghost');
147
147
  expect(ghost).toMatchObject({ status: 'warn' });
148
- expect(ghost.cause).toContain('robota-doctor-missing-binary');
148
+ expect(ghost.cause).toContain('agent-test-doctor-missing-binary');
149
149
  const declaration = byId(report, 'mcp.plugin.mcp-plugin@fixture-market.ready');
150
150
  expect(declaration.status).toBe('warn');
151
151
  expect(declaration.detail?.join(' ')).toContain('not a runtime source');
@@ -155,11 +155,11 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
155
155
  expect(byId(report, 'storage.user').status).toBe('ok');
156
156
  expect(byId(report, 'storage.project').status).toBe('not-configured');
157
157
  expect(byId(report, 'workspace.trust').status).toBe('warn');
158
- expect(report.repairable).toEqual(['settings.user.robota']);
158
+ expect(report.repairable).toEqual(['settings.user.fixture-product']);
159
159
  });
160
160
 
161
161
  it('TC-03: no rendered line carries a marker secret from any input', async () => {
162
- const f = fixture({ env: { ROBOTA_DOCTOR_MARKER: MARKERS.env } });
162
+ const f = fixture({ env: { FIXTURE_DOCTOR_MARKER: MARKERS.env } });
163
163
  installBrokenHome(f);
164
164
  // Overwrite the schema-invalid layer with a syntax error beside a secret (the parser-snippet case).
165
165
  f.write('.claude/settings.json', `{"providers":{"x":{"apiKey": ${MARKERS.adjacent}}}}`);
@@ -170,10 +170,10 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
170
170
  });
171
171
 
172
172
  it('TC-03: masks the resolved credential and inactive-profile literals in a clean configuration', async () => {
173
- const f = fixture({ env: { ROBOTA_DOCTOR_MARKER: MARKERS.env } });
174
- f.mkdir('.robota');
175
- f.mkdir('.robota/sessions');
176
- f.write('.robota/settings.json', JSON.stringify(CLEAN_SETTINGS));
173
+ const f = fixture({ env: { FIXTURE_DOCTOR_MARKER: MARKERS.env } });
174
+ f.mkdir('.fixture-product');
175
+ f.mkdir('.fixture-product/sessions');
176
+ f.write('.fixture-product/settings.json', JSON.stringify(CLEAN_SETTINGS));
177
177
  const report = await runDoctor(f.inputs, f.deps);
178
178
  const text = renderDoctorReport(report).join('\n');
179
179
  expect(report.exitCode).toBe(0);
@@ -184,15 +184,15 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
184
184
 
185
185
  it('TC-03: a parse error in a settings layer never renders the file excerpt around the fault', async () => {
186
186
  const f = fixture({ env: {} });
187
- f.mkdir('.robota');
188
- f.mkdir('.robota/sessions');
187
+ f.mkdir('.fixture-product');
188
+ f.mkdir('.fixture-product/sessions');
189
189
  // Unquoted value: JSON.parse's message quotes the bytes around the fault, i.e. the credential.
190
190
  f.write(
191
- '.robota/settings.json',
191
+ '.fixture-product/settings.json',
192
192
  `{"currentProvider":"p","providers":{"p":{"apiKey": ${MARKERS.adjacent}}}}`,
193
193
  );
194
194
  const report = await runDoctor(f.inputs, f.deps);
195
- const text = renderDoctorReport(report, 'robota doctor').join('\n');
195
+ const text = renderDoctorReport(report, 'fixture-agent doctor').join('\n');
196
196
  expect(text).not.toContain(MARKERS.adjacent);
197
197
  expect(text).not.toContain(MARKERS.adjacent.slice(0, 10));
198
198
  expect(text).not.toContain('..."');
@@ -200,14 +200,14 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
200
200
  status: 'fail',
201
201
  cause: expect.stringContaining('SettingsParseError'),
202
202
  });
203
- expect(byId(report, 'settings.user.robota')).toMatchObject({ status: 'fail' });
203
+ expect(byId(report, 'settings.user.fixture-product')).toMatchObject({ status: 'fail' });
204
204
  });
205
205
 
206
206
  it('an unparseable user settings file is a failed plugin check, not a crash', async () => {
207
207
  const f = fixture({ env: {} });
208
- f.mkdir('.robota');
209
- f.mkdir('.robota/sessions');
210
- f.write('.robota/settings.json', '{ "enabledPlugins": { "helper": false }, }');
208
+ f.mkdir('.fixture-product');
209
+ f.mkdir('.fixture-product/sessions');
210
+ f.write('.fixture-product/settings.json', '{ "enabledPlugins": { "helper": false }, }');
211
211
 
212
212
  const report = await runDoctor(f.inputs, f.deps);
213
213
 
@@ -220,7 +220,7 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
220
220
  it('TC-04: derives the reachability host from profile baseURL, then defaults.baseURL, then endpoint', async () => {
221
221
  const profile = fixture({ env: {} });
222
222
  profile.write(
223
- '.robota/settings.json',
223
+ '.fixture-product/settings.json',
224
224
  JSON.stringify({
225
225
  currentProvider: 'p',
226
226
  providers: {
@@ -241,7 +241,7 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
241
241
  ],
242
242
  });
243
243
  defaults.write(
244
- '.robota/settings.json',
244
+ '.fixture-product/settings.json',
245
245
  JSON.stringify({
246
246
  currentProvider: 'p',
247
247
  providers: { p: { type: 'fixture', model: 'm', apiKey: 'k1234' } },
@@ -252,7 +252,7 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
252
252
 
253
253
  const endpoint = fixture({ env: {} });
254
254
  endpoint.write(
255
- '.robota/settings.json',
255
+ '.fixture-product/settings.json',
256
256
  JSON.stringify({
257
257
  currentProvider: 'p',
258
258
  providers: { p: { type: 'fixture', model: 'm', apiKey: 'k1234' } },
@@ -267,7 +267,7 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
267
267
  providerDefinitions: [fixtureProviderDefinition({ endpoint: undefined })],
268
268
  });
269
269
  none.write(
270
- '.robota/settings.json',
270
+ '.fixture-product/settings.json',
271
271
  JSON.stringify({
272
272
  currentProvider: 'p',
273
273
  providers: { p: { type: 'fixture', model: 'm', apiKey: 'k1234' } },
@@ -280,10 +280,10 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
280
280
 
281
281
  it('TC-04: an unreachable host is warn, not fail, so an offline doctor still exits 0', async () => {
282
282
  const f = fixture({ env: {}, reachable: false });
283
- f.mkdir('.robota');
284
- f.mkdir('.robota/sessions');
283
+ f.mkdir('.fixture-product');
284
+ f.mkdir('.fixture-product/sessions');
285
285
  f.write(
286
- '.robota/settings.json',
286
+ '.fixture-product/settings.json',
287
287
  JSON.stringify({
288
288
  currentProvider: 'p',
289
289
  providers: { p: { type: 'fixture', model: 'm', apiKey: 'k1234' } },
@@ -301,15 +301,15 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
301
301
  expect(byId(report, 'provider.resolution').status).toBe('fail');
302
302
 
303
303
  const win = fixture({ env: {}, guarantee: 'windows-acl' });
304
- win.mkdir('.robota');
305
- win.mkdir('.robota/sessions');
304
+ win.mkdir('.fixture-product');
305
+ win.mkdir('.fixture-product/sessions');
306
306
  expect(byId(await runDoctor(win.inputs, win.deps), 'storage.user').status).toBe('not-probed');
307
307
  });
308
308
 
309
309
  it('TC-05: a too-open storage directory is warn and repairable', async () => {
310
310
  const f = fixture({ env: {} });
311
- f.mkdir('.robota', 0o755);
312
- f.mkdir('.robota/sessions', 0o755);
311
+ f.mkdir('.fixture-product', 0o755);
312
+ f.mkdir('.fixture-product/sessions', 0o755);
313
313
  const report = await runDoctor(f.inputs, f.deps);
314
314
  expect(byId(report, 'storage.user')).toMatchObject({ status: 'warn', repair: 'storage.user' });
315
315
  });
@@ -317,13 +317,13 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
317
317
  it('TC-07: repair rewrites only an empty user settings file, re-reads before writing, and is idempotent', async () => {
318
318
  const f = fixture({ env: {} });
319
319
  installBrokenHome(f);
320
- const path = join(f.home, '.robota', 'settings.json');
320
+ const path = join(f.home, '.fixture-product', 'settings.json');
321
321
 
322
322
  expect(planDoctorRepair('nope', f.inputs, f.deps)).toMatchObject({ ok: false });
323
323
  expect(planDoctorRepair('settings.user.claude', f.inputs, f.deps)).toMatchObject({ ok: false });
324
324
 
325
325
  const refused = await applyDoctorRepair(
326
- 'settings.user.robota',
326
+ 'settings.user.fixture-product',
327
327
  f.inputs,
328
328
  f.deps,
329
329
  async () => false,
@@ -332,26 +332,26 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
332
332
  expect(readFileSync(path, 'utf8')).toBe('');
333
333
 
334
334
  // The state changes between confirmation and write: refused, nothing overwritten.
335
- const raced = await applyDoctorRepair('settings.user.robota', f.inputs, f.deps, async () => {
336
- f.write('.robota/settings.json', '{"language":"ko"}');
335
+ const raced = await applyDoctorRepair('settings.user.fixture-product', f.inputs, f.deps, async () => {
336
+ f.write('.fixture-product/settings.json', '{"language":"ko"}');
337
337
  return true;
338
338
  });
339
339
  expect(raced).toMatchObject({ applied: false });
340
340
  expect(readFileSync(path, 'utf8')).toBe('{"language":"ko"}');
341
341
 
342
- f.write('.robota/settings.json', '');
342
+ f.write('.fixture-product/settings.json', '');
343
343
  const applied = await applyDoctorRepair(
344
- 'settings.user.robota',
344
+ 'settings.user.fixture-product',
345
345
  f.inputs,
346
346
  f.deps,
347
347
  async () => true,
348
348
  );
349
349
  expect(applied.applied).toBe(true);
350
350
  expect(JSON.parse(readFileSync(path, 'utf8'))).toEqual({});
351
- expect(byId(await runDoctor(f.inputs, f.deps), 'settings.user.robota').status).toBe('ok');
351
+ expect(byId(await runDoctor(f.inputs, f.deps), 'settings.user.fixture-product').status).toBe('ok');
352
352
 
353
353
  const again = await applyDoctorRepair(
354
- 'settings.user.robota',
354
+ 'settings.user.fixture-product',
355
355
  f.inputs,
356
356
  f.deps,
357
357
  async () => true,
@@ -363,7 +363,7 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
363
363
  const f = fixture({ env: {} });
364
364
  const applied = await applyDoctorRepair('storage.user', f.inputs, f.deps, async () => true);
365
365
  expect(applied.applied).toBe(true);
366
- expect(statSync(join(f.home, '.robota', 'sessions')).mode & 0o077).toBe(0);
366
+ expect(statSync(join(f.home, '.fixture-product', 'sessions')).mode & 0o077).toBe(0);
367
367
  expect(byId(await runDoctor(f.inputs, f.deps), 'storage.user').status).toBe('ok');
368
368
  expect(
369
369
  await applyDoctorRepair('storage.user', f.inputs, f.deps, async () => true),
@@ -372,14 +372,14 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
372
372
 
373
373
  it('TC-05 (skills): a skill definition session discovery throws on is a fail check, not a warning', async () => {
374
374
  const f = fixture({ env: {} });
375
- f.mkdir('.robota');
376
- f.mkdir('.robota/sessions');
377
- f.write('.robota/settings.json', JSON.stringify(CLEAN_SETTINGS));
378
- f.write('.robota/skills/bad-effort/SKILL.md', '---\nname: bad\neffort: extreme\n---\nbody\n');
379
- f.write('.robota/skills/no-frontmatter/SKILL.md', 'just text\n');
375
+ f.mkdir('.fixture-product');
376
+ f.mkdir('.fixture-product/sessions');
377
+ f.write('.fixture-product/settings.json', JSON.stringify(CLEAN_SETTINGS));
378
+ f.write('.fixture-product/skills/bad-effort/SKILL.md', '---\nname: bad\neffort: extreme\n---\nbody\n');
379
+ f.write('.fixture-product/skills/no-frontmatter/SKILL.md', 'just text\n');
380
380
  const report = await runDoctor(f.inputs, f.deps);
381
381
  expect(
382
- byId(report, `skill.${join('.robota', 'skills', 'bad-effort', 'SKILL.md')}`),
382
+ byId(report, `skill.${join('.fixture-product', 'skills', 'bad-effort', 'SKILL.md')}`),
383
383
  ).toMatchObject({
384
384
  status: 'fail',
385
385
  cause: 'frontmatter-invalid',
@@ -387,7 +387,7 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
387
387
  });
388
388
  expect(JSON.stringify(report)).not.toContain('received "extreme"');
389
389
  expect(
390
- byId(report, `skill.${join('.robota', 'skills', 'no-frontmatter', 'SKILL.md')}`).status,
390
+ byId(report, `skill.${join('.fixture-product', 'skills', 'no-frontmatter', 'SKILL.md')}`).status,
391
391
  ).toBe('warn');
392
392
  expect(report.exitCode).toBe(1);
393
393
  });
@@ -417,10 +417,10 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
417
417
 
418
418
  it('TC-05: reports the quarantined provider endpoint without printing either credential', async () => {
419
419
  const f = fixture({ env: {} });
420
- f.mkdir('.robota');
421
- f.mkdir('.robota/sessions');
420
+ f.mkdir('.fixture-product');
421
+ f.mkdir('.fixture-product/sessions');
422
422
  f.write(
423
- '.robota/settings.json',
423
+ '.fixture-product/settings.json',
424
424
  JSON.stringify({
425
425
  currentProvider: 'q',
426
426
  providers: {
@@ -442,16 +442,16 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
442
442
  status: 'warn',
443
443
  cause: expect.stringContaining('quarantined'),
444
444
  });
445
- const text = renderDoctorReport(report, 'robota doctor').join('\n');
445
+ const text = renderDoctorReport(report, 'fixture-agent doctor').join('\n');
446
446
  expect(text).not.toContain(MARKERS.profile);
447
447
  expect(text).toContain('[provider.security] warn');
448
448
  });
449
449
 
450
450
  it('a throwing endpoint probe is a provider fail check, not a rejected run', async () => {
451
451
  const f = fixture({ env: {} });
452
- f.mkdir('.robota');
453
- f.mkdir('.robota/sessions');
454
- f.write('.robota/settings.json', JSON.stringify(CLEAN_SETTINGS));
452
+ f.mkdir('.fixture-product');
453
+ f.mkdir('.fixture-product/sessions');
454
+ f.write('.fixture-product/settings.json', JSON.stringify(CLEAN_SETTINGS));
455
455
  const deps = {
456
456
  ...f.deps,
457
457
  probeEndpoint: () => Promise.reject(new RangeError('socket table exhausted')),
@@ -137,7 +137,7 @@ function trustCheck(inputs: IDoctorInputs): IDoctorCheck {
137
137
  }
138
138
 
139
139
  /**
140
- * The credential-quarantine report `robota diagnose` already shipped (CLI-067), over the inspection's
140
+ * The credential-quarantine report already shipped (CLI-067), over the inspection's
141
141
  * parsed layers instead of a private re-read: a lower-trust layer that changed a profile's `baseURL`
142
142
  * without supplying its own key had the inherited key removed. Neither key is ever shown.
143
143
  */
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * The doctor's evidence model (OBSERVABILITY-1991).
3
3
  *
4
- * `fail` is the only status that raises the exit code — the CLI-067 contract `robota diagnose`
5
- * shipped with. `not-configured` names an absent optional capability out loud instead of treating
4
+ * `fail` is the only status that raises the exit code — the diagnostic command contract shipped
5
+ * with. `not-configured` names an absent optional capability out loud instead of treating
6
6
  * it as healthy. `not-probed` is a CLOSED list of probes the read-only doctor deliberately does not
7
7
  * run (see {@link TDoctorNotProbed}); an UNEXPECTED inability to probe is `warn` with its reason,
8
8
  * never `not-probed` — the third state `enforcement-architecture.md` says must never read as a pass.
@@ -39,7 +39,7 @@ function fakeHandoff(canHandoff: boolean): ITerminalHandoff {
39
39
 
40
40
  /** Write a fake `$EDITOR`: a script that writes `content` into the file it is given as $1. */
41
41
  function installFakeEditor(content: string): string {
42
- const dir = realpathSync(mkdtempSync(join(tmpdir(), 'robota-fake-editor-')));
42
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), 'agent-test-fake-editor-')));
43
43
  const script = join(dir, 'fake-editor.sh');
44
44
  writeFileSync(
45
45
  script,
@@ -144,7 +144,7 @@ describe('/fork writes the copy and starts the job that resumes it (CLI-1994 TC-
144
144
  expect(result.success).toBe(false);
145
145
  expect(result.message).toContain('depth limit reached');
146
146
  expect(result.message).toContain(FORK_SESSION_ID);
147
- expect(result.message).not.toContain('robota');
147
+ expect(result.message).not.toContain('fixture-agent');
148
148
  });
149
149
 
150
150
  it('renders a host-selected resume command after a fork spawn failure', async () => {
@@ -56,6 +56,6 @@ export function createForkCommandModule(
56
56
  systemCommands: [createForkSystemCommand(formatResumeCommand)],
57
57
  // A fork is a background agent job resuming a session record; a host with no agent runtime has
58
58
  // neither half, so the command is not offered rather than offered and refused.
59
- sessionRequirements: ['agent-runtime'],
59
+ sessionRequirements: ['runtime'],
60
60
  };
61
61
  }
@@ -40,7 +40,7 @@ const providerSettingsAdapter: IProviderCommandSettingsAdapter = {
40
40
  writeTargetSettings: () => undefined,
41
41
  };
42
42
  /** User-local storage lives in a private per-run directory, never a fixed name under /tmp. */
43
- const USER_LOCAL_STORAGE_ROOT = mkdtempSync(join(tmpdir(), 'robota-test-'));
43
+ const USER_LOCAL_STORAGE_ROOT = mkdtempSync(join(tmpdir(), 'agent-test-test-'));
44
44
  afterAll(() => rmSync(USER_LOCAL_STORAGE_ROOT, { recursive: true, force: true }));
45
45
 
46
46
  const BASE_OPTIONS = { cwd: '/tmp', userLocalStorageRoot: USER_LOCAL_STORAGE_ROOT, providerDefinitions, providerSettingsAdapter } as const;
@@ -18,7 +18,7 @@ describe('/handoff command module', () => {
18
18
  const description =
19
19
  createHandoffCommandModule().commandSources?.[0]?.getCommands()[0]?.description;
20
20
  expect(description).toContain('/handoff <session-or-device-id>');
21
- expect(description).toMatch(/linked over the device mesh/);
21
+ expect(description).toMatch(/another linked device/);
22
22
  expect(description).toMatch(/user-only/i);
23
23
  expect(description).toContain('saved, not started');
24
24
  });
@@ -8,7 +8,7 @@ export function createHandoffCommandEntry(): ICommand {
8
8
  name: 'handoff',
9
9
  displayName: 'Hand off',
10
10
  description:
11
- "Push this conversation to another running Robota session of the same user on this machine, or to another of the user's devices linked over the device mesh, after the operator confirms what stays behind (uncommitted changes, running processes; credentials never travel). The receiving operator must also accept; the session arrives saved, not started, and this one ends once it is saved there. With no argument it lists the sessions and devices it could go to. User-only: the model cannot run it; when the user wants to continue this work in another session or on another device, suggest they run `/handoff <session-or-device-id>`.",
11
+ "Push this conversation to another running session for the same user on this machine, or to another linked device, after the operator confirms what stays behind (uncommitted changes, running processes; credentials never travel). The receiving operator must also accept; the session arrives saved, not started, and this one ends once it is saved there. With no argument it lists the sessions and devices it could go to. User-only: the model cannot run it; when the user wants to continue this work in another session or on another device, suggest they run `/handoff <session-or-device-id>`.",
12
12
  source: 'handoff',
13
13
  // User-only: a hand-off moves authority over the operator's work to another place, a decision
14
14
  // about where the person is, not about the task.
@@ -31,7 +31,7 @@ export function createKeybindingsCommandEntry(): ICommand {
31
31
  // A host without a terminal (the desktop app's sidecar) still answers, so the command is never
32
32
  // "unknown" there — it says where key bindings live instead.
33
33
  const KEYBINDINGS_UNAVAILABLE =
34
- 'Key bindings belong to the robota terminal, and this surface has none. Run /keybindings in the robota terminal.';
34
+ 'Key bindings are available in an interactive terminal. Run /keybindings there.';
35
35
 
36
36
  export async function executeKeybindingsCommand(
37
37
  file: IKeybindingsFilePort | undefined,
@@ -311,7 +311,7 @@ describe('/mcp and OAuth sign-in', () => {
311
311
  it('points a server that needs a sign-in at the session command and the terminal one', async () => {
312
312
  const result = await executeMCPActivationCommand(context(withServer('files')), 'status');
313
313
  expect(result.message).toContain(
314
- 'OAuth: sign-in required (run /mcp login files, or robota mcp login files in a terminal)',
314
+ 'OAuth: sign-in required (run /mcp login files, or sign in from a terminal)',
315
315
  );
316
316
  });
317
317
 
@@ -322,14 +322,14 @@ describe('/mcp and OAuth sign-in', () => {
322
322
  ['a backslash-quote', "\\';touch pwned;#"],
323
323
  ['a leading dash', '-rf'],
324
324
  ['a leading equals sign', '=cmd'],
325
- ['a newline', 'x\nrobota mcp login good'],
325
+ ['a newline', 'x\nfixture-agent mcp login good'],
326
326
  ['an escape sequence', 'x\u001b[2Kgood'],
327
327
  ['a right-to-left override', 'x‮good'],
328
328
  ])('never puts a name with %s into the sign-in command', async (_what, name) => {
329
329
  const result = await executeMCPActivationCommand(context(withServer(name)), 'status');
330
330
  const hint = /\(run \/mcp login [^)]*\)/.exec(result.message)?.[0];
331
331
  expect(hint).toBe(
332
- '(run /mcp login <server>, or robota mcp login <server> in a terminal; ' +
332
+ '(run /mcp login <server>, or sign in from a terminal; ' +
333
333
  'its name cannot be shown safely here)',
334
334
  );
335
335
  });
@@ -529,7 +529,7 @@ describe('/mcp login', () => {
529
529
  const h = harness(async (request) => signedIn(request.serverId));
530
530
  const result = await h.run('login files --client-secret');
531
531
  expect(result.success).toBe(false);
532
- expect(result.message).toContain('robota mcp login files --client-secret in a terminal');
532
+ expect(result.message).toContain('Sign in from a terminal configured for this product.');
533
533
  expect(h.requests).toEqual([]);
534
534
  });
535
535
 
@@ -542,7 +542,7 @@ describe('/mcp login', () => {
542
542
  tools: [],
543
543
  })).run('login files');
544
544
  expect((await failing(true)).message).toContain(
545
- 'run robota mcp login files --client-secret in a terminal',
545
+ 'sign in from a terminal configured for this product',
546
546
  );
547
547
  expect((await failing(false)).message).not.toContain('--client-secret');
548
548
  });
@@ -557,7 +557,7 @@ describe('/mcp login', () => {
557
557
  expect((await h.run('login files')).message).toContain('/mcp login files --no-browser');
558
558
  expect((await h.run('login x$(id)')).message).toContain('/mcp login <server> --no-browser');
559
559
  expect((await h.run('login =cmd --client-secret')).message).toContain(
560
- 'robota mcp login <server> --client-secret',
560
+ 'Sign in from a terminal configured for this product.',
561
561
  );
562
562
  });
563
563
 
@@ -880,14 +880,14 @@ describe('/mcp reload (#3282 §4 part b-2)', () => {
880
880
  });
881
881
 
882
882
  describe('/mcp command entry', () => {
883
- it('opens only `status` to the model, and describes login for the user', () => {
883
+ it('opens status and skill metadata to the model, and describes login for the user', () => {
884
884
  const entry = createMCPActivationCommandEntry();
885
885
  // Model-invocable only through its read-only `status`; every verb that changes trust or a
886
886
  // credential is declared user-only (model-exposure.test.ts pins the refusal).
887
887
  expect(entry.modelInvocable).toBe(true);
888
888
  expect(
889
889
  (entry.subcommands ?? []).filter((sub) => sub.modelInvocable === true).map((s) => s.name),
890
- ).toEqual(['status']);
890
+ ).toEqual(['status', 'skill-list']);
891
891
  expect(entry.description).toMatch(/sign in/);
892
892
  expect(entry.description).toMatch(/Returns/);
893
893
  expect(entry.argumentHint).toContain('login <server> [--no-browser]');
@@ -26,7 +26,7 @@ function summary(overrides: Partial<ICommandMCPActivationSummary>): ICommandMCPA
26
26
  status: 'pending',
27
27
  allowed: false,
28
28
  reason: 'SECRET-REASON-TEXT from a definition',
29
- provenanceId: 'project-settings:/home/me/repo/.robota/settings.json',
29
+ provenanceId: 'project-settings:/home/me/repo/.fixture-state/settings.json',
30
30
  definitionFingerprint: 'fingerprint-abc',
31
31
  securityIdentity: 'https://user:hunter2@mcp.example.com/?token=abc',
32
32
  ...overrides,
@@ -42,7 +42,7 @@ const adapter: ICommandMCPActivationAdapter = {
42
42
  summary({ serverId: 'gone', status: 'rejected' }),
43
43
  ],
44
44
  sourceProblems: () => [
45
- { source: 'project', origin: '/home/me/repo/.robota/settings.json', reason: 'not json' },
45
+ { source: 'project', origin: '/home/me/repo/.fixture-state/settings.json', reason: 'not json' },
46
46
  ],
47
47
  approve: () => {
48
48
  throw new Error('not used');
@@ -56,11 +56,14 @@ const adapter: ICommandMCPActivationAdapter = {
56
56
  oauthStatus: async () => [{ serverId: 'github', state: 'sign-in-required' }],
57
57
  };
58
58
 
59
- function host(source: TCommandInvocationSource) {
59
+ function host(source: TCommandInvocationSource, cliName = 'test-agent', terminal = false) {
60
60
  return createTestCommandHost({
61
61
  overrides: {
62
- getCommandHostAdapters: () => ({ mcpActivation: adapter }),
62
+ getCommandHostAdapters: () => ({
63
+ mcpActivation: terminal ? { ...adapter, userActionSurface: 'terminal' } : adapter,
64
+ }),
63
65
  getCommandInvocationSource: () => source,
66
+ getCommandProductVocabulary: () => ({ cliName, displayName: cliName }),
64
67
  },
65
68
  });
66
69
  }
@@ -75,7 +78,7 @@ describe('/mcp status as the model sees it', () => {
75
78
  'MCP servers:',
76
79
  ' github — approved — sign-in: sign-in-required — ask the user to run `/mcp login github`',
77
80
  ' linear — pending — ask the user to run `/mcp approve linear`',
78
- ' jira — untrusted — ask the user to run `robota trust`',
81
+ ' jira — untrusted — ask the user to run `test-agent trust`',
79
82
  ' (name not shown) — stale — ask the user to run `/mcp approve <server>`',
80
83
  ' gone — rejected',
81
84
  '1 MCP configuration source(s) could not be read; the user can see which with `/mcp status`.',
@@ -90,12 +93,13 @@ describe('/mcp status as the model sees it', () => {
90
93
  overrides: {
91
94
  getCommandHostAdapters: () => ({ mcpActivation: terminal }),
92
95
  getCommandInvocationSource: () => 'model',
96
+ getCommandProductVocabulary: () => ({ cliName: 'test-agent', displayName: 'Test Agent' }),
93
97
  },
94
98
  }),
95
99
  'status',
96
100
  );
97
101
  expect(result.message).toContain(
98
- 'github — approved — sign-in: sign-in-required — ask the user to run `robota mcp login github`',
102
+ 'github — approved — sign-in: sign-in-required — ask the user to run `test-agent mcp login github`',
99
103
  );
100
104
  expect(result.message).not.toContain('/mcp login');
101
105
  // Approval has no terminal command; it stays the session command.
@@ -168,24 +172,51 @@ describe('MCP notices for the model', () => {
168
172
  'cannot do this yourself; ask the user to run `/mcp approve linear`.',
169
173
  );
170
174
  expect(mcpUserActionNotice('github', 'sign-in')).toContain('`/mcp login github`');
171
- expect(mcpUserActionNotice('jira', 'trust-workspace')).toContain(
172
- '`robota trust` in a terminal, then restart the session',
175
+ expect(mcpUserActionNotice('jira', 'trust-workspace', 'session', 'test-agent')).toContain(
176
+ '`test-agent trust` in a terminal, then restart the session',
173
177
  );
174
178
  });
175
179
 
176
180
  it('names the terminal sign-in command where no /mcp command can be typed', () => {
177
- expect(mcpUserActionNotice('github', 'sign-in', 'terminal')).toBe(
181
+ expect(mcpUserActionNotice('github', 'sign-in', 'terminal', 'test-agent')).toBe(
178
182
  'MCP server "github" needs the user to sign in, so its tools are unavailable. You cannot do ' +
179
- 'this yourself; ask the user to run `robota mcp login github` in a terminal, then restart ' +
183
+ 'this yourself; ask the user to run `test-agent mcp login github` in a terminal, then restart ' +
180
184
  'the session.',
181
185
  );
182
- expect(mcpUserActionCommand('$(curl evil)', 'sign-in', 'terminal')).toBe(
183
- 'robota mcp login <server>',
186
+ expect(mcpUserActionCommand('$(curl evil)', 'sign-in', 'terminal', 'test-agent')).toBe(
187
+ 'test-agent mcp login <server>',
184
188
  );
185
189
  // Only sign-in moves: approval has no terminal command.
186
190
  expect(mcpUserActionCommand('linear', 'approve', 'terminal')).toBe('/mcp approve linear');
187
191
  });
188
192
 
193
+ it('uses the command vocabulary supplied by each host and stays generic when it is absent', async () => {
194
+ const first = await executeMCPActivationCommand(host('model', 'alpha-agent', true), 'status');
195
+ const second = await executeMCPActivationCommand(host('model', 'beta-agent', true), 'status');
196
+ const noVocabulary = createTestCommandHost({
197
+ overrides: {
198
+ getCommandHostAdapters: () => ({
199
+ mcpActivation: { ...adapter, userActionSurface: 'terminal' },
200
+ }),
201
+ getCommandInvocationSource: () => 'model',
202
+ },
203
+ });
204
+ const generic = await executeMCPActivationCommand(noVocabulary, 'status');
205
+
206
+ expect(first.message).toContain('`alpha-agent mcp login github`');
207
+ expect(first.message).not.toContain('beta-agent');
208
+ expect(second.message).toContain('`beta-agent mcp login github`');
209
+ expect(second.message).not.toContain('alpha-agent');
210
+ expect(generic.message).not.toContain('mcp login');
211
+ expect(generic.message).not.toMatch(/undefined|fixture-agent/i);
212
+ expect(mcpUserActionNotice('jira', 'trust-workspace', 'session', 'alpha-agent')).toContain(
213
+ '`alpha-agent trust` in a terminal, then restart the session',
214
+ );
215
+ expect(mcpUserActionNotice('jira', 'trust-workspace')).toContain(
216
+ 'ask the user to trust this workspace in a terminal',
217
+ );
218
+ });
219
+
189
220
  it('stays generic for a name that is not safe to show', () => {
190
221
  const notice = mcpUserActionNotice('$(curl evil)', 'sign-in');
191
222
  expect(notice).not.toContain('curl');