wtt-connect 0.2.17 → 0.2.18

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.17",
3
+ "version": "0.2.18",
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",
package/src/runner.js CHANGED
@@ -72,7 +72,7 @@ export class Runner {
72
72
  }
73
73
 
74
74
  runtimeInfo() {
75
- return buildRuntimeInfo(this.config);
75
+ return buildRuntimeInfo(this.config, this.store.getRuntime());
76
76
  }
77
77
 
78
78
  async onEvent(msg) {
@@ -167,10 +167,13 @@ export class Runner {
167
167
  const staged = await this.attachments.stageMessage(m);
168
168
  if (!content && !staged.files.length) return;
169
169
  await this.wtt.typing(topicId, 'start', { statusText: 'Agent 已接收消息,正在准备执行', statusKind: 'queued', ttlMs: 30000 });
170
+ let runtimeSelection = null;
170
171
  try {
171
172
  const transcripts = await this.transcribeAttachments(staged.files);
172
173
  const modelConfig = modelConfigFromMessage(m);
173
174
  const adapter = this.registry.select({ ...m, content, model_config: modelConfig });
175
+ runtimeSelection = { adapter: adapter.name, modelConfig };
176
+ this.recordRuntimeSelection(adapter.name, modelConfig, 'running');
174
177
  await this.wtt.typing(topicId, 'start', { statusText: `${adapterDisplayName(adapter.name)} 正在执行${modelConfig.model ? ` · ${modelConfig.model}` : ''}`, statusKind: 'running', adapter: adapter.name, model: modelConfig.model || undefined, ttlMs: 30000 });
175
178
  const agentProfile = await this.getAgentProfile();
176
179
  const agentSoul = renderAgentSoulContext(m.metadata, agentProfile?.role_template);
@@ -217,6 +220,7 @@ export class Runner {
217
220
  await this.wtt.publish(topicId, `执行失败:${err.message}`, 'CHAT_REPLY');
218
221
  log('error', 'chat failed', { topicId, error: err.message });
219
222
  } finally {
223
+ if (runtimeSelection) this.recordRuntimeSelection(runtimeSelection.adapter, runtimeSelection.modelConfig, 'idle');
220
224
  await this.wtt.typing(topicId, 'stop');
221
225
  }
222
226
  }
@@ -231,6 +235,7 @@ export class Runner {
231
235
  const transcripts = await this.transcribeAttachments(staged.files);
232
236
  const modelConfig = modelConfigFromMessage(task);
233
237
  const adapter = this.registry.select({ ...task, model_config: modelConfig });
238
+ this.recordRuntimeSelection(adapter.name, modelConfig, 'running');
234
239
  const agentProfile = await this.getAgentProfile();
235
240
  if (topicId) await this.wtt.typing(topicId, 'start', { statusText: `${adapterDisplayName(adapter.name)} 正在执行任务${modelConfig.model ? ` · ${modelConfig.model}` : ''}`, statusKind: 'running', adapter: adapter.name, model: modelConfig.model || undefined, ttlMs: 30000 });
236
241
  const prompt = buildTaskPrompt(task, this.config, staged, transcripts, agentProfile);
@@ -263,6 +268,7 @@ export class Runner {
263
268
  if (topicId) await this.wtt.publish(topicId, `任务失败:${err.message}`, 'TASK_BLOCKED');
264
269
  log('error', 'task failed', { taskId, error: err.message });
265
270
  } finally {
271
+ this.recordRuntimeSelection(adapter.name, modelConfig, 'idle');
266
272
  if (topicId) await this.wtt.typing(topicId, 'stop');
267
273
  }
268
274
  }
@@ -300,6 +306,17 @@ export class Runner {
300
306
  }
301
307
  }
302
308
 
309
+ recordRuntimeSelection(adapter, modelConfig = {}, status = 'idle') {
310
+ const model = String(modelConfig.model || modelConfig.model_id || modelConfig.modelId || '').trim();
311
+ const reasoning = String(modelConfig.reasoning_effort || modelConfig.reasoningEffort || '').trim().toLowerCase();
312
+ this.store.patchRuntime({
313
+ adapter,
314
+ ...(model ? { current_model: model, model, model_source: 'message_metadata' } : { model_source: 'config' }),
315
+ ...(reasoning ? { reasoning_effort: reasoning } : {}),
316
+ status,
317
+ });
318
+ }
319
+
303
320
  async transcribeAttachments(files) {
304
321
  try {
305
322
  return await this.stt.transcribeAll(files);
@@ -3,17 +3,20 @@ import path from 'node:path';
3
3
  import fs from 'node:fs';
4
4
  import { execFileSync } from 'node:child_process';
5
5
 
6
- export function buildRuntimeInfo(config) {
6
+ export function buildRuntimeInfo(config, runtimeState = {}) {
7
7
  const workdir = resolveWorkDir(config.workDir);
8
8
  const git = gitInfo(workdir);
9
- const model = runtimeModel(config);
9
+ const adapter = String(runtimeState.adapter || config.adapter || '').trim();
10
+ const model = runtimeModel(config, adapter, runtimeState);
10
11
  return {
11
12
  kind: 'wtt-connect',
12
13
  agent_id: config.agentId,
13
- adapter: config.adapter,
14
+ adapter: adapter || config.adapter,
14
15
  adapters: config.adapters,
15
16
  ...(model ? { model, current_model: model } : {}),
16
- ...(config.reasoningEffort ? { reasoning_effort: String(config.reasoningEffort) } : {}),
17
+ ...(runtimeState.model_source ? { model_source: String(runtimeState.model_source) } : {}),
18
+ ...(runtimeState.status ? { runtime_status: String(runtimeState.status) } : {}),
19
+ ...(runtimeState.reasoning_effort || config.reasoningEffort ? { reasoning_effort: String(runtimeState.reasoning_effort || config.reasoningEffort) } : {}),
17
20
  workdir,
18
21
  workdir_name: path.basename(workdir || ''),
19
22
  cwd: safeRealpath(process.cwd()),
@@ -28,20 +31,85 @@ export function buildRuntimeInfo(config) {
28
31
  };
29
32
  }
30
33
 
31
- function runtimeModel(config) {
34
+ function runtimeModel(config, adapter, runtimeState = {}) {
35
+ const fromRun = String(runtimeState.current_model || runtimeState.model || '').trim();
36
+ if (fromRun) return fromRun;
37
+
32
38
  const explicit = String(config.model || '').trim();
33
39
  if (explicit) return explicit;
34
40
 
35
- const adapter = String(config.adapter || '').trim().toLowerCase();
36
- if (adapter === 'codex') {
37
- return String(process.env.OPENAI_MODEL || process.env.CODEX_MODEL || '').trim();
41
+ const normalizedAdapter = String(adapter || config.adapter || '').trim().toLowerCase();
42
+ if (normalizedAdapter === 'codex') {
43
+ return String(process.env.OPENAI_MODEL || process.env.CODEX_MODEL || readCodexConfigModel() || '').trim();
38
44
  }
39
- if (adapter === 'claude-code' || adapter === 'claude' || adapter === 'claude_code') {
40
- return String(process.env.ANTHROPIC_MODEL || '').trim();
45
+ if (normalizedAdapter === 'claude-code' || normalizedAdapter === 'claude' || normalizedAdapter === 'claude_code') {
46
+ return String(process.env.ANTHROPIC_MODEL || readClaudeConfigModel() || '').trim();
41
47
  }
42
48
  return String(process.env.WTT_CONNECT_MODEL || '').trim();
43
49
  }
44
50
 
51
+ function readCodexConfigModel() {
52
+ const home = os.homedir();
53
+ const candidates = [
54
+ path.join(home, '.codex', 'config.toml'),
55
+ path.join(home, '.config', 'codex', 'config.toml'),
56
+ ];
57
+ for (const file of candidates) {
58
+ const model = readTomlStringKey(file, 'model');
59
+ if (model) return model;
60
+ }
61
+ return '';
62
+ }
63
+
64
+ function readClaudeConfigModel() {
65
+ const home = os.homedir();
66
+ const candidates = [
67
+ path.join(home, '.claude.json'),
68
+ path.join(home, '.claude', 'settings.json'),
69
+ path.join(home, '.config', 'claude', 'settings.json'),
70
+ ];
71
+ for (const file of candidates) {
72
+ const model = readJsonModelKey(file);
73
+ if (model) return model;
74
+ }
75
+ return '';
76
+ }
77
+
78
+ function readJsonModelKey(file) {
79
+ try {
80
+ if (!fs.existsSync(file)) return '';
81
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
82
+ return findModelValue(parsed);
83
+ } catch {
84
+ return '';
85
+ }
86
+ }
87
+
88
+ function findModelValue(value, depth = 0) {
89
+ if (!value || typeof value !== 'object' || depth > 4) return '';
90
+ for (const key of ['model', 'model_id', 'modelId', 'defaultModel', 'default_model']) {
91
+ const raw = value[key];
92
+ if (typeof raw === 'string' && raw.trim()) return raw.trim();
93
+ }
94
+ for (const child of Object.values(value)) {
95
+ const found = findModelValue(child, depth + 1);
96
+ if (found) return found;
97
+ }
98
+ return '';
99
+ }
100
+
101
+ function readTomlStringKey(file, key) {
102
+ try {
103
+ if (!fs.existsSync(file)) return '';
104
+ const text = fs.readFileSync(file, 'utf8');
105
+ const re = new RegExp(`^\\s*${key}\\s*=\\s*["']([^"']+)["']\\s*$`, 'm');
106
+ const match = text.match(re);
107
+ return match ? String(match[1] || '').trim() : '';
108
+ } catch {
109
+ return '';
110
+ }
111
+ }
112
+
45
113
  function resolveWorkDir(workDir) {
46
114
  const raw = String(workDir || process.cwd()).trim() || process.cwd();
47
115
  return safeRealpath(path.resolve(raw));
package/src/store.js CHANGED
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  export class DurableStore {
5
5
  constructor(file) {
6
6
  this.file = file;
7
- this.data = { version: 1, sessions: {}, seen: {}, artifacts: [] };
7
+ this.data = { version: 1, sessions: {}, seen: {}, artifacts: [], runtime: {} };
8
8
  this.loaded = false;
9
9
  }
10
10
 
@@ -37,6 +37,17 @@ export class DurableStore {
37
37
  this.save();
38
38
  }
39
39
 
40
+ getRuntime() {
41
+ this.load();
42
+ return this.data.runtime || {};
43
+ }
44
+
45
+ patchRuntime(patch) {
46
+ this.load();
47
+ this.data.runtime = { ...(this.data.runtime || {}), ...patch, updatedAt: new Date().toISOString() };
48
+ this.save();
49
+ }
50
+
40
51
  hasSeen(id, ttlMs = 24 * 3600_000) {
41
52
  this.load();
42
53
  const now = Date.now();