wtt-connect 0.2.63 → 0.2.64
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/runner.js +35 -1
- package/src/runtime-info.js +138 -0
package/package.json
CHANGED
package/src/runner.js
CHANGED
|
@@ -228,8 +228,9 @@ export class Runner {
|
|
|
228
228
|
return;
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
|
+
const skillSlashPrompt = isSlashPassthrough ? this.skillSlashPrompt(content, m.metadata) : '';
|
|
231
232
|
const compactSummary = this.store.getSession(sessionKey).compactSummary || '';
|
|
232
|
-
const prompt = isSlashPassthrough ? content : [
|
|
233
|
+
const prompt = isSlashPassthrough ? (skillSlashPrompt || content) : [
|
|
233
234
|
'You are replying to a WTT Web conversation. Do not mention implementation internals unless asked.',
|
|
234
235
|
`WTT topic_id: ${topicId}`,
|
|
235
236
|
`WTT topic_type: ${messageTopicType(m) || 'unknown'}`,
|
|
@@ -434,6 +435,12 @@ export class Runner {
|
|
|
434
435
|
return `${adapterDisplayName(adapter.name)} session cleared for this WTT topic. The next turn starts a fresh agent thread/session.`;
|
|
435
436
|
}
|
|
436
437
|
|
|
438
|
+
skillSlashPrompt(content, metadata = null) {
|
|
439
|
+
const runtime = this.runtimeInfo();
|
|
440
|
+
const commands = Array.isArray(runtime.skill_commands) ? runtime.skill_commands : [];
|
|
441
|
+
return buildSkillSlashPrompt(content, metadata, commands);
|
|
442
|
+
}
|
|
443
|
+
|
|
437
444
|
async compactAdapterSession(adapter, context = {}, command = '/compact', args = '') {
|
|
438
445
|
const sessionKey = context.sessionKey || 'default';
|
|
439
446
|
const topicId = String(context.topicId || '').trim();
|
|
@@ -764,6 +771,33 @@ export class Runner {
|
|
|
764
771
|
}
|
|
765
772
|
}
|
|
766
773
|
|
|
774
|
+
export function buildSkillSlashPrompt(content, metadata = null, commands = []) {
|
|
775
|
+
const parsed = parseSlashCommand(content);
|
|
776
|
+
if (!parsed) return '';
|
|
777
|
+
const meta = parseMetadata(metadata);
|
|
778
|
+
const metadataSkillId = String(meta?.skill_id || meta?.skillId || '').trim();
|
|
779
|
+
const metadataFamily = String(meta?.command_family || meta?.commandFamily || '').trim().toLowerCase();
|
|
780
|
+
const matched = (Array.isArray(commands) ? commands : []).find((item) => String(item?.cmd || '').trim().toLowerCase() === parsed.command)
|
|
781
|
+
|| (metadataFamily === 'skill' || metadataSkillId ? {
|
|
782
|
+
cmd: parsed.command,
|
|
783
|
+
skill_id: metadataSkillId || parsed.command.replace(/^\/+/, ''),
|
|
784
|
+
name: metadataSkillId || parsed.command.replace(/^\/+/, ''),
|
|
785
|
+
desc: '',
|
|
786
|
+
} : null);
|
|
787
|
+
if (!matched) return '';
|
|
788
|
+
const skillName = String(matched.name || matched.skill_id || matched.cmd || '').trim().replace(/^\/+/, '');
|
|
789
|
+
const skillId = String(matched.skill_id || skillName).trim();
|
|
790
|
+
const args = String(parsed.args || '').trim();
|
|
791
|
+
return [
|
|
792
|
+
`Use the ${skillName} skill${skillId && skillId !== skillName ? ` (${skillId})` : ''}.`,
|
|
793
|
+
matched.desc ? `Skill description: ${matched.desc}` : null,
|
|
794
|
+
'',
|
|
795
|
+
args ? `User request: ${args}` : 'User request: Run this skill for the current conversation.',
|
|
796
|
+
'',
|
|
797
|
+
`Original slash command: ${content}`,
|
|
798
|
+
].filter(Boolean).join('\n');
|
|
799
|
+
}
|
|
800
|
+
|
|
767
801
|
function adapterDisplayName(name) {
|
|
768
802
|
if (name === 'claude-code') return 'Claude Code';
|
|
769
803
|
if (name === 'codex') return 'Codex';
|
package/src/runtime-info.js
CHANGED
|
@@ -3,6 +3,9 @@ import path from 'node:path';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
5
|
|
|
6
|
+
let skillCommandCache = { at: 0, commands: [] };
|
|
7
|
+
const SKILL_COMMAND_CACHE_MS = 60_000;
|
|
8
|
+
|
|
6
9
|
export function buildRuntimeInfo(config, runtimeState = {}) {
|
|
7
10
|
const workdir = resolveWorkDir(config.workDir);
|
|
8
11
|
const git = gitInfo(workdir);
|
|
@@ -30,10 +33,145 @@ export function buildRuntimeInfo(config, runtimeState = {}) {
|
|
|
30
33
|
node: process.version,
|
|
31
34
|
mode: config.mode,
|
|
32
35
|
git,
|
|
36
|
+
skill_commands: discoverSkillCommands(config, workdir),
|
|
33
37
|
updated_at: new Date().toISOString(),
|
|
34
38
|
};
|
|
35
39
|
}
|
|
36
40
|
|
|
41
|
+
export function discoverSkillCommands(config = {}, workdir = resolveWorkDir(config.workDir)) {
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
if (now - skillCommandCache.at < SKILL_COMMAND_CACHE_MS) return skillCommandCache.commands;
|
|
44
|
+
const roots = skillRoots(config, workdir);
|
|
45
|
+
const seenCommands = new Set();
|
|
46
|
+
const commands = [];
|
|
47
|
+
for (const root of roots) {
|
|
48
|
+
for (const skillFile of findSkillFiles(root, 5)) {
|
|
49
|
+
const parsed = parseSkillFile(skillFile);
|
|
50
|
+
if (!parsed.id) continue;
|
|
51
|
+
const commandName = normalizeSkillCommandName(parsed.name || parsed.id);
|
|
52
|
+
if (!commandName) continue;
|
|
53
|
+
const cmd = `/${commandName}`;
|
|
54
|
+
const key = cmd.toLowerCase();
|
|
55
|
+
if (seenCommands.has(key)) continue;
|
|
56
|
+
seenCommands.add(key);
|
|
57
|
+
commands.push({
|
|
58
|
+
cmd,
|
|
59
|
+
desc: parsed.description || `Use ${parsed.name || parsed.id} skill`,
|
|
60
|
+
family: 'skill',
|
|
61
|
+
mode: 'passthrough',
|
|
62
|
+
skill_id: parsed.id,
|
|
63
|
+
name: parsed.name || parsed.id,
|
|
64
|
+
source: parsed.source,
|
|
65
|
+
});
|
|
66
|
+
if (commands.length >= 120) break;
|
|
67
|
+
}
|
|
68
|
+
if (commands.length >= 120) break;
|
|
69
|
+
}
|
|
70
|
+
skillCommandCache = { at: now, commands };
|
|
71
|
+
return commands;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function skillRoots(config = {}, workdir = '') {
|
|
75
|
+
const home = os.homedir();
|
|
76
|
+
const roots = [
|
|
77
|
+
path.join(home, '.codex', 'skills'),
|
|
78
|
+
path.join(home, '.agents', 'skills'),
|
|
79
|
+
path.join(home, '.claude', 'skills'),
|
|
80
|
+
path.join(home, '.claude', 'mattpocock-skills', 'skills'),
|
|
81
|
+
workdir ? path.join(workdir, '.codex', 'skills') : '',
|
|
82
|
+
workdir ? path.join(workdir, '.claude', 'skills') : '',
|
|
83
|
+
process.cwd() ? path.join(process.cwd(), '.codex', 'skills') : '',
|
|
84
|
+
process.cwd() ? path.join(process.cwd(), '.claude', 'skills') : '',
|
|
85
|
+
];
|
|
86
|
+
const extra = String(process.env.WTT_CONNECT_SKILL_ROOTS || config.skillRoots || '').split(path.delimiter);
|
|
87
|
+
return Array.from(new Set(
|
|
88
|
+
[...roots, ...extra]
|
|
89
|
+
.map((item) => String(item || '').trim())
|
|
90
|
+
.filter(Boolean)
|
|
91
|
+
.map((item) => path.resolve(item))
|
|
92
|
+
));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function findSkillFiles(root, maxDepth = 4) {
|
|
96
|
+
const out = [];
|
|
97
|
+
if (!root || !fs.existsSync(root)) return out;
|
|
98
|
+
const visit = (dir, depth) => {
|
|
99
|
+
if (depth < 0 || out.length >= 160) return;
|
|
100
|
+
let entries = [];
|
|
101
|
+
try {
|
|
102
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
103
|
+
} catch {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (entries.some((entry) => entry.isFile() && entry.name === 'SKILL.md')) {
|
|
107
|
+
out.push(path.join(dir, 'SKILL.md'));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
for (const entry of entries) {
|
|
111
|
+
if (!entry.isDirectory()) continue;
|
|
112
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.git')) continue;
|
|
113
|
+
visit(path.join(dir, entry.name), depth - 1);
|
|
114
|
+
if (out.length >= 160) return;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
visit(root, maxDepth);
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function parseSkillFile(file) {
|
|
122
|
+
try {
|
|
123
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
124
|
+
const fallbackId = path.basename(path.dirname(file));
|
|
125
|
+
const frontmatter = text.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
126
|
+
const meta = frontmatter ? parseSimpleYaml(frontmatter[1]) : {};
|
|
127
|
+
const id = String(meta.name || fallbackId || '').trim();
|
|
128
|
+
return {
|
|
129
|
+
id,
|
|
130
|
+
name: id,
|
|
131
|
+
description: String(meta.description || firstMarkdownSentence(text) || '').trim(),
|
|
132
|
+
source: sourceForSkillPath(file),
|
|
133
|
+
};
|
|
134
|
+
} catch {
|
|
135
|
+
return { id: '', name: '', description: '', source: 'local' };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function parseSimpleYaml(text) {
|
|
140
|
+
const out = {};
|
|
141
|
+
for (const line of String(text || '').split(/\r?\n/)) {
|
|
142
|
+
const match = line.match(/^([A-Za-z0-9_-]+)\s*:\s*(.*)$/);
|
|
143
|
+
if (!match) continue;
|
|
144
|
+
const key = match[1];
|
|
145
|
+
let value = match[2].trim();
|
|
146
|
+
value = value.replace(/^['"]|['"]$/g, '');
|
|
147
|
+
out[key] = value;
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function firstMarkdownSentence(text) {
|
|
153
|
+
const body = String(text || '').replace(/^---\s*\n[\s\S]*?\n---/, '').trim();
|
|
154
|
+
const line = body.split(/\r?\n/).map((item) => item.trim().replace(/^#+\s*/, '')).find(Boolean);
|
|
155
|
+
return line || '';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function normalizeSkillCommandName(value) {
|
|
159
|
+
const raw = String(value || '').trim().split(':').pop() || '';
|
|
160
|
+
return raw
|
|
161
|
+
.replace(/[^A-Za-z0-9_-]+/g, '-')
|
|
162
|
+
.replace(/^-+|-+$/g, '')
|
|
163
|
+
.slice(0, 80)
|
|
164
|
+
.toLowerCase();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function sourceForSkillPath(file) {
|
|
168
|
+
const normalized = String(file || '');
|
|
169
|
+
if (normalized.includes(`${path.sep}.codex${path.sep}`)) return 'codex-local';
|
|
170
|
+
if (normalized.includes(`${path.sep}.claude${path.sep}`)) return 'claude-local';
|
|
171
|
+
if (normalized.includes(`${path.sep}.agents${path.sep}`)) return 'agents-local';
|
|
172
|
+
return 'local';
|
|
173
|
+
}
|
|
174
|
+
|
|
37
175
|
function runtimeModel(config, adapter, runtimeState = {}) {
|
|
38
176
|
const fromRun = String(runtimeState.current_model || runtimeState.model || '').trim();
|
|
39
177
|
if (fromRun) return fromRun;
|