deepclause-sdk 0.0.61 → 0.0.62

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 (63) hide show
  1. package/README.md +47 -5
  2. package/dist/cli/compile.d.ts +10 -0
  3. package/dist/cli/compile.d.ts.map +1 -1
  4. package/dist/cli/compile.js +62 -11
  5. package/dist/cli/compile.js.map +1 -1
  6. package/dist/cli/config.d.ts +5 -0
  7. package/dist/cli/config.d.ts.map +1 -1
  8. package/dist/cli/config.js +61 -0
  9. package/dist/cli/config.js.map +1 -1
  10. package/dist/cli/index.js +118 -9
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/cli/run.d.ts +1 -0
  13. package/dist/cli/run.d.ts.map +1 -1
  14. package/dist/cli/run.js +1 -0
  15. package/dist/cli/run.js.map +1 -1
  16. package/dist/cli/tui.d.ts +27 -1
  17. package/dist/cli/tui.d.ts.map +1 -1
  18. package/dist/cli/tui.js +430 -23
  19. package/dist/cli/tui.js.map +1 -1
  20. package/dist/compiler.d.ts +10 -0
  21. package/dist/compiler.d.ts.map +1 -1
  22. package/dist/compiler.js +27 -6
  23. package/dist/compiler.js.map +1 -1
  24. package/dist/runner.d.ts.map +1 -1
  25. package/dist/runner.js +26 -19
  26. package/dist/runner.js.map +1 -1
  27. package/dist/sdk.d.ts.map +1 -1
  28. package/dist/sdk.js +44 -0
  29. package/dist/sdk.js.map +1 -1
  30. package/dist/system/assets/docs/CONDUCTOR_PROMPT.md +30 -5
  31. package/dist/system/assets/docs/TUI.md +139 -0
  32. package/dist/system/assets/index.d.ts +11 -1
  33. package/dist/system/assets/index.d.ts.map +1 -1
  34. package/dist/system/assets/index.js +62 -7
  35. package/dist/system/assets/index.js.map +1 -1
  36. package/dist/system/runtime/agentvm-manager.d.ts +28 -1
  37. package/dist/system/runtime/agentvm-manager.d.ts.map +1 -1
  38. package/dist/system/runtime/agentvm-manager.js +18 -2
  39. package/dist/system/runtime/agentvm-manager.js.map +1 -1
  40. package/dist/system/runtime/conductor.d.ts.map +1 -1
  41. package/dist/system/runtime/conductor.js +8 -4
  42. package/dist/system/runtime/conductor.js.map +1 -1
  43. package/dist/system/runtime/dml-executor.js +58 -54
  44. package/dist/system/runtime/dml-executor.js.map +1 -1
  45. package/dist/system/runtime/runtime-tools.d.ts +2 -1
  46. package/dist/system/runtime/runtime-tools.d.ts.map +1 -1
  47. package/dist/system/runtime/runtime-tools.js +17 -2
  48. package/dist/system/runtime/runtime-tools.js.map +1 -1
  49. package/dist/system/runtime/shell-manager.d.ts +3 -3
  50. package/dist/system/runtime/shell-manager.d.ts.map +1 -1
  51. package/dist/system/runtime/shell-manager.js +31 -3
  52. package/dist/system/runtime/shell-manager.js.map +1 -1
  53. package/dist/system/runtime/shell-tool-events.d.ts +11 -0
  54. package/dist/system/runtime/shell-tool-events.d.ts.map +1 -0
  55. package/dist/system/runtime/shell-tool-events.js +139 -0
  56. package/dist/system/runtime/shell-tool-events.js.map +1 -0
  57. package/dist/system/runtime/skill-creator.d.ts +3 -0
  58. package/dist/system/runtime/skill-creator.d.ts.map +1 -1
  59. package/dist/system/runtime/skill-creator.js +41 -25
  60. package/dist/system/runtime/skill-creator.js.map +1 -1
  61. package/dist/types.d.ts +12 -0
  62. package/dist/types.d.ts.map +1 -1
  63. package/package.json +1 -1
package/dist/cli/tui.js CHANGED
@@ -4,11 +4,15 @@ import { emitKeypressEvents } from 'readline';
4
4
  import { createInterface } from 'readline/promises';
5
5
  import { stdin as input, stdout as output } from 'process';
6
6
  import { listCommands } from './commands.js';
7
- import { getConfigPath, loadConfig, resolveModelSlot } from './config.js';
7
+ import { getConfigPath, loadConfig, resolveModelSlot, setModel } from './config.js';
8
8
  import { run } from './run.js';
9
9
  import { createLocalSkill, createConductorSession, getConductorSessionDetail, listConductorSessions, runConductorTurn, } from '../system/runtime/conductor.js';
10
+ import { getSystemAssetSourcePaths } from '../system/assets/index.js';
11
+ import { createShellManager } from '../system/runtime/shell-manager.js';
12
+ import { buildToolCompletionEvent, buildToolFailureEvent, buildToolStartEvent, createShellToolEventBridge, } from '../system/runtime/shell-tool-events.js';
10
13
  const IGNORED_LIVE_LOG_TOOLS = new Set(['set_result', 'update_memory']);
