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 +1 -1
- package/src/artifacts.js +63 -0
- package/src/main.js +28 -0
- package/src/opendesign.js +68 -0
- package/src/runner.js +77 -68
- package/src/wtt-api.js +10 -0
- package/systemd/wtt-connect-claude.service +0 -14
- package/systemd/wtt-connect-codex.service +0 -14
package/package.json
CHANGED
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
|
|
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'
|
|
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
|
|
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
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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,
|
|
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
|