wtt-connect 0.2.28 → 0.2.29

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.28",
3
+ "version": "0.2.29",
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",
@@ -13,7 +13,8 @@
13
13
  "setup": "node ./bin/wtt-connect.js setup",
14
14
  "smoke:task": "node ./bin/wtt-connect.js smoke-task",
15
15
  "smoke:chat": "node ./bin/wtt-connect.js smoke-chat",
16
- "start": "node ./bin/wtt-connect.js start"
16
+ "start": "node ./bin/wtt-connect.js start",
17
+ "test": "node --test test/*.test.js"
17
18
  },
18
19
  "engines": {
19
20
  "node": ">=22"
@@ -3,6 +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
7
 
7
8
  const execFileAsync = promisify(execFile);
8
9
 
@@ -74,7 +75,7 @@ export class CodexAdapter {
74
75
  case '/init':
75
76
  return this.runInit(context, sessionKey);
76
77
  default:
77
- return `Unsupported Codex slash command: ${parsed.command}\n\nTry \`/help\` for supported WTT/Codex commands.`;
78
+ return null;
78
79
  }
79
80
  }
80
81
 
@@ -179,13 +180,6 @@ export class CodexAdapter {
179
180
  }
180
181
  }
181
182
 
182
- function parseSlashCommand(text) {
183
- const trimmed = String(text || '').trim();
184
- if (!trimmed.startsWith('/')) return null;
185
- const [command, ...rest] = trimmed.split(/\s+/);
186
- return { command: command.toLowerCase(), args: rest.join(' ') };
187
- }
188
-
189
183
  async function commandOutput(bin, args, cwd, timeoutMs = 30000, maxChars = 8000) {
190
184
  try {
191
185
  const { stdout, stderr } = await execFileAsync(bin, args, { cwd, timeout: timeoutMs, maxBuffer: Math.max(maxChars * 4, 1024 * 1024) });
package/src/runner.js CHANGED
@@ -20,6 +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
24
 
24
25
  const TERMINAL_STATUSES = new Set(['review', 'done', 'approved', 'cancelled']);
25
26
  const GENERATED_FILE_EXTENSIONS = new Set([
@@ -335,21 +336,26 @@ export class Runner {
335
336
  if (parsed.command === '/upgrade') {
336
337
  return this.handleUpgradeCommand(parsed.args);
337
338
  }
338
- if (adapter.name === 'codex' && typeof adapter.handleSlashCommand === 'function') {
339
- return adapter.handleSlashCommand(content, context);
340
- }
341
339
  if (parsed.command === '/status') {
342
340
  return this.runtimeStatusText(adapter, context);
343
341
  }
344
- if (parsed.command === '/clear') {
345
- this.store.clearSession(context.sessionKey || 'default', { adapter: adapter.name, clearedBy: '/clear' });
346
- if (adapter.threadBySession?.delete) adapter.threadBySession.delete(context.sessionKey || 'default');
347
- if (adapter.sessionByKey?.delete) adapter.sessionByKey.delete(context.sessionKey || 'default');
348
- return `${adapterDisplayName(adapter.name)} session cleared for this WTT topic.`;
342
+ if (['/clear', '/new', '/reset', '/new-session', '/new-thread'].includes(parsed.command)) {
343
+ return this.clearAdapterSession(adapter, context.sessionKey || 'default', parsed.command);
344
+ }
345
+ if (adapter.name === 'codex' && typeof adapter.handleSlashCommand === 'function') {
346
+ const handled = await adapter.handleSlashCommand(content, context);
347
+ if (handled !== null && handled !== undefined) return handled;
349
348
  }
350
349
  return null;
351
350
  }
352
351
 
352
+ clearAdapterSession(adapter, sessionKey, command) {
353
+ this.store.clearSession(sessionKey, { adapter: adapter.name, clearedBy: command });
354
+ if (adapter.threadBySession?.delete) adapter.threadBySession.delete(sessionKey);
355
+ if (adapter.sessionByKey?.delete) adapter.sessionByKey.delete(sessionKey);
356
+ return `${adapterDisplayName(adapter.name)} session cleared for this WTT topic. The next turn starts a fresh agent thread/session.`;
357
+ }
358
+
353
359
  runtimeStatusText(adapter, context = {}) {
354
360
  const runtime = this.runtimeInfo();
355
361
  const modelConfig = context.modelConfig || {};
@@ -518,13 +524,6 @@ function adapterDisplayName(name) {
518
524
  return name || 'Agent';
519
525
  }
520
526
 
521
- function parseSlashCommand(text) {
522
- const trimmed = String(text || '').trim();
523
- if (!trimmed.startsWith('/')) return null;
524
- const [command, ...rest] = trimmed.split(/\s+/);
525
- return { command: command.toLowerCase(), args: rest.join(' ') };
526
- }
527
-
528
527
  function wttSlashHelp() {
529
528
  return [
530
529
  'WTT slash commands:',
@@ -903,12 +902,6 @@ function stripWttSourceIdentity(content) {
903
902
  return String(content || '').replace(/^┌─ 来源标识[^\n]*\n(?:│[^\n]*\n)?└[^\n]*\n?/, '');
904
903
  }
905
904
 
906
- function isAgentSlashCommand(message) {
907
- const meta = parseMetadata(message?.metadata || message?.msg_metadata || message?.meta);
908
- const slashType = String(meta?.slash_type || meta?.slashType || '').trim().toLowerCase();
909
- return slashType === 'agent_passthrough';
910
- }
911
-
912
905
  function messageTopicType(m) {
913
906
  return String(m.topic_type || metadataValue(m.metadata, 'topic_type') || metadataValue(m.metadata, 'topicType') || '').toLowerCase();
914
907
  }
package/src/slash.js ADDED
@@ -0,0 +1,22 @@
1
+ export function parseSlashCommand(text) {
2
+ const trimmed = String(text || '').trim();
3
+ if (!trimmed.startsWith('/')) return null;
4
+ const [command, ...rest] = trimmed.split(/\s+/);
5
+ return { command: command.toLowerCase(), args: rest.join(' ') };
6
+ }
7
+
8
+ export function parseMetadata(metadata) {
9
+ if (!metadata) return null;
10
+ let obj = metadata;
11
+ if (typeof metadata === 'string') {
12
+ try { obj = JSON.parse(metadata); } catch { return null; }
13
+ }
14
+ return obj && typeof obj === 'object' ? obj : null;
15
+ }
16
+
17
+ export function isAgentSlashCommand(message = {}, content = '') {
18
+ const meta = parseMetadata(message?.metadata || message?.msg_metadata || message?.meta);
19
+ const slashType = String(meta?.slash_type || meta?.slashType || '').trim().toLowerCase();
20
+ if (slashType === 'agent_passthrough') return true;
21
+ return String(content || message?.content || '').trim().startsWith('/');
22
+ }