11
- const BUILTIN_SLASH_COMMANDS = ['new', 'sessions', 'help', 'compile', 'skill-creator', 'cancel', 'exit', 'quit'];
14
+ const BUILTIN_SLASH_COMMANDS = ['new', 'sessions', 'help', 'compile', 'skill-creator', 'set-model', 'cancel', 'exit', 'quit'];
15
+ const MODEL_SLOTS = ['gateway', 'run', 'compile'];
12
16
  const CHILD_EVENT_INDENT = '\t';
13
17
  const CHILD_EVENT_TAB_WIDTH = 4;
14
18
  const MAX_ACTIVITY_LINES = 400;
@@ -67,10 +71,7 @@ export class LiveExecutionPrinter {
67
71
  this.flushStream();
68
72
  switch (event.type) {
69
73
  case 'tool_call':
70
- if (!event.toolName || IGNORED_LIVE_LOG_TOOLS.has(event.toolName)) {
71
- return;
72
- }
73
- this.writeLine(`${formatEventPrefix(logEvent)}tool ${event.toolName}(${formatToolArgs(event.toolArgs)})`);
74
+ this.handleToolCall(logEvent);
74
75
  break;
75
76
  case 'output':
76
77
  if (event.content) {
@@ -126,9 +127,17 @@ export class LiveExecutionPrinter {
126
127
  this.streamOpen = false;
127
128
  this.activeStreamKey = null;
128
129
  }
130
+ handleToolCall(logEvent) {
131
+ const line = formatToolEventLine(logEvent);
132
+ if (!line) {
133
+ return;
134
+ }
135
+ this.writeLine(line);
136
+ }
129
137
  }
130
- class ActivityBuffer {
138
+ export class ActivityBuffer {
131
139
  lines = [];
140
+ activeTools = new Map();
132
141
  activeStreamKey = null;
133
142
  activeStreamLine = '';
134
143
  handle(logEvent) {
@@ -143,10 +152,7 @@ class ActivityBuffer {
143
152
  this.flushStream();
144
153
  switch (event.type) {
145
154
  case 'tool_call':
146
- if (!event.toolName || IGNORED_LIVE_LOG_TOOLS.has(event.toolName)) {
147
- return;
148
- }
149
- this.pushLine(`${formatEventPrefix(logEvent)}tool ${event.toolName}(${formatToolArgs(event.toolArgs)})`);
155
+ this.handleToolCall(logEvent);
150
156
  break;
151
157
  case 'output':
152
158
  if (event.content) {
@@ -185,16 +191,25 @@ class ActivityBuffer {
185
191
  }
186
192
  finish() {
187
193
  this.flushStream();
194
+ this.activeTools.clear();
188
195
  }
189
196
  clear() {
190
197
  this.lines.length = 0;
198
+ this.activeTools.clear();
191
199
  this.activeStreamKey = null;
192
200
  this.activeStreamLine = '';
193
201
  }
194
202
  snapshot() {
195
- return this.activeStreamLine
203
+ const body = this.activeStreamLine
196
204
  ? [...this.lines, this.activeStreamLine]
197
205
  : [...this.lines];
206
+ const activeLines = this.buildActiveToolLines();
207
+ if (activeLines.length === 0) {
208
+ return body;
209
+ }
210
+ return body.length > 0
211
+ ? [...activeLines, '', ...body]
212
+ : activeLines;
198
213
  }
199
214
  handleStream(logEvent) {
200
215
  const streamKey = logEvent.scope === 'child' ? `child:${logEvent.childSlug ?? '?'}` : 'main';
@@ -225,6 +240,43 @@ class ActivityBuffer {
225
240
  this.lines.splice(0, this.lines.length - MAX_ACTIVITY_LINES);
226
241
  }
227
242
  }
243
+ handleToolCall(logEvent) {
244
+ const { event } = logEvent;
245
+ if (!event.toolName || IGNORED_LIVE_LOG_TOOLS.has(event.toolName)) {
246
+ return;
247
+ }
248
+ if (event.toolState) {
249
+ const scopeKey = toolScopeKey(logEvent);
250
+ if (event.toolState === 'starting' || event.toolState === 'running') {
251
+ this.activeTools.set(scopeKey, {
252
+ scopeKey,
253
+ scopeLabel: toolScopeLabel(logEvent),
254
+ toolName: event.toolName,
255
+ toolState: event.toolState,
256
+ toolPid: event.toolPid,
257
+ toolBackend: event.toolBackend,
258
+ });
259
+ }
260
+ else {
261
+ this.activeTools.delete(scopeKey);
262
+ }
263
+ }
264
+ const line = formatToolEventLine(logEvent);
265
+ if (line) {
266
+ this.pushLine(line);
267
+ }
268
+ }
269
+ buildActiveToolLines() {
270
+ if (this.activeTools.size === 0) {
271
+ return [];
272
+ }
273
+ const lines = ['Active Tool Status'];
274
+ const entries = [...this.activeTools.values()].sort((left, right) => left.scopeKey.localeCompare(right.scopeKey));
275
+ for (const entry of entries) {
276
+ lines.push(formatActiveToolStatus(entry));
277
+ }
278
+ return lines;
279
+ }
228
280
  }
229
281
  class FullscreenTui {
230
282
  workspaceRoot;
@@ -263,6 +315,7 @@ class FullscreenTui {
263
315
  editorState = null;
264
316
  recentFiles = [];
265
317
  lastSubmittedInput = null;
318
+ shellWorkspacePath;
266
319
  renderTimer = null;
267
320
  animationTimer = null;
268
321
  closeResolver = null;
@@ -272,6 +325,7 @@ class FullscreenTui {
272
325
  constructor(workspaceRoot, options = {}) {
273
326
  this.workspaceRoot = workspaceRoot;
274
327
  this.options = options;
328
+ this.shellWorkspacePath = path.resolve(workspaceRoot, 'workspace');
275
329
  }
276
330
  async start() {
277
331
  try {
@@ -428,8 +482,12 @@ class FullscreenTui {
428
482
  await this.applyCompletion();
429
483
  return;
430
484
  case 'escape':
431
- this.inputValue = '';
432
- this.cursor = 0;
485
+ {
486
+ const previousInput = this.inputValue;
487
+ this.inputValue = '';
488
+ this.cursor = 0;
489
+ this.refreshCommandBarStatus(previousInput);
490
+ }
433
491
  break;
434
492
  default:
435
493
  if (text && !key.ctrl && !key.meta) {
@@ -711,7 +769,7 @@ class FullscreenTui {
711
769
  this.requestRender();
712
770
  return;
713
771
  }
714
- const parsed = parseSlashInput(trimmedInput);
772
+ const parsed = parseCommandBarInput(rawInput);
715
773
  if (this.busy && !this.pendingPrompt && !canSubmitParsedInputWhileBusy(parsed)) {
716
774
  this.statusLine = 'Execution is still running. Use /cancel or Ctrl+C to stop it.';
717
775
  this.requestRender();
@@ -725,6 +783,10 @@ class FullscreenTui {
725
783
  await this.executeSkill(parsed.name, parsed.args);
726
784
  return;
727
785
  }
786
+ if (parsed.kind === 'shell') {
787
+ await this.executeShellCommand(parsed.command);
788
+ return;
789
+ }
728
790
  await this.executeTask(parsed.prompt);
729
791
  }
730
792
  async executeBuiltin(parsed) {
@@ -749,6 +811,9 @@ class FullscreenTui {
749
811
  case 'skill-creator':
750
812
  await this.executeSkillCreator(parsed.name, parsed.rawArgs);
751
813
  return;
814
+ case 'set-model':
815
+ await this.executeSetModel(parsed.rawArgs);
816
+ return;
752
817
  case 'sessions':
753
818
  await this.refreshSessions({ createIfMissing: true, selectedSessionId: this.selectedSessionId });
754
819
  await this.refreshCommands();
@@ -931,6 +996,78 @@ class FullscreenTui {
931
996
  }
932
997
  }
933
998
  }
999
+ async executeShellCommand(commandText) {
1000
+ const trimmedCommand = commandText.trim();
1001
+ if (!trimmedCommand) {
1002
+ this.statusLine = 'Enter a shell command after !.';
1003
+ this.requestRender();
1004
+ return;
1005
+ }
1006
+ const sessionId = this.selectedSessionId || 'global';
1007
+ const activity = this.activityFor(sessionId);
1008
+ this.lastSubmittedInput = { kind: 'shell', command: trimmedCommand };
1009
+ this.focusedPane = 'messages';
1010
+ this.paneScroll.messages = 0;
1011
+ this.paneScroll.process = 0;
1012
+ this.beginExecutionPreview(sessionId, 'shell', {
1013
+ role: 'system',
1014
+ kind: 'output',
1015
+ tag: 'shell',
1016
+ content: `!${trimmedCommand}`,
1017
+ }, 'shell');
1018
+ activity.pushLine(`shell !${trimmedCommand}`);
1019
+ this.busy = true;
1020
+ this.currentAbortController = new AbortController();
1021
+ this.startAnimation();
1022
+ this.statusLine = `Running !${trimmedCommand}...`;
1023
+ this.requestRender();
1024
+ try {
1025
+ const result = await runShellCommand(this.workspaceRoot, trimmedCommand, {
1026
+ sandbox: this.options.sandbox,
1027
+ signal: this.currentAbortController.signal,
1028
+ onEvent: (event) => {
1029
+ activity.handle(event);
1030
+ this.requestRender();
1031
+ },
1032
+ });
1033
+ activity.finish();
1034
+ await this.refreshCommands();
1035
+ this.finishExecutionPreview(result.success
1036
+ ? { persist: true, finalText: formatShellCommandSummary(result) }
1037
+ : { persist: true, error: formatShellCommandSummary(result) });
1038
+ this.statusLine = result.success ? 'Shell command finished.' : 'Shell command failed.';
1039
+ }
1040
+ catch (error) {
1041
+ activity.finish();
1042
+ const message = error.message;
1043
+ this.finishExecutionPreview({ persist: true, error: message });
1044
+ this.statusLine = isAbortError(error) ? 'Shell command cancelled.' : 'Shell command failed.';
1045
+ }
1046
+ finally {
1047
+ this.busy = false;
1048
+ this.currentAbortController = null;
1049
+ this.stopAnimation();
1050
+ if (this.exitRequested) {
1051
+ this.close();
1052
+ }
1053
+ else {
1054
+ this.requestRender();
1055
+ }
1056
+ }
1057
+ }
1058
+ async executeSetModel(rawArgs) {
1059
+ try {
1060
+ const { model, slot } = parseSetModelCommandArgs(rawArgs);
1061
+ const result = await setModel(this.workspaceRoot, model, slot);
1062
+ const slotsLabel = result.updatedSlots.join(', ');
1063
+ this.currentProcessActivity().pushLine(`config model ${result.modelId} slots=${slotsLabel}`);
1064
+ this.statusLine = `Model set to ${result.modelId} for ${slotsLabel}. Applies on the next turn.`;
1065
+ }
1066
+ catch (error) {
1067
+ this.statusLine = error.message;
1068
+ }
1069
+ this.requestRender();
1070
+ }
934
1071
  async requestClarification(promptText) {
935
1072
  this.ensureClarificationVisible(promptText);
936
1073
  return this.promptForValue(promptText, 'clarify');
@@ -1101,7 +1238,8 @@ class FullscreenTui {
1101
1238
  label: 'Run',
1102
1239
  items: [
1103
1240
  { id: 'run.prompt', label: 'Send Prompt…', description: 'Return to the command bar and enter a freeform prompt.', shortcut: 'Enter' },
1104
- { id: 'run.repeat', label: 'Repeat Last Command', description: 'Re-run the last prompt or skill invocation.', enabled: !!this.lastSubmittedInput },
1241
+ { id: 'run.shell', label: 'Run Shell Command…', description: 'Prefill the command bar for a direct workspace shell command.' },
1242
+ { id: 'run.repeat', label: 'Repeat Last Command', description: 'Re-run the last prompt, shell command, or skill invocation.', enabled: !!this.lastSubmittedInput },
1105
1243
  { id: 'run.cancel', label: 'Cancel Current Execution', description: 'Abort the running task or skill.', shortcut: '/cancel', enabled: this.busy },
1106
1244
  { id: 'run.clear', label: 'Clear Execution Pane', description: 'Discard the current session execution log.' },
1107
1245
  { id: 'run.refreshSkills', label: 'Refresh Skill Catalog', description: 'Re-scan the workspace for compiled skills.' },
@@ -1124,7 +1262,7 @@ class FullscreenTui {
1124
1262
  label: 'Help',
1125
1263
  items: [
1126
1264
  { id: 'help.shortcuts', label: 'Keyboard Shortcuts', description: 'Show the fullscreen keyboard map.' },
1127
- { id: 'help.slash', label: 'Slash Commands', description: 'Show available slash commands and direct skill invocation.' },
1265
+ { id: 'help.slash', label: 'Commands', description: 'Show slash commands, shell commands, and direct skill invocation.' },
1128
1266
  { id: 'help.about', label: 'About This TUI', description: 'Show a short explanation of the fullscreen workspace.' },
1129
1267
  ],
1130
1268
  },
@@ -1204,6 +1342,13 @@ class FullscreenTui {
1204
1342
  this.closeMenuMode();
1205
1343
  this.statusLine = 'Command bar ready.';
1206
1344
  return;
1345
+ case 'run.shell':
1346
+ this.closeMenuMode();
1347
+ this.inputValue = '!';
1348
+ this.cursor = this.inputValue.length;
1349
+ this.statusLine = formatShellModeStatus(this.workspaceRoot, this.shellWorkspacePath, this.options.sandbox);
1350
+ this.requestRender();
1351
+ return;
1207
1352
  case 'run.repeat':
1208
1353
  await this.repeatLastCommand();
1209
1354
  return;
@@ -1245,13 +1390,14 @@ class FullscreenTui {
1245
1390
  this.openTextViewer('Keyboard Shortcuts', getShortcutHelpLines().join('\n'), { preferredExtension: '.txt' });
1246
1391
  return;
1247
1392
  case 'help.slash':
1248
- this.openTextViewer('Slash Commands', getSlashHelpLines().join('\n'), { preferredExtension: '.txt' });
1393
+ this.openTextViewer('Commands', getSlashHelpLines().join('\n'), { preferredExtension: '.txt' });
1249
1394
  return;
1250
1395
  case 'help.about':
1251
1396
  this.openTextViewer('About This TUI', [
1252
1397
  'DeepClause fullscreen TUI',
1253
1398
  '',
1254
1399
  'Use F10 or Ctrl+G for the menu bar, Ctrl+W to cycle panes, and /cancel to stop a running execution.',
1400
+ 'Type !<command> to run a direct shell command in the configured workspace shell.',
1255
1401
  'The Skills and Files menus open searchable pickers, and Ctrl+P opens the action palette directly.',
1256
1402
  ].join('\n'), { preferredExtension: '.txt' });
1257
1403
  return;
@@ -1787,7 +1933,7 @@ class FullscreenTui {
1787
1933
  async repeatLastCommand() {
1788
1934
  if (!this.lastSubmittedInput) {
1789
1935
  this.closeMenuMode();
1790
- this.statusLine = 'No previous prompt or skill to repeat yet.';
1936
+ this.statusLine = 'No previous prompt, shell command, or skill to repeat yet.';
1791
1937
  this.requestRender();
1792
1938
  return;
1793
1939
  }
@@ -1802,6 +1948,10 @@ class FullscreenTui {
1802
1948
  await this.executeSkill(this.lastSubmittedInput.name, this.lastSubmittedInput.args);
1803
1949
  return;
1804
1950
  }
1951
+ if (this.lastSubmittedInput.kind === 'shell') {
1952
+ await this.executeShellCommand(this.lastSubmittedInput.command);
1953
+ return;
1954
+ }
1805
1955
  if (this.lastSubmittedInput.kind === 'builtin') {
1806
1956
  await this.executeBuiltin(this.lastSubmittedInput);
1807
1957
  }
@@ -1818,7 +1968,9 @@ class FullscreenTui {
1818
1968
  kind,
1819
1969
  rootTag,
1820
1970
  activeChildTag: kind === 'skill' ? rootTag : undefined,
1821
- entries: [lead, { role: 'assistant', content: '', pending: true }],
1971
+ entries: kind === 'shell'
1972
+ ? [lead]
1973
+ : [lead, { role: 'assistant', content: '', pending: true }],
1822
1974
  };
1823
1975
  }
1824
1976
  updatePreviewFromEvent(logEvent) {
@@ -1863,6 +2015,9 @@ class FullscreenTui {
1863
2015
  if (this.currentPreview.kind === 'task') {
1864
2016
  return logEvent.scope === 'main';
1865
2017
  }
2018
+ if (this.currentPreview.kind === 'shell') {
2019
+ return false;
2020
+ }
1866
2021
  return logEvent.scope === 'child' && logEvent.childSlug === this.currentPreview.rootTag;
1867
2022
  }
1868
2023
  insertPreviewMessage(entry) {
@@ -1919,6 +2074,23 @@ class FullscreenTui {
1919
2074
  return;
1920
2075
  }
1921
2076
  this.currentPreview.activeChildTag = undefined;
2077
+ if (this.currentPreview.kind === 'shell') {
2078
+ const detail = typeof options.error === 'string' && options.error.trim()
2079
+ ? { role: 'system', kind: 'output', tag: 'shell', content: options.error, error: true }
2080
+ : typeof options.finalText === 'string' && options.finalText.trim()
2081
+ ? { role: 'system', kind: 'output', tag: 'shell', content: options.finalText }
2082
+ : null;
2083
+ if (detail) {
2084
+ this.currentPreview.entries.push(detail);
2085
+ }
2086
+ if (options.persist) {
2087
+ const existing = this.ephemeralMessagesBySessionId.get(this.currentPreview.sessionId) ?? [];
2088
+ existing.push(...this.currentPreview.entries.map((entry) => ({ ...entry, pending: false })));
2089
+ this.ephemeralMessagesBySessionId.set(this.currentPreview.sessionId, existing);
2090
+ }
2091
+ this.currentPreview = null;
2092
+ return;
2093
+ }
1922
2094
  const assistant = this.ensurePreviewAssistantMessage();
1923
2095
  if (typeof options.finalText === 'string' && options.finalText.trim()) {
1924
2096
  assistant.content = options.finalText;
@@ -2029,24 +2201,49 @@ class FullscreenTui {
2029
2201
  return this.activityFor(this.selectedSessionId || 'global');
2030
2202
  }
2031
2203
  insertText(text) {
2204
+ const previousInput = this.inputValue;
2032
2205
  this.inputValue = `${this.inputValue.slice(0, this.cursor)}${text}${this.inputValue.slice(this.cursor)}`;
2033
2206
  this.cursor += text.length;
2207
+ this.refreshCommandBarStatus(previousInput);
2034
2208
  }
2035
2209
  deleteBackward() {
2036
2210
  if (this.cursor === 0) {
2037
2211
  return;
2038
2212
  }
2213
+ const previousInput = this.inputValue;
2039
2214
  this.inputValue = `${this.inputValue.slice(0, this.cursor - 1)}${this.inputValue.slice(this.cursor)}`;
2040
2215
  this.cursor -= 1;
2216
+ this.refreshCommandBarStatus(previousInput);
2041
2217
  }
2042
2218
  deleteForward() {
2043
2219
  if (this.cursor >= this.inputValue.length) {
2044
2220
  return;
2045
2221
  }
2222
+ const previousInput = this.inputValue;
2046
2223
  this.inputValue = `${this.inputValue.slice(0, this.cursor)}${this.inputValue.slice(this.cursor + 1)}`;
2224
+ this.refreshCommandBarStatus(previousInput);
2047
2225
  }
2048
2226
  async refreshCommands() {
2049
2227
  this.commands = await listCommands(this.workspaceRoot);
2228
+ await this.refreshShellContext();
2229
+ }
2230
+ async refreshShellContext() {
2231
+ const config = await loadConfig(this.workspaceRoot);
2232
+ this.shellWorkspacePath = path.resolve(this.workspaceRoot, config.workspace || './workspace');
2233
+ }
2234
+ refreshCommandBarStatus(previousInput = '') {
2235
+ if (this.busy || this.pendingPrompt || this.uiMode !== 'command') {
2236
+ return;
2237
+ }
2238
+ const wasShell = previousInput.trimStart().startsWith('!');
2239
+ const isShell = this.inputValue.trimStart().startsWith('!');
2240
+ if (isShell) {
2241
+ this.statusLine = formatShellModeStatus(this.workspaceRoot, this.shellWorkspacePath, this.options.sandbox);
2242
+ return;
2243
+ }
2244
+ if (wasShell) {
2245
+ this.statusLine = 'Command bar ready.';
2246
+ }
2050
2247
  }
2051
2248
  async refreshSessions(options = {}) {
2052
2249
  let sessions = await listConductorSessions(this.workspaceRoot);
@@ -2284,6 +2481,7 @@ class FullscreenTui {
2284
2481
  return ['No session selected.'];
2285
2482
  }
2286
2483
  const contextSize = summarizeContextSize(this.sessionDetail);
2484
+ const systemAssetSources = getSystemAssetSourcePaths(this.workspaceRoot);
2287
2485
  const usageEntries = Object.entries(this.sessionDetail.usageByModel ?? {})
2288
2486
  .sort((left, right) => right[1].totalTokens - left[1].totalTokens || left[0].localeCompare(right[0]));
2289
2487
  const body = [
@@ -2294,6 +2492,17 @@ class FullscreenTui {
2294
2492
  `Assistant Memory ${formatByteSize(contextSize.assistantMemoryBytes)} (~${formatInteger(contextSize.assistantMemoryTokens)} tok)`,
2295
2493
  `Total ${formatByteSize(contextSize.totalBytes)} (~${formatInteger(contextSize.totalTokens)} tok)`,
2296
2494
  '',
2495
+ 'System Asset Sources',
2496
+ `Conductor DML ${formatSystemAssetSourcePath(this.workspaceRoot, systemAssetSources.conductorDml)}`,
2497
+ `Conductor Prompt ${formatSystemAssetSourcePath(this.workspaceRoot, systemAssetSources.conductorPrompt)}`,
2498
+ `Skill Creator DML ${formatSystemAssetSourcePath(this.workspaceRoot, systemAssetSources.skillCreatorDml)}`,
2499
+ `Skill Creator Prompt ${formatSystemAssetSourcePath(this.workspaceRoot, systemAssetSources.skillCreatorPrompt)}`,
2500
+ 'Changes apply on the next turn or /compile run. No TUI reload is required.',
2501
+ '',
2502
+ 'Shell Execution',
2503
+ `Backend ${this.options.sandbox ? 'sandbox' : 'host'}`,
2504
+ `CWD ${this.shellWorkspacePath}`,
2505
+ '',
2297
2506
  'Session Token Usage',
2298
2507
  ];
2299
2508
  if (usageEntries.length === 0) {
@@ -2553,7 +2762,7 @@ async function startLineTui(workspaceRoot = process.cwd(), options = {}) {
2553
2762
  if (!line) {
2554
2763
  continue;
2555
2764
  }
2556
- const parsed = parseSlashInput(line);
2765
+ const parsed = parseCommandBarInput(line);
2557
2766
  if (parsed.kind === 'builtin') {
2558
2767
  switch (parsed.name) {
2559
2768
  case 'cancel':
@@ -2611,7 +2820,40 @@ async function startLineTui(workspaceRoot = process.cwd(), options = {}) {
2611
2820
  console.log(divider('-'));
2612
2821
  console.log('');
2613
2822
  continue;
2823
+ case 'set-model': {
2824
+ try {
2825
+ const { model, slot } = parseSetModelCommandArgs(parsed.rawArgs);
2826
+ const result = await setModel(workspaceRoot, model, slot);
2827
+ console.log(`Model set to ${result.modelId} (${result.updatedSlots.join(', ')}).`);
2828
+ }
2829
+ catch (error) {
2830
+ console.log(`${paint('error', ANSI.red)} ${error.message}`);
2831
+ }
2832
+ continue;
2833
+ }
2834
+ }
2835
+ }
2836
+ if (parsed.kind === 'shell') {
2837
+ console.log(divider('-'));
2838
+ console.log(`${paint('shell', ANSI.dim)} !${parsed.command}`);
2839
+ console.log(divider('-'));
2840
+ const printer = new LiveExecutionPrinter();
2841
+ try {
2842
+ const result = await runShellCommand(workspaceRoot, parsed.command, {
2843
+ sandbox: options.sandbox,
2844
+ onEvent: (event) => printer.handle(event),
2845
+ });
2846
+ printer.finish();
2847
+ console.log(formatShellCommandSummary(result));
2614
2848
  }
2849
+ catch (error) {
2850
+ printer.finish();
2851
+ console.log(`${paint('error', ANSI.red)} ${error.message}`);
2852
+ }
2853
+ console.log('');
2854
+ console.log(divider('-'));
2855
+ console.log('');
2856
+ continue;
2615
2857
  }
2616
2858
  if (parsed.kind === 'skill') {
2617
2859
  console.log(divider('-'));
@@ -2734,6 +2976,40 @@ async function runSkillCreatorCommand(workspaceRoot, sessionId, spec, options =
2734
2976
  onEvent: options.onEvent,
2735
2977
  });
2736
2978
  }
2979
+ async function runShellCommand(workspaceRoot, command, options = {}) {
2980
+ const trimmedCommand = command.trim();
2981
+ if (!trimmedCommand) {
2982
+ throw new Error('command is required');
2983
+ }
2984
+ const toolName = 'shell';
2985
+ const toolArgs = { command: trimmedCommand };
2986
+ const emit = (event) => options.onEvent?.({ scope: 'main', event });
2987
+ emit(buildToolStartEvent(toolName, toolArgs));
2988
+ let shellManager = null;
2989
+ try {
2990
+ const config = await loadConfig(workspaceRoot);
2991
+ const workspacePath = path.resolve(workspaceRoot, config.workspace || './workspace');
2992
+ shellManager = createShellManager({
2993
+ workspacePath,
2994
+ sandbox: options.sandbox,
2995
+ });
2996
+ const observer = createShellToolEventBridge({
2997
+ toolName,
2998
+ toolArgs,
2999
+ emit,
3000
+ });
3001
+ const result = await shellManager.exec(trimmedCommand, options.signal, observer);
3002
+ emit(buildToolCompletionEvent(toolName, toolArgs, result));
3003
+ return result;
3004
+ }
3005
+ catch (error) {
3006
+ emit(buildToolFailureEvent(toolName, toolArgs, error));
3007
+ throw error;
3008
+ }
3009
+ finally {
3010
+ await shellManager?.dispose();
3011
+ }
3012
+ }
2737
3013
  function formatSkillCreatorSummary(workspaceRoot, result) {
2738
3014
  const slug = typeof result.slug === 'string' ? result.slug : 'unknown-skill';
2739
3015
  const outputPath = typeof result.output_path === 'string' ? result.output_path : '';
@@ -2743,6 +3019,15 @@ function formatSkillCreatorSummary(workspaceRoot, result) {
2743
3019
  const relativeOutputPath = path.relative(workspaceRoot, outputPath);
2744
3020
  return `Published /${slug} to ${relativeOutputPath || outputPath}.`;
2745
3021
  }
3022
+ function formatShellCommandSummary(result) {
3023
+ if (result.success) {
3024
+ return result.summary || 'Shell command completed successfully.';
3025
+ }
3026
+ return result.summary || `Shell command failed with exit code ${result.exitCode}.`;
3027
+ }
3028
+ function isAbortError(error) {
3029
+ return error instanceof Error && error.name === 'AbortError';
3030
+ }
2746
3031
  export function previewMessageFromEvent(logEvent) {
2747
3032
  switch (logEvent.event.type) {
2748
3033
  case 'output':
@@ -2826,7 +3111,14 @@ export function parseCommandArgs(rawArgs) {
2826
3111
  }
2827
3112
  return args;
2828
3113
  }
2829
- export function parseSlashInput(line) {
3114
+ export function parseCommandBarInput(line) {
3115
+ const trimmedStart = line.trimStart();
3116
+ if (trimmedStart.startsWith('\\!')) {
3117
+ return { kind: 'text', prompt: trimmedStart.slice(1).trim() };
3118
+ }
3119
+ if (trimmedStart.startsWith('!')) {
3120
+ return { kind: 'shell', command: trimmedStart.slice(1).trim() };
3121
+ }
2830
3122
  const trimmed = line.trim();
2831
3123
  if (!trimmed.startsWith('/')) {
2832
3124
  return { kind: 'text', prompt: trimmed };
@@ -2858,6 +3150,9 @@ export function parseSlashInput(line) {
2858
3150
  args: parseCommandArgs(rawArgs),
2859
3151
  };
2860
3152
  }
3153
+ export function parseSlashInput(line) {
3154
+ return parseCommandBarInput(line);
3155
+ }
2861
3156
  export function canSubmitParsedInputWhileBusy(parsed) {
2862
3157
  return parsed.kind === 'builtin' && (parsed.name === 'cancel' || parsed.name === 'exit' || parsed.name === 'quit');
2863
3158
  }
@@ -2940,11 +3235,56 @@ async function createSessionInteractive(rl, workspaceRoot) {
2940
3235
  function renderHeader(session) {
2941
3236
  console.log(divider('='));
2942
3237
  console.log(`${paint('DeepClause', ANSI.bold, ANSI.cyan)} ${paint('interactive conductor', ANSI.dim)}`);
2943
- console.log(`${paint('commands', ANSI.dim)} /compile <spec> /new /sessions /help /exit /skill args`);
3238
+ console.log(`${paint('commands', ANSI.dim)} /compile <spec> /set-model <model> /new /sessions /help /exit /skill args`);
2944
3239
  console.log(`${paint('session', ANSI.dim)} ${paint(session.id, ANSI.cyan)}`);
2945
3240
  console.log(divider('='));
2946
3241
  console.log('');
2947
3242
  }
3243
+ export function parseSetModelCommandArgs(rawArgs) {
3244
+ const args = parseCommandArgs(rawArgs);
3245
+ let model;
3246
+ let slot;
3247
+ for (let index = 0; index < args.length; index += 1) {
3248
+ const arg = args[index];
3249
+ if (arg === '--slot') {
3250
+ const candidate = args[index + 1];
3251
+ if (!candidate) {
3252
+ throw new Error('Provide a slot after --slot.');
3253
+ }
3254
+ if (!isModelSlot(candidate)) {
3255
+ throw new Error('Slot must be one of: gateway, run, compile.');
3256
+ }
3257
+ if (slot) {
3258
+ throw new Error('Provide --slot only once.');
3259
+ }
3260
+ slot = candidate;
3261
+ index += 1;
3262
+ continue;
3263
+ }
3264
+ if (arg.startsWith('--slot=')) {
3265
+ const candidate = arg.slice('--slot='.length);
3266
+ if (!isModelSlot(candidate)) {
3267
+ throw new Error('Slot must be one of: gateway, run, compile.');
3268
+ }
3269
+ if (slot) {
3270
+ throw new Error('Provide --slot only once.');
3271
+ }
3272
+ slot = candidate;
3273
+ continue;
3274
+ }
3275
+ if (arg.startsWith('--')) {
3276
+ throw new Error(`Unknown option: ${arg}`);
3277
+ }
3278
+ if (model) {
3279
+ throw new Error('Provide exactly one model for /set-model.');
3280
+ }
3281
+ model = arg;
3282
+ }
3283
+ if (!model) {
3284
+ throw new Error('Provide a model after /set-model.');
3285
+ }
3286
+ return { model, slot };
3287
+ }
2948
3288
  function streamLabel(logEvent) {
2949
3289
  return logEvent.scope === 'child'
2950
3290
  ? `${CHILD_EVENT_INDENT}[${logEvent.childSlug ?? '?'}] llm`
@@ -2955,6 +3295,16 @@ function formatEventPrefix(logEvent) {
2955
3295
  ? `${CHILD_EVENT_INDENT}[${logEvent.childSlug ?? '?'}] `
2956
3296
  : '';
2957
3297
  }
3298
+ function toolScopeKey(logEvent) {
3299
+ return logEvent.scope === 'child'
3300
+ ? `child:${logEvent.childSlug ?? '?'}`
3301
+ : 'main';
3302
+ }
3303
+ function toolScopeLabel(logEvent) {
3304
+ return logEvent.scope === 'child'
3305
+ ? logEvent.childSlug ?? '?'
3306
+ : 'main';
3307
+ }
2958
3308
  function parseTaggedPrompt(promptText) {
2959
3309
  const match = promptText.match(/^\[([^\]]+)\]\s*(.*)$/);
2960
3310
  if (!match) {
@@ -3010,6 +3360,58 @@ function formatToolArgs(args) {
3010
3360
  }
3011
3361
  return parts.join(', ');
3012
3362
  }
3363
+ function formatSystemAssetSourcePath(workspaceRoot, filePath) {
3364
+ const relativePath = path.relative(workspaceRoot, filePath);
3365
+ if (relativePath && !relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
3366
+ return relativePath || '.';
3367
+ }
3368
+ return filePath;
3369
+ }
3370
+ function formatToolEventLine(logEvent) {
3371
+ const { event } = logEvent;
3372
+ if (!event.toolName || IGNORED_LIVE_LOG_TOOLS.has(event.toolName)) {
3373
+ return null;
3374
+ }
3375
+ const prefix = formatEventPrefix(logEvent);
3376
+ const callLabel = `${event.toolName}(${formatToolArgs(event.toolArgs)})`;
3377
+ if (!event.toolState) {
3378
+ return `${prefix}tool ${callLabel}`;
3379
+ }
3380
+ return `${prefix}tool ${callLabel} ${event.toolState}${formatToolEventDetails(event)}`;
3381
+ }
3382
+ function formatToolEventDetails(event) {
3383
+ const details = [];
3384
+ if (typeof event.toolPid === 'number') {
3385
+ details.push(`pid=${event.toolPid}`);
3386
+ }
3387
+ else if (event.toolBackend === 'sandbox') {
3388
+ details.push('sandbox');
3389
+ }
3390
+ if (typeof event.toolExitCode === 'number') {
3391
+ details.push(`exit=${event.toolExitCode}`);
3392
+ }
3393
+ const message = event.toolError
3394
+ ?? (event.toolSummary && event.toolSummary !== 'Command completed successfully' ? event.toolSummary : undefined);
3395
+ if (message) {
3396
+ details.push(message);
3397
+ }
3398
+ return details.length > 0 ? ` ${details.join(' ')}` : '';
3399
+ }
3400
+ function formatActiveToolStatus(entry) {
3401
+ const details = [];
3402
+ if (typeof entry.toolPid === 'number') {
3403
+ details.push(`pid=${entry.toolPid}`);
3404
+ }
3405
+ else if (entry.toolBackend === 'sandbox') {
3406
+ details.push('sandbox');
3407
+ }
3408
+ const detailSuffix = details.length > 0 ? ` ${details.join(' ')}` : '';
3409
+ return `${entry.scopeLabel} ${entry.toolName} ${entry.toolState}${detailSuffix}`;
3410
+ }
3411
+ function formatShellModeStatus(workspaceRoot, shellWorkspacePath, sandbox) {
3412
+ const displayPath = path.relative(workspaceRoot, shellWorkspacePath) || '.';
3413
+ return `Shell mode: ${sandbox ? 'AgentVM sandbox' : 'host shell'} in ${displayPath}. Enter runs, /cancel stops.`;
3414
+ }
3013
3415
  function buildHelpLines() {
3014
3416
  return [
3015
3417
  'commands',
@@ -3017,10 +3419,12 @@ function buildHelpLines() {
3017
3419
  '/sessions refresh or choose sessions',
3018
3420
  '/compile <spec> compile and publish a new local skill',
3019
3421
  '/skill-creator <spec> alias for /compile',
3422
+ '/set-model <model> [--slot <slot>] update the configured model selection',
3020
3423
  '/cancel abort the current execution',
3021
3424
  '/help show this help',
3022
3425
  '/quit exit the TUI',
3023
3426
  '/<skill> [args] run a compiled skill directly',
3427
+ '!<command> run a shell command directly in the workspace shell',
3024
3428
  'keys',
3025
3429
  'F10 / Ctrl+G open the menu bar',
3026
3430
  'Ctrl+P open the action palette',
@@ -3041,6 +3445,9 @@ function getShortcutHelpLines() {
3041
3445
  const keysIndex = lines.indexOf('keys');
3042
3446
  return keysIndex === -1 ? [] : lines.slice(keysIndex);
3043
3447
  }
3448
+ function isModelSlot(value) {
3449
+ return MODEL_SLOTS.includes(value);
3450
+ }
3044
3451
  function computePaneWidths(columns) {
3045
3452
  const available = Math.max(36, columns - 2);
3046
3453
  let left = Math.max(18, Math.floor(available * 0.2));