termdock 1.4.71 → 1.4.73
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/README.md +59 -0
- package/dist/client/assets/{ModelPreview-CG6vLwyw.js → ModelPreview-BYS46cJp.js} +1 -1
- package/dist/client/assets/{abnfDiagram-VRR7QNED-CrXEZLWX.js → abnfDiagram-VRR7QNED-Df5iizBM.js} +1 -1
- package/dist/client/assets/{arc-DbKGR7Ex.js → arc-BRLIrHaj.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-PF5BoUbI.js → architectureDiagram-ZJ3FMSHR-BmIKhrrq.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-D1gRzVWB.js → blockDiagram-677ZJIJ3-PBtq2qLG.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-CwvOLync.js → c4Diagram-LMCZKHZV-6pevynP4.js} +1 -1
- package/dist/client/assets/channel-CfecnPVa.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-BeRdP-xP.js → chunk-2Q5K7J3B-78EZViva.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-CrqR5rvl.js → chunk-32BRIVSS-CekczK5l.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-CjbUr-ko.js → chunk-5VM5RSS4-fMhlGbVA.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-BVEk3Wma.js → chunk-EX3LRPZG-DOBewV4Z.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-WitJe8kR.js → chunk-JWPE2WC7-rtk-mGC7.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-BCewlLYk.js → chunk-MOJQB5TN-CWUZocq_.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-CoP0AA4G.js → chunk-RYQCIY6F-DcHAjeyV.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-CSZrnmp_.js → chunk-V7JOEXUC-C2paDSKz.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-BB-7zz-9.js → chunk-VR4S4FIN-Cq0ZrFUq.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-CcZZlCik.js → chunk-XXDRQBXY-BboySYmP.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-CFpMarVm.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-CFpMarVm.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-DcmzUDwh.js → cose-bilkent-JH36ORCC-CMnHIr6y.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-C1XlDVoJ.js → cynefin-VYW2F7L2-BpOtLuYh.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-zY6Aet2S.js → cynefinDiagram-TSTJHNR4-CmrMqA7z.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-DqGTi6zR.js → dagre-VKFMJZFB-iXOV-rm7.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-DStcsU7S.js → diagram-FQU43EPY-D5KagnmX.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-Cs6IMNip.js → diagram-G47NLZAW-CglgkfyY.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-yA2bpWN7.js → diagram-NH7WQ7WH-Dos-y1pl.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-Dzf9M0PI.js → diagram-OA4YK3LP-Cw6yJqFX.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-D75lpQKA.js → diagram-WEI45ONY-C_MHHYRA.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-CkQvIpWy.js → ebnfDiagram-CCIWWBDH-BB7x8X2l.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-Ca00Vjxz.js → erDiagram-Q63AITRT-BznrrU3P.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U-CGrnSTEp.js → flowDiagram-23GEKE2U-CMzPvwGa.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-0SHfOIP4.js → ganttDiagram-NO4QXBWP-Dc9JCHv3.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-BeigYWvs.js → gitGraphDiagram-IHSO6WYX-WlPpmZQm.js} +1 -1
- package/dist/client/assets/index-wX3Y6Ks7.js +451 -0
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-CiY1-xjN.js → infoDiagram-FWYZ7A6U-DC6PBJiN.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-CmtDjUm_.js → ishikawaDiagram-FXEZZL3T-DPfxPu0W.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-B7qOr9LY.js → journeyDiagram-5HDEW3XC-aA2ovr_f.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-DO2P2Um3.js → kanban-definition-HUTT4EX6-_T30f7Or.js} +1 -1
- package/dist/client/assets/{linear-B832hJt0.js → linear-DRZcO7_m.js} +1 -1
- package/dist/client/assets/{mermaid.core-CsAzdiCU.js → mermaid.core-BGXjYfdb.js} +4 -4
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-HI__vXhQ.js → mindmap-definition-LN4V7U3C-Bv0lhpqs.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR--rZIwjXq.js → pegDiagram-2B236MQR-D-EIIOZf.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-D9jPf5x9.js → pieDiagram-ENE6RG2P-CATAcPgl.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-DTCP5ji2.js → quadrantDiagram-ABIIQ3AL-2tglHVSk.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-D2Gspvrl.js → railroadDiagram-RFXS5EU6-BMKC-e8R.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-DEJC02IW.js → requirementDiagram-TGXJPOKE-B5J5_wFW.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-opOhKPap.js → sankeyDiagram-HTMAVEWB-Djlm71ae.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-_sz2CRAG.js → sequenceDiagram-DBY2YBRQ-BtR8OPSJ.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-D0Q2QkYF.js → sizeCapture-X5ZJPWSS-CDelD9h_.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-VgeSQTtW.js → stateDiagram-2N3HPSRC-C50kGru1.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-gIr1L4WZ.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-ClvBvSl0.js → swimlanes-5IMT3BWC-COHxl86-.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-COE4p0kX.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-BXNsrECV.js → timeline-definition-FHXFAJF6-br-VI8Fd.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-BgwiqZd-.js → vennDiagram-L72KCM5P-zwMtEv81.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-DiqN5ERx.js → wardleyDiagram-EHGQE667-_C8v4nnr.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-DQjFVqC6.js → xychartDiagram-FW5EYKEG-CSU2R6pt.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/client/sw.js +1 -1
- package/dist/server/agent/autoTitle.js +3 -0
- package/dist/server/agent/pluginPackages.js +289 -0
- package/dist/server/agent/plugins.js +139 -20
- package/dist/server/agent/titleNamerCatalog.js +235 -40
- package/dist/server/cli.js +277 -17
- package/dist/server/entry.js +11 -2
- package/dist/server/routes/terminal.js +241 -30
- package/dist/server/utils/csrfProtection.js +4 -1
- package/dist/server/utils/settings.js +14 -0
- package/package.json +1 -1
- package/runtime-manifest.json +2 -2
- package/dist/client/assets/channel-JTEuKVy6.js +0 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-BiFJB_5s.js +0 -1
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-BiFJB_5s.js +0 -1
- package/dist/client/assets/index-D5PKdbhW.js +0 -451
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-BVh2FRO-.js +0 -1
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-C17V-kgm.js +0 -8
|
@@ -4,10 +4,24 @@ import os from 'os';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { promisify } from 'util';
|
|
6
6
|
import { loadPlugins } from './plugins.js';
|
|
7
|
+
import { getAutoRenameAgentsSetting, getAutoRenameNamerSetting } from '../utils/settings.js';
|
|
7
8
|
const execFileAsync = promisify(execFile);
|
|
8
9
|
const CACHE_FRESH_MS = 24 * 60 * 60_000;
|
|
9
10
|
const PROBE_TIMEOUT_MS = 10_000;
|
|
10
11
|
const CACHE_FILE = path.join(os.homedir(), '.termdock', 'title-namer-catalog.json');
|
|
12
|
+
function pluginCommandEnv() {
|
|
13
|
+
const allowed = ['PATH', 'HOME', 'USER', 'LOGNAME', 'SHELL', 'LANG', 'TERM', 'TMPDIR', 'XDG_CONFIG_HOME', 'XDG_CACHE_HOME', 'XDG_DATA_HOME'];
|
|
14
|
+
const env = { NO_COLOR: '1', TERM: 'dumb' };
|
|
15
|
+
for (const key of allowed) {
|
|
16
|
+
if (process.env[key] !== undefined)
|
|
17
|
+
env[key] = process.env[key];
|
|
18
|
+
}
|
|
19
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
20
|
+
if (key.startsWith('LC_') && value !== undefined)
|
|
21
|
+
env[key] = value;
|
|
22
|
+
}
|
|
23
|
+
return env;
|
|
24
|
+
}
|
|
11
25
|
let cached = null;
|
|
12
26
|
let refreshPromise = null;
|
|
13
27
|
export function invalidateTitleNamerCatalog() {
|
|
@@ -27,19 +41,16 @@ function normalizeCachedCatalog(input) {
|
|
|
27
41
|
return [];
|
|
28
42
|
if (!Array.isArray(candidate.models))
|
|
29
43
|
return [];
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
&& typeof model.isDefault === 'boolean'));
|
|
44
|
+
const catalog = normalizeDiscoveredModelCatalog({
|
|
45
|
+
models: candidate.models,
|
|
46
|
+
recommendedModel: candidate.recommendedModel,
|
|
47
|
+
});
|
|
35
48
|
return [{
|
|
36
49
|
slug: candidate.slug,
|
|
37
50
|
displayName: typeof candidate.displayName === 'string' ? candidate.displayName : candidate.slug,
|
|
38
51
|
available: candidate.available === true,
|
|
39
|
-
models,
|
|
40
|
-
recommendedModel:
|
|
41
|
-
? candidate.recommendedModel
|
|
42
|
-
: recommendTitleModel(models),
|
|
52
|
+
models: catalog.models,
|
|
53
|
+
recommendedModel: catalog.recommendedModel,
|
|
43
54
|
}];
|
|
44
55
|
});
|
|
45
56
|
return value.length > 0 ? { version: 1, updatedAt: doc.updatedAt, value } : null;
|
|
@@ -60,7 +71,7 @@ async function savePersistentCache(doc) {
|
|
|
60
71
|
await fs.promises.rename(temporary, CACHE_FILE);
|
|
61
72
|
}
|
|
62
73
|
export function recommendTitleModel(models) {
|
|
63
|
-
return models.find((model) =>
|
|
74
|
+
return models.find((model) => model.isEconomical)?.id
|
|
64
75
|
?? models.find((model) => model.isDefault)?.id
|
|
65
76
|
?? null;
|
|
66
77
|
}
|
|
@@ -120,6 +131,7 @@ async function listCodexModels() {
|
|
|
120
131
|
displayName: model.displayName,
|
|
121
132
|
description: typeof model.description === 'string' ? model.description : '',
|
|
122
133
|
isDefault: model.isDefault === true,
|
|
134
|
+
isEconomical: false,
|
|
123
135
|
}];
|
|
124
136
|
}) : [];
|
|
125
137
|
finish(models);
|
|
@@ -141,7 +153,7 @@ export function parseClaudeSupportedModels(output) {
|
|
|
141
153
|
.split(/,|\band\b/i)
|
|
142
154
|
.map((model) => model.trim().replace(/^["'`]+|["'`]+$/g, ''))
|
|
143
155
|
.filter((model) => /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,119}$/.test(model))
|
|
144
|
-
.map((model) => ({ id: model, displayName: model, description: '', isDefault: false }));
|
|
156
|
+
.map((model) => ({ id: model, displayName: model, description: '', isDefault: false, isEconomical: false }));
|
|
145
157
|
}
|
|
146
158
|
async function listClaudeModels() {
|
|
147
159
|
try {
|
|
@@ -159,35 +171,85 @@ async function listClaudeModels() {
|
|
|
159
171
|
return parseClaudeSupportedModels(`${candidate.stdout ?? ''}\n${candidate.stderr ?? ''}`);
|
|
160
172
|
}
|
|
161
173
|
}
|
|
162
|
-
function
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
174
|
+
export function normalizeDiscoveredModelCatalog(input) {
|
|
175
|
+
const root = input && typeof input === 'object' && !Array.isArray(input)
|
|
176
|
+
? input
|
|
177
|
+
: null;
|
|
178
|
+
const entries = Array.isArray(input)
|
|
179
|
+
? input
|
|
180
|
+
: Array.isArray(root?.models)
|
|
181
|
+
? root.models
|
|
182
|
+
: Array.isArray(root?.data)
|
|
183
|
+
? root.data
|
|
184
|
+
: [];
|
|
185
|
+
let ignoredEntries = 0;
|
|
186
|
+
const seenIds = new Set();
|
|
187
|
+
const models = entries.flatMap((entry) => {
|
|
188
|
+
const rawId = typeof entry === 'string'
|
|
189
|
+
? entry
|
|
190
|
+
: entry && typeof entry === 'object'
|
|
191
|
+
? (() => {
|
|
192
|
+
const model = entry;
|
|
193
|
+
return typeof model.id === 'string'
|
|
194
|
+
? model.id
|
|
195
|
+
: typeof model.name === 'string'
|
|
196
|
+
? model.name
|
|
197
|
+
: typeof model.model === 'string'
|
|
198
|
+
? model.model
|
|
199
|
+
: '';
|
|
200
|
+
})()
|
|
201
|
+
: '';
|
|
202
|
+
const id = rawId.trim();
|
|
203
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,119}$/.test(id) || seenIds.has(id)) {
|
|
204
|
+
ignoredEntries += 1;
|
|
205
|
+
return [];
|
|
168
206
|
}
|
|
169
|
-
|
|
207
|
+
seenIds.add(id);
|
|
208
|
+
if (typeof entry === 'string') {
|
|
209
|
+
return [{ id, displayName: id, description: '', isDefault: false, isEconomical: false }];
|
|
210
|
+
}
|
|
211
|
+
if (!entry || typeof entry !== 'object') {
|
|
212
|
+
ignoredEntries += 1;
|
|
170
213
|
return [];
|
|
214
|
+
}
|
|
171
215
|
const model = entry;
|
|
172
|
-
|
|
173
|
-
|
|
216
|
+
const rawDisplayName = typeof model.displayName === 'string'
|
|
217
|
+
? model.displayName
|
|
218
|
+
: typeof model.display_name === 'string'
|
|
219
|
+
? model.display_name
|
|
220
|
+
: typeof model.label === 'string'
|
|
221
|
+
? model.label
|
|
222
|
+
: id;
|
|
223
|
+
const displayName = rawDisplayName.trim().slice(0, 160) || id;
|
|
174
224
|
return [{
|
|
175
|
-
id
|
|
176
|
-
displayName
|
|
177
|
-
description: typeof model.description === 'string' ? model.description : '',
|
|
178
|
-
isDefault: model.isDefault === true,
|
|
225
|
+
id,
|
|
226
|
+
displayName,
|
|
227
|
+
description: typeof model.description === 'string' ? model.description.trim().slice(0, 1000) : '',
|
|
228
|
+
isDefault: model.isDefault === true || model.is_default === true || model.default === true,
|
|
229
|
+
isEconomical: model.isEconomical === true || model.is_economical === true || model.economical === true,
|
|
179
230
|
}];
|
|
180
231
|
});
|
|
232
|
+
const declared = typeof (root?.recommendedModel ?? root?.recommended_model) === 'string'
|
|
233
|
+
? String(root?.recommendedModel ?? root?.recommended_model).trim()
|
|
234
|
+
: '';
|
|
235
|
+
const recommendedModel = declared && models.some((model) => model.id === declared)
|
|
236
|
+
? declared
|
|
237
|
+
: recommendTitleModel(models);
|
|
238
|
+
return { models, recommendedModel, ignoredEntries };
|
|
181
239
|
}
|
|
182
|
-
|
|
240
|
+
function expandPluginPath(value, pluginDir) {
|
|
241
|
+
return value.replaceAll('{pluginDir}', pluginDir);
|
|
242
|
+
}
|
|
243
|
+
async function listPluginModels(config, pluginDir) {
|
|
183
244
|
if (!config.models)
|
|
184
|
-
return [];
|
|
185
|
-
const { stdout } = await execFileAsync(config.models.command, config.models.args ?? [], {
|
|
245
|
+
return { models: [], recommendedModel: null, ignoredEntries: 0 };
|
|
246
|
+
const { stdout } = await execFileAsync(expandPluginPath(config.models.command, pluginDir), (config.models.args ?? []).map((arg) => expandPluginPath(arg, pluginDir)), {
|
|
186
247
|
timeout: PROBE_TIMEOUT_MS,
|
|
187
248
|
maxBuffer: 256 * 1024,
|
|
188
|
-
|
|
249
|
+
cwd: pluginDir,
|
|
250
|
+
env: pluginCommandEnv(),
|
|
189
251
|
});
|
|
190
|
-
return
|
|
252
|
+
return normalizeDiscoveredModelCatalog(JSON.parse(stdout));
|
|
191
253
|
}
|
|
192
254
|
function pluginTitleNamers() {
|
|
193
255
|
return loadPlugins().plugins.flatMap((plugin) => plugin.manifest.titleNamer
|
|
@@ -195,12 +257,144 @@ function pluginTitleNamers() {
|
|
|
195
257
|
slug: plugin.manifest.slug,
|
|
196
258
|
displayName: plugin.manifest.displayName,
|
|
197
259
|
config: plugin.manifest.titleNamer,
|
|
260
|
+
dir: plugin.dir,
|
|
198
261
|
}]
|
|
199
262
|
: []);
|
|
200
263
|
}
|
|
264
|
+
export function shouldRunPluginTitleCommands(slug, selected, enabledAgents) {
|
|
265
|
+
return selected === slug
|
|
266
|
+
|| (selected === 'auto' && enabledAgents.includes(slug));
|
|
267
|
+
}
|
|
268
|
+
function isPluginTitleExecutionEnabled(slug) {
|
|
269
|
+
return shouldRunPluginTitleCommands(slug, getAutoRenameNamerSetting(), getAutoRenameAgentsSetting());
|
|
270
|
+
}
|
|
271
|
+
async function cacheExplicitPluginProbe(slug, displayName, catalog) {
|
|
272
|
+
if (!cached)
|
|
273
|
+
cached = await loadPersistentCache();
|
|
274
|
+
const current = cached?.value ?? await getTitleNamerCatalog();
|
|
275
|
+
const value = current.map((entry) => entry.slug === slug
|
|
276
|
+
? {
|
|
277
|
+
slug,
|
|
278
|
+
displayName,
|
|
279
|
+
available: true,
|
|
280
|
+
models: catalog.models,
|
|
281
|
+
recommendedModel: catalog.recommendedModel,
|
|
282
|
+
}
|
|
283
|
+
: entry);
|
|
284
|
+
cached = { version: 1, updatedAt: Date.now(), value };
|
|
285
|
+
await savePersistentCache(cached).catch(() => undefined);
|
|
286
|
+
}
|
|
287
|
+
function expandTitleArg(value, pluginDir, prompt, model) {
|
|
288
|
+
return expandPluginPath(value, pluginDir).replaceAll('{prompt}', prompt).replaceAll('{model}', model);
|
|
289
|
+
}
|
|
290
|
+
export function buildPluginTitleArgs(config, pluginDir, prompt, model) {
|
|
291
|
+
const baseArgs = [];
|
|
292
|
+
for (const arg of config.args) {
|
|
293
|
+
if (arg.includes('{model}') && !model) {
|
|
294
|
+
// Legacy v2 manifests put paired flags in args. Remove an immediately
|
|
295
|
+
// preceding standalone flag too, so `-c model={model}` cannot degrade
|
|
296
|
+
// into the invalid `-c -p ...`. New plugins should use modelArgs.
|
|
297
|
+
if (baseArgs.at(-1)?.startsWith('-') && !baseArgs.at(-1)?.includes('='))
|
|
298
|
+
baseArgs.pop();
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
baseArgs.push(expandTitleArg(arg, pluginDir, prompt, model ?? ''));
|
|
302
|
+
}
|
|
303
|
+
const selectedModelArgs = model && config.modelArgs
|
|
304
|
+
? config.modelArgs.map((arg) => expandTitleArg(arg, pluginDir, prompt, model))
|
|
305
|
+
: [];
|
|
306
|
+
return [...selectedModelArgs, ...baseArgs];
|
|
307
|
+
}
|
|
308
|
+
export async function probePluginTitleNamer(slug) {
|
|
309
|
+
const plugin = loadPlugins().plugins.find((entry) => entry.manifest.slug === slug);
|
|
310
|
+
if (!plugin)
|
|
311
|
+
return null;
|
|
312
|
+
const config = plugin.manifest.titleNamer;
|
|
313
|
+
if (!config) {
|
|
314
|
+
return {
|
|
315
|
+
slug,
|
|
316
|
+
displayName: plugin.manifest.displayName,
|
|
317
|
+
hasTitleNamer: false,
|
|
318
|
+
hasModelCommand: false,
|
|
319
|
+
status: 'missing-title-namer',
|
|
320
|
+
models: [],
|
|
321
|
+
recommendedModel: null,
|
|
322
|
+
selectionBehavior: 'This plugin cannot generate titles until manifest.titleNamer is added.',
|
|
323
|
+
warnings: ['manifest.json has no titleNamer declaration.'],
|
|
324
|
+
error: null,
|
|
325
|
+
nextSteps: ['Run `td agent-plugin --json`, add manifest.titleNamer, then update/reinstall the plugin.'],
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
const warnings = [];
|
|
329
|
+
if (!config.modelArgs && config.args.some((arg) => arg.includes('{model}'))) {
|
|
330
|
+
warnings.push('Legacy {model} placement detected in args; use modelArgs for optional paired flags.');
|
|
331
|
+
}
|
|
332
|
+
if (!config.models) {
|
|
333
|
+
return {
|
|
334
|
+
slug,
|
|
335
|
+
displayName: plugin.manifest.displayName,
|
|
336
|
+
hasTitleNamer: true,
|
|
337
|
+
hasModelCommand: false,
|
|
338
|
+
status: 'cli-default',
|
|
339
|
+
models: [],
|
|
340
|
+
recommendedModel: null,
|
|
341
|
+
selectionBehavior: 'Termdock will omit all model arguments and use the Agent CLI default model.',
|
|
342
|
+
warnings,
|
|
343
|
+
error: null,
|
|
344
|
+
nextSteps: ['Optional: add titleNamer.models to expose selectable models; otherwise the CLI default is used.'],
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
const catalog = await listPluginModels(config, plugin.dir);
|
|
349
|
+
await cacheExplicitPluginProbe(slug, plugin.manifest.displayName, catalog);
|
|
350
|
+
if (catalog.ignoredEntries > 0) {
|
|
351
|
+
warnings.push(`${catalog.ignoredEntries} model entr${catalog.ignoredEntries === 1 ? 'y was' : 'ies were'} ignored because its identifier was invalid or duplicated.`);
|
|
352
|
+
}
|
|
353
|
+
if (catalog.models.length > 0 && !catalog.recommendedModel) {
|
|
354
|
+
warnings.push('No recommendedModel, isEconomical, or isDefault marker was found; automatic mode will use the Agent CLI default model.');
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
slug,
|
|
358
|
+
displayName: plugin.manifest.displayName,
|
|
359
|
+
hasTitleNamer: true,
|
|
360
|
+
hasModelCommand: true,
|
|
361
|
+
status: catalog.models.length > 0 ? 'ok' : 'no-models',
|
|
362
|
+
models: catalog.models,
|
|
363
|
+
recommendedModel: catalog.recommendedModel,
|
|
364
|
+
selectionBehavior: catalog.recommendedModel
|
|
365
|
+
? `Automatic selection will pass model ${catalog.recommendedModel}.`
|
|
366
|
+
: 'No recommended model was declared; Termdock will omit model arguments and use the Agent CLI default model.',
|
|
367
|
+
warnings: catalog.models.length === 0
|
|
368
|
+
? [...warnings, 'The model command succeeded but returned no usable models.']
|
|
369
|
+
: warnings,
|
|
370
|
+
error: null,
|
|
371
|
+
nextSteps: catalog.models.length > 0
|
|
372
|
+
? catalog.recommendedModel
|
|
373
|
+
? ['Select this provider/model in Settings, then enable automatic titles for the Agent.']
|
|
374
|
+
: ['Declare recommendedModel, mark one model isEconomical/isDefault, or let users select a model manually.']
|
|
375
|
+
: ['Return a JSON array (or {models}) using id/name/model plus optional displayName, description, isDefault, and isEconomical fields.'],
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
return {
|
|
380
|
+
slug,
|
|
381
|
+
displayName: plugin.manifest.displayName,
|
|
382
|
+
hasTitleNamer: true,
|
|
383
|
+
hasModelCommand: true,
|
|
384
|
+
status: 'probe-failed',
|
|
385
|
+
models: [],
|
|
386
|
+
recommendedModel: null,
|
|
387
|
+
selectionBehavior: 'Model selection is unavailable until the probe succeeds; title generation falls back to the Agent CLI default model.',
|
|
388
|
+
warnings,
|
|
389
|
+
error: error.message,
|
|
390
|
+
nextSteps: ['Run the declared models command directly, ensure it prints JSON only, or use a {pluginDir} adapter script to normalize its output.'],
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
201
394
|
export async function runTitleNamer(slug, prompt, model) {
|
|
202
395
|
let command;
|
|
203
396
|
let args;
|
|
397
|
+
let cwd;
|
|
204
398
|
if (slug === 'codex') {
|
|
205
399
|
command = 'codex';
|
|
206
400
|
args = ['exec', '--ephemeral', '--skip-git-repo-check', '--color', 'never', ...(model ? ['--model', model] : []), prompt];
|
|
@@ -211,20 +405,18 @@ export async function runTitleNamer(slug, prompt, model) {
|
|
|
211
405
|
}
|
|
212
406
|
else {
|
|
213
407
|
const provider = pluginTitleNamers().find((entry) => entry.slug === slug);
|
|
214
|
-
if (!provider)
|
|
408
|
+
if (!provider || !isPluginTitleExecutionEnabled(slug))
|
|
215
409
|
return null;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return [];
|
|
220
|
-
return [arg.replaceAll('{prompt}', prompt).replaceAll('{model}', model ?? '')];
|
|
221
|
-
});
|
|
410
|
+
cwd = provider.dir;
|
|
411
|
+
command = expandPluginPath(provider.config.command, provider.dir);
|
|
412
|
+
args = buildPluginTitleArgs(provider.config, provider.dir, prompt, model);
|
|
222
413
|
}
|
|
223
414
|
try {
|
|
224
415
|
const { stdout } = await execFileAsync(command, args, {
|
|
225
416
|
timeout: 45_000,
|
|
226
417
|
maxBuffer: 256 * 1024,
|
|
227
|
-
|
|
418
|
+
cwd,
|
|
419
|
+
env: cwd ? pluginCommandEnv() : { ...process.env, NO_COLOR: '1', TERM: 'dumb' },
|
|
228
420
|
});
|
|
229
421
|
return stdout;
|
|
230
422
|
}
|
|
@@ -240,7 +432,9 @@ async function refreshTitleNamerCatalog() {
|
|
|
240
432
|
const [codex, claude, ...pluginResults] = await Promise.allSettled([
|
|
241
433
|
listCodexModels(),
|
|
242
434
|
listClaudeModels(),
|
|
243
|
-
...plugins.map((provider) =>
|
|
435
|
+
...plugins.map((provider) => provider.config.models && isPluginTitleExecutionEnabled(provider.slug)
|
|
436
|
+
? listPluginModels(provider.config, provider.dir)
|
|
437
|
+
: Promise.resolve({ models: [], recommendedModel: null, ignoredEntries: 0 })),
|
|
244
438
|
]);
|
|
245
439
|
const previousCodex = cached?.value.find((namer) => namer.slug === 'codex');
|
|
246
440
|
const previousClaude = cached?.value.find((namer) => namer.slug === 'claude');
|
|
@@ -264,13 +458,14 @@ async function refreshTitleNamerCatalog() {
|
|
|
264
458
|
...plugins.map((provider, index) => {
|
|
265
459
|
const result = pluginResults[index];
|
|
266
460
|
const previous = cached?.value.find((namer) => namer.slug === provider.slug);
|
|
267
|
-
const
|
|
461
|
+
const discovered = result?.status === 'fulfilled' ? result.value : null;
|
|
462
|
+
const models = discovered?.models ?? previous?.models ?? [];
|
|
268
463
|
return {
|
|
269
464
|
slug: provider.slug,
|
|
270
465
|
displayName: provider.displayName,
|
|
271
466
|
available: provider.config.models ? result?.status === 'fulfilled' || previous?.available === true : true,
|
|
272
467
|
models,
|
|
273
|
-
recommendedModel: recommendTitleModel(models),
|
|
468
|
+
recommendedModel: discovered?.recommendedModel ?? recommendTitleModel(models),
|
|
274
469
|
};
|
|
275
470
|
}),
|
|
276
471
|
];
|