wtt-connect 0.1.6 → 0.1.8

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.1.6",
3
+ "version": "0.1.8",
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/artifacts.js CHANGED
@@ -47,6 +47,39 @@ export class ArtifactManager {
47
47
  return { file, asset: await this.uploadFile(file) };
48
48
  }
49
49
 
50
+ async uploadDirectory(dirPath, options = {}) {
51
+ const dir = path.resolve(dirPath);
52
+ const files = await collectFiles(dir);
53
+ if (!files.length) throw new Error(`artifact directory has no files: ${dir}`);
54
+ const payloadFiles = [];
55
+ for (const file of files) {
56
+ const rel = path.relative(dir, file).split(path.sep).join('/');
57
+ const bytes = await fs.readFile(file);
58
+ payloadFiles.push({
59
+ path: rel,
60
+ content_base64: bytes.toString('base64'),
61
+ mime_type: lookupMime(file),
62
+ });
63
+ }
64
+ const artifact = await this.fetchJson('/artifacts', {
65
+ method: 'POST',
66
+ headers: { 'content-type': 'application/json', ...this.authHeaders(), ...this.agentHeaders() },
67
+ body: JSON.stringify({
68
+ source: options.source || 'feed',
69
+ source_id: options.sourceId || '',
70
+ agent_id: this.config.agentId || '',
71
+ title: options.title || path.basename(dir),
72
+ type: options.type || 'opendesign',
73
+ entry_file: options.entry || inferEntry(files, dir),
74
+ metadata: options.metadata || {},
75
+ files: payloadFiles,
76
+ }),
77
+ });
78
+ this.store?.addArtifact({ filePath: dir, asset: artifact });
79
+ log('info', 'artifact directory uploaded', { dir, files: files.length, preview: artifact.preview_url });
80
+ return artifact;
81
+ }
82
+
50
83
  async fetchJson(url, options) {
51
84
  const r = await fetch(absoluteUrl(this.config.wttBaseUrl, url), options);
52
85
  if (!r.ok) throw new Error(`WTT media API failed: ${r.status} ${await r.text()}`);
@@ -56,6 +89,10 @@ export class ArtifactManager {
56
89
  authHeaders() {
57
90
  return this.config.httpToken ? { Authorization: `Bearer ${this.config.httpToken}` } : {};
58
91
  }
92
+
93
+ agentHeaders() {
94
+ return this.config.token ? { 'X-Agent-Token': this.config.token } : {};
95
+ }
59
96
  }
60
97
 
61
98
  function absoluteUrl(base, maybeRelative) {
@@ -66,3 +103,29 @@ function absoluteUrl(base, maybeRelative) {
66
103
  function safeName(name) {
67
104
  return String(name || 'artifact.txt').replace(/[^a-zA-Z0-9._-]+/g, '-').slice(0, 120) || 'artifact.txt';
68
105
  }
106
+
107
+ async function collectFiles(root) {
108
+ const out = [];
109
+ async function walk(dir) {
110
+ const entries = await fs.readdir(dir, { withFileTypes: true });
111
+ for (const entry of entries) {
112
+ if (entry.name.startsWith('.')) continue;
113
+ const full = path.join(dir, entry.name);
114
+ if (entry.isDirectory()) {
115
+ await walk(full);
116
+ } else if (entry.isFile()) {
117
+ out.push(full);
118
+ }
119
+ }
120
+ }
121
+ await walk(root);
122
+ return out;
123
+ }
124
+
125
+ function inferEntry(files, root) {
126
+ const rels = files.map((file) => path.relative(root, file).split(path.sep).join('/'));
127
+ return rels.find((file) => file === 'index.html')
128
+ || rels.find((file) => file.endsWith('/index.html'))
129
+ || rels.find((file) => file.toLowerCase().endsWith('.html'))
130
+ || rels[0];
131
+ }
package/src/main.js CHANGED
@@ -5,6 +5,8 @@ import { Runner } from './runner.js';
5
5
  import { PermissionBroker } from './permissions.js';
6
6
  import { setup } from './setup.js';
7
7
  import { smokeChat, smokeTask } from './smoke.js';
8
+ import { ArtifactManager } from './artifacts.js';
9
+ import { DurableStore } from './store.js';
8
10
  import { log, redact } from './logger.js';
9
11
  import { adapterBin, normalizeProfileName } from './adapters/generic-cli.js';
10
12
  import { normalizeAdapterName } from './adapters/index.js';
@@ -27,6 +29,7 @@ export async function main(args) {
27
29
  if (cmd === 'claim-code') return setup(loadConfig(argv), { ...argv, noRegister: true, claimCode: true });
28
30
  if (cmd === 'smoke-task') return smokeTask(loadConfig(argv), argv);
29
31
  if (cmd === 'smoke-chat') return smokeChat(loadConfig(argv), argv);
32
+ if (cmd === 'upload-artifact' || cmd === 'opendesign-upload') return uploadArtifact(loadConfig(argv), argv);
30
33
  if (cmd === 'start') {
31
34
  const config = loadConfig(argv);
32
35
  if (!config.agentId) throw new Error('WTT_AGENT_ID is required');
@@ -68,6 +71,12 @@ function parseArgs(args) {
68
71
  else if (a === '--expected') out.expected = args[++i];
69
72
  else if (a === '--prompt') out.prompt = args[++i];
70
73
  else if (a === '--message') out.message = args[++i];
74
+ else if (a === '--dir') out.dir = args[++i];
75
+ else if (a === '--source') out.source = args[++i];
76
+ else if (a === '--source-id') out.sourceId = args[++i];
77
+ else if (a === '--title') out.title = args[++i];
78
+ else if (a === '--entry') out.entry = args[++i];
79
+ else if (a === '--type') out.type = args[++i];
71
80
  else if (a === '--timeout') out.timeout = Number(args[++i]) * 1000;
72
81
  else if (a === '--sender-agent-id') out.senderAgentId = args[++i];
73
82
  else if (a === '--sender-token') out.senderToken = args[++i];
@@ -98,10 +107,29 @@ Commands:
98
107
  smoke-task Create/run a WTT task and wait for connector result
99
108
  smoke-chat Send P2P chat from smoke sender and wait for reply
100
109
  claim-code Print claim code for current WTT_AGENT_ID/TOKEN
110
+ upload-artifact --dir <path> Upload an OpenDesign/artifact directory to WTT
111
+ opendesign-upload --dir <path>
112
+ Alias for upload-artifact
101
113
  help Show this help
102
114
  `);
103
115
  }
104
116
 
117
+ async function uploadArtifact(config, argv) {
118
+ const dir = argv.dir || argv._[0];
119
+ if (!dir) throw new Error('upload-artifact requires --dir <path>');
120
+ const store = new DurableStore(config.storeFile).load();
121
+ const artifacts = new ArtifactManager(config, store);
122
+ const artifact = await artifacts.uploadDirectory(dir, {
123
+ source: argv.source || 'feed',
124
+ sourceId: argv.sourceId || '',
125
+ title: argv.title || 'OpenDesign artifact',
126
+ entry: argv.entry || '',
127
+ type: argv.type || 'opendesign',
128
+ metadata: { uploaded_by: 'wtt-connect', command: 'upload-artifact' },
129
+ });
130
+ console.log(JSON.stringify(artifact, null, 2));
131
+ }
132
+
105
133
 
106
134
  function doctor(config) {
107
135
  log('info', 'wtt-connect doctor', {
@@ -0,0 +1,68 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ const DESIGN_KEYWORDS = /动画|动效|原理图|流程图|架构图|可视化|页面|UI|界面|原型|设计|白板|公式|推导|diagram|animation|visual|prototype|wireframe|design|ui|dashboard|deck|formula|architecture/i;
5
+
6
+ export function shouldRenderOpenDesignArtifact(message, reply) {
7
+ const meta = parseMetadata(message?.metadata);
8
+ if (meta?.opendesign_render === true || meta?.open_design_render === true) return true;
9
+ if (meta?.whiteboard_required === true || meta?.whiteboard_require_html === true) return true;
10
+ const text = `${message?.content || ''}\n${reply || ''}`;
11
+ return DESIGN_KEYWORDS.test(text);
12
+ }
13
+
14
+ export async function prepareOpenDesignDir(config, topicId) {
15
+ const dir = path.join(config.artifactDir, 'opendesign', `${Date.now()}-${safeSegment(topicId || 'topic')}`);
16
+ await fs.mkdir(dir, { recursive: true });
17
+ return dir;
18
+ }
19
+
20
+ export function buildOpenDesignPrompt({ outputDir, userMessage, reply, topicId, locale = 'zh' }) {
21
+ const zh = locale === 'zh';
22
+ return [
23
+ 'You are running the OpenDesign workflow inside wtt-connect.',
24
+ 'Use OpenDesign skills as the governing design rules: senior designer role, HTML as output medium, context-first, anti-slop, committed aesthetic direction, artifact-first delivery.',
25
+ '',
26
+ 'OpenDesign rules to follow strictly:',
27
+ '- Output a real design artifact, not chat prose.',
28
+ '- Avoid generic AI gradients, emoji icons, unearned cards, and vague placeholder copy.',
29
+ '- Use distinctive typography and a committed visual direction.',
30
+ '- Build a self-contained HTML artifact with CSS/SVG animation when explaining principles, formulas, flows, architecture, or UI.',
31
+ '- Every visible control or animation must have a purpose.',
32
+ '- Use labeled placeholders only when a real asset is unavailable.',
33
+ '- Close every non-void tag and keep the page runnable without network access.',
34
+ '',
35
+ `Write the artifact files directly into this directory: ${outputDir}`,
36
+ 'Required files:',
37
+ '- index.html',
38
+ '- style.css if useful',
39
+ '- main.js only if interaction/state is genuinely needed',
40
+ '',
41
+ 'Hard requirements:',
42
+ '- index.html must be complete and runnable in a sandbox iframe.',
43
+ '- Do not use external CDNs, remote images, external fonts, forms, cookies, localStorage, or network calls.',
44
+ '- Use inline SVG or local CSS/JS only.',
45
+ '- For formula/principle explanations include: a main diagram, step animation, symbol/metric table, minimal concrete example, explanatory text beside visuals, and final compact summary.',
46
+ '- For UI/prototype requests include realistic fake data, hover states, and meaningful state transitions.',
47
+ '',
48
+ `WTT topic_id: ${topicId || 'unknown'}`,
49
+ '',
50
+ zh ? '用户原始消息:' : 'Original user message:',
51
+ String(userMessage || '').slice(0, 5000),
52
+ '',
53
+ zh ? 'Agent 正文回答,OpenDesign artifact 必须基于这段内容重新设计,不要照抄:' : 'Agent answer to transform into the artifact:',
54
+ String(reply || '').slice(0, 12000),
55
+ '',
56
+ 'After writing the files, reply with only a short one-line summary and the relative entry file name.',
57
+ ].join('\n');
58
+ }
59
+
60
+ function parseMetadata(metadata) {
61
+ if (!metadata) return null;
62
+ if (typeof metadata === 'object') return metadata;
63
+ try { return JSON.parse(String(metadata)); } catch { return null; }
64
+ }
65
+
66
+ function safeSegment(value) {
67
+ return String(value || 'artifact').replace(/[^a-zA-Z0-9._-]+/g, '-').slice(0, 80) || 'artifact';
68
+ }
package/src/runner.js CHANGED
@@ -13,6 +13,7 @@ import { log } from './logger.js';
13
13
  import { buildRuntimeInfo } from './runtime-info.js';
14
14
  import { runShellCommand } from './shell-runner.js';
15
15
  import { TerminalSessionManager } from './terminal-session.js';
16
+ import { buildOpenDesignPrompt, prepareOpenDesignDir, shouldRenderOpenDesignArtifact } from './opendesign.js';
16
17
 
17
18
  const TERMINAL_STATUSES = new Set(['review', 'done', 'approved', 'cancelled']);
18
19
 
@@ -35,6 +36,7 @@ export class Runner {
35
36
  this.queuedTasks = new Set();
36
37
  this.runningTasks = new Set();
37
38
  this.doneTasks = new Map();
39
+ this.agentProfileCache = { value: null, expiresAt: 0 };
38
40
  }
39
41
 
40
42
  async start() {
@@ -156,9 +158,10 @@ export class Runner {
156
158
  const transcripts = await this.transcribeAttachments(staged.files);
157
159
  const adapter = this.registry.select({ ...m, content });
158
160
  await this.wtt.typing(topicId, 'start', { statusText: `${adapterDisplayName(adapter.name)} 正在执行`, statusKind: 'running', adapter: adapter.name, ttlMs: 30000 });
159
- const agentSoul = renderAgentSoulContext(m.metadata, this.config.agentId);
161
+ const agentProfile = await this.getAgentProfile();
162
+ const agentSoul = renderAgentSoulContext(m.metadata, agentProfile?.role_template);
160
163
  const discussionRouting = renderDiscussionRoutingInstruction(m, this.config);
161
- const currentDisplayName = effectiveAgentDisplayName(this.config);
164
+ const currentDisplayName = effectiveAgentDisplayName(this.config, agentProfile);
162
165
  const prompt = [
163
166
  'You are replying to a WTT Web conversation. Do not mention implementation internals unless asked.',
164
167
  `WTT topic_id: ${topicId}`,
@@ -189,7 +192,8 @@ export class Runner {
189
192
  });
190
193
  const reply = stripHiddenContextLeak(output || '(empty response)') || '(empty response)';
191
194
  await this.maybeAttachSpeech(topicId, reply, `chat-${topicId}`);
192
- await this.wtt.publish(topicId, reply, 'CHAT_REPLY', roleContextMetadataForRelay(m.metadata));
195
+ await this.wtt.publish(topicId, reply, 'CHAT_REPLY');
196
+ await this.maybeRenderOpenDesignArtifact(m, reply, adapter);
193
197
  log('info', 'chat replied', { topicId, chars: reply.length });
194
198
  } catch (err) {
195
199
  await this.wtt.publish(topicId, `执行失败:${err.message}`, 'CHAT_REPLY');
@@ -208,8 +212,9 @@ export class Runner {
208
212
  const staged = await this.attachments.stageMessage(task);
209
213
  const transcripts = await this.transcribeAttachments(staged.files);
210
214
  const adapter = this.registry.select(task);
215
+ const agentProfile = await this.getAgentProfile();
211
216
  if (topicId) await this.wtt.typing(topicId, 'start', { statusText: `${adapterDisplayName(adapter.name)} 正在执行任务`, statusKind: 'running', adapter: adapter.name, ttlMs: 30000 });
212
- const prompt = buildTaskPrompt(task, this.config, staged, transcripts);
217
+ const prompt = buildTaskPrompt(task, this.config, staged, transcripts, agentProfile);
213
218
  try {
214
219
  const output = await adapter.run(prompt, {
215
220
  sessionKey: `wtt:task:${taskId}`,
@@ -240,6 +245,14 @@ export class Runner {
240
245
  }
241
246
  }
242
247
 
248
+ async getAgentProfile() {
249
+ const now = Date.now();
250
+ if (this.agentProfileCache.value && now < this.agentProfileCache.expiresAt) return this.agentProfileCache.value;
251
+ const value = await this.api.getAgentProfile(this.config.agentId);
252
+ this.agentProfileCache = { value, expiresAt: now + 30_000 };
253
+ return value;
254
+ }
255
+
243
256
  async transcribeAttachments(files) {
244
257
  try {
245
258
  return await this.stt.transcribeAll(files);
@@ -261,6 +274,48 @@ export class Runner {
261
274
  }
262
275
  }
263
276
 
277
+ async maybeRenderOpenDesignArtifact(message, reply, adapter) {
278
+ const topicId = String(message.topic_id || '');
279
+ if (!topicId || !shouldRenderOpenDesignArtifact(message, reply)) return;
280
+ try {
281
+ await this.wtt.typing(topicId, 'start', {
282
+ statusText: 'OpenDesign 正在生成可视化 artifact',
283
+ statusKind: 'opendesign',
284
+ adapter: adapter.name,
285
+ ttlMs: 30000,
286
+ });
287
+ const outputDir = await prepareOpenDesignDir(this.config, topicId);
288
+ const prompt = buildOpenDesignPrompt({
289
+ outputDir,
290
+ userMessage: message.content || '',
291
+ reply,
292
+ topicId,
293
+ locale: inferLocale(`${message.content || ''}\n${reply || ''}`),
294
+ });
295
+ await adapter.run(prompt, {
296
+ sessionKey: `wtt:opendesign:${topicId}`,
297
+ topicId,
298
+ onProgress: (event) => this.maybePublishProgress(topicId, event, adapter.name),
299
+ });
300
+ const artifact = await this.artifacts.uploadDirectory(outputDir, {
301
+ source: message.topic_type || 'feed',
302
+ sourceId: topicId,
303
+ title: `OpenDesign · ${topicId.slice(0, 8)}`,
304
+ entry: 'index.html',
305
+ type: 'opendesign',
306
+ metadata: { message_id: message.id || message.message_id || '', generated_by: adapter.name },
307
+ });
308
+ const previewUrl = artifact.preview_url || artifact.url;
309
+ if (previewUrl) {
310
+ const title = artifact.title || 'OpenDesign artifact';
311
+ await this.wtt.publish(topicId, `[opendesign:${title}](${previewUrl})`, 'TASK_ARTIFACT');
312
+ }
313
+ log('info', 'opendesign artifact completed', { topicId, preview: previewUrl });
314
+ } catch (err) {
315
+ log('warn', 'opendesign artifact failed', { topicId, error: err.message });
316
+ }
317
+ }
318
+
264
319
  async materializeTaskArtifact(taskId, summary) {
265
320
  try {
266
321
  return await this.artifacts.uploadText(`task-${taskId}-summary.md`, summary);
@@ -311,6 +366,10 @@ function adapterDisplayName(name) {
311
366
  return name || 'Agent';
312
367
  }
313
368
 
369
+ function inferLocale(text) {
370
+ return /[\u4e00-\u9fff]/.test(String(text || '')) ? 'zh' : 'en';
371
+ }
372
+
314
373
  function messageTopicType(m) {
315
374
  return String(m.topic_type || metadataValue(m.metadata, 'topic_type') || metadataValue(m.metadata, 'topicType') || '').toLowerCase();
316
375
  }
@@ -473,51 +532,26 @@ function parseMetadata(metadata) {
473
532
  return obj && typeof obj === 'object' ? obj : null;
474
533
  }
475
534
 
476
- function effectiveAgentDisplayName(config) {
535
+ function effectiveAgentDisplayName(config, profile = null) {
536
+ const profileName = String(profile?.display_name || '').trim();
537
+ if (profileName && profileName.toLowerCase() !== 'wtt-connect') return profileName;
477
538
  const name = String(config.setupDisplayName || '').trim();
478
539
  if (!name || name.toLowerCase() === 'wtt-connect') return '';
479
540
  return name;
480
541
  }
481
542
 
482
- function roleContextMetadataForRelay(metadata) {
543
+ function renderAgentSoulContext(metadata, profileRole = null) {
483
544
  const meta = parseMetadata(metadata);
484
- if (!meta || typeof meta !== 'object') return null;
485
- const out = {};
486
- for (const key of [
487
- 'agent_role_templates_by_agent',
488
- 'agentRoleTemplatesByAgent',
489
- ]) {
490
- const value = meta[key];
491
- if (value && typeof value === 'object') out[key] = value;
492
- }
493
- return Object.keys(out).length ? out : null;
494
- }
495
-
496
- function renderAgentSoulContext(metadata, currentAgentId = '') {
497
- const meta = parseMetadata(metadata);
498
- if (!meta) return '';
499
545
 
500
546
  const lines = [];
501
- const soul = meta.agent_soul;
502
- const role = meta.agent_role_template;
503
- const hasRoleMap = hasAgentRoleTemplateMap(meta);
504
- const roleByAgent = getRoleTemplateForAgent(meta, currentAgentId);
505
- const persona = meta.worker_persona;
506
- const workerContext = meta.worker_context;
507
-
508
- if (roleByAgent) {
509
- if (roleByAgent.label) lines.push(`Adopt this agent role silently: ${roleByAgent.label}.`);
510
- if (Array.isArray(roleByAgent.skills) && roleByAgent.skills.length) lines.push(`Relevant capabilities: ${roleByAgent.skills.join(', ')}.`);
511
- const systemPrompt = roleByAgent.system_prompt || roleByAgent.systemPrompt || roleByAgent.instructions;
512
- if (systemPrompt) lines.push(String(systemPrompt));
513
- } else if (!hasRoleMap && soul && typeof soul === 'object') {
514
- if (soul.role) lines.push(`Adopt this agent role silently: ${soul.role}.`);
515
- if (Array.isArray(soul.skills) && soul.skills.length) lines.push(`Relevant capabilities: ${soul.skills.join(', ')}.`);
516
- if (soul.instructions) lines.push(String(soul.instructions));
517
- } else if (!hasRoleMap && role && typeof role === 'object') {
518
- if (role.label) lines.push(`Adopt this agent role silently: ${role.label}.`);
519
- if (Array.isArray(role.skills) && role.skills.length) lines.push(`Relevant capabilities: ${role.skills.join(', ')}.`);
520
- const systemPrompt = role.system_prompt || role.systemPrompt || role.instructions;
547
+ const persona = meta?.worker_persona;
548
+ const workerContext = meta?.worker_context;
549
+
550
+ const profileRoleObject = profileRole && typeof profileRole === 'object' ? profileRole : null;
551
+ if (profileRoleObject && (profileRoleObject.label || profileRoleObject.system_prompt || profileRoleObject.systemPrompt)) {
552
+ if (profileRoleObject.label) lines.push(`Adopt this agent role silently: ${profileRoleObject.label}.`);
553
+ if (Array.isArray(profileRoleObject.skills) && profileRoleObject.skills.length) lines.push(`Relevant capabilities: ${profileRoleObject.skills.join(', ')}.`);
554
+ const systemPrompt = profileRoleObject.system_prompt || profileRoleObject.systemPrompt || profileRoleObject.instructions;
521
555
  if (systemPrompt) lines.push(String(systemPrompt));
522
556
  }
523
557
 
@@ -539,31 +573,6 @@ function renderAgentSoulContext(metadata, currentAgentId = '') {
539
573
  ].join('\n');
540
574
  }
541
575
 
542
- function hasAgentRoleTemplateMap(meta) {
543
- const byAgent = meta.agent_role_templates_by_agent || meta.agentRoleTemplatesByAgent;
544
- return Boolean(byAgent && typeof byAgent === 'object');
545
- }
546
-
547
- function getRoleTemplateForAgent(meta, currentAgentId) {
548
- const byAgent = meta.agent_role_templates_by_agent || meta.agentRoleTemplatesByAgent;
549
- if (!byAgent || typeof byAgent !== 'object' || !currentAgentId) return null;
550
-
551
- const exact = byAgent[currentAgentId];
552
- if (exact && typeof exact === 'object') return exact;
553
-
554
- const normalizedCurrent = normalizeAgentRoleMapKey(currentAgentId);
555
- for (const [agentId, role] of Object.entries(byAgent)) {
556
- if (normalizeAgentRoleMapKey(agentId) === normalizedCurrent && role && typeof role === 'object') {
557
- return role;
558
- }
559
- }
560
- return null;
561
- }
562
-
563
- function normalizeAgentRoleMapKey(value) {
564
- return String(value || '').trim().toLowerCase();
565
- }
566
-
567
576
  function stripHiddenContextLeak(text) {
568
577
  return String(text || '')
569
578
  .replace(/\[Agent Role Template\][\s\S]*?\[\/Agent Role Template\]\s*/gi, '')
@@ -639,10 +648,10 @@ function taskFromEvent(taskId, event) {
639
648
  return { id: taskId, title: m.title || `WTT task ${taskId}`, description: m.content || '', topic_id: m.topic_id };
640
649
  }
641
650
 
642
- function buildTaskPrompt(task, config, staged = { promptBlock: '' }, transcripts = []) {
651
+ function buildTaskPrompt(task, config, staged = { promptBlock: '' }, transcripts = [], agentProfile = null) {
643
652
  const title = task.title || task.name || `Task ${task.id}`;
644
653
  const description = task.description || task.content || task.task_request || '';
645
- const agentSoul = renderAgentSoulContext(task.metadata || task.msg_metadata, config.agentId);
654
+ const agentSoul = renderAgentSoulContext(task.metadata || task.msg_metadata, agentProfile?.role_template);
646
655
  return [
647
656
  'You are executing a WTT task through wtt-connect.',
648
657
  `Task ID: ${task.id}`,
package/src/wtt-api.js CHANGED
@@ -49,6 +49,16 @@ export class WTTApi {
49
49
  }
50
50
  }
51
51
 
52
+ async getAgentProfile(agentId = this.config.agentId) {
53
+ if (!agentId) return null;
54
+ try {
55
+ return await this.request('GET', `/agents/${encodeURIComponent(agentId)}/profile`);
56
+ } catch (err) {
57
+ log('warn', 'get_agent_profile failed', { agentId, error: err.message });
58
+ return null;
59
+ }
60
+ }
61
+
52
62
  async patchTask(taskId, payload) {
53
63
  if (this.config.dryRun) {
54
64
  log('info', 'dry-run patch task', { taskId, fields: Object.keys(payload).join(',') });
@@ -1,14 +0,0 @@
1
- [Unit]
2
- Description=WTT Connect Claude Code Agent
3
- After=network-online.target
4
- Wants=network-online.target
5
-
6
- [Service]
7
- Type=simple
8
- WorkingDirectory=/mnt/wd10t/saiph/wtt/wtt/tools/wtt-connect
9
- ExecStart=/usr/bin/node --experimental-websocket /mnt/wd10t/saiph/wtt/wtt/tools/wtt-connect/bin/wtt-connect.js start --env-file /mnt/wd10t/saiph/wtt/wtt/tools/wtt-connect/.env.claude
10
- Restart=always
11
- RestartSec=5
12
-
13
- [Install]
14
- WantedBy=default.target
@@ -1,14 +0,0 @@
1
- [Unit]
2
- Description=WTT Connect Codex Agent
3
- After=network-online.target
4
- Wants=network-online.target
5
-
6
- [Service]
7
- Type=simple
8
- WorkingDirectory=/mnt/wd10t/saiph/wtt/wtt/tools/wtt-connect
9
- ExecStart=/usr/bin/node --experimental-websocket /mnt/wd10t/saiph/wtt/wtt/tools/wtt-connect/bin/wtt-connect.js start --env-file /mnt/wd10t/saiph/wtt/wtt/tools/wtt-connect/.env
10
- Restart=always
11
- RestartSec=5
12
-
13
- [Install]
14
- WantedBy=default.target