deepclause-sdk 0.0.61 → 0.0.63

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