dsh-nextsteps 1.3.2 → 1.3.4
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/client.js +6 -3
- package/index.js +74 -19
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -9,7 +9,7 @@ window.__ModuleLoader__.load({
|
|
|
9
9
|
const TOOL_NAME = 'suggest_followups';
|
|
10
10
|
const MAX_FOLLOWUPS = 6;
|
|
11
11
|
const PROMPT_TRUNCATE = 60;
|
|
12
|
-
const CLICKED_KEY = 'dsh
|
|
12
|
+
const CLICKED_KEY = 'dsh-nextsteps.clicked';
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
const CSS = `
|
|
@@ -226,9 +226,12 @@ window.__ModuleLoader__.load({
|
|
|
226
226
|
function captureForTurn(sessionId, turn) {
|
|
227
227
|
const entries = captures.get(sessionId);
|
|
228
228
|
if (entries === undefined) return null;
|
|
229
|
+
const external = typeof pluginSettings.model === 'string' && pluginSettings.model !== '';
|
|
229
230
|
let best = null;
|
|
230
|
-
for (const entry of entries.
|
|
231
|
-
if (entry.turn
|
|
231
|
+
for (const [callId, entry] of entries.entries()) {
|
|
232
|
+
if (entry.turn !== turn) continue;
|
|
233
|
+
if (external && !callId.startsWith('generated-')) continue;
|
|
234
|
+
best = entry;
|
|
232
235
|
}
|
|
233
236
|
return best;
|
|
234
237
|
}
|
package/index.js
CHANGED
|
@@ -124,17 +124,19 @@ const PARAMETER_SCHEMA = {
|
|
|
124
124
|
required: ['followups']
|
|
125
125
|
};
|
|
126
126
|
|
|
127
|
-
function toolDescription(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
function toolDescription(external = false) {
|
|
128
|
+
if (external) {
|
|
129
|
+
return [
|
|
130
|
+
'This tool is not used any more: the host generates the followup suggestions after your turn with a separate model.',
|
|
131
|
+
'Do not call it. Just finish your answer as usual.'
|
|
132
|
+
].join('\n');
|
|
133
|
+
}
|
|
132
134
|
return [
|
|
133
135
|
'MANDATORY in every turn: once all real work and other tool calls are done, call this tool BEFORE writing your final answer — the answer must come after this tool returns, so the turn ends cleanly right after it. Never write your final answer before calling it.',
|
|
134
|
-
|
|
136
|
+
'Suggest exactly the number of followup prompts stated in the nextsteps section of the system prompt.',
|
|
135
137
|
'Good suggestions include alternatives to the latest implementation, related features, cleanup opportunities, tests to add, or verification steps you could run yourself; if plan steps remain, one suggestion may be continuing with the next step.',
|
|
136
138
|
`Each prompt must be short (usually one sentence), goal-oriented — name the outcome, not the steps — stand alone without conversation context, and be phrased in the user's first-person voice.`,
|
|
137
|
-
|
|
139
|
+
'Write every prompt and label in the language the nextsteps section of the system prompt states.',
|
|
138
140
|
'Never suggest actions the user would carry out themselves outside this session (e.g. "commit these changes"); judge by the tools you actually have.',
|
|
139
141
|
'This never waits for an answer: its result only confirms the suggestions were recorded.',
|
|
140
142
|
'Example input:',
|
|
@@ -142,12 +144,31 @@ function toolDescription(settings) {
|
|
|
142
144
|
].join('\n');
|
|
143
145
|
}
|
|
144
146
|
|
|
147
|
+
function nextstepsSectionText() {
|
|
148
|
+
const settings = settingsLoad();
|
|
149
|
+
if (typeof settings.model === 'string' && settings.model !== '') {
|
|
150
|
+
return [
|
|
151
|
+
'nextsteps (the followup suggestion cards): the host generates them after your turn with a separate model, so you must not call any followup tool. Just finish your answer as usual.'
|
|
152
|
+
].join('\n');
|
|
153
|
+
}
|
|
154
|
+
const language = LANGUAGE_NAMES[settings.lang] ?? LANGUAGE_NAMES.auto;
|
|
155
|
+
const languageRule = settings.lang === 'auto'
|
|
156
|
+
? `Write every prompt and label in the same language the conversation has been using.`
|
|
157
|
+
: `LANGUAGE OVERRIDE, applies to the suggest_followups tool only: write every prompt and label in ${language} — even if the conversation or the session history is in another language. Never copy the conversation's language here.`;
|
|
158
|
+
return [
|
|
159
|
+
'nextsteps (the followup suggestion cards):',
|
|
160
|
+
`- Suggest exactly ${settings.maxFollowups} followup prompts when you call suggest_followups.`,
|
|
161
|
+
`- ${languageRule}`
|
|
162
|
+
].join('\n');
|
|
163
|
+
}
|
|
164
|
+
|
|
145
165
|
const PLUGIN_REV = 'sf-2';
|
|
146
166
|
|
|
147
|
-
function toolSpec(settings) {
|
|
167
|
+
function toolSpec(settings = { lang: 'auto', maxFollowups: 3, model: '' }) {
|
|
168
|
+
const external = typeof settings.model === 'string' && settings.model !== '';
|
|
148
169
|
return {
|
|
149
170
|
name: TOOL_NAME,
|
|
150
|
-
description: toolDescription(
|
|
171
|
+
description: toolDescription(external),
|
|
151
172
|
parameters: PARAMETER_SCHEMA,
|
|
152
173
|
output: {
|
|
153
174
|
schema: { type: 'string' },
|
|
@@ -157,6 +178,7 @@ function toolSpec(settings) {
|
|
|
157
178
|
]
|
|
158
179
|
},
|
|
159
180
|
async execute(args) {
|
|
181
|
+
if (external) return 'The host generates the followup suggestions after this turn. Do not call this tool again.';
|
|
160
182
|
const followups = coerceFollowups(args.followups);
|
|
161
183
|
const problem = validationProblem(followups);
|
|
162
184
|
if (problem !== null) return problem;
|
|
@@ -193,15 +215,33 @@ async function modelCatalog(ctx) {
|
|
|
193
215
|
return out;
|
|
194
216
|
}
|
|
195
217
|
|
|
218
|
+
function textOf(content) {
|
|
219
|
+
if (typeof content === 'string') return content;
|
|
220
|
+
if (!Array.isArray(content)) return '';
|
|
221
|
+
const parts = [];
|
|
222
|
+
for (const part of content) {
|
|
223
|
+
if (part === null || typeof part !== 'object') continue;
|
|
224
|
+
if (part.type === 'text' && typeof part.text === 'string') parts.push(part.text);
|
|
225
|
+
else if (part.type === 'reasoning' && typeof part.text === 'string') parts.push(part.text);
|
|
226
|
+
}
|
|
227
|
+
return parts.join('\n');
|
|
228
|
+
}
|
|
229
|
+
|
|
196
230
|
function turnTranscript(events, afterSeq) {
|
|
197
231
|
const lines = [];
|
|
198
232
|
for (const entry of events) {
|
|
199
233
|
const event = entry?.event ?? entry;
|
|
200
234
|
if (event === null || typeof event !== 'object') continue;
|
|
201
235
|
if (Number.isSafeInteger(afterSeq) && Number.isSafeInteger(event.seq) && event.seq > afterSeq) break;
|
|
202
|
-
if (event.type === 'user/message'
|
|
203
|
-
|
|
204
|
-
|
|
236
|
+
if (event.type === 'user/message') {
|
|
237
|
+
const text = textOf(event.data?.content);
|
|
238
|
+
if (text !== '' && event.data?.source?.kind === 'user') lines.push(`User: ${text.slice(0, 2000)}`);
|
|
239
|
+
} else if (event.type === 'assistant/message') {
|
|
240
|
+
const text = textOf(event.data?.message?.content);
|
|
241
|
+
if (text !== '') lines.push(`Assistant: ${text.slice(0, 2000)}`);
|
|
242
|
+
} else if (event.type === 'tool/call' && typeof event.data?.name === 'string') {
|
|
243
|
+
lines.push(`Tool call: ${event.data.name}`);
|
|
244
|
+
}
|
|
205
245
|
}
|
|
206
246
|
return lines.join('\n\n');
|
|
207
247
|
}
|
|
@@ -308,20 +348,35 @@ async function apply(ctx) {
|
|
|
308
348
|
let settings = settingsLoad();
|
|
309
349
|
const json = (value, status = 200) => new Response(JSON.stringify(value), { status, headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' } });
|
|
310
350
|
const isExternal = (next) => typeof next.model === 'string' && next.model !== '';
|
|
311
|
-
try {
|
|
312
|
-
if (!isExternal(settings)) ctx.tools.register(toolSpec(settings));
|
|
313
|
-
} catch (error) {
|
|
314
|
-
console.error('dsh-nextsteps: tool registration failed:', error);
|
|
315
|
-
}
|
|
316
351
|
const reapplyTool = (next) => {
|
|
317
352
|
settings = next;
|
|
318
353
|
try {
|
|
319
|
-
|
|
320
|
-
|
|
354
|
+
try { ctx.tools.unregister(TOOL_NAME); } catch { /* not registered yet */ }
|
|
355
|
+
ctx.tools.register(toolSpec(next));
|
|
321
356
|
} catch (error) {
|
|
322
357
|
console.error('dsh-nextsteps: tool re-registration failed:', error);
|
|
323
358
|
}
|
|
324
359
|
};
|
|
360
|
+
try {
|
|
361
|
+
ctx.tools.register(toolSpec(settings));
|
|
362
|
+
} catch (error) {
|
|
363
|
+
console.error('dsh-nextsteps: tool registration failed:', error);
|
|
364
|
+
}
|
|
365
|
+
if (typeof ctx.inject === 'function') {
|
|
366
|
+
ctx.inject(['tools', 'systemPrompt'], (sub) => {
|
|
367
|
+
try {
|
|
368
|
+
try { sub.tools.unregister(TOOL_NAME); } catch { /* not registered yet */ }
|
|
369
|
+
sub.tools.register(toolSpec(settings));
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.error('dsh-nextsteps: tool registration via inject failed:', error);
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
sub.systemPrompt.section({ name: 'dsh-nextsteps', order: 640, interpolate: false, text: () => nextstepsSectionText() });
|
|
375
|
+
} catch (error) {
|
|
376
|
+
console.error('dsh-nextsteps: system prompt section rejected:', error);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
}
|
|
325
380
|
ctx.connection?.fetch?.register({
|
|
326
381
|
path: '/api/dsh-nextsteps.settings',
|
|
327
382
|
methods: ['GET', 'POST'],
|