dsh-nextsteps 1.4.1 → 1.4.3

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 CHANGED
@@ -226,11 +226,9 @@ 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 !== '';
230
229
  let best = null;
231
230
  for (const [callId, entry] of entries.entries()) {
232
231
  if (entry.turn !== turn) continue;
233
- if (external && !callId.startsWith('generated-')) continue;
234
232
  best = entry;
235
233
  }
236
234
  return best;
@@ -366,9 +364,9 @@ window.__ModuleLoader__.load({
366
364
  const SETTINGS_ROUTE = '/api/dsh-nextsteps.settings';
367
365
  const UPDATE_ROUTE = '/api/dsh-nextsteps.update';
368
366
  const DICTIONARIES = {
369
- en: {"header":"Suggested followups","showEarlier":"Show earlier suggestions","hideEarlier":"Hide earlier suggestions","srFollowupSent":"Followup sent","sendFailed":"Could not send: ","intro":"Suggested next-step prompts under every finished agent turn — one click sends the followup.","setLanguage":"Language","setLanguageTip":"Followups are generated in this language. Auto follows the conversation.","optAuto":"Auto (follow the conversation)","optEn":"English","optPt":"Português (Brasil)","optZh":"简体中文","setModel":"Model","setModelTip":"The model that generates the suggestions. Auto keeps them inside the main turn.","optModelAuto":"Auto (same model as the chat)","modelUnavailable":"No model list available in this install","setMax":"Followups per turn","setMaxTip":"How many suggested prompts the model should produce (1-6).","setHeader":"Show header","setHeaderTip":"The small “Suggested followups” label above the cards.","updates":"Updates","current":"Version","checking":"Checking…","upToDate":"up to date","available":"{v} is available","manualCopy":"Development or renamed copy — update it manually.","installing":"Installing {v}…","restartMsg":"Version {v} was installed. Restart the dsh to use it.","noReleases":"No releases published yet","checkFailed":"Update check failed ({e})","updateNow":"Update now","check":"Check for updates","openRelease":"Open release page","releaseNotes":"Release notes","repo":"GitHub repository","issues":"Report an issue"},
370
- 'pt-BR': {"header":"Sugestões de acompanhamento","showEarlier":"Mostrar sugestões anteriores","hideEarlier":"Ocultar sugestões anteriores","srFollowupSent":"Follow-up enviado","sendFailed":"Não consegui enviar: ","intro":"Sugestões de próximos passos sob cada turno concluído do agente — um clique envia o followup.","setLanguage":"Idioma","setLanguageTip":"Os followups são gerados neste idioma. Automático segue a conversa.","optAuto":"Automático (segue a conversa)","optEn":"English","optPt":"Português (Brasil)","optZh":"简体中文","setModel":"Modelo","setModelTip":"O modelo que gera as sugestões. Automático as mantém no turno principal.","optModelAuto":"Automático (mesmo modelo da conversa)","modelUnavailable":"Nenhuma lista de modelos disponível nesta instalação","setMax":"Followups por turno","setMaxTip":"Quantas sugestões o modelo deve produzir (1-6).","setHeader":"Mostrar cabeçalho","setHeaderTip":"O rótulo pequeno “Sugestões de acompanhamento” acima dos cartões.","updates":"Atualizações","current":"Versão","checking":"Verificando…","upToDate":"em dia","available":"{v} disponível","manualCopy":"Cópia de desenvolvimento ou renomeada — atualize manualmente.","installing":"Instalando {v}…","restartMsg":"Versão {v} instalada. Reinicie o dsh para usá-la.","noReleases":"Nenhum release publicado ainda","checkFailed":"Falha ao verificar atualizações ({e})","updateNow":"Atualizar agora","check":"Verificar atualizações","openRelease":"Abrir página do release","releaseNotes":"Notas do release","repo":"Repositório no GitHub","issues":"Reportar um problema"},
371
- 'zh-CN': {"header":"建议的后续操作","showEarlier":"显示较早的建议","hideEarlier":"隐藏较早的建议","srFollowupSent":"后续操作已发送","sendFailed":"发送失败:","intro":"在每个智能体回合完成后的下方推荐下一步提示——单击即可发送。","setLanguage":"语言","setLanguageTip":"后续操作提示将以此语言生成。“自动”会跟随对话语言。","optAuto":"自动(跟随对话)","optEn":"English","optPt":"Português (Brasil)","optZh":"简体中文","setModel":"模型","setModelTip":"生成建议的模型。自动会跟随对话所用的模型。","optModelAuto":"自动(与对话相同的模型)","modelUnavailable":"此安装没有可用的模型列表","setMax":"每回合提示数","setMaxTip":"模型应生成的建议提示数量(1-6)。","setHeader":"显示标题","setHeaderTip":"卡片上方的小标题“建议的后续操作”。","updates":"更新","current":"版本","checking":"检查中…","upToDate":"已是最新","available":"{v} 可用","manualCopy":"开发或重命名的副本——请手动更新。","installing":"正在安装 {v}…","restartMsg":"版本 {v} 已安装。请重启 dsh 以使用。","noReleases":"尚未发布任何版本","checkFailed":"检查更新失败({e})","updateNow":"立即更新","check":"检查更新","openRelease":"打开发布页","releaseNotes":"发布说明","repo":"GitHub 仓库","issues":"反馈问题"}
367
+ en: {"header":"Suggested followups","srFollowupSent":"Followup sent","sendFailed":"Could not send: ","intro":"Suggested next-step prompts under every finished agent turn — one click sends the followup.","setLanguage":"Language","setLanguageTip":"Followups are generated in this language. Auto follows the conversation.","optAuto":"Auto (follow the conversation)","setModel":"Model","setModelTip":"The model that generates the suggestions. Auto keeps them inside the main turn.","optModelAuto":"Auto (same model as the chat)","modelUnavailable":"No model list available in this install","setMax":"Followups per turn","setMaxTip":"How many suggested prompts the model should produce (1-6).","setHeader":"Show header","setHeaderTip":"The small “Suggested followups” label above the cards.","updates":"Updates","current":"Version","checking":"Checking…","upToDate":"up to date","available":"{v} is available","manualCopy":"Development or renamed copy — update it manually.","installing":"Installing {v}…","restartMsg":"Version {v} was installed. Restart the dsh to use it.","noReleases":"No releases published yet","checkFailed":"Update check failed ({e})","updateNow":"Update now","check":"Check for updates","openRelease":"Open release page","releaseNotes":"Release notes","repo":"GitHub repository","issues":"Report an issue"},
368
+ 'pt-BR': {"header":"Sugestões de acompanhamento","srFollowupSent":"Follow-up enviado","sendFailed":"Não consegui enviar: ","intro":"Sugestões de próximos passos sob cada turno concluído do agente — um clique envia o followup.","setLanguage":"Idioma","setLanguageTip":"Os followups são gerados neste idioma. Automático segue a conversa.","optAuto":"Automático (segue a conversa)","setModel":"Modelo","setModelTip":"O modelo que gera as sugestões. Automático as mantém no turno principal.","optModelAuto":"Automático (mesmo modelo da conversa)","modelUnavailable":"Nenhuma lista de modelos disponível nesta instalação","setMax":"Followups por turno","setMaxTip":"Quantas sugestões o modelo deve produzir (1-6).","setHeader":"Mostrar cabeçalho","setHeaderTip":"O rótulo pequeno “Sugestões de acompanhamento” acima dos cartões.","updates":"Atualizações","current":"Versão","checking":"Verificando…","upToDate":"em dia","available":"{v} disponível","manualCopy":"Cópia de desenvolvimento ou renomeada — atualize manualmente.","installing":"Instalando {v}…","restartMsg":"Versão {v} instalada. Reinicie o dsh para usá-la.","noReleases":"Nenhum release publicado ainda","checkFailed":"Falha ao verificar atualizações ({e})","updateNow":"Atualizar agora","check":"Verificar atualizações","openRelease":"Abrir página do release","releaseNotes":"Notas do release","repo":"Repositório no GitHub","issues":"Reportar um problema"},
369
+ 'zh-CN': {"header":"建议的后续操作","srFollowupSent":"后续操作已发送","sendFailed":"发送失败:","intro":"在每个智能体回合完成后的下方推荐下一步提示——单击即可发送。","setLanguage":"语言","setLanguageTip":"后续操作提示将以此语言生成。“自动”会跟随对话语言。","optAuto":"自动(跟随对话)","setModel":"模型","setModelTip":"生成建议的模型。自动会跟随对话所用的模型。","optModelAuto":"自动(与对话相同的模型)","modelUnavailable":"此安装没有可用的模型列表","setMax":"每回合提示数","setMaxTip":"模型应生成的建议提示数量(1-6)。","setHeader":"显示标题","setHeaderTip":"卡片上方的小标题“建议的后续操作”。","updates":"更新","current":"版本","checking":"检查中…","upToDate":"已是最新","available":"{v} 可用","manualCopy":"开发或重命名的副本——请手动更新。","installing":"正在安装 {v}…","restartMsg":"版本 {v} 已安装。请重启 dsh 以使用。","noReleases":"尚未发布任何版本","checkFailed":"检查更新失败({e})","updateNow":"立即更新","check":"检查更新","openRelease":"打开发布页","releaseNotes":"发布说明","repo":"GitHub 仓库","issues":"反馈问题"}
372
370
  };
373
371
  let pluginSettings = { lang: 'auto', maxFollowups: 3, showHeader: true };
374
372
  let pluginVersion;
@@ -492,7 +490,7 @@ window.__ModuleLoader__.load({
492
490
  }, []);
493
491
  const generatedRef = React.useRef(null);
494
492
  React.useEffect(() => {
495
- if (pluginSettings.model === '' || sessionId === '' || turn === undefined || afterSeq === undefined) return undefined;
493
+ if (sessionId === '' || turn === undefined || afterSeq === undefined) return undefined;
496
494
  const key = sessionId + '|' + turn;
497
495
  if (generatedRef.current === key) return undefined;
498
496
  if (captureForTurn(sessionId, turn) !== null) return undefined;
package/index.js CHANGED
@@ -124,7 +124,16 @@ const PARAMETER_SCHEMA = {
124
124
  required: ['followups']
125
125
  };
126
126
 
127
- function toolDescription(external = false) {
127
+ function languageRuleOf(settings) {
128
+ const language = LANGUAGE_NAMES[settings.lang] ?? LANGUAGE_NAMES.auto;
129
+ if (settings.lang === 'auto') {
130
+ return 'Write every prompt and label in the same language the conversation has been using.';
131
+ }
132
+ return `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.`;
133
+ }
134
+
135
+ function toolDescription(settings = { lang: 'auto', maxFollowups: 3, model: '' }) {
136
+ const external = typeof settings.model === 'string' && settings.model !== '';
128
137
  if (external) {
129
138
  return [
130
139
  'This tool is not used any more: the host generates the followup suggestions after your turn with a separate model.',
@@ -133,10 +142,10 @@ function toolDescription(external = false) {
133
142
  }
134
143
  return [
135
144
  '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.',
136
- 'Suggest exactly the number of followup prompts stated in the nextsteps section of the system prompt; when there is no such section, suggest three.',
145
+ `Suggest exactly ${settings.maxFollowups} followup prompts when you call suggest_followups.`,
137
146
  '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.',
138
147
  `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.`,
139
- 'Write every prompt and label in the language the nextsteps section of the system prompt states; when there is no such section, use the language of the conversation.',
148
+ languageRuleOf(settings),
140
149
  'Never suggest actions the user would carry out themselves outside this session (e.g. "commit these changes"); judge by the tools you actually have.',
141
150
  'This never waits for an answer: its result only confirms the suggestions were recorded.',
142
151
  'Example input:',
@@ -144,21 +153,22 @@ function toolDescription(external = false) {
144
153
  ].join('\n');
145
154
  }
146
155
 
156
+ const CURRENT_STATEMENT = 'This is the current setting for this turn; if an earlier message in this conversation stated a different one, ignore that earlier statement.';
157
+
147
158
  function nextstepsSectionText() {
148
159
  const settings = settingsLoad();
149
160
  if (typeof settings.model === 'string' && settings.model !== '') {
150
161
  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.'
162
+ 'nextsteps (the followup suggestion cards):',
163
+ `- ${CURRENT_STATEMENT}`,
164
+ '- 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
165
  ].join('\n');
153
166
  }
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
167
  return [
159
168
  'nextsteps (the followup suggestion cards):',
169
+ `- ${CURRENT_STATEMENT}`,
160
170
  `- Suggest exactly ${settings.maxFollowups} followup prompts when you call suggest_followups.`,
161
- `- ${languageRule}`
171
+ `- ${languageRuleOf(settings)}`
162
172
  ].join('\n');
163
173
  }
164
174
 
@@ -168,7 +178,7 @@ function toolSpec(settings = { lang: 'auto', maxFollowups: 3, model: '' }) {
168
178
  const external = typeof settings.model === 'string' && settings.model !== '';
169
179
  return {
170
180
  name: TOOL_NAME,
171
- description: toolDescription(external),
181
+ description: toolDescription(settings),
172
182
  parameters: PARAMETER_SCHEMA,
173
183
  output: {
174
184
  schema: { type: 'string' },
@@ -283,8 +293,15 @@ async function generateForTurn(ctx, settings, sessionId, afterSeq, signal) {
283
293
  let transcript;
284
294
  try {
285
295
  const events = observation.events ?? [];
286
- const last = events.at(-1)?.event ?? events.at(-1);
287
- if (last?.type !== 'turn/end' || last.data?.reason?.kind !== 'completed' || last.seq !== afterSeq) {
296
+ const turnEnd = events.find((entry) => {
297
+ const event = entry?.event ?? entry;
298
+ return event?.type === 'turn/end' && event.seq === afterSeq;
299
+ });
300
+ const newerTurn = events.some((entry) => {
301
+ const event = entry?.event ?? entry;
302
+ return event?.type === 'turn/end' && Number.isSafeInteger(event.seq) && event.seq > afterSeq;
303
+ });
304
+ if (turnEnd === undefined || newerTurn || turnEnd.data?.reason?.kind !== 'completed') {
288
305
  throw Object.assign(new Error('the chat moved on'), { status: 409 });
289
306
  }
290
307
  transcript = turnTranscript(events, afterSeq);
@@ -292,6 +309,7 @@ async function generateForTurn(ctx, settings, sessionId, afterSeq, signal) {
292
309
  observation[Symbol.dispose]?.();
293
310
  }
294
311
  if (transcript.trim() === '') return { followups: [] };
312
+ if (typeof settings.model !== 'string' || settings.model === '') return { followups: [] };
295
313
  const catalog = await modelCatalog(ctx);
296
314
  const route = settings.model !== ''
297
315
  ? (() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-nextsteps",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.js",
@@ -51,8 +51,9 @@
51
51
  "url": "https://github.com/klaus-2/dsh-nextsteps/issues"
52
52
  },
53
53
  "scripts": {
54
- "test": "node tools/regression.mjs",
55
- "regression": "node tools/regression.mjs"
54
+ "test": "node tools/regression.mjs && node scripts/test.mjs",
55
+ "regression": "node tools/regression.mjs",
56
+ "test:unit": "node scripts/test.mjs"
56
57
  },
57
58
  "icon": "./docs/assets/icon.png"
58
59
  }
@@ -79,7 +79,7 @@ test("the description survives a host without a system prompt service", async ()
79
79
  const mod = await import(moduleUrl.href);
80
80
  await mod.apply(ctx);
81
81
  const description = registered[0].description;
82
- assertIncludes(description, "when there is no such section, use the language of the conversation", "the description must fall back to the conversation language");
82
+ assertMatch(description, /write every prompt and label in/i, "the description must carry the language rule without the system prompt section");
83
83
  });
84
84
 
85
85
  test("the bundle does not require optional host services to load", async () => {