pi-forge 1.2.3 → 1.2.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/README.md +1 -1
- package/dist/client/assets/{CodeMirrorEditor-1gu-DS9k.js → CodeMirrorEditor-M7HIAKX2.js} +2 -2
- package/dist/client/assets/{CodeMirrorEditor-1gu-DS9k.js.map → CodeMirrorEditor-M7HIAKX2.js.map} +1 -1
- package/dist/client/assets/index-DFDpaYie.css +1 -0
- package/dist/client/assets/index-DjYKKZRm.js +363 -0
- package/dist/client/assets/index-DjYKKZRm.js.map +1 -0
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/client/sw.js.map +1 -1
- package/dist/server/ask-user-question/envelope.js +56 -0
- package/dist/server/ask-user-question/envelope.js.map +1 -0
- package/dist/server/ask-user-question/prompt-strings.js +44 -0
- package/dist/server/ask-user-question/prompt-strings.js.map +1 -0
- package/dist/server/ask-user-question/registry.js +157 -0
- package/dist/server/ask-user-question/registry.js.map +1 -0
- package/dist/server/ask-user-question/tool.js +115 -0
- package/dist/server/ask-user-question/tool.js.map +1 -0
- package/dist/server/ask-user-question/types.js +27 -0
- package/dist/server/ask-user-question/types.js.map +1 -0
- package/dist/server/ask-user-question/validate.js +135 -0
- package/dist/server/ask-user-question/validate.js.map +1 -0
- package/dist/server/config.js +10 -0
- package/dist/server/config.js.map +1 -1
- package/dist/server/index.js +13 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/quick-actions.js +141 -0
- package/dist/server/quick-actions.js.map +1 -0
- package/dist/server/routes/ask-user-question.js +129 -0
- package/dist/server/routes/ask-user-question.js.map +1 -0
- package/dist/server/routes/config.js +7 -0
- package/dist/server/routes/config.js.map +1 -1
- package/dist/server/routes/quick-actions.js +384 -0
- package/dist/server/routes/quick-actions.js.map +1 -0
- package/dist/server/routes/todos.js +67 -0
- package/dist/server/routes/todos.js.map +1 -0
- package/dist/server/session-registry.js +52 -4
- package/dist/server/session-registry.js.map +1 -1
- package/dist/server/sse-bridge.js +84 -0
- package/dist/server/sse-bridge.js.map +1 -1
- package/dist/server/todo/envelope.js +87 -0
- package/dist/server/todo/envelope.js.map +1 -0
- package/dist/server/todo/invariants.js +21 -0
- package/dist/server/todo/invariants.js.map +1 -0
- package/dist/server/todo/prompt-strings.js +29 -0
- package/dist/server/todo/prompt-strings.js.map +1 -0
- package/dist/server/todo/reducer.js +189 -0
- package/dist/server/todo/reducer.js.map +1 -0
- package/dist/server/todo/replay.js +45 -0
- package/dist/server/todo/replay.js.map +1 -0
- package/dist/server/todo/store.js +92 -0
- package/dist/server/todo/store.js.map +1 -0
- package/dist/server/todo/task-graph.js +60 -0
- package/dist/server/todo/task-graph.js.map +1 -0
- package/dist/server/todo/tool.js +95 -0
- package/dist/server/todo/tool.js.map +1 -0
- package/dist/server/todo/types.js +23 -0
- package/dist/server/todo/types.js.map +1 -0
- package/package.json +1 -1
- package/dist/client/assets/index-BxZV6ddv.js +0 -359
- package/dist/client/assets/index-BxZV6ddv.js.map +0 -1
- package/dist/client/assets/index-KUhxvBxw.css +0 -1
package/dist/client/index.html
CHANGED
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
<link rel="apple-touch-icon" sizes="192x192" href="/icons/icon-192.png" />
|
|
27
27
|
<link rel="apple-touch-icon" sizes="512x512" href="/icons/icon-512.png" />
|
|
28
28
|
<title>pi-forge</title>
|
|
29
|
-
<script type="module" crossorigin src="/assets/index-
|
|
30
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
29
|
+
<script type="module" crossorigin src="/assets/index-DjYKKZRm.js"></script>
|
|
30
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DFDpaYie.css">
|
|
31
31
|
<link rel="manifest" href="/manifest.webmanifest"></head>
|
|
32
32
|
<body class="bg-neutral-950 text-neutral-100 antialiased">
|
|
33
33
|
<div id="root"></div>
|
package/dist/client/sw.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
if(!self.define){let e,i={};const n=(n,s)=>(n=new URL(n+".js",s).href,i[n]||new Promise(i=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=i,document.head.appendChild(e)}else e=n,importScripts(n),i()}).then(()=>{let e=i[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(s,o)=>{const c=e||("document"in self?document.currentScript.src:"")||location.href;if(i[c])return;let r={};const f=e=>n(e,c),l={module:{uri:c},exports:r,require:f};i[c]=Promise.all(s.map(e=>l[e]||f(e))).then(e=>(o(...e),r))}}define(["./workbox-6d7155ed"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"offline.html",revision:"e4f087b40040ff045a6fd572b5fed726"},{url:"manifest.webmanifest",revision:"aef9cb4935c3fba2754da10580256c9e"},{url:"index.html",revision:"
|
|
1
|
+
if(!self.define){let e,i={};const n=(n,s)=>(n=new URL(n+".js",s).href,i[n]||new Promise(i=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=i,document.head.appendChild(e)}else e=n,importScripts(n),i()}).then(()=>{let e=i[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(s,o)=>{const c=e||("document"in self?document.currentScript.src:"")||location.href;if(i[c])return;let r={};const f=e=>n(e,c),l={module:{uri:c},exports:r,require:f};i[c]=Promise.all(s.map(e=>l[e]||f(e))).then(e=>(o(...e),r))}}define(["./workbox-6d7155ed"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"offline.html",revision:"e4f087b40040ff045a6fd572b5fed726"},{url:"manifest.webmanifest",revision:"aef9cb4935c3fba2754da10580256c9e"},{url:"index.html",revision:"02c324c8fc4f9b36cfd294f930ee1fcf"},{url:"icons/icon.svg",revision:"549b462207e763a57c9dfbae214afc31"},{url:"icons/icon-maskable-512.png",revision:"734c73b2ee3cc3856c67bed196e7557f"},{url:"icons/icon-512.png",revision:"c1b37dbf870c852577dbb4733f45c9c2"},{url:"icons/icon-192.png",revision:"54082a8d4e49f4e5c2ad5646b7fb7551"},{url:"assets/workbox-window.prod.es5-Cch4wiA5.js",revision:null},{url:"assets/index-DjYKKZRm.js",revision:null},{url:"assets/index-DFDpaYie.css",revision:null},{url:"assets/CodeMirrorEditor-M7HIAKX2.js",revision:null},{url:"offline.html",revision:"e4f087b40040ff045a6fd572b5fed726"},{url:"icons/icon-192.png",revision:"54082a8d4e49f4e5c2ad5646b7fb7551"},{url:"icons/icon-512.png",revision:"c1b37dbf870c852577dbb4733f45c9c2"},{url:"icons/icon-maskable-512.png",revision:"734c73b2ee3cc3856c67bed196e7557f"},{url:"icons/icon.svg",revision:"549b462207e763a57c9dfbae214afc31"},{url:"manifest.webmanifest",revision:"aef9cb4935c3fba2754da10580256c9e"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"),{denylist:[/^\/api\//]})),e.registerRoute(({url:e})=>e.pathname.startsWith("/api/v1/"),new e.NetworkOnly,"GET"),e.registerRoute(({request:e})=>"navigate"===e.mode,new e.NetworkFirst({cacheName:"pi-navigation",networkTimeoutSeconds:3,plugins:[new e.PrecacheFallbackPlugin({fallbackURL:"/offline.html"})]}),"GET")});
|
|
2
2
|
//# sourceMappingURL=sw.js.map
|
|
3
3
|
//# sourceMappingURL=sw.js.map
|
package/dist/client/sw.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sw.js","sources":["../../../../../../../tmp/
|
|
1
|
+
{"version":3,"file":"sw.js","sources":["../../../../../../../tmp/643db5744678e787315dd9b46486342f/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/home/runner/work/pi-forge/pi-forge/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/home/runner/work/pi-forge/pi-forge/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {PrecacheFallbackPlugin as workbox_precaching_PrecacheFallbackPlugin} from '/home/runner/work/pi-forge/pi-forge/node_modules/workbox-precaching/PrecacheFallbackPlugin.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/home/runner/work/pi-forge/pi-forge/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/home/runner/work/pi-forge/pi-forge/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/home/runner/work/pi-forge/pi-forge/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {cleanupOutdatedCaches as workbox_precaching_cleanupOutdatedCaches} from '/home/runner/work/pi-forge/pi-forge/node_modules/workbox-precaching/cleanupOutdatedCaches.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/home/runner/work/pi-forge/pi-forge/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/home/runner/work/pi-forge/pi-forge/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\nself.skipWaiting();\nworkbox_core_clientsClaim();\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"offline.html\",\n \"revision\": \"e4f087b40040ff045a6fd572b5fed726\"\n },\n {\n \"url\": \"manifest.webmanifest\",\n \"revision\": \"aef9cb4935c3fba2754da10580256c9e\"\n },\n {\n \"url\": \"index.html\",\n \"revision\": \"02c324c8fc4f9b36cfd294f930ee1fcf\"\n },\n {\n \"url\": \"icons/icon.svg\",\n \"revision\": \"549b462207e763a57c9dfbae214afc31\"\n },\n {\n \"url\": \"icons/icon-maskable-512.png\",\n \"revision\": \"734c73b2ee3cc3856c67bed196e7557f\"\n },\n {\n \"url\": \"icons/icon-512.png\",\n \"revision\": \"c1b37dbf870c852577dbb4733f45c9c2\"\n },\n {\n \"url\": \"icons/icon-192.png\",\n \"revision\": \"54082a8d4e49f4e5c2ad5646b7fb7551\"\n },\n {\n \"url\": \"assets/workbox-window.prod.es5-Cch4wiA5.js\",\n \"revision\": null\n },\n {\n \"url\": \"assets/index-DjYKKZRm.js\",\n \"revision\": null\n },\n {\n \"url\": \"assets/index-DFDpaYie.css\",\n \"revision\": null\n },\n {\n \"url\": \"assets/CodeMirrorEditor-M7HIAKX2.js\",\n \"revision\": null\n },\n {\n \"url\": \"offline.html\",\n \"revision\": \"e4f087b40040ff045a6fd572b5fed726\"\n },\n {\n \"url\": \"icons/icon-192.png\",\n \"revision\": \"54082a8d4e49f4e5c2ad5646b7fb7551\"\n },\n {\n \"url\": \"icons/icon-512.png\",\n \"revision\": \"c1b37dbf870c852577dbb4733f45c9c2\"\n },\n {\n \"url\": \"icons/icon-maskable-512.png\",\n \"revision\": \"734c73b2ee3cc3856c67bed196e7557f\"\n },\n {\n \"url\": \"icons/icon.svg\",\n \"revision\": \"549b462207e763a57c9dfbae214afc31\"\n },\n {\n \"url\": \"manifest.webmanifest\",\n \"revision\": \"aef9cb4935c3fba2754da10580256c9e\"\n }\n], {});\nworkbox_precaching_cleanupOutdatedCaches();workbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"/index.html\"), {\n denylist: [/^\\/api\\//],}));\nworkbox_routing_registerRoute(({ url }) => url.pathname.startsWith(\"/api/v1/\"), new workbox_strategies_NetworkOnly(), 'GET');\nworkbox_routing_registerRoute(({ request }) => request.mode === \"navigate\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"pi-navigation\",\"networkTimeoutSeconds\":3, plugins: [new workbox_precaching_PrecacheFallbackPlugin({ fallbackURL: '/offline.html' })] }), 'GET');\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","url","revision","workbox_precaching_cleanupOutdatedCaches","workbox_routing_registerRoute","workbox_routing_NavigationRoute","workbox_precaching_createHandlerBoundToURL","denylist","pathname","startsWith","workbox_strategies_NetworkOnly","request","mode","workbox_strategies_NetworkFirst","cacheName","networkTimeoutSeconds","plugins","workbox_precaching_PrecacheFallbackPlugin","fallbackURL"],"mappings":"inBAuBAA,KAAKC,cACLC,EAAAA,eAMAC,EAAAA,iBAAoC,CAClC,CACEC,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,SAAY,oCAEd,CACED,IAAO,aACPC,SAAY,oCAEd,CACED,IAAO,iBACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,qBACPC,SAAY,oCAEd,CACED,IAAO,qBACPC,SAAY,oCAEd,CACED,IAAO,6CACPC,SAAY,MAEd,CACED,IAAO,2BACPC,SAAY,MAEd,CACED,IAAO,4BACPC,SAAY,MAEd,CACED,IAAO,sCACPC,SAAY,MAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,qBACPC,SAAY,oCAEd,CACED,IAAO,qBACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,iBACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,SAAY,qCAEb,CAAA,GACHC,EAAAA,wBAA2CC,EAAAA,cAA8B,IAAIC,EAAAA,gBAAgCC,0BAA2C,eAAgB,CACpKC,SAAU,CAAC,eACfH,EAAAA,cAA8B,EAAGH,SAAUA,EAAIO,SAASC,WAAW,YAAa,IAAIC,EAAAA,YAAkC,OACtHN,EAAAA,cAA8B,EAAGO,aAA+B,aAAjBA,EAAQC,KAAqB,IAAIC,EAAAA,aAAgC,CAAEC,UAAY,gBAAgBC,sBAAwB,EAAGC,QAAS,CAAC,IAAIC,yBAA0C,CAAEC,YAAa,qBAAwB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the tool result the agent sees. The shape matches the
|
|
3
|
+
* upstream plugin: a single text content block summarising what
|
|
4
|
+
* happened, plus a `details` object the agent can introspect when
|
|
5
|
+
* it needs the structured answers.
|
|
6
|
+
*
|
|
7
|
+
* The `text` summary is the channel the model actually reads when it
|
|
8
|
+
* continues the conversation — short, neutral phrasing, one line
|
|
9
|
+
* per answered question.
|
|
10
|
+
*/
|
|
11
|
+
export function buildResult(answers, opts = {}) {
|
|
12
|
+
const cancelled = opts.cancelled === true;
|
|
13
|
+
const details = { answers, cancelled };
|
|
14
|
+
if (opts.error !== undefined)
|
|
15
|
+
details.error = opts.error;
|
|
16
|
+
const text = renderText(answers, {
|
|
17
|
+
cancelled,
|
|
18
|
+
error: opts.error,
|
|
19
|
+
questionCount: opts.questionCount ?? answers.length,
|
|
20
|
+
});
|
|
21
|
+
return { content: [{ type: "text", text }], details };
|
|
22
|
+
}
|
|
23
|
+
function renderText(answers, ctx) {
|
|
24
|
+
if (ctx.error !== undefined && answers.length === 0) {
|
|
25
|
+
return `Error: ${ctx.error}`;
|
|
26
|
+
}
|
|
27
|
+
if (ctx.cancelled && answers.length === 0) {
|
|
28
|
+
return "User cancelled the questionnaire without answering. Continue in free-form conversation.";
|
|
29
|
+
}
|
|
30
|
+
const lines = [];
|
|
31
|
+
for (const a of answers) {
|
|
32
|
+
lines.push(renderAnswerLine(a));
|
|
33
|
+
}
|
|
34
|
+
if (ctx.cancelled && answers.length < ctx.questionCount) {
|
|
35
|
+
lines.push(`(User cancelled the remaining ${ctx.questionCount - answers.length} question(s); continue with what was answered.)`);
|
|
36
|
+
}
|
|
37
|
+
return lines.join("\n");
|
|
38
|
+
}
|
|
39
|
+
function renderAnswerLine(a) {
|
|
40
|
+
const prefix = `Q${a.questionIndex + 1} "${a.question}"`;
|
|
41
|
+
switch (a.kind) {
|
|
42
|
+
case "option":
|
|
43
|
+
return `${prefix} → ${a.answer ?? ""}${a.notes !== undefined && a.notes.length > 0 ? ` (note: ${a.notes})` : ""}`;
|
|
44
|
+
case "custom": {
|
|
45
|
+
const text = a.answer ?? "(no text provided)";
|
|
46
|
+
return `${prefix} → custom: ${text}`;
|
|
47
|
+
}
|
|
48
|
+
case "chat":
|
|
49
|
+
return `${prefix} → user chose to chat about this`;
|
|
50
|
+
case "multi": {
|
|
51
|
+
const items = (a.selected ?? []).join(", ");
|
|
52
|
+
return `${prefix} → selected: ${items.length > 0 ? items : "(none)"}`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../src/ask-user-question/envelope.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CACzB,OAAyB,EACzB,OAAwE,EAAE;IAE1E,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC;IAC1C,MAAM,OAAO,GAA2B,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;IAC/D,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACzD,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,EAAE;QAC/B,SAAS;QACT,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,OAAO,CAAC,MAAM;KACpD,CAAC,CAAC;IACH,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC;AACxD,CAAC;AAED,SAAS,UAAU,CACjB,OAAyB,EACzB,GAA6E;IAE7E,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,OAAO,UAAU,GAAG,CAAC,KAAK,EAAE,CAAC;IAC/B,CAAC;IACD,IAAI,GAAG,CAAC,SAAS,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1C,OAAO,yFAAyF,CAAC;IACnG,CAAC;IACD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;IAClC,CAAC;IACD,IAAI,GAAG,CAAC,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC,aAAa,EAAE,CAAC;QACxD,KAAK,CAAC,IAAI,CACR,iCAAiC,GAAG,CAAC,aAAa,GAAG,OAAO,CAAC,MAAM,iDAAiD,CACrH,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,gBAAgB,CAAC,CAAiB;IACzC,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,aAAa,GAAG,CAAC,KAAK,CAAC,CAAC,QAAQ,GAAG,CAAC;IACzD,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,QAAQ;YACX,OAAO,GAAG,MAAM,MAAM,CAAC,CAAC,MAAM,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACpH,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,IAAI,oBAAoB,CAAC;YAC9C,OAAO,GAAG,MAAM,cAAc,IAAI,EAAE,CAAC;QACvC,CAAC;QACD,KAAK,MAAM;YACT,OAAO,GAAG,MAAM,kCAAkC,CAAC;QACrD,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC5C,OAAO,GAAG,MAAM,gBAAgB,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACxE,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt snippet + guidelines for the `ask_user_question` tool.
|
|
3
|
+
*
|
|
4
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
5
|
+
* Adapted from @juicesharp/rpiv-ask-user-question (MIT).
|
|
6
|
+
* Copyright (c) 2026 juicesharp.
|
|
7
|
+
* https://github.com/juicesharp/rpiv-mono/tree/main/packages/rpiv-ask-user-question
|
|
8
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
9
|
+
*
|
|
10
|
+
* The wording is preserved (with one substitution: "Type something."
|
|
11
|
+
* → "Type something" in places where it's an input affordance, not
|
|
12
|
+
* the sentinel-label text) because it's been tuned against real
|
|
13
|
+
* model behavior. Rewriting from scratch risks worse tool-invocation
|
|
14
|
+
* patterns. The functional implementation in tool.ts, validate.ts,
|
|
15
|
+
* envelope.ts, and the React UI is independent.
|
|
16
|
+
*/
|
|
17
|
+
import { MAX_OPTIONS, MAX_QUESTIONS, MIN_OPTIONS } from "./types.js";
|
|
18
|
+
export const PROMPT_SNIPPET = `Ask the user up to ${MAX_QUESTIONS} structured questions (${MIN_OPTIONS}-${MAX_OPTIONS} options each) when requirements are ambiguous`;
|
|
19
|
+
export const PROMPT_GUIDELINES = [
|
|
20
|
+
`Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to ${MAX_QUESTIONS} questions per invocation.`,
|
|
21
|
+
`Each question MUST have ${MIN_OPTIONS}-${MAX_OPTIONS} options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer ("Type something." row is appended automatically to single-select questions) or pick "Chat about this" to abandon the questionnaire.`,
|
|
22
|
+
`Set multiSelect: true when multiple answers are valid; this suppresses the "Type something." row. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. NOTE: any non-empty preview on a single-select question ALSO suppresses the "Type something." row (no room in the side-by-side layout); "Chat about this" remains the escape hatch. If you recommend a specific option, make it the first option and append "(Recommended)" to its label.`,
|
|
23
|
+
"Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.",
|
|
24
|
+
];
|
|
25
|
+
export const TOOL_DESCRIPTION = `Ask the user one or more structured questions during execution. Use when you need to:
|
|
26
|
+
1. Gather user preferences or requirements
|
|
27
|
+
2. Clarify ambiguous instructions
|
|
28
|
+
3. Get decisions on implementation choices as you work
|
|
29
|
+
4. Offer choices to the user about what direction to take
|
|
30
|
+
|
|
31
|
+
Usage notes:
|
|
32
|
+
- Users will always be able to type a custom answer ("Type something." row is appended automatically to every single-select question) or pick "Chat about this" to abandon the questionnaire and continue in free-form conversation. Do NOT author "Other" / "Type something." / "Chat about this" labels yourself — duplicates are rejected at runtime.
|
|
33
|
+
- Use multiSelect: true to allow multiple answers to be selected for a question. The "Type something." row is suppressed on multi-select questions, and is ALSO suppressed on single-select questions where any option carries a \`preview\` (the side-by-side layout has no room for inline custom text — "Chat about this" remains as the free-form escape hatch).
|
|
34
|
+
- If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label.
|
|
35
|
+
|
|
36
|
+
Preview feature:
|
|
37
|
+
Use the optional \`preview\` field on options when presenting concrete artifacts that users need to visually compare:
|
|
38
|
+
- ASCII mockups of UI layouts or components
|
|
39
|
+
- Code snippets showing different implementations
|
|
40
|
+
- Diagram variations
|
|
41
|
+
- Configuration examples
|
|
42
|
+
|
|
43
|
+
Preview content is rendered as markdown in a monospace box. Multi-line text with newlines is supported. When any option has a preview, the UI switches to a side-by-side layout with a vertical option list on the left and preview on the right. Do not use previews for simple preference questions where labels and descriptions suffice. Note: previews are only supported for single-select questions (not multiSelect).`;
|
|
44
|
+
//# sourceMappingURL=prompt-strings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-strings.js","sourceRoot":"","sources":["../../src/ask-user-question/prompt-strings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,MAAM,CAAC,MAAM,cAAc,GAAG,sBAAsB,aAAa,0BAA0B,WAAW,IAAI,WAAW,gDAAgD,CAAC;AAEtK,MAAM,CAAC,MAAM,iBAAiB,GAAa;IACzC,6IAA6I,aAAa,4BAA4B;IACtL,2BAA2B,WAAW,IAAI,WAAW,uTAAuT;IAC5W,qiBAAqiB;IACriB,kHAAkH;CACnH,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;8ZAkB8X,CAAC"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
const byRequestId = new Map();
|
|
3
|
+
const bySessionId = new Map();
|
|
4
|
+
const listeners = new Set();
|
|
5
|
+
export function subscribe(fn) {
|
|
6
|
+
listeners.add(fn);
|
|
7
|
+
return () => {
|
|
8
|
+
listeners.delete(fn);
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function notify(event) {
|
|
12
|
+
for (const fn of listeners) {
|
|
13
|
+
try {
|
|
14
|
+
fn(event);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// listener errors must not break the registry — best-effort fanout
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Register a pending request. The returned promise resolves when
|
|
23
|
+
* the browser answers, or when the caller's `signal` aborts (in
|
|
24
|
+
* which case the tool result will be a cancelled envelope built by
|
|
25
|
+
* the caller).
|
|
26
|
+
*
|
|
27
|
+
* `signal` is honored: if the agent is aborted (e.g. user hit Stop)
|
|
28
|
+
* the entry is dropped and the promise rejects with an
|
|
29
|
+
* `AbortError`. The caller catches that and returns the cancelled
|
|
30
|
+
* envelope so the agent sees a clean tool result.
|
|
31
|
+
*/
|
|
32
|
+
export function registerPending(args) {
|
|
33
|
+
const requestId = randomUUID();
|
|
34
|
+
let resolveFn;
|
|
35
|
+
let rejectFn;
|
|
36
|
+
const result = new Promise((resolve, reject) => {
|
|
37
|
+
resolveFn = resolve;
|
|
38
|
+
rejectFn = reject;
|
|
39
|
+
});
|
|
40
|
+
const entry = {
|
|
41
|
+
requestId,
|
|
42
|
+
sessionId: args.sessionId,
|
|
43
|
+
questions: args.questions,
|
|
44
|
+
createdAt: new Date(),
|
|
45
|
+
resolve: resolveFn,
|
|
46
|
+
};
|
|
47
|
+
byRequestId.set(requestId, entry);
|
|
48
|
+
const set = bySessionId.get(args.sessionId) ?? new Set();
|
|
49
|
+
set.add(requestId);
|
|
50
|
+
bySessionId.set(args.sessionId, set);
|
|
51
|
+
if (args.signal !== undefined) {
|
|
52
|
+
const onAbort = () => {
|
|
53
|
+
// Drop the entry and tell SSE clients to close the modal.
|
|
54
|
+
// The tool's execute() catches the rejection and returns a
|
|
55
|
+
// cancelled envelope to the agent.
|
|
56
|
+
if (byRequestId.has(requestId)) {
|
|
57
|
+
removeEntry(requestId);
|
|
58
|
+
notify({
|
|
59
|
+
type: "ask_user_question_cancelled",
|
|
60
|
+
sessionId: args.sessionId,
|
|
61
|
+
requestId,
|
|
62
|
+
reason: "aborted",
|
|
63
|
+
});
|
|
64
|
+
rejectFn(new Error("aborted"));
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
if (args.signal.aborted)
|
|
68
|
+
onAbort();
|
|
69
|
+
else
|
|
70
|
+
args.signal.addEventListener("abort", onAbort, { once: true });
|
|
71
|
+
}
|
|
72
|
+
notify({
|
|
73
|
+
type: "ask_user_question",
|
|
74
|
+
sessionId: args.sessionId,
|
|
75
|
+
requestId,
|
|
76
|
+
questions: args.questions,
|
|
77
|
+
});
|
|
78
|
+
return { requestId, result };
|
|
79
|
+
}
|
|
80
|
+
function removeEntry(requestId) {
|
|
81
|
+
const e = byRequestId.get(requestId);
|
|
82
|
+
if (e === undefined)
|
|
83
|
+
return;
|
|
84
|
+
byRequestId.delete(requestId);
|
|
85
|
+
const set = bySessionId.get(e.sessionId);
|
|
86
|
+
if (set !== undefined) {
|
|
87
|
+
set.delete(requestId);
|
|
88
|
+
if (set.size === 0)
|
|
89
|
+
bySessionId.delete(e.sessionId);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Resolve the pending entry with the user's answers. Idempotent —
|
|
94
|
+
* if the entry was already resolved (e.g. concurrent answer +
|
|
95
|
+
* abort race), this returns `false` rather than throwing so the
|
|
96
|
+
* route layer can decide whether to 200 or 404.
|
|
97
|
+
*/
|
|
98
|
+
export function answerPending(requestId, expectedSessionId, result) {
|
|
99
|
+
const e = byRequestId.get(requestId);
|
|
100
|
+
if (e === undefined)
|
|
101
|
+
return false;
|
|
102
|
+
if (e.sessionId !== expectedSessionId)
|
|
103
|
+
return false;
|
|
104
|
+
removeEntry(requestId);
|
|
105
|
+
// SSE clients listen for "the modal should now close" — emit a
|
|
106
|
+
// cancelled event with reason "answered" so the modal tears down
|
|
107
|
+
// on every other browser tab watching the same session.
|
|
108
|
+
notify({
|
|
109
|
+
type: "ask_user_question_cancelled",
|
|
110
|
+
sessionId: e.sessionId,
|
|
111
|
+
requestId,
|
|
112
|
+
reason: "answered",
|
|
113
|
+
});
|
|
114
|
+
e.resolve(result);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Explicit cancel from the client side (user clicked "Chat about
|
|
119
|
+
* this" or closed the modal). Same shape as answer, but the
|
|
120
|
+
* caller-supplied envelope carries `cancelled: true` and an
|
|
121
|
+
* empty `answers` array — or partial answers if the user filled
|
|
122
|
+
* some tabs before bailing.
|
|
123
|
+
*/
|
|
124
|
+
export function cancelPending(requestId, expectedSessionId, result) {
|
|
125
|
+
// Same path as answerPending — the distinction is in the envelope
|
|
126
|
+
// shape, not the registry mechanics.
|
|
127
|
+
return answerPending(requestId, expectedSessionId, result);
|
|
128
|
+
}
|
|
129
|
+
export function getPendingForSession(sessionId) {
|
|
130
|
+
const ids = bySessionId.get(sessionId);
|
|
131
|
+
if (ids === undefined)
|
|
132
|
+
return [];
|
|
133
|
+
const out = [];
|
|
134
|
+
for (const id of ids) {
|
|
135
|
+
const e = byRequestId.get(id);
|
|
136
|
+
if (e !== undefined) {
|
|
137
|
+
out.push({
|
|
138
|
+
requestId: e.requestId,
|
|
139
|
+
sessionId: e.sessionId,
|
|
140
|
+
questions: e.questions,
|
|
141
|
+
createdAt: e.createdAt,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Test-only reset. Clears all pending state without notifying
|
|
149
|
+
* listeners — call between integration test cases to avoid
|
|
150
|
+
* cross-contamination.
|
|
151
|
+
*/
|
|
152
|
+
export function _resetForTests() {
|
|
153
|
+
byRequestId.clear();
|
|
154
|
+
bySessionId.clear();
|
|
155
|
+
listeners.clear();
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/ask-user-question/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { registerPending } from "./registry.js";
|
|
3
|
+
import { validateQuestionnaire } from "./validate.js";
|
|
4
|
+
import { buildResult } from "./envelope.js";
|
|
5
|
+
import { PROMPT_GUIDELINES, PROMPT_SNIPPET, TOOL_DESCRIPTION } from "./prompt-strings.js";
|
|
6
|
+
import { MAX_HEADER_LENGTH, MAX_LABEL_LENGTH, MAX_OPTIONS, MAX_QUESTIONS, MIN_OPTIONS, } from "./types.js";
|
|
7
|
+
export const ASK_USER_QUESTION_TOOL_NAME = "ask_user_question";
|
|
8
|
+
/**
|
|
9
|
+
* JSON Schema for the tool's params. We hand-write the schema
|
|
10
|
+
* (rather than reaching for TypeBox) so the shape matches what the
|
|
11
|
+
* upstream plugin advertises field-for-field. The structural caps
|
|
12
|
+
* (1..MAX_QUESTIONS, MIN..MAX_OPTIONS, maxLength) act as a fast
|
|
13
|
+
* pre-filter; `validateQuestionnaire` runs after for semantic
|
|
14
|
+
* checks the schema can't express (reserved labels, dupes).
|
|
15
|
+
*
|
|
16
|
+
* Type.Unsafe wraps the raw JSON Schema as a TypeBox schema so it
|
|
17
|
+
* satisfies the ToolDefinition.parameters type without dragging
|
|
18
|
+
* the rest of the TypeBox DSL into our code.
|
|
19
|
+
*/
|
|
20
|
+
const inputSchema = {
|
|
21
|
+
type: "object",
|
|
22
|
+
required: ["questions"],
|
|
23
|
+
properties: {
|
|
24
|
+
questions: {
|
|
25
|
+
type: "array",
|
|
26
|
+
minItems: 1,
|
|
27
|
+
maxItems: MAX_QUESTIONS,
|
|
28
|
+
items: {
|
|
29
|
+
type: "object",
|
|
30
|
+
required: ["question", "header", "options"],
|
|
31
|
+
properties: {
|
|
32
|
+
question: { type: "string", minLength: 1 },
|
|
33
|
+
header: { type: "string", minLength: 1, maxLength: MAX_HEADER_LENGTH },
|
|
34
|
+
multiSelect: { type: "boolean" },
|
|
35
|
+
options: {
|
|
36
|
+
type: "array",
|
|
37
|
+
minItems: MIN_OPTIONS,
|
|
38
|
+
maxItems: MAX_OPTIONS,
|
|
39
|
+
items: {
|
|
40
|
+
type: "object",
|
|
41
|
+
required: ["label", "description"],
|
|
42
|
+
properties: {
|
|
43
|
+
label: { type: "string", minLength: 1, maxLength: MAX_LABEL_LENGTH },
|
|
44
|
+
description: { type: "string", minLength: 1 },
|
|
45
|
+
preview: { type: "string" },
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Build the per-session `ask_user_question` tool. Bound to one
|
|
56
|
+
* session so `execute()` knows which browser to push the
|
|
57
|
+
* questions to.
|
|
58
|
+
*
|
|
59
|
+
* The tool is contract-compatible with
|
|
60
|
+
* `@juicesharp/rpiv-ask-user-question` — agents prompted to use
|
|
61
|
+
* `ask_user_question` get the same input schema, the same
|
|
62
|
+
* response envelope (`{content:[{type:"text",text}], details:{
|
|
63
|
+
* answers, cancelled, error?}}`), and the same answer-kind
|
|
64
|
+
* vocabulary (`option | custom | chat | multi`). Implementation
|
|
65
|
+
* is independent; the contract is reproduced via the published
|
|
66
|
+
* schema descriptions.
|
|
67
|
+
*/
|
|
68
|
+
export function createAskUserQuestionTool(sessionId) {
|
|
69
|
+
return {
|
|
70
|
+
name: ASK_USER_QUESTION_TOOL_NAME,
|
|
71
|
+
label: "Ask User Question",
|
|
72
|
+
description: TOOL_DESCRIPTION,
|
|
73
|
+
promptSnippet: PROMPT_SNIPPET,
|
|
74
|
+
promptGuidelines: PROMPT_GUIDELINES,
|
|
75
|
+
parameters: Type.Unsafe(inputSchema),
|
|
76
|
+
async execute(_toolCallId, params, signal) {
|
|
77
|
+
const validation = validateQuestionnaire(params);
|
|
78
|
+
if (!validation.ok) {
|
|
79
|
+
// Validation failure is communicated to the agent in the
|
|
80
|
+
// same shape the plugin uses — `details.error` carries the
|
|
81
|
+
// discriminated code, `details.cancelled` is true, and the
|
|
82
|
+
// text block summarises so the model has something to read.
|
|
83
|
+
return buildResult([], {
|
|
84
|
+
cancelled: true,
|
|
85
|
+
error: validation.error,
|
|
86
|
+
questionCount: 0,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const { params: q } = validation;
|
|
90
|
+
try {
|
|
91
|
+
const args = {
|
|
92
|
+
sessionId,
|
|
93
|
+
questions: q.questions,
|
|
94
|
+
};
|
|
95
|
+
if (signal !== undefined)
|
|
96
|
+
args.signal = signal;
|
|
97
|
+
const { result } = registerPending(args);
|
|
98
|
+
return await result;
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
// Abort path — `registerPending` rejects with Error("aborted")
|
|
102
|
+
// when the agent's signal fires. Return a clean cancelled
|
|
103
|
+
// envelope so the agent sees a tool result rather than an
|
|
104
|
+
// unhandled exception in its loop.
|
|
105
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
106
|
+
return buildResult([], {
|
|
107
|
+
cancelled: true,
|
|
108
|
+
error: message,
|
|
109
|
+
questionCount: q.questions.length,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool.js","sourceRoot":"","sources":["../../src/ask-user-question/tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape definitions for the `ask_user_question` tool. The wire schema
|
|
3
|
+
* (questions[1..4] with options[2..4]; header ≤16 chars; label ≤60
|
|
4
|
+
* chars; reserved sentinel labels) is contract-compatible with
|
|
5
|
+
* `@juicesharp/rpiv-ask-user-question` — an agent prompt written
|
|
6
|
+
* against the plugin works against this implementation unchanged.
|
|
7
|
+
*
|
|
8
|
+
* Implementation is independent; constants and validation rules were
|
|
9
|
+
* derived from the plugin's published schema descriptions and test
|
|
10
|
+
* fixtures rather than copied. See `docs/ask-user-question.md` for the
|
|
11
|
+
* cross-reference.
|
|
12
|
+
*/
|
|
13
|
+
export const MAX_QUESTIONS = 4;
|
|
14
|
+
export const MIN_OPTIONS = 2;
|
|
15
|
+
export const MAX_OPTIONS = 4;
|
|
16
|
+
export const MAX_HEADER_LENGTH = 16;
|
|
17
|
+
export const MAX_LABEL_LENGTH = 60;
|
|
18
|
+
/**
|
|
19
|
+
* Labels the validator rejects at submit time. The four runtime
|
|
20
|
+
* sentinels ("Type something.", "Chat about this", "Next") are
|
|
21
|
+
* appended by the UI so authoring them would collide; "Other" is
|
|
22
|
+
* reserved for CC-style parity (the model often reaches for "Other"
|
|
23
|
+
* when given the chance — we want the runtime sentinel to be the
|
|
24
|
+
* single source of truth).
|
|
25
|
+
*/
|
|
26
|
+
export const RESERVED_LABELS = ["Other", "Type something.", "Chat about this", "Next"];
|
|
27
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/ask-user-question/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC;AAC/B,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAC7B,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAC7B,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AACpC,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAEnC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,CAAU,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { MAX_HEADER_LENGTH, MAX_LABEL_LENGTH, MAX_OPTIONS, MAX_QUESTIONS, MIN_OPTIONS, RESERVED_LABELS, } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Runtime validation for the agent-supplied questionnaire. Mirrors
|
|
4
|
+
* the upstream plugin's rules without porting code. Each failure
|
|
5
|
+
* returns the same error-code vocabulary the plugin uses so a
|
|
6
|
+
* downstream parser doesn't care which implementation answered.
|
|
7
|
+
*
|
|
8
|
+
* The schema layer (Fastify body validation) catches type/shape
|
|
9
|
+
* errors first; this validator enforces the semantic ones: reserved
|
|
10
|
+
* sentinel labels, duplicate labels, the question / option count
|
|
11
|
+
* caps, and the byte-cap on header and label fields. Byte caps live
|
|
12
|
+
* here too (not just in the schema) so a caller that bypassed the
|
|
13
|
+
* schema — e.g. a future programmatic invocation path — still gets
|
|
14
|
+
* the same enforcement.
|
|
15
|
+
*/
|
|
16
|
+
export function validateQuestionnaire(input) {
|
|
17
|
+
if (typeof input !== "object" || input === null) {
|
|
18
|
+
return { ok: false, error: "no_questions", message: "params must be an object" };
|
|
19
|
+
}
|
|
20
|
+
const params = input;
|
|
21
|
+
const questions = params.questions;
|
|
22
|
+
if (!Array.isArray(questions) || questions.length === 0) {
|
|
23
|
+
return {
|
|
24
|
+
ok: false,
|
|
25
|
+
error: "no_questions",
|
|
26
|
+
message: "questions[] must have at least one entry",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (questions.length > MAX_QUESTIONS) {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
error: "too_many_questions",
|
|
33
|
+
message: `at most ${MAX_QUESTIONS} questions per invocation`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const reserved = new Set(RESERVED_LABELS);
|
|
37
|
+
for (let qi = 0; qi < questions.length; qi += 1) {
|
|
38
|
+
const q = questions[qi];
|
|
39
|
+
if (typeof q !== "object" || q === null) {
|
|
40
|
+
return {
|
|
41
|
+
ok: false,
|
|
42
|
+
error: "missing_question_text",
|
|
43
|
+
message: `question[${qi}] is not an object`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const question = q.question;
|
|
47
|
+
if (typeof question !== "string" || question.trim().length === 0) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
error: "missing_question_text",
|
|
51
|
+
message: `question[${qi}].question is required`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const header = q.header;
|
|
55
|
+
if (typeof header !== "string" || header.length === 0) {
|
|
56
|
+
return { ok: false, error: "missing_header", message: `question[${qi}].header is required` };
|
|
57
|
+
}
|
|
58
|
+
if (header.length > MAX_HEADER_LENGTH) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
error: "header_too_long",
|
|
62
|
+
message: `question[${qi}].header exceeds ${MAX_HEADER_LENGTH} chars`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const options = q.options;
|
|
66
|
+
if (!Array.isArray(options) || options.length < MIN_OPTIONS) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
error: "too_few_options",
|
|
70
|
+
message: `question[${qi}] requires at least ${MIN_OPTIONS} options`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (options.length > MAX_OPTIONS) {
|
|
74
|
+
return {
|
|
75
|
+
ok: false,
|
|
76
|
+
error: "too_many_options",
|
|
77
|
+
message: `question[${qi}] allows at most ${MAX_OPTIONS} options`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const seenLabels = new Set();
|
|
81
|
+
for (let oi = 0; oi < options.length; oi += 1) {
|
|
82
|
+
const opt = options[oi];
|
|
83
|
+
if (typeof opt !== "object" || opt === null) {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
error: "missing_label",
|
|
87
|
+
message: `question[${qi}].options[${oi}] is not an object`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const label = opt.label;
|
|
91
|
+
if (typeof label !== "string" || label.length === 0) {
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
error: "missing_label",
|
|
95
|
+
message: `question[${qi}].options[${oi}].label is required`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (label.length > MAX_LABEL_LENGTH) {
|
|
99
|
+
return {
|
|
100
|
+
ok: false,
|
|
101
|
+
error: "label_too_long",
|
|
102
|
+
message: `question[${qi}].options[${oi}].label exceeds ${MAX_LABEL_LENGTH} chars`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// Reserved-label check applies to every kind (multiSelect too)
|
|
106
|
+
// so the runtime sentinels stay the single source of truth even
|
|
107
|
+
// when the UI suppresses them.
|
|
108
|
+
if (reserved.has(label)) {
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
error: "reserved_label",
|
|
112
|
+
message: `question[${qi}].options[${oi}].label "${label}" is reserved — pick a different label`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (seenLabels.has(label)) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
error: "duplicate_label",
|
|
119
|
+
message: `question[${qi}].options[${oi}].label "${label}" duplicates an earlier option`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
seenLabels.add(label);
|
|
123
|
+
const description = opt.description;
|
|
124
|
+
if (typeof description !== "string" || description.length === 0) {
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
error: "missing_description",
|
|
128
|
+
message: `question[${qi}].options[${oi}].description is required`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return { ok: true, params: params };
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/ask-user-question/validate.ts"],"names":[],"mappings":"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"}
|