wtt-connect 0.2.29 → 0.2.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wtt-connect",
3
- "version": "0.2.29",
3
+ "version": "0.2.31",
4
4
  "private": false,
5
5
  "description": "WTT-native connector daemon for Codex, Claude Code, Cursor, Gemini, ACP, and other coding agent surfaces.",
6
6
  "type": "module",
@@ -3,7 +3,7 @@ import { execFile } from 'node:child_process';
3
3
  import readline from 'node:readline';
4
4
  import { promisify } from 'node:util';
5
5
  import { log } from '../logger.js';
6
- import { parseSlashCommand } from '../slash.js';
6
+ import { CODEX_SLASH_COMMANDS, formatSlashCommandHelp, parseSlashCommand } from '../slash.js';
7
7
 
8
8
  const execFileAsync = promisify(execFile);
9
9
 
@@ -47,26 +47,21 @@ export class CodexAdapter {
47
47
 
48
48
  switch (parsed.command) {
49
49
  case '/help':
50
- return [
51
- 'Codex slash commands supported by WTT:',
52
- '- `/status`: show Codex runtime/session status.',
53
- '- `/model`: show the model selected by WTT for this run. Use the WTT model picker to switch models.',
54
- '- `/approvals`: show the current approval/sandbox mode.',
55
- '- `/diff`: show current git diff summary and patch excerpt.',
56
- '- `/review`: run Codex native code review for the current workspace.',
57
- '- `/init`: ask Codex to inspect the workspace and create/update AGENTS.md guidance.',
58
- '- `/clear`: clear the stored Codex resume thread for this WTT topic.',
59
- '- `/compact`: non-interactive Codex cannot compact an existing TUI context; WTT clears the stored resume thread instead.',
60
- ].join('\n');
50
+ return formatSlashCommandHelp(this.name);
61
51
  case '/status':
62
52
  return this.codexStatus(sessionKey, context);
63
53
  case '/model':
64
54
  return this.codexModel(context, parsed.args);
65
55
  case '/approvals':
56
+ case '/permissions':
66
57
  return this.codexApprovals();
67
58
  case '/diff':
68
59
  return this.gitDiff();
69
60
  case '/clear':
61
+ case '/new':
62
+ case '/reset':
63
+ case '/new-session':
64
+ case '/new-thread':
70
65
  return this.clearSession(sessionKey, 'clear');
71
66
  case '/compact':
72
67
  return this.clearSession(sessionKey, 'compact');
@@ -75,6 +70,7 @@ export class CodexAdapter {
75
70
  case '/init':
76
71
  return this.runInit(context, sessionKey);
77
72
  default:
73
+ if (CODEX_TUI_ONLY_COMMANDS.has(parsed.command)) return codexTuiOnlyMessage(parsed.command);
78
74
  return null;
79
75
  }
80
76
  }
@@ -180,6 +176,37 @@ export class CodexAdapter {
180
176
  }
181
177
  }
182
178
 
179
+ const CODEX_HEADLESS_COMPAT_COMMANDS = new Set([
180
+ '/help',
181
+ '/status',
182
+ '/model',
183
+ '/approvals',
184
+ '/permissions',
185
+ '/diff',
186
+ '/clear',
187
+ '/new',
188
+ '/reset',
189
+ '/new-session',
190
+ '/new-thread',
191
+ '/compact',
192
+ '/review',
193
+ '/init',
194
+ ]);
195
+
196
+ const CODEX_TUI_ONLY_COMMANDS = new Set(
197
+ CODEX_SLASH_COMMANDS.map(([cmd]) => cmd).filter((cmd) => !CODEX_HEADLESS_COMPAT_COMMANDS.has(cmd)),
198
+ );
199
+
200
+ function codexTuiOnlyMessage(command) {
201
+ return [
202
+ `Codex ${command} is a native interactive TUI slash command.`,
203
+ '',
204
+ 'WTT runs Codex through `codex exec` for chat/agent automation. In Codex CLI 0.133.0, `codex exec` treats slash text from stdin as a normal prompt instead of dispatching the TUI slash command.',
205
+ '',
206
+ 'Open the Agent Terminal to run this command in native Codex TUI, or use WTT-compatible commands such as `/status`, `/model`, `/permissions`, `/diff`, `/review`, `/init`, `/new`, `/clear`, and `/compact` from chat.',
207
+ ].join('\n');
208
+ }
209
+
183
210
  async function commandOutput(bin, args, cwd, timeoutMs = 30000, maxChars = 8000) {
184
211
  try {
185
212
  const { stdout, stderr } = await execFileAsync(bin, args, { cwd, timeout: timeoutMs, maxBuffer: Math.max(maxChars * 4, 1024 * 1024) });
package/src/config.js CHANGED
@@ -55,6 +55,8 @@ export function loadConfig(argv = {}) {
55
55
  providerMap: fileConfig.modelSwitch?.providerMap || parseJsonEnv(process.env.WTT_CONNECT_MODEL_PROVIDER_MAP, {}),
56
56
  },
57
57
  workDir,
58
+ cloudSandbox: parseBool(process.env.WTT_CONNECT_CLOUD_SANDBOX, fileConfig.cloudSandbox ?? false),
59
+ persistentOutputDir: process.env.WTT_CONNECT_PERSISTENT_OUTPUT_DIR || process.env.WTT_CONNECT_R2_OUTPUT_DIR || fileConfig.persistentOutputDir || '',
58
60
  model: process.env.WTT_CONNECT_MODEL || fileConfig.model || '',
59
61
  mode,
60
62
  allowYolo: parseBool(process.env.WTT_CONNECT_ALLOW_YOLO, fileConfig.allowYolo ?? mode === 'yolo'),
package/src/main.js CHANGED
@@ -186,7 +186,7 @@ function packageUploadFiles(files, config, argv) {
186
186
  return { path: zipPath, originalFiles: files };
187
187
  }
188
188
 
189
- function resolveUploadFilePath(filePath, config) {
189
+ export function resolveUploadFilePath(filePath, config) {
190
190
  const raw = String(filePath || '').trim();
191
191
  if (!raw) throw new Error('empty file path');
192
192
  const expanded = raw.startsWith('~/') ? path.join(process.env.HOME || '', raw.slice(2)) : raw;
@@ -197,9 +197,10 @@ function resolveUploadFilePath(filePath, config) {
197
197
  config.artifactDir,
198
198
  config.storeFile ? path.dirname(config.storeFile) : '',
199
199
  config.inboxDir,
200
+ config.persistentOutputDir,
200
201
  ].filter(Boolean).map((root) => path.resolve(root));
201
202
  if (!roots.some((root) => isPathInside(resolved, root))) {
202
- throw new Error(`upload-file refused path outside WTT workspace/state roots: ${resolved}`);
203
+ throw new Error(`upload-file refused path outside WTT workspace/state/persistent roots: ${resolved}`);
203
204
  }
204
205
  return resolved;
205
206
  }
package/src/runner.js CHANGED
@@ -20,7 +20,7 @@ import { buildRuntimeInfo } from './runtime-info.js';
20
20
  import { runShellCommand } from './shell-runner.js';
21
21
  import { TerminalSessionManager } from './terminal-session.js';
22
22
  import { isModelSwitcherEnabled, switchModelProvider } from './model-switcher.js';
23
- import { isAgentSlashCommand, parseSlashCommand } from './slash.js';
23
+ import { formatSlashCommandHelp, isAgentSlashCommand, parseSlashCommand, slashCommandsForAdapter } from './slash.js';
24
24
 
25
25
  const TERMINAL_STATUSES = new Set(['review', 'done', 'approved', 'cancelled']);
26
26
  const GENERATED_FILE_EXTENSIONS = new Set([
@@ -182,9 +182,10 @@ export class Runner {
182
182
  const discussionRouting = renderDiscussionRoutingInstruction(m, this.config);
183
183
  const currentDisplayName = effectiveAgentDisplayName(this.config, agentProfile);
184
184
  const isSlashPassthrough = isAgentSlashCommand(m, content);
185
+ const sessionKey = `wtt:topic:${topicId}:${adapter.name}:${sessionModelKey(modelConfig)}`;
185
186
  if (isSlashPassthrough) {
186
187
  const localSlash = await this.handleLocalSlashCommand(content, adapter, {
187
- sessionKey: `wtt:topic:${topicId}:${adapter.name}:${sessionModelKey(modelConfig)}`,
188
+ sessionKey,
188
189
  topicId,
189
190
  modelConfig,
190
191
  files: staged.files,
@@ -198,6 +199,7 @@ export class Runner {
198
199
  return;
199
200
  }
200
201
  }
202
+ const compactSummary = this.store.getSession(sessionKey).compactSummary || '';
201
203
  const prompt = isSlashPassthrough ? content : [
202
204
  'You are replying to a WTT Web conversation. Do not mention implementation internals unless asked.',
203
205
  `WTT topic_id: ${topicId}`,
@@ -212,16 +214,19 @@ export class Runner {
212
214
  discussionRouting ? '' : null,
213
215
  agentSoul,
214
216
  agentSoul ? '' : null,
217
+ renderCompactSummaryBlock(compactSummary),
218
+ compactSummary ? '' : null,
215
219
  'User message:',
216
220
  content,
217
221
  '',
218
222
  staged.promptBlock,
219
223
  renderTranscriptBlock(transcripts),
224
+ renderCloudSandboxStorageInstruction(this.config),
220
225
  renderGeneratedFileArtifactInstruction(this.config, topicId),
221
226
  'Reply naturally and concisely unless the user asks for detail.',
222
227
  ].filter(Boolean).join('\n');
223
228
  const output = await this.runAdapter(adapter, prompt, {
224
- sessionKey: `wtt:topic:${topicId}:${adapter.name}:${sessionModelKey(modelConfig)}`,
229
+ sessionKey,
225
230
  topicId,
226
231
  modelConfig,
227
232
  files: staged.files,
@@ -336,9 +341,18 @@ export class Runner {
336
341
  if (parsed.command === '/upgrade') {
337
342
  return this.handleUpgradeCommand(parsed.args);
338
343
  }
344
+ if (parsed.command === '/help' || parsed.command === '/?') {
345
+ return formatSlashCommandHelp(adapter.name);
346
+ }
339
347
  if (parsed.command === '/status') {
340
348
  return this.runtimeStatusText(adapter, context);
341
349
  }
350
+ if (parsed.command === '/model' || parsed.command === '/models') {
351
+ return this.runtimeModelText(adapter, context, parsed.args);
352
+ }
353
+ if (['/compact', '/compress'].includes(parsed.command)) {
354
+ return this.compactAdapterSession(adapter, context, parsed.command, parsed.args);
355
+ }
342
356
  if (['/clear', '/new', '/reset', '/new-session', '/new-thread'].includes(parsed.command)) {
343
357
  return this.clearAdapterSession(adapter, context.sessionKey || 'default', parsed.command);
344
358
  }
@@ -346,6 +360,9 @@ export class Runner {
346
360
  const handled = await adapter.handleSlashCommand(content, context);
347
361
  if (handled !== null && handled !== undefined) return handled;
348
362
  }
363
+ if (isKnownAdapterSlashCommand(adapter.name, parsed.command)) {
364
+ return headlessSlashUnsupportedMessage(adapter.name, parsed.command);
365
+ }
349
366
  return null;
350
367
  }
351
368
 
@@ -356,6 +373,72 @@ export class Runner {
356
373
  return `${adapterDisplayName(adapter.name)} session cleared for this WTT topic. The next turn starts a fresh agent thread/session.`;
357
374
  }
358
375
 
376
+ async compactAdapterSession(adapter, context = {}, command = '/compact', args = '') {
377
+ const sessionKey = context.sessionKey || 'default';
378
+ const topicId = String(context.topicId || '').trim();
379
+ if (!topicId) return this.clearAdapterSession(adapter, sessionKey, command);
380
+ const messages = await this.api.getTopicMessages(topicId, { limit: 160, includeHistory: true });
381
+ const transcript = formatTopicMessagesForCompaction(messages, {
382
+ currentAgentId: this.config.agentId,
383
+ excludeSlashCommands: new Set(['/compact', '/compress']),
384
+ });
385
+ if (!transcript) {
386
+ return `${adapterDisplayName(adapter.name)} compact skipped: no topic history was available.`;
387
+ }
388
+ const focus = String(args || '').trim();
389
+ const prompt = [
390
+ 'You are compacting a WTT agent conversation for continuation in a fresh headless CLI session.',
391
+ 'Create a durable context summary that will be injected before the next user message.',
392
+ '',
393
+ 'Requirements:',
394
+ '- Preserve user goals, constraints, decisions, unresolved questions, file paths, commands, errors, and current next steps.',
395
+ '- Preserve any model/tool/API/deployment details that matter for future turns.',
396
+ '- Drop greetings, duplicate status noise, progress chatter, and irrelevant old details.',
397
+ '- Do not invent facts. If uncertain, say so briefly.',
398
+ '- Keep it concise but complete enough to continue work without the previous CLI session.',
399
+ focus ? `- User focus for this compaction: ${focus}` : null,
400
+ '',
401
+ 'Return Markdown with these sections:',
402
+ '1. Current Goal',
403
+ '2. Key Facts And Decisions',
404
+ '3. Files/Commands/Environment',
405
+ '4. Open Issues',
406
+ '5. Next Steps',
407
+ '',
408
+ 'Conversation transcript:',
409
+ transcript,
410
+ ].filter(Boolean).join('\n');
411
+ await this.wtt.typing(topicId, 'start', {
412
+ statusText: `${adapterDisplayName(adapter.name)} 正在压缩当前上下文`,
413
+ statusKind: 'compact',
414
+ adapter: adapter.name,
415
+ model: context.modelConfig?.model || undefined,
416
+ ttlMs: 120000,
417
+ });
418
+ const compactSessionKey = `${sessionKey}:compact:${Date.now()}`;
419
+ const summary = stripHiddenContextLeak(await this.runAdapter(adapter, prompt, {
420
+ ...context,
421
+ sessionKey: compactSessionKey,
422
+ files: [],
423
+ images: [],
424
+ }) || '').trim();
425
+ if (!summary) return `${adapterDisplayName(adapter.name)} compact failed: summary was empty.`;
426
+ this.store.clearSession(sessionKey, {
427
+ adapter: adapter.name,
428
+ clearedBy: command,
429
+ compactSummary: truncate(summary, 30000),
430
+ compactedAt: new Date().toISOString(),
431
+ compactedMessageCount: messages.length,
432
+ });
433
+ if (adapter.threadBySession?.delete) adapter.threadBySession.delete(sessionKey);
434
+ if (adapter.sessionByKey?.delete) adapter.sessionByKey.delete(sessionKey);
435
+ return [
436
+ `${adapterDisplayName(adapter.name)} context compacted for this WTT topic. The next turn starts a fresh headless CLI session with this summary injected.`,
437
+ '',
438
+ summary,
439
+ ].join('\n');
440
+ }
441
+
359
442
  runtimeStatusText(adapter, context = {}) {
360
443
  const runtime = this.runtimeInfo();
361
444
  const modelConfig = context.modelConfig || {};
@@ -371,6 +454,22 @@ export class Runner {
371
454
  `- wtt-connect: ${runtime.wtt_connect || runtime.wttConnect || '(unknown)'}`,
372
455
  `- claude: ${runtime.claude_code || runtime.claude || '(unknown)'}`,
373
456
  `- codex: ${runtime.codex || '(unknown)'}`,
457
+ `- gemini: ${runtime.gemini || '(unknown)'}`,
458
+ ].join('\n');
459
+ }
460
+
461
+ runtimeModelText(adapter, context = {}, args = '') {
462
+ const runtime = this.runtimeInfo();
463
+ const modelConfig = context.modelConfig || {};
464
+ const model = modelConfig.model || runtime.current_model || runtime.model || this.config.model || '(config/default)';
465
+ const reasoning = modelConfig.reasoning_effort || modelConfig.reasoningEffort || runtime.reasoning_effort || runtime.thinking_mode || '(default)';
466
+ return [
467
+ `${adapterDisplayName(adapter.name)} model`,
468
+ `- current model: ${model}`,
469
+ `- reasoning/thinking: ${reasoning}`,
470
+ args?.trim()
471
+ ? '- note: WTT Web model picker controls the next headless run. Native interactive /model switching requires Agent Terminal.'
472
+ : '- use the WTT Web model picker to switch the next headless run.',
374
473
  ].join('\n');
375
474
  }
376
475
 
@@ -521,9 +620,27 @@ export class Runner {
521
620
  function adapterDisplayName(name) {
522
621
  if (name === 'claude-code') return 'Claude Code';
523
622
  if (name === 'codex') return 'Codex';
623
+ if (name === 'gemini') return 'Gemini';
524
624
  return name || 'Agent';
525
625
  }
526
626
 
627
+ function isKnownAdapterSlashCommand(adapterName, command) {
628
+ const normalized = String(command || '').trim().toLowerCase();
629
+ if (!normalized) return false;
630
+ return slashCommandsForAdapter(adapterName).some(([cmd]) => cmd === normalized);
631
+ }
632
+
633
+ function headlessSlashUnsupportedMessage(adapterName, command) {
634
+ const display = adapterDisplayName(adapterName);
635
+ return [
636
+ `${display} ${command} is a native interactive slash command.`,
637
+ '',
638
+ `WTT currently runs ${display} through its non-interactive/headless executable mode for chat automation. In this mode, the CLI does not reliably dispatch interactive slash commands; it may treat them as normal prompts instead.`,
639
+ '',
640
+ 'Use Agent Terminal for the native TUI command, or use WTT-compatible chat commands such as `/help`, `/status`, `/model`, `/new`, `/clear`, and `/compact`.',
641
+ ].join('\n');
642
+ }
643
+
527
644
  function wttSlashHelp() {
528
645
  return [
529
646
  'WTT slash commands:',
@@ -787,10 +904,11 @@ function truncate(text, maxChars) {
787
904
 
788
905
  function renderGeneratedFileArtifactInstruction(config, topicId = '') {
789
906
  const workDir = config.workDir || process.cwd();
907
+ const outputDir = config.cloudSandbox && config.persistentOutputDir ? config.persistentOutputDir : workDir;
790
908
  const uploadCommand = `wtt-connect upload-file --topic-id ${topicId || '<topic_id>'} --title "optional display name" <file-or-files>`;
791
909
  return [
792
910
  'Generated file artifact rule, for internal execution only:',
793
- `- If you create a user-facing file (.docx, .pptx, .xlsx, .pdf, .csv, .md, .zip, images, or video), save it under your current WTT agent workspace: ${workDir}.`,
911
+ `- If you create a user-facing file (.docx, .pptx, .xlsx, .pdf, .csv, .md, .zip, images, or video), save it under: ${outputDir}.`,
794
912
  `- After creating the file, immediately publish it to this WTT conversation by running: ${uploadCommand}`,
795
913
  '- For multiple related files, pass all file paths to one upload-file command; wtt-connect will package them into one zip before publishing.',
796
914
  '- If upload-file is unavailable, include one marker line per generated file in your final response, exactly like: [WTT_ARTIFACT_FILE path="relative/or/absolute/file.docx" title="optional display name"].',
@@ -799,6 +917,34 @@ function renderGeneratedFileArtifactInstruction(config, topicId = '') {
799
917
  ].join('\n');
800
918
  }
801
919
 
920
+ function renderCloudSandboxStorageInstruction(config) {
921
+ if (!config.cloudSandbox) return '';
922
+ const workDir = config.workDir || process.cwd();
923
+ const persistentDir = String(config.persistentOutputDir || '').trim();
924
+ const lines = [
925
+ 'Cloud Sandbox storage policy, for internal execution only:',
926
+ `- This agent is running inside WTT Cloud Sandbox. Keep the active workspace small: ${workDir}.`,
927
+ ];
928
+ if (persistentDir) {
929
+ lines.push(
930
+ `- Put durable outputs, datasets, downloads, archives, generated reports, build products, and any file expected to survive cleanup under: ${persistentDir}.`,
931
+ `- Create that directory before use: mkdir -p ${shellishQuote(persistentDir)}.`,
932
+ );
933
+ } else {
934
+ lines.push('- Put durable large outputs under the mounted R2 storage area when available, not in the active workspace.');
935
+ }
936
+ lines.push(
937
+ '- Do not install large dependencies, clone large repositories, download datasets, or write bulky generated files into the workspace unless the user explicitly asks for local temporary storage.',
938
+ '- Keep only source edits, small manifests, notes, and temporary scratch files in the workspace.',
939
+ '- If a generated user-facing file is stored in R2/persistent output storage and should appear in WTT chat, still publish it with `wtt-connect upload-file` or a WTT artifact marker.',
940
+ );
941
+ return lines.join('\n');
942
+ }
943
+
944
+ function shellishQuote(value) {
945
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
946
+ }
947
+
802
948
  async function packageGeneratedFilesIfNeeded(files, config, context = {}) {
803
949
  if (!files.length) return [];
804
950
  if (files.length === 1) return files;
@@ -1235,6 +1381,7 @@ function buildTaskPrompt(task, config, staged = { promptBlock: '' }, transcripts
1235
1381
  renderTranscriptBlock(transcripts),
1236
1382
  '',
1237
1383
  config.requireCommitPush ? 'For code changes, commit and push before final response, and include commit id.' : '',
1384
+ renderCloudSandboxStorageInstruction(config),
1238
1385
  renderGeneratedFileArtifactInstruction(config, task.topic_id || task.topicId || ''),
1239
1386
  'Return a concise final summary with evidence, changed files, tests, artifacts, and blockers.',
1240
1387
  ].filter(Boolean).join('\n');
@@ -1256,3 +1403,41 @@ function renderTranscriptBlock(transcripts = []) {
1256
1403
  if (!transcripts.length) return '';
1257
1404
  return ['Audio transcripts:', ...transcripts.map((t) => `- ${t.file.name || t.file.path}: ${t.text}`)].join('\n');
1258
1405
  }
1406
+
1407
+ function renderCompactSummaryBlock(summary) {
1408
+ const value = String(summary || '').trim();
1409
+ if (!value) return '';
1410
+ return [
1411
+ 'Compacted prior WTT conversation context:',
1412
+ '```markdown',
1413
+ value,
1414
+ '```',
1415
+ 'Use this summary as the durable context from previous turns. Do not mention that compaction happened unless relevant.',
1416
+ ].join('\n');
1417
+ }
1418
+
1419
+ function formatTopicMessagesForCompaction(messages = [], options = {}) {
1420
+ const exclude = options.excludeSlashCommands || new Set();
1421
+ const currentAgentId = String(options.currentAgentId || '');
1422
+ const lines = [];
1423
+ for (const message of messages || []) {
1424
+ const content = String(message?.content || '').trim();
1425
+ if (!content) continue;
1426
+ const command = content.startsWith('/') ? content.split(/\s+/, 1)[0].toLowerCase() : '';
1427
+ if (command && exclude.has(command)) continue;
1428
+ const senderId = String(message?.sender_id || '');
1429
+ const senderType = String(message?.sender_type || '').toLowerCase();
1430
+ const role = senderId === currentAgentId || senderType === 'agent' ? 'assistant' : 'user';
1431
+ const name = String(message?.sender_display_name || senderId || role).slice(0, 80);
1432
+ const semantic = String(message?.semantic_type || '').toLowerCase();
1433
+ const text = truncateForTranscript(content, 5000);
1434
+ lines.push(`[${role}${name ? `:${name}` : ''}${semantic ? ` ${semantic}` : ''}] ${text}`);
1435
+ }
1436
+ return truncateForTranscript(lines.join('\n\n'), 90000);
1437
+ }
1438
+
1439
+ function truncateForTranscript(text, maxChars) {
1440
+ const value = String(text || '');
1441
+ if (value.length <= maxChars) return value;
1442
+ return `${value.slice(0, maxChars)}\n...[truncated ${value.length - maxChars} chars]`;
1443
+ }
package/src/slash.js CHANGED
@@ -20,3 +20,237 @@ export function isAgentSlashCommand(message = {}, content = '') {
20
20
  if (slashType === 'agent_passthrough') return true;
21
21
  return String(content || message?.content || '').trim().startsWith('/');
22
22
  }
23
+
24
+ export const CODEX_SLASH_COMMANDS = [
25
+ ['/agent', 'Configure or switch Codex agent settings.'],
26
+ ['/apps', 'Browse available Codex apps/connectors.'],
27
+ ['/plugins', 'Browse and manage Codex plugins.'],
28
+ ['/hooks', 'View and manage lifecycle hooks.'],
29
+ ['/clear', 'Clear the current terminal view and start fresh.'],
30
+ ['/compact', 'Summarize earlier context to free tokens.'],
31
+ ['/copy', 'Copy the latest response.'],
32
+ ['/diff', 'Show the current git diff, including untracked files.'],
33
+ ['/exit', 'Exit Codex. Alias: /quit.'],
34
+ ['/quit', 'Exit Codex. Alias: /exit.'],
35
+ ['/experimental', 'Toggle experimental Codex features.'],
36
+ ['/approve', 'Approve one retry after an auto-review denial.'],
37
+ ['/memories', 'Configure Codex memory use and generation.'],
38
+ ['/skills', 'Browse and use skills.'],
39
+ ['/feedback', 'Send diagnostics or feedback to Codex maintainers.'],
40
+ ['/init', 'Generate or update AGENTS.md guidance.'],
41
+ ['/logout', 'Sign out of Codex.'],
42
+ ['/mcp', 'List configured MCP tools; use verbose for diagnostics.'],
43
+ ['/mention', 'Attach a file or folder to the conversation.'],
44
+ ['/model', 'Choose the active model and reasoning effort.'],
45
+ ['/fast', 'Toggle or inspect Fast service tier when available.'],
46
+ ['/plan', 'Switch to plan mode, optionally with a prompt.'],
47
+ ['/goal', 'Set, view, pause, resume, or clear a task goal.'],
48
+ ['/personality', 'Choose response style.'],
49
+ ['/ps', 'Show background terminals and recent output.'],
50
+ ['/stop', 'Stop background terminals.'],
51
+ ['/clean', 'Alias for /stop.'],
52
+ ['/fork', 'Fork the current conversation into a new thread.'],
53
+ ['/side', 'Start an ephemeral side conversation.'],
54
+ ['/raw', 'Toggle raw scrollback mode.'],
55
+ ['/resume', 'Resume a saved conversation.'],
56
+ ['/new', 'Start a new conversation in the same workspace.'],
57
+ ['/review', 'Review the working tree.'],
58
+ ['/status', 'Show session configuration, token usage, and runtime status.'],
59
+ ['/debug-config', 'Print configuration diagnostics.'],
60
+ ['/statusline', 'Configure TUI status-line fields.'],
61
+ ['/title', 'Configure terminal title fields.'],
62
+ ['/theme', 'Choose syntax-highlighting theme.'],
63
+ ['/permissions', 'Adjust approval and sandbox permissions.'],
64
+ ['/ide', 'Configure IDE integration.'],
65
+ ['/keymap', 'Configure keyboard shortcuts.'],
66
+ ['/vim', 'Toggle Vim editing mode.'],
67
+ ['/sandbox-add-read-dir', 'Add an extra read-only sandbox directory.'],
68
+ ];
69
+
70
+ export const CLAUDE_CODE_SLASH_COMMANDS = [
71
+ ['/add-dir', 'Add a working directory for file access.'],
72
+ ['/agents', 'Manage agent/subagent configurations.'],
73
+ ['/autofix-pr', 'Spawn Claude Code on the web to fix PR/CI feedback.'],
74
+ ['/background', 'Detach the current session to run as a background agent. Alias: /bg.'],
75
+ ['/bg', 'Alias for /background.'],
76
+ ['/batch', 'Decompose large work across parallel worktrees/subagents.'],
77
+ ['/branch', 'Branch the current conversation. Alias: /fork.'],
78
+ ['/fork', 'Alias for /branch in Claude Code.'],
79
+ ['/btw', 'Ask a side question without bloating history.'],
80
+ ['/chrome', 'Configure Claude in Chrome settings.'],
81
+ ['/claude-api', 'Load Claude API reference or migration workflows.'],
82
+ ['/clear', 'Start a new conversation with empty context. Aliases: /reset, /new.'],
83
+ ['/reset', 'Alias for /clear.'],
84
+ ['/new', 'Alias for /clear.'],
85
+ ['/code-review', 'Review current diff and optionally apply fixes.'],
86
+ ['/color', 'Set prompt bar color.'],
87
+ ['/compact', 'Summarize conversation to free context.'],
88
+ ['/config', 'Open settings. Alias: /settings.'],
89
+ ['/settings', 'Alias for /config.'],
90
+ ['/context', 'Visualize context usage.'],
91
+ ['/copy', 'Copy a recent assistant response.'],
92
+ ['/cost', 'Alias for /usage.'],
93
+ ['/debug', 'Enable debug logging and troubleshoot.'],
94
+ ['/deep-research', 'Run a deep-research workflow.'],
95
+ ['/desktop', 'Continue in Claude Code Desktop. Alias: /app.'],
96
+ ['/app', 'Alias for /desktop.'],
97
+ ['/diff', 'Open an interactive diff viewer.'],
98
+ ['/doctor', 'Diagnose installation and settings.'],
99
+ ['/effort', 'Set reasoning effort level.'],
100
+ ['/exit', 'Exit or detach from a background session. Alias: /quit.'],
101
+ ['/quit', 'Alias for /exit.'],
102
+ ['/export', 'Export the current conversation.'],
103
+ ['/fast', 'Toggle fast mode.'],
104
+ ['/feedback', 'Submit feedback or a bug report. Aliases: /bug, /share.'],
105
+ ['/bug', 'Alias for /feedback.'],
106
+ ['/share', 'Alias for /feedback.'],
107
+ ['/fewer-permission-prompts', 'Generate an allowlist to reduce prompts.'],
108
+ ['/focus', 'Toggle focus view.'],
109
+ ['/goal', 'Set, view, or clear a persistent goal.'],
110
+ ['/heapdump', 'Write a heap snapshot for diagnostics.'],
111
+ ['/help', 'Show Claude Code help and commands.'],
112
+ ['/hooks', 'View hook configuration.'],
113
+ ['/ide', 'Manage IDE integrations.'],
114
+ ['/init', 'Initialize CLAUDE.md project guidance.'],
115
+ ['/insights', 'Analyze Claude Code sessions.'],
116
+ ['/install-github-app', 'Install Claude GitHub Actions app.'],
117
+ ['/install-slack-app', 'Install Claude Slack app.'],
118
+ ['/keybindings', 'Open keybindings configuration.'],
119
+ ['/login', 'Sign in to Anthropic.'],
120
+ ['/logout', 'Sign out from Anthropic.'],
121
+ ['/loop', 'Run a prompt repeatedly. Alias: /proactive.'],
122
+ ['/proactive', 'Alias for /loop.'],
123
+ ['/mcp', 'Manage MCP server connections and OAuth.'],
124
+ ['/memory', 'Edit memory files and auto-memory entries.'],
125
+ ['/mobile', 'Show mobile app QR code. Aliases: /ios, /android.'],
126
+ ['/ios', 'Alias for /mobile.'],
127
+ ['/android', 'Alias for /mobile.'],
128
+ ['/model', 'Switch model and save defaults.'],
129
+ ['/passes', 'Share Claude Code passes when eligible.'],
130
+ ['/permissions', 'Manage tool permissions. Alias: /allowed-tools.'],
131
+ ['/allowed-tools', 'Alias for /permissions.'],
132
+ ['/plan', 'Enter plan mode, optionally with a task.'],
133
+ ['/plugin', 'Manage Claude Code plugins.'],
134
+ ['/powerup', 'Discover Claude Code features.'],
135
+ ['/pr-comments', 'Legacy PR comments helper.'],
136
+ ['/privacy-settings', 'View/update privacy settings.'],
137
+ ['/radio', 'Open Claude FM radio.'],
138
+ ['/recap', 'Generate a one-line session recap.'],
139
+ ['/release-notes', 'View changelog.'],
140
+ ['/reload-plugins', 'Reload active plugins.'],
141
+ ['/reload-skills', 'Reload skills and command directories.'],
142
+ ['/remote-control', 'Enable remote control from claude.ai. Alias: /rc.'],
143
+ ['/rc', 'Alias for /remote-control.'],
144
+ ['/remote-env', 'Configure default remote environment.'],
145
+ ['/rename', 'Rename current session.'],
146
+ ['/resume', 'Resume a previous conversation. Alias: /continue.'],
147
+ ['/continue', 'Alias for /resume.'],
148
+ ['/review', 'Review a PR locally.'],
149
+ ['/rewind', 'Rewind conversation/code to a checkpoint. Aliases: /checkpoint, /undo.'],
150
+ ['/checkpoint', 'Alias for /rewind.'],
151
+ ['/undo', 'Alias for /rewind.'],
152
+ ['/run', 'Launch and verify the project app.'],
153
+ ['/run-skill-generator', 'Generate project run/verify skills.'],
154
+ ['/sandbox', 'Toggle sandbox mode where supported.'],
155
+ ['/schedule', 'Create or manage routines. Alias: /routines.'],
156
+ ['/routines', 'Alias for /schedule.'],
157
+ ['/scroll-speed', 'Adjust mouse wheel scroll speed.'],
158
+ ['/security-review', 'Analyze pending changes for security risks.'],
159
+ ['/setup-bedrock', 'Configure Amazon Bedrock.'],
160
+ ['/setup-vertex', 'Configure Google Vertex AI.'],
161
+ ['/simplify', 'Review changes for cleanup opportunities.'],
162
+ ['/skills', 'List and manage skills.'],
163
+ ['/stats', 'Alias for /usage.'],
164
+ ['/status', 'Open status/settings view.'],
165
+ ['/statusline', 'Configure status line.'],
166
+ ['/stickers', 'Order stickers.'],
167
+ ['/stop', 'Stop an attached background session.'],
168
+ ['/tasks', 'List and manage background tasks. Alias: /bashes.'],
169
+ ['/bashes', 'Alias for /tasks.'],
170
+ ['/team-onboarding', 'Generate team onboarding guide.'],
171
+ ['/teleport', 'Pull a web session into terminal. Alias: /tp.'],
172
+ ['/tp', 'Alias for /teleport.'],
173
+ ['/terminal-setup', 'Configure terminal keybindings.'],
174
+ ['/theme', 'Change color theme.'],
175
+ ['/tui', 'Set terminal UI renderer.'],
176
+ ['/ultraplan', 'Draft a plan in an ultraplan session.'],
177
+ ['/ultrareview', 'Run deep multi-agent code review.'],
178
+ ['/upgrade', 'Open plan upgrade page.'],
179
+ ['/usage', 'Show cost, usage limits, and activity stats.'],
180
+ ['/usage-credits', 'Configure usage credits.'],
181
+ ['/verify', 'Verify a code change by running the app.'],
182
+ ['/vim', 'Legacy Vim-mode command.'],
183
+ ['/voice', 'Toggle voice dictation.'],
184
+ ['/web-setup', 'Connect GitHub for Claude Code on the web.'],
185
+ ['/workflows', 'Watch and manage workflows.'],
186
+ ];
187
+
188
+ export const GEMINI_SLASH_COMMANDS = [
189
+ ['/about', 'Show version information.'],
190
+ ['/agents', 'Manage local and remote subagents.'],
191
+ ['/auth', 'Change authentication method.'],
192
+ ['/bug', 'File an issue or bug report.'],
193
+ ['/chat', 'Alias for /resume and checkpoint management.'],
194
+ ['/clear', 'Clear visible terminal/session history.'],
195
+ ['/commands', 'List or reload custom slash commands.'],
196
+ ['/compress', 'Compress chat context into a summary.'],
197
+ ['/copy', 'Copy the last output.'],
198
+ ['/directory', 'Manage workspace directories. Alias: /dir.'],
199
+ ['/dir', 'Alias for /directory.'],
200
+ ['/docs', 'Open Gemini CLI documentation.'],
201
+ ['/editor', 'Select editor integration.'],
202
+ ['/extensions', 'Manage Gemini CLI extensions.'],
203
+ ['/help', 'Show Gemini CLI help. Alias: /?.'],
204
+ ['/?', 'Alias for /help.'],
205
+ ['/hooks', 'Manage lifecycle hooks.'],
206
+ ['/ide', 'Manage IDE integration.'],
207
+ ['/init', 'Generate or update GEMINI.md.'],
208
+ ['/mcp', 'Manage MCP servers.'],
209
+ ['/memory', 'Inspect or refresh GEMINI.md memory.'],
210
+ ['/model', 'Manage model configuration.'],
211
+ ['/permissions', 'Manage folder trust and permissions.'],
212
+ ['/plan', 'Switch to plan mode.'],
213
+ ['/policies', 'List active policies.'],
214
+ ['/privacy', 'Display privacy notice and consent options.'],
215
+ ['/quit', 'Exit Gemini CLI. Alias: /exit.'],
216
+ ['/exit', 'Alias for /quit.'],
217
+ ['/restore', 'Restore files to a checkpoint.'],
218
+ ['/rewind', 'Navigate backward through conversation history.'],
219
+ ['/resume', 'Browse, resume, save, delete, or share sessions.'],
220
+ ['/settings', 'Open settings editor.'],
221
+ ['/shells', 'Toggle background shells view. Alias: /bashes.'],
222
+ ['/bashes', 'Alias for /shells.'],
223
+ ['/setup-github', 'Set up GitHub Actions integration.'],
224
+ ['/skills', 'Manage Agent Skills.'],
225
+ ['/stats', 'Show Gemini CLI session statistics.'],
226
+ ['/terminal-setup', 'Configure multiline keybindings.'],
227
+ ['/theme', 'Change visual theme.'],
228
+ ['/tools', 'Show available tools.'],
229
+ ['/upgrade', 'Open Gemini Code Assist upgrade page.'],
230
+ ['/vim', 'Toggle Vim mode.'],
231
+ ];
232
+
233
+ export function slashCommandsForAdapter(adapterName) {
234
+ const name = String(adapterName || '').trim().toLowerCase();
235
+ if (name === 'codex' || name.includes('codex')) return CODEX_SLASH_COMMANDS;
236
+ if (name === 'claude-code' || name === 'claude' || name.includes('claude')) return CLAUDE_CODE_SLASH_COMMANDS;
237
+ if (name === 'gemini' || name.includes('gemini')) return GEMINI_SLASH_COMMANDS;
238
+ return [
239
+ ['/help', 'Show help.'],
240
+ ['/status', 'Show WTT runtime status.'],
241
+ ['/model', 'Show or switch model when supported.'],
242
+ ['/clear', 'Clear the WTT topic session.'],
243
+ ['/new', 'Start a fresh WTT topic session.'],
244
+ ['/compact', 'Compact context when supported.'],
245
+ ];
246
+ }
247
+
248
+ export function formatSlashCommandHelp(adapterName) {
249
+ const title = adapterName ? `${adapterName} slash commands` : 'Agent slash commands';
250
+ return [
251
+ `${title}:`,
252
+ ...slashCommandsForAdapter(adapterName).map(([cmd, desc]) => `- \`${cmd}\`: ${desc}`),
253
+ '',
254
+ 'Note: WTT handles deterministic commands such as /status, /new, /clear, /compact, /diff, /review, and /init where possible. Other commands are passed through to the underlying CLI; interactive TUI-only commands may require opening the agent terminal.',
255
+ ].join('\n');
256
+ }
package/src/wtt-api.js CHANGED
@@ -76,6 +76,22 @@ export class WTTApi {
76
76
  return this.request('POST', '/tasks', { json: payload });
77
77
  }
78
78
 
79
+ async getTopicMessages(topicId, { limit = 120, includeHistory = true } = {}) {
80
+ if (!topicId) return [];
81
+ const params = new URLSearchParams({
82
+ limit: String(limit),
83
+ agent_id: this.config.agentId || '',
84
+ include_history: includeHistory ? 'true' : 'false',
85
+ });
86
+ try {
87
+ const rows = await this.request('GET', `/topics/${encodeURIComponent(topicId)}/messages?${params.toString()}`);
88
+ return Array.isArray(rows) ? rows : [];
89
+ } catch (err) {
90
+ log('warn', 'get_topic_messages failed', { topicId, error: err.message });
91
+ return [];
92
+ }
93
+ }
94
+
79
95
  async runTask(taskId, payload = {}) {
80
96
  return this.request('POST', `/tasks/${taskId}/run`, { json: payload });
81
97
  }