@bastani/atomic 0.8.28-alpha.2 → 0.8.28-alpha.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/CHANGELOG.md +13 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +24 -0
- package/dist/builtin/workflows/README.md +1 -1
- package/dist/builtin/workflows/builtin/deep-research-codebase.ts +2 -1
- package/dist/builtin/workflows/builtin/goal.ts +2 -1
- package/dist/builtin/workflows/builtin/open-claude-design.ts +2 -1
- package/dist/builtin/workflows/builtin/ralph.ts +4 -2
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/authoring.d.ts +5 -2
- package/dist/builtin/workflows/src/extension/dispatcher.ts +2 -0
- package/dist/builtin/workflows/src/extension/index.ts +8 -0
- package/dist/builtin/workflows/src/extension/render-result.ts +5 -2
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +18 -0
- package/dist/builtin/workflows/src/runs/background/status.ts +4 -0
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +1251 -110
- package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +34 -10
- package/dist/builtin/workflows/src/shared/expanded-workflow-graph.ts +10 -2
- package/dist/builtin/workflows/src/shared/persistence-restore.ts +28 -9
- package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +9 -3
- package/dist/builtin/workflows/src/shared/store-types.ts +10 -3
- package/dist/builtin/workflows/src/shared/store.ts +29 -7
- package/dist/builtin/workflows/src/shared/types.ts +12 -10
- package/dist/builtin/workflows/src/tui/run-detail.ts +12 -0
- package/dist/builtin/workflows/src/tui/status-helpers.ts +4 -0
- package/dist/builtin/workflows/src/tui/status-list.ts +15 -1
- package/dist/builtin/workflows/src/tui/store-widget-installer.ts +1 -1
- package/dist/builtin/workflows/src/tui/widget.ts +12 -3
- package/dist/builtin/workflows/src/workflows/define-workflow.ts +3 -3
- package/dist/core/agent-session-services.d.ts +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +4 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +12 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/sdk.d.ts +4 -2
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/inline-input.d.ts +28 -0
- package/dist/core/tools/ask-user-question/state/inline-input.d.ts.map +1 -0
- package/dist/core/tools/ask-user-question/state/inline-input.js +56 -0
- package/dist/core/tools/ask-user-question/state/inline-input.js.map +1 -0
- package/dist/core/tools/ask-user-question/state/key-router.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/key-router.js +30 -4
- package/dist/core/tools/ask-user-question/state/key-router.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/questionnaire-session.js +9 -8
- package/dist/core/tools/ask-user-question/state/questionnaire-session.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/row-intent.d.ts +3 -2
- package/dist/core/tools/ask-user-question/state/row-intent.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/row-intent.js +1 -1
- package/dist/core/tools/ask-user-question/state/row-intent.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts +2 -0
- package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/selectors/contract.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/selectors/projections.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/selectors/projections.js +2 -0
- package/dist/core/tools/ask-user-question/state/selectors/projections.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/state-reducer.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/state-reducer.js +36 -24
- package/dist/core/tools/ask-user-question/state/state-reducer.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/state.d.ts +8 -0
- package/dist/core/tools/ask-user-question/state/state.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/state.js.map +1 -1
- package/dist/core/tools/ask-user-question/tool/format-answer.d.ts +6 -0
- package/dist/core/tools/ask-user-question/tool/format-answer.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/tool/format-answer.js +19 -1
- package/dist/core/tools/ask-user-question/tool/format-answer.js.map +1 -1
- package/dist/core/tools/ask-user-question/tool/response-envelope.d.ts +3 -2
- package/dist/core/tools/ask-user-question/tool/response-envelope.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/tool/response-envelope.js +15 -3
- package/dist/core/tools/ask-user-question/tool/response-envelope.js.map +1 -1
- package/dist/core/tools/ask-user-question/tool/types.d.ts +2 -1
- package/dist/core/tools/ask-user-question/tool/types.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/tool/types.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/chat-row-view.d.ts +5 -2
- package/dist/core/tools/ask-user-question/view/components/chat-row-view.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/chat-row-view.js +2 -0
- package/dist/core/tools/ask-user-question/view/components/chat-row-view.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts +1 -0
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.js +2 -1
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/props-adapter.d.ts +3 -3
- package/dist/core/tools/ask-user-question/view/props-adapter.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/props-adapter.js +11 -4
- package/dist/core/tools/ask-user-question/view/props-adapter.js.map +1 -1
- package/dist/core/tools/bash-policy.d.ts +62 -0
- package/dist/core/tools/bash-policy.d.ts.map +1 -0
- package/dist/core/tools/bash-policy.js +1069 -0
- package/dist/core/tools/bash-policy.js.map +1 -0
- package/dist/core/tools/bash.d.ts +5 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +7 -0
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +1 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/docs/sdk.md +42 -0
- package/docs/security.md +2 -0
- package/docs/workflows.md +127 -15
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readInlineCaret, resolveInlineDraftValue, withInlineDraft } from "./inline-input.js";
|
|
1
2
|
import { ROW_INTENT_META } from "./row-intent.js";
|
|
2
3
|
import { computeFocusedOptionHasPreview } from "./selectors/derivations.js";
|
|
3
4
|
function orderedAnswers(state, questions) {
|
|
@@ -58,22 +59,23 @@ function clampCaret(value, caret) {
|
|
|
58
59
|
return value.length;
|
|
59
60
|
return Math.max(0, Math.min(caret, value.length));
|
|
60
61
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
function inlineOwnerForItem(item) {
|
|
63
|
+
if (!item || !ROW_INTENT_META[item.kind].activatesInputMode)
|
|
64
|
+
return null;
|
|
65
|
+
switch (item.kind) {
|
|
66
|
+
case "other":
|
|
67
|
+
case "chat":
|
|
68
|
+
return item.kind;
|
|
69
|
+
case "option":
|
|
70
|
+
case "next":
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
67
73
|
}
|
|
68
|
-
function
|
|
69
|
-
const value =
|
|
70
|
-
const caret = clampCaret(value,
|
|
71
|
-
const customDraftByTab = new Map(state.customDraftByTab);
|
|
72
|
-
const customCaretByTab = new Map(state.customCaretByTab);
|
|
73
|
-
customDraftByTab.set(state.currentTab, value);
|
|
74
|
-
customCaretByTab.set(state.currentTab, caret);
|
|
74
|
+
function hydrateInlineInputResult(state, owner) {
|
|
75
|
+
const value = resolveInlineDraftValue(state, owner);
|
|
76
|
+
const caret = clampCaret(value, readInlineCaret(state, owner));
|
|
75
77
|
return {
|
|
76
|
-
state: { ...state,
|
|
78
|
+
state: withInlineDraft({ ...state, inputMode: true, inlineInputOwner: owner }, owner, value, caret),
|
|
77
79
|
effects: [{ kind: "set_input_buffer", value, caret }],
|
|
78
80
|
};
|
|
79
81
|
}
|
|
@@ -84,6 +86,7 @@ function switchTabResult(state, nextTab, ctx) {
|
|
|
84
86
|
currentTab: nextTab,
|
|
85
87
|
optionIndex: 0,
|
|
86
88
|
inputMode: false,
|
|
89
|
+
inlineInputOwner: null,
|
|
87
90
|
notesVisible: false,
|
|
88
91
|
chatFocused: false,
|
|
89
92
|
submitChoiceIndex: 0,
|
|
@@ -106,9 +109,15 @@ function doneFor(state, ctx, cancelled) {
|
|
|
106
109
|
const navHandler = (state, action, ctx) => {
|
|
107
110
|
const items = ctx.itemsByTab[state.currentTab] ?? [];
|
|
108
111
|
const item = items[action.nextIndex];
|
|
109
|
-
const
|
|
110
|
-
const next = withFocusedOptionHasPreview({
|
|
111
|
-
|
|
112
|
+
const owner = inlineOwnerForItem(item);
|
|
113
|
+
const next = withFocusedOptionHasPreview({
|
|
114
|
+
...state,
|
|
115
|
+
chatFocused: false,
|
|
116
|
+
optionIndex: action.nextIndex,
|
|
117
|
+
inputMode: owner !== null,
|
|
118
|
+
inlineInputOwner: owner,
|
|
119
|
+
}, ctx.questions);
|
|
120
|
+
return owner ? hydrateInlineInputResult(next, owner) : { state: next, effects: [] };
|
|
112
121
|
};
|
|
113
122
|
const tabSwitchHandler = (state, action, ctx) => switchTabResult(state, action.nextTab, ctx);
|
|
114
123
|
const confirmHandler = (state, action, ctx) => {
|
|
@@ -203,9 +212,15 @@ const notesExitHandler = (state, _action, _ctx) => {
|
|
|
203
212
|
const focusOptionsHandler = (state, action, ctx) => {
|
|
204
213
|
const items = ctx.itemsByTab[state.currentTab] ?? [];
|
|
205
214
|
const focused = items[action.optionIndex];
|
|
206
|
-
const
|
|
207
|
-
const next = withFocusedOptionHasPreview({
|
|
208
|
-
|
|
215
|
+
const owner = inlineOwnerForItem(focused);
|
|
216
|
+
const next = withFocusedOptionHasPreview({
|
|
217
|
+
...state,
|
|
218
|
+
chatFocused: false,
|
|
219
|
+
optionIndex: action.optionIndex,
|
|
220
|
+
inputMode: owner !== null,
|
|
221
|
+
inlineInputOwner: owner,
|
|
222
|
+
}, ctx.questions);
|
|
223
|
+
return owner ? hydrateInlineInputResult(next, owner) : { state: next, effects: [] };
|
|
209
224
|
};
|
|
210
225
|
const cancelHandler = (s, _a, c) => doneFor(s, c, true);
|
|
211
226
|
const submitHandler = (s, _a, c) => doneFor(s, c, false);
|
|
@@ -213,10 +228,7 @@ const submitNavHandler = (s, a, _c) => ({
|
|
|
213
228
|
state: { ...s, submitChoiceIndex: a.nextIndex },
|
|
214
229
|
effects: [],
|
|
215
230
|
});
|
|
216
|
-
const focusChatHandler = (s, _a, _c) => ({
|
|
217
|
-
state: { ...s, chatFocused: true },
|
|
218
|
-
effects: [],
|
|
219
|
-
});
|
|
231
|
+
const focusChatHandler = (s, _a, _c) => hydrateInlineInputResult({ ...s, chatFocused: true, inputMode: true, inlineInputOwner: "chat" }, "chat");
|
|
220
232
|
const notesForwardHandler = (s, a, _c) => ({
|
|
221
233
|
state: s,
|
|
222
234
|
effects: [{ kind: "forward_notes_keystroke", data: a.data }],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-reducer.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/state-reducer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { QuestionAnswer, QuestionData, QuestionnaireResult } from \"../tool/types.ts\";\nimport type { WrappingSelectItem } from \"../view/components/wrapping-select.ts\";\nimport type { QuestionnaireAction } from \"./key-router.ts\";\nimport { ROW_INTENT_META } from \"./row-intent.ts\";\nimport { computeFocusedOptionHasPreview } from \"./selectors/derivations.ts\";\nimport type { QuestionnaireState } from \"./state.ts\";\n\n/** Session-lifetime constants. No live-component reads — peripheral values live on canonical state. */\nexport interface ApplyContext {\n\tquestions: readonly QuestionData[];\n\titemsByTab: ReadonlyArray<readonly WrappingSelectItem[]>;\n}\n\n/**\n * Declarative side-effects emitted by `reduce`. The runtime executes them after\n * committing the new state, then asks the props-adapter to re-project. Closed set —\n * adding an effect requires updating both the union AND the runtime's `runEffect` switch\n * (compiler-enforced exhaustive). No string-keyed escape hatch.\n */\nexport type Effect =\n\t| { kind: \"set_input_buffer\"; value: string; caret: number }\n\t| { kind: \"set_notes_value\"; value: string }\n\t| { kind: \"set_notes_focused\"; focused: boolean }\n\t| { kind: \"forward_notes_keystroke\"; data: string }\n\t| { kind: \"done\"; result: QuestionnaireResult };\n\nexport interface ApplyResult {\n\tstate: QuestionnaireState;\n\teffects: readonly Effect[];\n}\n\nfunction orderedAnswers(state: QuestionnaireState, questions: readonly QuestionData[]): QuestionAnswer[] {\n\tconst out: QuestionAnswer[] = [];\n\tfor (let i = 0; i < questions.length; i++) {\n\t\tconst a = state.answers.get(i);\n\t\tif (a) out.push(a);\n\t}\n\treturn out;\n}\n\nfunction withFocusedOptionHasPreview(\n\tstate: QuestionnaireState,\n\tquestions: readonly QuestionData[],\n): QuestionnaireState {\n\tconst focusedOptionHasPreview = computeFocusedOptionHasPreview(questions, state.currentTab, state.optionIndex);\n\tif (state.focusedOptionHasPreview === focusedOptionHasPreview) return state;\n\treturn { ...state, focusedOptionHasPreview };\n}\n\nfunction syncMultiSelectFromAnswers(\n\tanswers: ReadonlyMap<number, QuestionAnswer>,\n\tquestions: readonly QuestionData[],\n\ttab: number,\n): ReadonlySet<number> {\n\tconst q = questions[tab];\n\tif (!q?.multiSelect) return new Set();\n\tconst saved = answers.get(tab);\n\tconst labels = saved?.selected ?? [];\n\tconst indices = new Set<number>();\n\tfor (let i = 0; i < q.options.length; i++) {\n\t\tif (labels.includes(q.options[i]!.label)) indices.add(i);\n\t}\n\treturn indices;\n}\n\nfunction persistMultiSelectAnswer(state: QuestionnaireState, ctx: ApplyContext): ReadonlyMap<number, QuestionAnswer> {\n\tconst q = ctx.questions[state.currentTab];\n\tif (!q?.multiSelect) return state.answers;\n\tconst selected: string[] = [];\n\tfor (let i = 0; i < q.options.length; i++) {\n\t\tif (state.multiSelectChecked.has(i)) selected.push(q.options[i]!.label);\n\t}\n\tconst out = new Map(state.answers);\n\tif (selected.length === 0) {\n\t\tout.delete(state.currentTab);\n\t\treturn out;\n\t}\n\tconst pendingNotes = state.notesByTab.get(state.currentTab);\n\tout.set(state.currentTab, {\n\t\tquestionIndex: state.currentTab,\n\t\tquestion: q.question,\n\t\tkind: \"multi\",\n\t\tanswer: null,\n\t\tselected,\n\t\t...(pendingNotes && pendingNotes.length > 0 ? { notes: pendingNotes } : {}),\n\t});\n\treturn out;\n}\n\nfunction clampCaret(value: string, caret: number | undefined): number {\n\tif (caret === undefined || !Number.isFinite(caret)) return value.length;\n\treturn Math.max(0, Math.min(caret, value.length));\n}\n\nfunction customDraftValue(state: QuestionnaireState): string {\n\tconst draft = state.customDraftByTab.get(state.currentTab);\n\tif (draft !== undefined) return draft;\n\tconst prior = state.answers.get(state.currentTab);\n\treturn prior?.kind === \"custom\" && typeof prior.answer === \"string\" ? prior.answer : \"\";\n}\n\nfunction hydrateCustomInputResult(state: QuestionnaireState): ApplyResult {\n\tconst value = customDraftValue(state);\n\tconst caret = clampCaret(value, state.customCaretByTab.get(state.currentTab));\n\tconst customDraftByTab = new Map(state.customDraftByTab);\n\tconst customCaretByTab = new Map(state.customCaretByTab);\n\tcustomDraftByTab.set(state.currentTab, value);\n\tcustomCaretByTab.set(state.currentTab, caret);\n\treturn {\n\t\tstate: { ...state, customDraftByTab, customCaretByTab },\n\t\teffects: [{ kind: \"set_input_buffer\", value, caret }],\n\t};\n}\n\nfunction switchTabResult(state: QuestionnaireState, nextTab: number, ctx: ApplyContext): ApplyResult {\n\tconst notesValue = state.notesByTab.get(nextTab) ?? state.answers.get(nextTab)?.notes ?? \"\";\n\tconst transitioned: QuestionnaireState = {\n\t\t...state,\n\t\tcurrentTab: nextTab,\n\t\toptionIndex: 0,\n\t\tinputMode: false,\n\t\tnotesVisible: false,\n\t\tchatFocused: false,\n\t\tsubmitChoiceIndex: 0,\n\t\tmultiSelectChecked: syncMultiSelectFromAnswers(state.answers, ctx.questions, nextTab),\n\t\tnotesDraft: notesValue,\n\t};\n\tconst finalState = withFocusedOptionHasPreview(transitioned, ctx.questions);\n\treturn {\n\t\tstate: finalState,\n\t\teffects: [\n\t\t\t{ kind: \"set_notes_focused\", focused: false },\n\t\t\t{ kind: \"set_notes_value\", value: notesValue },\n\t\t],\n\t};\n}\n\nfunction doneFor(state: QuestionnaireState, ctx: ApplyContext, cancelled: boolean): ApplyResult {\n\tconst result: QuestionnaireResult = { answers: orderedAnswers(state, ctx.questions), cancelled };\n\treturn { state, effects: [{ kind: \"done\", result }] };\n}\n\n/**\n * Per-kind handler signature: action payload narrows to the matching union member\n * via `Extract`, so handlers consume fully-typed actions without `as` casts.\n */\ntype Handler<K extends QuestionnaireAction[\"kind\"]> = (\n\tstate: QuestionnaireState,\n\taction: Extract<QuestionnaireAction, { kind: K }>,\n\tctx: ApplyContext,\n) => ApplyResult;\n\nconst navHandler: Handler<\"nav\"> = (state, action, ctx) => {\n\tconst items = ctx.itemsByTab[state.currentTab] ?? [];\n\tconst item = items[action.nextIndex];\n\tconst inputMode = item ? ROW_INTENT_META[item.kind].activatesInputMode : false;\n\tconst next = withFocusedOptionHasPreview({ ...state, optionIndex: action.nextIndex, inputMode }, ctx.questions);\n\treturn inputMode ? hydrateCustomInputResult(next) : { state: next, effects: [] };\n};\n\nconst tabSwitchHandler: Handler<\"tab_switch\"> = (state, action, ctx) => switchTabResult(state, action.nextTab, ctx);\n\nconst confirmHandler: Handler<\"confirm\"> = (state, action, ctx) => {\n\tlet answer = action.answer;\n\tif (answer.kind === \"option\" && answer.answer) {\n\t\tconst q = ctx.questions[answer.questionIndex];\n\t\tconst matched = q?.options.find((o) => o.label === answer.answer);\n\t\tif (matched?.preview && matched.preview.length > 0) {\n\t\t\tanswer = { ...answer, preview: matched.preview };\n\t\t}\n\t}\n\tconst pendingNotes = state.notesByTab.get(answer.questionIndex);\n\tif (pendingNotes && pendingNotes.length > 0) {\n\t\tanswer = { ...answer, notes: pendingNotes };\n\t}\n\tconst answers = new Map(state.answers);\n\tanswers.set(answer.questionIndex, answer);\n\tconst next: QuestionnaireState = { ...state, answers };\n\tif (answer.kind === \"chat\") return doneFor(next, ctx, false);\n\tif (action.autoAdvanceTab !== undefined) return switchTabResult(next, action.autoAdvanceTab, ctx);\n\treturn doneFor(next, ctx, false);\n};\n\nconst toggleHandler: Handler<\"toggle\"> = (state, action, ctx) => {\n\tconst checked = new Set(state.multiSelectChecked);\n\tif (checked.has(action.index)) checked.delete(action.index);\n\telse checked.add(action.index);\n\tconst intermediate: QuestionnaireState = { ...state, multiSelectChecked: checked };\n\tconst answers = persistMultiSelectAnswer(intermediate, ctx);\n\treturn { state: { ...intermediate, answers }, effects: [] };\n};\n\nconst multiConfirmHandler: Handler<\"multi_confirm\"> = (state, action, ctx) => {\n\tconst q = ctx.questions[state.currentTab];\n\tif (!q) return { state, effects: [] };\n\tconst pendingNotes = state.notesByTab.get(state.currentTab);\n\tconst answers = new Map(state.answers);\n\tanswers.set(state.currentTab, {\n\t\tquestionIndex: state.currentTab,\n\t\tquestion: q.question,\n\t\tkind: \"multi\",\n\t\tanswer: null,\n\t\tselected: action.selected,\n\t\t...(pendingNotes && pendingNotes.length > 0 ? { notes: pendingNotes } : {}),\n\t});\n\tconst synced: QuestionnaireState = {\n\t\t...state,\n\t\tanswers,\n\t\tmultiSelectChecked: syncMultiSelectFromAnswers(answers, ctx.questions, state.currentTab),\n\t};\n\tif (action.autoAdvanceTab !== undefined) return switchTabResult(synced, action.autoAdvanceTab, ctx);\n\treturn doneFor(synced, ctx, false);\n};\n\nconst notesEnterHandler: Handler<\"notes_enter\"> = (state, _action, _ctx) => {\n\tconst value = state.answers.get(state.currentTab)?.notes ?? \"\";\n\treturn {\n\t\tstate: { ...state, notesVisible: true, notesDraft: value },\n\t\teffects: [\n\t\t\t{ kind: \"set_notes_value\", value },\n\t\t\t{ kind: \"set_notes_focused\", focused: true },\n\t\t],\n\t};\n};\n\nconst notesExitHandler: Handler<\"notes_exit\"> = (state, _action, _ctx) => {\n\tconst trimmed = state.notesDraft.trim();\n\tconst notes = new Map(state.notesByTab);\n\tconst answers = new Map(state.answers);\n\tif (trimmed.length === 0) {\n\t\tnotes.delete(state.currentTab);\n\t\tconst prev = answers.get(state.currentTab);\n\t\tif (prev?.notes) {\n\t\t\tconst stripped = { ...prev };\n\t\t\tdelete (stripped as { notes?: string }).notes;\n\t\t\tanswers.set(state.currentTab, stripped);\n\t\t}\n\t} else {\n\t\tnotes.set(state.currentTab, trimmed);\n\t\tconst prev = answers.get(state.currentTab);\n\t\tif (prev) answers.set(state.currentTab, { ...prev, notes: trimmed });\n\t}\n\treturn {\n\t\tstate: { ...state, notesByTab: notes, answers, notesVisible: false },\n\t\teffects: [{ kind: \"set_notes_focused\", focused: false }],\n\t};\n};\n\nconst focusOptionsHandler: Handler<\"focus_options\"> = (state, action, ctx) => {\n\tconst items = ctx.itemsByTab[state.currentTab] ?? [];\n\tconst focused = items[action.optionIndex];\n\tconst inputMode = focused ? ROW_INTENT_META[focused.kind].activatesInputMode : false;\n\tconst next = withFocusedOptionHasPreview(\n\t\t{ ...state, chatFocused: false, optionIndex: action.optionIndex, inputMode },\n\t\tctx.questions,\n\t);\n\treturn inputMode ? hydrateCustomInputResult(next) : { state: next, effects: [] };\n};\n\nconst cancelHandler: Handler<\"cancel\"> = (s, _a, c) => doneFor(s, c, true);\nconst submitHandler: Handler<\"submit\"> = (s, _a, c) => doneFor(s, c, false);\nconst submitNavHandler: Handler<\"submit_nav\"> = (s, a, _c) => ({\n\tstate: { ...s, submitChoiceIndex: a.nextIndex },\n\teffects: [],\n});\nconst focusChatHandler: Handler<\"focus_chat\"> = (s, _a, _c) => ({\n\tstate: { ...s, chatFocused: true },\n\teffects: [],\n});\nconst notesForwardHandler: Handler<\"notes_forward\"> = (s, a, _c) => ({\n\tstate: s,\n\teffects: [{ kind: \"forward_notes_keystroke\", data: a.data }],\n});\nconst ignoreHandler: Handler<\"ignore\"> = (s, _a, _c) => ({ state: s, effects: [] });\n\n/**\n * Compile-time-exhaustive dispatch table. `{ [K in Kind]: Handler<K> }` requires\n * an entry per union member — adding a new `QuestionnaireAction` variant fails to\n * compile here until a handler is registered, mirroring the `Record<RowKind, …>`\n * pattern used by `ROW_INTENT_META`.\n */\nconst HANDLERS: { [K in QuestionnaireAction[\"kind\"]]: Handler<K> } = {\n\tnav: navHandler,\n\ttab_switch: tabSwitchHandler,\n\tconfirm: confirmHandler,\n\ttoggle: toggleHandler,\n\tmulti_confirm: multiConfirmHandler,\n\tcancel: cancelHandler,\n\tnotes_enter: notesEnterHandler,\n\tnotes_exit: notesExitHandler,\n\tnotes_forward: notesForwardHandler,\n\tsubmit: submitHandler,\n\tsubmit_nav: submitNavHandler,\n\tfocus_chat: focusChatHandler,\n\tfocus_options: focusOptionsHandler,\n\tignore: ignoreHandler,\n};\n\n/**\n * Pure reducer: (state, action, ctx) → (state, Effect[]). Mirrors `rpiv-todo`'s `applyTaskMutation`.\n * Delegates to `HANDLERS` — per-kind handlers above are pure, named, and individually testable.\n * `ignore` is also handled outside the reducer by `handleIgnoreInline` in the runtime fast path.\n */\nexport function reduce(state: QuestionnaireState, action: QuestionnaireAction, ctx: ApplyContext): ApplyResult {\n\tconst handler = HANDLERS[action.kind] as Handler<typeof action.kind>;\n\treturn handler(state, action as never, ctx);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"state-reducer.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/state-reducer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { QuestionAnswer, QuestionData, QuestionnaireResult } from \"../tool/types.ts\";\nimport type { WrappingSelectItem } from \"../view/components/wrapping-select.ts\";\nimport type { QuestionnaireAction } from \"./key-router.ts\";\nimport { readInlineCaret, resolveInlineDraftValue, withInlineDraft } from \"./inline-input.ts\";\nimport { ROW_INTENT_META } from \"./row-intent.ts\";\nimport { computeFocusedOptionHasPreview } from \"./selectors/derivations.ts\";\nimport type { InlineInputOwner, QuestionnaireState } from \"./state.ts\";\n\n/** Session-lifetime constants. No live-component reads — peripheral values live on canonical state. */\nexport interface ApplyContext {\n\tquestions: readonly QuestionData[];\n\titemsByTab: ReadonlyArray<readonly WrappingSelectItem[]>;\n}\n\n/**\n * Declarative side-effects emitted by `reduce`. The runtime executes them after\n * committing the new state, then asks the props-adapter to re-project. Closed set —\n * adding an effect requires updating both the union AND the runtime's `runEffect` switch\n * (compiler-enforced exhaustive). No string-keyed escape hatch.\n */\nexport type Effect =\n\t| { kind: \"set_input_buffer\"; value: string; caret: number }\n\t| { kind: \"set_notes_value\"; value: string }\n\t| { kind: \"set_notes_focused\"; focused: boolean }\n\t| { kind: \"forward_notes_keystroke\"; data: string }\n\t| { kind: \"done\"; result: QuestionnaireResult };\n\nexport interface ApplyResult {\n\tstate: QuestionnaireState;\n\teffects: readonly Effect[];\n}\n\nfunction orderedAnswers(state: QuestionnaireState, questions: readonly QuestionData[]): QuestionAnswer[] {\n\tconst out: QuestionAnswer[] = [];\n\tfor (let i = 0; i < questions.length; i++) {\n\t\tconst a = state.answers.get(i);\n\t\tif (a) out.push(a);\n\t}\n\treturn out;\n}\n\nfunction withFocusedOptionHasPreview(\n\tstate: QuestionnaireState,\n\tquestions: readonly QuestionData[],\n): QuestionnaireState {\n\tconst focusedOptionHasPreview = computeFocusedOptionHasPreview(questions, state.currentTab, state.optionIndex);\n\tif (state.focusedOptionHasPreview === focusedOptionHasPreview) return state;\n\treturn { ...state, focusedOptionHasPreview };\n}\n\nfunction syncMultiSelectFromAnswers(\n\tanswers: ReadonlyMap<number, QuestionAnswer>,\n\tquestions: readonly QuestionData[],\n\ttab: number,\n): ReadonlySet<number> {\n\tconst q = questions[tab];\n\tif (!q?.multiSelect) return new Set();\n\tconst saved = answers.get(tab);\n\tconst labels = saved?.selected ?? [];\n\tconst indices = new Set<number>();\n\tfor (let i = 0; i < q.options.length; i++) {\n\t\tif (labels.includes(q.options[i]!.label)) indices.add(i);\n\t}\n\treturn indices;\n}\n\nfunction persistMultiSelectAnswer(state: QuestionnaireState, ctx: ApplyContext): ReadonlyMap<number, QuestionAnswer> {\n\tconst q = ctx.questions[state.currentTab];\n\tif (!q?.multiSelect) return state.answers;\n\tconst selected: string[] = [];\n\tfor (let i = 0; i < q.options.length; i++) {\n\t\tif (state.multiSelectChecked.has(i)) selected.push(q.options[i]!.label);\n\t}\n\tconst out = new Map(state.answers);\n\tif (selected.length === 0) {\n\t\tout.delete(state.currentTab);\n\t\treturn out;\n\t}\n\tconst pendingNotes = state.notesByTab.get(state.currentTab);\n\tout.set(state.currentTab, {\n\t\tquestionIndex: state.currentTab,\n\t\tquestion: q.question,\n\t\tkind: \"multi\",\n\t\tanswer: null,\n\t\tselected,\n\t\t...(pendingNotes && pendingNotes.length > 0 ? { notes: pendingNotes } : {}),\n\t});\n\treturn out;\n}\n\nfunction clampCaret(value: string, caret: number | undefined): number {\n\tif (caret === undefined || !Number.isFinite(caret)) return value.length;\n\treturn Math.max(0, Math.min(caret, value.length));\n}\n\nfunction inlineOwnerForItem(item: WrappingSelectItem | undefined): InlineInputOwner | null {\n\tif (!item || !ROW_INTENT_META[item.kind].activatesInputMode) return null;\n\tswitch (item.kind) {\n\t\tcase \"other\":\n\t\tcase \"chat\":\n\t\t\treturn item.kind;\n\t\tcase \"option\":\n\t\tcase \"next\":\n\t\t\treturn null;\n\t}\n}\n\nfunction hydrateInlineInputResult(state: QuestionnaireState, owner: InlineInputOwner): ApplyResult {\n\tconst value = resolveInlineDraftValue(state, owner);\n\tconst caret = clampCaret(value, readInlineCaret(state, owner));\n\treturn {\n\t\tstate: withInlineDraft({ ...state, inputMode: true, inlineInputOwner: owner }, owner, value, caret),\n\t\teffects: [{ kind: \"set_input_buffer\", value, caret }],\n\t};\n}\n\nfunction switchTabResult(state: QuestionnaireState, nextTab: number, ctx: ApplyContext): ApplyResult {\n\tconst notesValue = state.notesByTab.get(nextTab) ?? state.answers.get(nextTab)?.notes ?? \"\";\n\tconst transitioned: QuestionnaireState = {\n\t\t...state,\n\t\tcurrentTab: nextTab,\n\t\toptionIndex: 0,\n\t\tinputMode: false,\n\t\tinlineInputOwner: null,\n\t\tnotesVisible: false,\n\t\tchatFocused: false,\n\t\tsubmitChoiceIndex: 0,\n\t\tmultiSelectChecked: syncMultiSelectFromAnswers(state.answers, ctx.questions, nextTab),\n\t\tnotesDraft: notesValue,\n\t};\n\tconst finalState = withFocusedOptionHasPreview(transitioned, ctx.questions);\n\treturn {\n\t\tstate: finalState,\n\t\teffects: [\n\t\t\t{ kind: \"set_notes_focused\", focused: false },\n\t\t\t{ kind: \"set_notes_value\", value: notesValue },\n\t\t],\n\t};\n}\n\nfunction doneFor(state: QuestionnaireState, ctx: ApplyContext, cancelled: boolean): ApplyResult {\n\tconst result: QuestionnaireResult = { answers: orderedAnswers(state, ctx.questions), cancelled };\n\treturn { state, effects: [{ kind: \"done\", result }] };\n}\n\n/**\n * Per-kind handler signature: action payload narrows to the matching union member\n * via `Extract`, so handlers consume fully-typed actions without `as` casts.\n */\ntype Handler<K extends QuestionnaireAction[\"kind\"]> = (\n\tstate: QuestionnaireState,\n\taction: Extract<QuestionnaireAction, { kind: K }>,\n\tctx: ApplyContext,\n) => ApplyResult;\n\nconst navHandler: Handler<\"nav\"> = (state, action, ctx) => {\n\tconst items = ctx.itemsByTab[state.currentTab] ?? [];\n\tconst item = items[action.nextIndex];\n\tconst owner = inlineOwnerForItem(item);\n\tconst next = withFocusedOptionHasPreview(\n\t\t{\n\t\t\t...state,\n\t\t\tchatFocused: false,\n\t\t\toptionIndex: action.nextIndex,\n\t\t\tinputMode: owner !== null,\n\t\t\tinlineInputOwner: owner,\n\t\t},\n\t\tctx.questions,\n\t);\n\treturn owner ? hydrateInlineInputResult(next, owner) : { state: next, effects: [] };\n};\n\nconst tabSwitchHandler: Handler<\"tab_switch\"> = (state, action, ctx) => switchTabResult(state, action.nextTab, ctx);\n\nconst confirmHandler: Handler<\"confirm\"> = (state, action, ctx) => {\n\tlet answer = action.answer;\n\tif (answer.kind === \"option\" && answer.answer) {\n\t\tconst q = ctx.questions[answer.questionIndex];\n\t\tconst matched = q?.options.find((o) => o.label === answer.answer);\n\t\tif (matched?.preview && matched.preview.length > 0) {\n\t\t\tanswer = { ...answer, preview: matched.preview };\n\t\t}\n\t}\n\tconst pendingNotes = state.notesByTab.get(answer.questionIndex);\n\tif (pendingNotes && pendingNotes.length > 0) {\n\t\tanswer = { ...answer, notes: pendingNotes };\n\t}\n\tconst answers = new Map(state.answers);\n\tanswers.set(answer.questionIndex, answer);\n\tconst next: QuestionnaireState = { ...state, answers };\n\tif (answer.kind === \"chat\") return doneFor(next, ctx, false);\n\tif (action.autoAdvanceTab !== undefined) return switchTabResult(next, action.autoAdvanceTab, ctx);\n\treturn doneFor(next, ctx, false);\n};\n\nconst toggleHandler: Handler<\"toggle\"> = (state, action, ctx) => {\n\tconst checked = new Set(state.multiSelectChecked);\n\tif (checked.has(action.index)) checked.delete(action.index);\n\telse checked.add(action.index);\n\tconst intermediate: QuestionnaireState = { ...state, multiSelectChecked: checked };\n\tconst answers = persistMultiSelectAnswer(intermediate, ctx);\n\treturn { state: { ...intermediate, answers }, effects: [] };\n};\n\nconst multiConfirmHandler: Handler<\"multi_confirm\"> = (state, action, ctx) => {\n\tconst q = ctx.questions[state.currentTab];\n\tif (!q) return { state, effects: [] };\n\tconst pendingNotes = state.notesByTab.get(state.currentTab);\n\tconst answers = new Map(state.answers);\n\tanswers.set(state.currentTab, {\n\t\tquestionIndex: state.currentTab,\n\t\tquestion: q.question,\n\t\tkind: \"multi\",\n\t\tanswer: null,\n\t\tselected: action.selected,\n\t\t...(pendingNotes && pendingNotes.length > 0 ? { notes: pendingNotes } : {}),\n\t});\n\tconst synced: QuestionnaireState = {\n\t\t...state,\n\t\tanswers,\n\t\tmultiSelectChecked: syncMultiSelectFromAnswers(answers, ctx.questions, state.currentTab),\n\t};\n\tif (action.autoAdvanceTab !== undefined) return switchTabResult(synced, action.autoAdvanceTab, ctx);\n\treturn doneFor(synced, ctx, false);\n};\n\nconst notesEnterHandler: Handler<\"notes_enter\"> = (state, _action, _ctx) => {\n\tconst value = state.answers.get(state.currentTab)?.notes ?? \"\";\n\treturn {\n\t\tstate: { ...state, notesVisible: true, notesDraft: value },\n\t\teffects: [\n\t\t\t{ kind: \"set_notes_value\", value },\n\t\t\t{ kind: \"set_notes_focused\", focused: true },\n\t\t],\n\t};\n};\n\nconst notesExitHandler: Handler<\"notes_exit\"> = (state, _action, _ctx) => {\n\tconst trimmed = state.notesDraft.trim();\n\tconst notes = new Map(state.notesByTab);\n\tconst answers = new Map(state.answers);\n\tif (trimmed.length === 0) {\n\t\tnotes.delete(state.currentTab);\n\t\tconst prev = answers.get(state.currentTab);\n\t\tif (prev?.notes) {\n\t\t\tconst stripped = { ...prev };\n\t\t\tdelete (stripped as { notes?: string }).notes;\n\t\t\tanswers.set(state.currentTab, stripped);\n\t\t}\n\t} else {\n\t\tnotes.set(state.currentTab, trimmed);\n\t\tconst prev = answers.get(state.currentTab);\n\t\tif (prev) answers.set(state.currentTab, { ...prev, notes: trimmed });\n\t}\n\treturn {\n\t\tstate: { ...state, notesByTab: notes, answers, notesVisible: false },\n\t\teffects: [{ kind: \"set_notes_focused\", focused: false }],\n\t};\n};\n\nconst focusOptionsHandler: Handler<\"focus_options\"> = (state, action, ctx) => {\n\tconst items = ctx.itemsByTab[state.currentTab] ?? [];\n\tconst focused = items[action.optionIndex];\n\tconst owner = inlineOwnerForItem(focused);\n\tconst next = withFocusedOptionHasPreview(\n\t\t{\n\t\t\t...state,\n\t\t\tchatFocused: false,\n\t\t\toptionIndex: action.optionIndex,\n\t\t\tinputMode: owner !== null,\n\t\t\tinlineInputOwner: owner,\n\t\t},\n\t\tctx.questions,\n\t);\n\treturn owner ? hydrateInlineInputResult(next, owner) : { state: next, effects: [] };\n};\n\nconst cancelHandler: Handler<\"cancel\"> = (s, _a, c) => doneFor(s, c, true);\nconst submitHandler: Handler<\"submit\"> = (s, _a, c) => doneFor(s, c, false);\nconst submitNavHandler: Handler<\"submit_nav\"> = (s, a, _c) => ({\n\tstate: { ...s, submitChoiceIndex: a.nextIndex },\n\teffects: [],\n});\nconst focusChatHandler: Handler<\"focus_chat\"> = (s, _a, _c) =>\n\thydrateInlineInputResult({ ...s, chatFocused: true, inputMode: true, inlineInputOwner: \"chat\" }, \"chat\");\nconst notesForwardHandler: Handler<\"notes_forward\"> = (s, a, _c) => ({\n\tstate: s,\n\teffects: [{ kind: \"forward_notes_keystroke\", data: a.data }],\n});\nconst ignoreHandler: Handler<\"ignore\"> = (s, _a, _c) => ({ state: s, effects: [] });\n\n/**\n * Compile-time-exhaustive dispatch table. `{ [K in Kind]: Handler<K> }` requires\n * an entry per union member — adding a new `QuestionnaireAction` variant fails to\n * compile here until a handler is registered, mirroring the `Record<RowKind, …>`\n * pattern used by `ROW_INTENT_META`.\n */\nconst HANDLERS: { [K in QuestionnaireAction[\"kind\"]]: Handler<K> } = {\n\tnav: navHandler,\n\ttab_switch: tabSwitchHandler,\n\tconfirm: confirmHandler,\n\ttoggle: toggleHandler,\n\tmulti_confirm: multiConfirmHandler,\n\tcancel: cancelHandler,\n\tnotes_enter: notesEnterHandler,\n\tnotes_exit: notesExitHandler,\n\tnotes_forward: notesForwardHandler,\n\tsubmit: submitHandler,\n\tsubmit_nav: submitNavHandler,\n\tfocus_chat: focusChatHandler,\n\tfocus_options: focusOptionsHandler,\n\tignore: ignoreHandler,\n};\n\n/**\n * Pure reducer: (state, action, ctx) → (state, Effect[]). Mirrors `rpiv-todo`'s `applyTaskMutation`.\n * Delegates to `HANDLERS` — per-kind handlers above are pure, named, and individually testable.\n * `ignore` is also handled outside the reducer by `handleIgnoreInline` in the runtime fast path.\n */\nexport function reduce(state: QuestionnaireState, action: QuestionnaireAction, ctx: ApplyContext): ApplyResult {\n\tconst handler = HANDLERS[action.kind] as Handler<typeof action.kind>;\n\treturn handler(state, action as never, ctx);\n}\n"]}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { QuestionAnswer, QuestionData } from "../tool/types.ts";
|
|
2
2
|
import type { WrappingSelectItem } from "../view/components/wrapping-select.ts";
|
|
3
|
+
import type { RowKind } from "./row-intent.ts";
|
|
4
|
+
export type InlineInputOwner = Extract<RowKind, "other" | "chat">;
|
|
3
5
|
/**
|
|
4
6
|
* Canonical state for the questionnaire dialog. Single source of truth — both the
|
|
5
7
|
* dispatcher (`routeKey`) and the view layer read this same shape.
|
|
@@ -10,6 +12,8 @@ export interface QuestionnaireState {
|
|
|
10
12
|
inputMode: boolean;
|
|
11
13
|
notesVisible: boolean;
|
|
12
14
|
chatFocused: boolean;
|
|
15
|
+
/** Active owner of the shared inline input, or null when no inline row is being edited. */
|
|
16
|
+
inlineInputOwner: InlineInputOwner | null;
|
|
13
17
|
answers: ReadonlyMap<number, QuestionAnswer>;
|
|
14
18
|
multiSelectChecked: ReadonlySet<number>;
|
|
15
19
|
/**
|
|
@@ -28,6 +32,10 @@ export interface QuestionnaireState {
|
|
|
28
32
|
customDraftByTab: ReadonlyMap<number, string>;
|
|
29
33
|
/** Caret offsets for in-flight custom drafts keyed by question tab. */
|
|
30
34
|
customCaretByTab: ReadonlyMap<number, number>;
|
|
35
|
+
/** In-flight "Chat about this" inline drafts keyed by question tab. */
|
|
36
|
+
chatDraftByTab: ReadonlyMap<number, string>;
|
|
37
|
+
/** Caret offsets for in-flight chat drafts keyed by question tab. */
|
|
38
|
+
chatCaretByTab: ReadonlyMap<number, number>;
|
|
31
39
|
}
|
|
32
40
|
/**
|
|
33
41
|
* Per-tick context the dispatcher needs alongside canonical state. Held separately
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC;AAElE;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,2FAA2F;IAC3F,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC1C,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC7C,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACxC;;;;OAIG;IACH,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,gIAAgI;IAChI,uBAAuB,EAAE,OAAO,CAAC;IACjC,0FAA0F;IAC1F,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uGAAuG;IACvG,UAAU,EAAE,MAAM,CAAC;IACnB,4GAA4G;IAC5G,gBAAgB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,uEAAuE;IACvE,gBAAgB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,uEAAuE;IACvE,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,qEAAqE;IACrE,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5C;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACpC,WAAW,EAAE;QAAE,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC9D,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,SAAS,YAAY,EAAE,CAAC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAC5C,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACrC","sourcesContent":["import type { QuestionAnswer, QuestionData } from \"../tool/types.ts\";\nimport type { WrappingSelectItem } from \"../view/components/wrapping-select.ts\";\nimport type { RowKind } from \"./row-intent.ts\";\n\nexport type InlineInputOwner = Extract<RowKind, \"other\" | \"chat\">;\n\n/**\n * Canonical state for the questionnaire dialog. Single source of truth — both the\n * dispatcher (`routeKey`) and the view layer read this same shape.\n */\nexport interface QuestionnaireState {\n\tcurrentTab: number;\n\toptionIndex: number;\n\tinputMode: boolean;\n\tnotesVisible: boolean;\n\tchatFocused: boolean;\n\t/** Active owner of the shared inline input, or null when no inline row is being edited. */\n\tinlineInputOwner: InlineInputOwner | null;\n\tanswers: ReadonlyMap<number, QuestionAnswer>;\n\tmultiSelectChecked: ReadonlySet<number>;\n\t/**\n\t * Pre-answer notes side-band, keyed by tab index. Decoupled from `answers` so adding\n\t * notes does NOT mark a question answered (the Submit-tab missing-check would falsely\n\t * pass otherwise). Merged into the answer at confirm time.\n\t */\n\tnotesByTab: ReadonlyMap<number, string>;\n\t/** True iff the focused option carries a non-empty `preview` string. Gates `notes_enter` and the \"n to add notes\" hint chip. */\n\tfocusedOptionHasPreview: boolean;\n\t/** Focused row in the Submit-tab picker (0 = Submit, 1 = Cancel). Reset on tab switch. */\n\tsubmitChoiceIndex: number;\n\t/** Canonical mirror of the in-flight notes editor; runtime mirrors after `forward_notes_keystroke`. */\n\tnotesDraft: string;\n\t/** In-flight custom \"Type something.\" drafts keyed by question tab. Preserved when focus leaves the row. */\n\tcustomDraftByTab: ReadonlyMap<number, string>;\n\t/** Caret offsets for in-flight custom drafts keyed by question tab. */\n\tcustomCaretByTab: ReadonlyMap<number, number>;\n\t/** In-flight \"Chat about this\" inline drafts keyed by question tab. */\n\tchatDraftByTab: ReadonlyMap<number, string>;\n\t/** Caret offsets for in-flight chat drafts keyed by question tab. */\n\tchatCaretByTab: ReadonlyMap<number, number>;\n}\n\n/**\n * Per-tick context the dispatcher needs alongside canonical state. Held separately\n * because `keybindings` / `inputBuffer` must never reach view setProps consumers.\n */\nexport interface QuestionnaireRuntime {\n\tkeybindings: { matches(data: string, name: string): boolean };\n\tinputBuffer: string;\n\tquestions: readonly QuestionData[];\n\tisMulti: boolean;\n\tcurrentItem: WrappingSelectItem | undefined;\n\titems: readonly WrappingSelectItem[];\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/state.ts"],"names":[],"mappings":"","sourcesContent":["import type { QuestionAnswer, QuestionData } from \"../tool/types.ts\";\nimport type { WrappingSelectItem } from \"../view/components/wrapping-select.ts\";\n\n/**\n * Canonical state for the questionnaire dialog. Single source of truth — both the\n * dispatcher (`routeKey`) and the view layer read this same shape.\n */\nexport interface QuestionnaireState {\n\tcurrentTab: number;\n\toptionIndex: number;\n\tinputMode: boolean;\n\tnotesVisible: boolean;\n\tchatFocused: boolean;\n\tanswers: ReadonlyMap<number, QuestionAnswer>;\n\tmultiSelectChecked: ReadonlySet<number>;\n\t/**\n\t * Pre-answer notes side-band, keyed by tab index. Decoupled from `answers` so adding\n\t * notes does NOT mark a question answered (the Submit-tab missing-check would falsely\n\t * pass otherwise). Merged into the answer at confirm time.\n\t */\n\tnotesByTab: ReadonlyMap<number, string>;\n\t/** True iff the focused option carries a non-empty `preview` string. Gates `notes_enter` and the \"n to add notes\" hint chip. */\n\tfocusedOptionHasPreview: boolean;\n\t/** Focused row in the Submit-tab picker (0 = Submit, 1 = Cancel). Reset on tab switch. */\n\tsubmitChoiceIndex: number;\n\t/** Canonical mirror of the in-flight notes editor; runtime mirrors after `forward_notes_keystroke`. */\n\tnotesDraft: string;\n\t/** In-flight custom \"Type something.\" drafts keyed by question tab. Preserved when focus leaves the row. */\n\tcustomDraftByTab: ReadonlyMap<number, string>;\n\t/** Caret offsets for in-flight custom drafts keyed by question tab. */\n\tcustomCaretByTab: ReadonlyMap<number, number>;\n}\n\n/**\n * Per-tick context the dispatcher needs alongside canonical state. Held separately\n * because `keybindings` / `inputBuffer` must never reach view setProps consumers.\n */\nexport interface QuestionnaireRuntime {\n\tkeybindings: { matches(data: string, name: string): boolean };\n\tinputBuffer: string;\n\tquestions: readonly QuestionData[];\n\tisMulti: boolean;\n\tcurrentItem: WrappingSelectItem | undefined;\n\titems: readonly WrappingSelectItem[];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/state.ts"],"names":[],"mappings":"","sourcesContent":["import type { QuestionAnswer, QuestionData } from \"../tool/types.ts\";\nimport type { WrappingSelectItem } from \"../view/components/wrapping-select.ts\";\nimport type { RowKind } from \"./row-intent.ts\";\n\nexport type InlineInputOwner = Extract<RowKind, \"other\" | \"chat\">;\n\n/**\n * Canonical state for the questionnaire dialog. Single source of truth — both the\n * dispatcher (`routeKey`) and the view layer read this same shape.\n */\nexport interface QuestionnaireState {\n\tcurrentTab: number;\n\toptionIndex: number;\n\tinputMode: boolean;\n\tnotesVisible: boolean;\n\tchatFocused: boolean;\n\t/** Active owner of the shared inline input, or null when no inline row is being edited. */\n\tinlineInputOwner: InlineInputOwner | null;\n\tanswers: ReadonlyMap<number, QuestionAnswer>;\n\tmultiSelectChecked: ReadonlySet<number>;\n\t/**\n\t * Pre-answer notes side-band, keyed by tab index. Decoupled from `answers` so adding\n\t * notes does NOT mark a question answered (the Submit-tab missing-check would falsely\n\t * pass otherwise). Merged into the answer at confirm time.\n\t */\n\tnotesByTab: ReadonlyMap<number, string>;\n\t/** True iff the focused option carries a non-empty `preview` string. Gates `notes_enter` and the \"n to add notes\" hint chip. */\n\tfocusedOptionHasPreview: boolean;\n\t/** Focused row in the Submit-tab picker (0 = Submit, 1 = Cancel). Reset on tab switch. */\n\tsubmitChoiceIndex: number;\n\t/** Canonical mirror of the in-flight notes editor; runtime mirrors after `forward_notes_keystroke`. */\n\tnotesDraft: string;\n\t/** In-flight custom \"Type something.\" drafts keyed by question tab. Preserved when focus leaves the row. */\n\tcustomDraftByTab: ReadonlyMap<number, string>;\n\t/** Caret offsets for in-flight custom drafts keyed by question tab. */\n\tcustomCaretByTab: ReadonlyMap<number, number>;\n\t/** In-flight \"Chat about this\" inline drafts keyed by question tab. */\n\tchatDraftByTab: ReadonlyMap<number, string>;\n\t/** Caret offsets for in-flight chat drafts keyed by question tab. */\n\tchatCaretByTab: ReadonlyMap<number, number>;\n}\n\n/**\n * Per-tick context the dispatcher needs alongside canonical state. Held separately\n * because `keybindings` / `inputBuffer` must never reach view setProps consumers.\n */\nexport interface QuestionnaireRuntime {\n\tkeybindings: { matches(data: string, name: string): boolean };\n\tinputBuffer: string;\n\tquestions: readonly QuestionData[];\n\tisMulti: boolean;\n\tcurrentItem: WrappingSelectItem | undefined;\n\titems: readonly WrappingSelectItem[];\n}\n"]}
|
|
@@ -18,6 +18,12 @@ export declare const CHAT_SUMMARY_MESSAGE = "User wants to chat about this";
|
|
|
18
18
|
*/
|
|
19
19
|
export declare const NO_INPUT_PLACEHOLDER = "(no input)";
|
|
20
20
|
export type FormatAnswerVariant = "summary" | "envelope";
|
|
21
|
+
/**
|
|
22
|
+
* Return the user-provided chat message, or undefined for the legacy signal-only
|
|
23
|
+
* chat answer. Whitespace-only text and the reserved sentinel label both keep
|
|
24
|
+
* the legacy stop/wait semantics.
|
|
25
|
+
*/
|
|
26
|
+
export declare function chatAnswerIntent(a: QuestionAnswer): string | undefined;
|
|
21
27
|
/**
|
|
22
28
|
* Format a `QuestionAnswer` to its scalar string form. Variant controls only the
|
|
23
29
|
* `kind: "chat"` branch — the envelope's stop/wait directive is needed by the LLM,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-answer.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/tool/format-answer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"format-answer.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/tool/format-answer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,oGAC4D,CAAC;AAEnG;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,kCAAkC,CAAC;AAEpE;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,eAAe,CAAC;AAEjD,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,UAAU,CAAC;AAEzD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAKtE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAc1F","sourcesContent":["import { ROW_INTENT_META } from \"../state/row-intent.ts\";\nimport type { QuestionAnswer } from \"./types.ts\";\n\n/**\n * Continuation message used in the LLM-facing envelope. Two-sentence form —\n * the model needs the \"Stop…wait…\" directive to know what to do next after the\n * user picks chat instead of answering.\n */\nexport const CHAT_CONTINUATION_MESSAGE =\n\t\"User wants to chat about this. Stop the current task flow and wait for the user's next message.\";\n\n/**\n * One-sentence summary form shown in the on-screen Submit-tab review pane. The dialog\n * already shows the question; the imperative continuation directive belongs in the\n * envelope, not in the user-facing summary box.\n */\nexport const CHAT_SUMMARY_MESSAGE = \"User wants to chat about this\";\n\n/**\n * Placeholder for empty / null answer text. Used uniformly across both variants — the\n * earlier `(no answer)` fallback in the dialog summary was accidental drift; tests pin\n * `(no input)` only.\n */\nexport const NO_INPUT_PLACEHOLDER = \"(no input)\";\n\nexport type FormatAnswerVariant = \"summary\" | \"envelope\";\n\n/**\n * Return the user-provided chat message, or undefined for the legacy signal-only\n * chat answer. Whitespace-only text and the reserved sentinel label both keep\n * the legacy stop/wait semantics.\n */\nexport function chatAnswerIntent(a: QuestionAnswer): string | undefined {\n\tif (a.kind !== \"chat\" || typeof a.answer !== \"string\") return undefined;\n\tconst trimmed = a.answer.trim();\n\tif (trimmed.length === 0 || trimmed === ROW_INTENT_META.chat.label) return undefined;\n\treturn a.answer;\n}\n\n/**\n * Format a `QuestionAnswer` to its scalar string form. Variant controls only the\n * `kind: \"chat\"` branch — the envelope's stop/wait directive is needed by the LLM,\n * the dialog summary's one-sentence reminder is not. All other branches return identical\n * strings; the `kind: \"custom\"` empty-string handling and the option fallback both unify\n * on `NO_INPUT_PLACEHOLDER`. Switch is exhaustive — non-`void` return enforces every\n * variant is handled.\n */\nexport function formatAnswerScalar(a: QuestionAnswer, variant: FormatAnswerVariant): string {\n\tswitch (a.kind) {\n\t\tcase \"chat\": {\n\t\t\tconst typedChat = chatAnswerIntent(a);\n\t\t\tif (typedChat !== undefined) return typedChat;\n\t\t\treturn variant === \"envelope\" ? CHAT_CONTINUATION_MESSAGE : CHAT_SUMMARY_MESSAGE;\n\t\t}\n\t\tcase \"multi\":\n\t\t\treturn a.selected && a.selected.length > 0 ? a.selected.join(\", \") : NO_INPUT_PLACEHOLDER;\n\t\tcase \"custom\":\n\t\t\treturn a.answer && a.answer.length > 0 ? a.answer : NO_INPUT_PLACEHOLDER;\n\t\tcase \"option\":\n\t\t\treturn a.answer ?? NO_INPUT_PLACEHOLDER;\n\t}\n}\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ROW_INTENT_META } from "../state/row-intent.js";
|
|
1
2
|
/**
|
|
2
3
|
* Continuation message used in the LLM-facing envelope. Two-sentence form —
|
|
3
4
|
* the model needs the "Stop…wait…" directive to know what to do next after the
|
|
@@ -16,6 +17,19 @@ export const CHAT_SUMMARY_MESSAGE = "User wants to chat about this";
|
|
|
16
17
|
* `(no input)` only.
|
|
17
18
|
*/
|
|
18
19
|
export const NO_INPUT_PLACEHOLDER = "(no input)";
|
|
20
|
+
/**
|
|
21
|
+
* Return the user-provided chat message, or undefined for the legacy signal-only
|
|
22
|
+
* chat answer. Whitespace-only text and the reserved sentinel label both keep
|
|
23
|
+
* the legacy stop/wait semantics.
|
|
24
|
+
*/
|
|
25
|
+
export function chatAnswerIntent(a) {
|
|
26
|
+
if (a.kind !== "chat" || typeof a.answer !== "string")
|
|
27
|
+
return undefined;
|
|
28
|
+
const trimmed = a.answer.trim();
|
|
29
|
+
if (trimmed.length === 0 || trimmed === ROW_INTENT_META.chat.label)
|
|
30
|
+
return undefined;
|
|
31
|
+
return a.answer;
|
|
32
|
+
}
|
|
19
33
|
/**
|
|
20
34
|
* Format a `QuestionAnswer` to its scalar string form. Variant controls only the
|
|
21
35
|
* `kind: "chat"` branch — the envelope's stop/wait directive is needed by the LLM,
|
|
@@ -26,8 +40,12 @@ export const NO_INPUT_PLACEHOLDER = "(no input)";
|
|
|
26
40
|
*/
|
|
27
41
|
export function formatAnswerScalar(a, variant) {
|
|
28
42
|
switch (a.kind) {
|
|
29
|
-
case "chat":
|
|
43
|
+
case "chat": {
|
|
44
|
+
const typedChat = chatAnswerIntent(a);
|
|
45
|
+
if (typedChat !== undefined)
|
|
46
|
+
return typedChat;
|
|
30
47
|
return variant === "envelope" ? CHAT_CONTINUATION_MESSAGE : CHAT_SUMMARY_MESSAGE;
|
|
48
|
+
}
|
|
31
49
|
case "multi":
|
|
32
50
|
return a.selected && a.selected.length > 0 ? a.selected.join(", ") : NO_INPUT_PLACEHOLDER;
|
|
33
51
|
case "custom":
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-answer.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/tool/format-answer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"format-answer.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/tool/format-answer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GACrC,iGAAiG,CAAC;AAEnG;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,+BAA+B,CAAC;AAEpE;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,YAAY,CAAC;AAIjD;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAiB;IACjD,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACxE,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IAChC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,KAAK,eAAe,CAAC,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IACrF,OAAO,CAAC,CAAC,MAAM,CAAC;AACjB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,CAAiB,EAAE,OAA4B;IACjF,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QAChB,KAAK,MAAM,EAAE,CAAC;YACb,MAAM,SAAS,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;YACtC,IAAI,SAAS,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAC;YAC9C,OAAO,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,oBAAoB,CAAC;QAClF,CAAC;QACD,KAAK,OAAO;YACX,OAAO,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC;QAC3F,KAAK,QAAQ;YACZ,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,oBAAoB,CAAC;QAC1E,KAAK,QAAQ;YACZ,OAAO,CAAC,CAAC,MAAM,IAAI,oBAAoB,CAAC;IAC1C,CAAC;AACF,CAAC","sourcesContent":["import { ROW_INTENT_META } from \"../state/row-intent.ts\";\nimport type { QuestionAnswer } from \"./types.ts\";\n\n/**\n * Continuation message used in the LLM-facing envelope. Two-sentence form —\n * the model needs the \"Stop…wait…\" directive to know what to do next after the\n * user picks chat instead of answering.\n */\nexport const CHAT_CONTINUATION_MESSAGE =\n\t\"User wants to chat about this. Stop the current task flow and wait for the user's next message.\";\n\n/**\n * One-sentence summary form shown in the on-screen Submit-tab review pane. The dialog\n * already shows the question; the imperative continuation directive belongs in the\n * envelope, not in the user-facing summary box.\n */\nexport const CHAT_SUMMARY_MESSAGE = \"User wants to chat about this\";\n\n/**\n * Placeholder for empty / null answer text. Used uniformly across both variants — the\n * earlier `(no answer)` fallback in the dialog summary was accidental drift; tests pin\n * `(no input)` only.\n */\nexport const NO_INPUT_PLACEHOLDER = \"(no input)\";\n\nexport type FormatAnswerVariant = \"summary\" | \"envelope\";\n\n/**\n * Return the user-provided chat message, or undefined for the legacy signal-only\n * chat answer. Whitespace-only text and the reserved sentinel label both keep\n * the legacy stop/wait semantics.\n */\nexport function chatAnswerIntent(a: QuestionAnswer): string | undefined {\n\tif (a.kind !== \"chat\" || typeof a.answer !== \"string\") return undefined;\n\tconst trimmed = a.answer.trim();\n\tif (trimmed.length === 0 || trimmed === ROW_INTENT_META.chat.label) return undefined;\n\treturn a.answer;\n}\n\n/**\n * Format a `QuestionAnswer` to its scalar string form. Variant controls only the\n * `kind: \"chat\"` branch — the envelope's stop/wait directive is needed by the LLM,\n * the dialog summary's one-sentence reminder is not. All other branches return identical\n * strings; the `kind: \"custom\"` empty-string handling and the option fallback both unify\n * on `NO_INPUT_PLACEHOLDER`. Switch is exhaustive — non-`void` return enforces every\n * variant is handled.\n */\nexport function formatAnswerScalar(a: QuestionAnswer, variant: FormatAnswerVariant): string {\n\tswitch (a.kind) {\n\t\tcase \"chat\": {\n\t\t\tconst typedChat = chatAnswerIntent(a);\n\t\t\tif (typedChat !== undefined) return typedChat;\n\t\t\treturn variant === \"envelope\" ? CHAT_CONTINUATION_MESSAGE : CHAT_SUMMARY_MESSAGE;\n\t\t}\n\t\tcase \"multi\":\n\t\t\treturn a.selected && a.selected.length > 0 ? a.selected.join(\", \") : NO_INPUT_PLACEHOLDER;\n\t\tcase \"custom\":\n\t\t\treturn a.answer && a.answer.length > 0 ? a.answer : NO_INPUT_PLACEHOLDER;\n\t\tcase \"option\":\n\t\t\treturn a.answer ?? NO_INPUT_PLACEHOLDER;\n\t}\n}\n"]}
|
|
@@ -13,8 +13,9 @@ export declare function hasChatAnswer(result: QuestionnaireResult): boolean;
|
|
|
13
13
|
* `DECLINE_MESSAGE` so the model sees a single canonical "didn't answer" signal
|
|
14
14
|
* regardless of why.
|
|
15
15
|
*
|
|
16
|
-
* Chat rule:
|
|
17
|
-
* `terminate
|
|
16
|
+
* Chat rule: signal-only chat keeps the legacy `terminate: true` stop/wait
|
|
17
|
+
* wording. Typed chat omits `terminate` and the generic continuation suffix so
|
|
18
|
+
* the model can respond to the inline user message in this same turn.
|
|
18
19
|
*/
|
|
19
20
|
export declare function buildQuestionnaireResponse(result: QuestionnaireResult | null | undefined, params: QuestionParams): {
|
|
20
21
|
content: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response-envelope.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/tool/response-envelope.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEtF,eAAO,MAAM,eAAe,sCAAsC,CAAC;AACnE,eAAO,MAAM,eAAe,sCAAsC,CAAC;AACnE,eAAO,MAAM,eAAe,0DAA0D,CAAC;
|
|
1
|
+
{"version":3,"file":"response-envelope.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/tool/response-envelope.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEtF,eAAO,MAAM,eAAe,sCAAsC,CAAC;AACnE,eAAO,MAAM,eAAe,sCAAsC,CAAC;AACnE,eAAO,MAAM,eAAe,0DAA0D,CAAC;AAMvF;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAElE;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,GAAG,SAAS,EAAE,MAAM,EAAE,cAAc;;cA8C7F,MAAM;;;;;EAfzB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,cAAc,GAAG,MAAM,CAK5D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE;;cAEzF,MAAM;;;;;EAIzB","sourcesContent":["import { chatAnswerIntent, formatAnswerScalar } from \"./format-answer.ts\";\nimport type { QuestionAnswer, QuestionnaireResult, QuestionParams } from \"./types.ts\";\n\nexport const DECLINE_MESSAGE = \"User declined to answer questions\";\nexport const ENVELOPE_PREFIX = \"User has answered your questions:\";\nexport const ENVELOPE_SUFFIX = \"You can now continue with the user's answers in mind.\";\nconst CHAT_TERMINATION_DIRECTIVE =\n\t\"User wants to chat about this before choosing. Stop the current task flow and wait for the user's next message.\";\nconst TYPED_CHAT_DIRECTIVE =\n\t\"User wants to chat about this before choosing and provided inline text. Stop the structured-choice flow and respond to the user's message.\";\n\n/**\n * True when any answer in the result carries `kind: \"chat\"`.\n * Used by `buildQuestionnaireResponse` to switch to the terminate path.\n */\nexport function hasChatAnswer(result: QuestionnaireResult): boolean {\n\treturn result.answers.some((a) => a.kind === \"chat\");\n}\n\n/**\n * Map a `QuestionnaireResult` (or null/cancelled) to the LLM-facing tool envelope.\n * Pure of `(result, params)`; cancelled and non-chat \"no segments\" both fall to\n * `DECLINE_MESSAGE` so the model sees a single canonical \"didn't answer\" signal\n * regardless of why.\n *\n * Chat rule: signal-only chat keeps the legacy `terminate: true` stop/wait\n * wording. Typed chat omits `terminate` and the generic continuation suffix so\n * the model can respond to the inline user message in this same turn.\n */\nexport function buildQuestionnaireResponse(result: QuestionnaireResult | null | undefined, params: QuestionParams) {\n\tif (!result || result.cancelled) {\n\t\treturn buildToolResult(DECLINE_MESSAGE, {\n\t\t\tanswers: result?.answers ?? [],\n\t\t\tcancelled: true,\n\t\t});\n\t}\n\tconst containsChatAnswer = hasChatAnswer(result);\n\tconst segments: string[] = [];\n\tfor (let i = 0; i < params.questions.length; i++) {\n\t\tconst a = result.answers.find((x) => x.questionIndex === i);\n\t\tif (a) segments.push(buildAnswerSegment(a));\n\t}\n\tif (containsChatAnswer) {\n\t\tconst answerSegments = segments.length > 0 ? ` ${segments.join(\" \")}` : \"\";\n\t\t// Mixed-dialog precedence: signal-only chat WINS. In a multi-question dialog where one\n\t\t// question carried typed chat and another is signal-only, the whole envelope takes the\n\t\t// `terminate: true` stop/wait path — a bare \"chat about this\" is an explicit request to\n\t\t// pause the structured flow, so it dominates. The typed message is not lost: it still\n\t\t// rides along in `answerSegments` for context. This precedence is a deliberate product\n\t\t// decision; mixing typed + signal-only chat across questions is a rare edge.\n\t\tconst hasSignalOnlyChat = result.answers.some((a) => a.kind === \"chat\" && chatAnswerIntent(a) === undefined);\n\t\tif (!hasSignalOnlyChat) {\n\t\t\treturn buildToolResult(`${TYPED_CHAT_DIRECTIVE}${answerSegments}`, result);\n\t\t}\n\t\treturn buildToolResult(`${CHAT_TERMINATION_DIRECTIVE}${answerSegments}`, result, { terminate: true });\n\t}\n\tif (segments.length === 0) {\n\t\treturn buildToolResult(DECLINE_MESSAGE, { answers: result.answers, cancelled: true });\n\t}\n\treturn buildToolResult(`${ENVELOPE_PREFIX} ${segments.join(\" \")} ${ENVELOPE_SUFFIX}`, result);\n}\n\n/**\n * Format a single answer segment for the envelope. Pure of `a`. The `\"Q\"=\"A\"` shape and\n * the optional `selected preview:` / `user notes:` suffixes are pinned by envelope tests.\n */\nexport function buildAnswerSegment(a: QuestionAnswer): string {\n\tconst parts: string[] = [`\"${a.question}\"=\"${formatAnswerScalar(a, \"envelope\")}\"`];\n\tif (a.preview && a.preview.length > 0) parts.push(`selected preview: ${a.preview}`);\n\tif (a.notes && a.notes.length > 0) parts.push(`user notes: ${a.notes}`);\n\treturn `${parts.join(\". \")}.`;\n}\n\nexport function buildToolResult(text: string, details: QuestionnaireResult, options?: { terminate?: boolean }) {\n\treturn {\n\t\tcontent: [{ type: \"text\" as const, text }],\n\t\tdetails,\n\t\t...(options?.terminate === true ? { terminate: true } : {}),\n\t};\n}\n"]}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { formatAnswerScalar } from "./format-answer.js";
|
|
1
|
+
import { chatAnswerIntent, formatAnswerScalar } from "./format-answer.js";
|
|
2
2
|
export const DECLINE_MESSAGE = "User declined to answer questions";
|
|
3
3
|
export const ENVELOPE_PREFIX = "User has answered your questions:";
|
|
4
4
|
export const ENVELOPE_SUFFIX = "You can now continue with the user's answers in mind.";
|
|
5
5
|
const CHAT_TERMINATION_DIRECTIVE = "User wants to chat about this before choosing. Stop the current task flow and wait for the user's next message.";
|
|
6
|
+
const TYPED_CHAT_DIRECTIVE = "User wants to chat about this before choosing and provided inline text. Stop the structured-choice flow and respond to the user's message.";
|
|
6
7
|
/**
|
|
7
8
|
* True when any answer in the result carries `kind: "chat"`.
|
|
8
9
|
* Used by `buildQuestionnaireResponse` to switch to the terminate path.
|
|
@@ -16,8 +17,9 @@ export function hasChatAnswer(result) {
|
|
|
16
17
|
* `DECLINE_MESSAGE` so the model sees a single canonical "didn't answer" signal
|
|
17
18
|
* regardless of why.
|
|
18
19
|
*
|
|
19
|
-
* Chat rule:
|
|
20
|
-
* `terminate
|
|
20
|
+
* Chat rule: signal-only chat keeps the legacy `terminate: true` stop/wait
|
|
21
|
+
* wording. Typed chat omits `terminate` and the generic continuation suffix so
|
|
22
|
+
* the model can respond to the inline user message in this same turn.
|
|
21
23
|
*/
|
|
22
24
|
export function buildQuestionnaireResponse(result, params) {
|
|
23
25
|
if (!result || result.cancelled) {
|
|
@@ -35,6 +37,16 @@ export function buildQuestionnaireResponse(result, params) {
|
|
|
35
37
|
}
|
|
36
38
|
if (containsChatAnswer) {
|
|
37
39
|
const answerSegments = segments.length > 0 ? ` ${segments.join(" ")}` : "";
|
|
40
|
+
// Mixed-dialog precedence: signal-only chat WINS. In a multi-question dialog where one
|
|
41
|
+
// question carried typed chat and another is signal-only, the whole envelope takes the
|
|
42
|
+
// `terminate: true` stop/wait path — a bare "chat about this" is an explicit request to
|
|
43
|
+
// pause the structured flow, so it dominates. The typed message is not lost: it still
|
|
44
|
+
// rides along in `answerSegments` for context. This precedence is a deliberate product
|
|
45
|
+
// decision; mixing typed + signal-only chat across questions is a rare edge.
|
|
46
|
+
const hasSignalOnlyChat = result.answers.some((a) => a.kind === "chat" && chatAnswerIntent(a) === undefined);
|
|
47
|
+
if (!hasSignalOnlyChat) {
|
|
48
|
+
return buildToolResult(`${TYPED_CHAT_DIRECTIVE}${answerSegments}`, result);
|
|
49
|
+
}
|
|
38
50
|
return buildToolResult(`${CHAT_TERMINATION_DIRECTIVE}${answerSegments}`, result, { terminate: true });
|
|
39
51
|
}
|
|
40
52
|
if (segments.length === 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response-envelope.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/tool/response-envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"response-envelope.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/tool/response-envelope.ts"],"names":[],"mappings":"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","sourcesContent":["import { chatAnswerIntent, formatAnswerScalar } from \"./format-answer.ts\";\nimport type { QuestionAnswer, QuestionnaireResult, QuestionParams } from \"./types.ts\";\n\nexport const DECLINE_MESSAGE = \"User declined to answer questions\";\nexport const ENVELOPE_PREFIX = \"User has answered your questions:\";\nexport const ENVELOPE_SUFFIX = \"You can now continue with the user's answers in mind.\";\nconst CHAT_TERMINATION_DIRECTIVE =\n\t\"User wants to chat about this before choosing. Stop the current task flow and wait for the user's next message.\";\nconst TYPED_CHAT_DIRECTIVE =\n\t\"User wants to chat about this before choosing and provided inline text. Stop the structured-choice flow and respond to the user's message.\";\n\n/**\n * True when any answer in the result carries `kind: \"chat\"`.\n * Used by `buildQuestionnaireResponse` to switch to the terminate path.\n */\nexport function hasChatAnswer(result: QuestionnaireResult): boolean {\n\treturn result.answers.some((a) => a.kind === \"chat\");\n}\n\n/**\n * Map a `QuestionnaireResult` (or null/cancelled) to the LLM-facing tool envelope.\n * Pure of `(result, params)`; cancelled and non-chat \"no segments\" both fall to\n * `DECLINE_MESSAGE` so the model sees a single canonical \"didn't answer\" signal\n * regardless of why.\n *\n * Chat rule: signal-only chat keeps the legacy `terminate: true` stop/wait\n * wording. Typed chat omits `terminate` and the generic continuation suffix so\n * the model can respond to the inline user message in this same turn.\n */\nexport function buildQuestionnaireResponse(result: QuestionnaireResult | null | undefined, params: QuestionParams) {\n\tif (!result || result.cancelled) {\n\t\treturn buildToolResult(DECLINE_MESSAGE, {\n\t\t\tanswers: result?.answers ?? [],\n\t\t\tcancelled: true,\n\t\t});\n\t}\n\tconst containsChatAnswer = hasChatAnswer(result);\n\tconst segments: string[] = [];\n\tfor (let i = 0; i < params.questions.length; i++) {\n\t\tconst a = result.answers.find((x) => x.questionIndex === i);\n\t\tif (a) segments.push(buildAnswerSegment(a));\n\t}\n\tif (containsChatAnswer) {\n\t\tconst answerSegments = segments.length > 0 ? ` ${segments.join(\" \")}` : \"\";\n\t\t// Mixed-dialog precedence: signal-only chat WINS. In a multi-question dialog where one\n\t\t// question carried typed chat and another is signal-only, the whole envelope takes the\n\t\t// `terminate: true` stop/wait path — a bare \"chat about this\" is an explicit request to\n\t\t// pause the structured flow, so it dominates. The typed message is not lost: it still\n\t\t// rides along in `answerSegments` for context. This precedence is a deliberate product\n\t\t// decision; mixing typed + signal-only chat across questions is a rare edge.\n\t\tconst hasSignalOnlyChat = result.answers.some((a) => a.kind === \"chat\" && chatAnswerIntent(a) === undefined);\n\t\tif (!hasSignalOnlyChat) {\n\t\t\treturn buildToolResult(`${TYPED_CHAT_DIRECTIVE}${answerSegments}`, result);\n\t\t}\n\t\treturn buildToolResult(`${CHAT_TERMINATION_DIRECTIVE}${answerSegments}`, result, { terminate: true });\n\t}\n\tif (segments.length === 0) {\n\t\treturn buildToolResult(DECLINE_MESSAGE, { answers: result.answers, cancelled: true });\n\t}\n\treturn buildToolResult(`${ENVELOPE_PREFIX} ${segments.join(\" \")} ${ENVELOPE_SUFFIX}`, result);\n}\n\n/**\n * Format a single answer segment for the envelope. Pure of `a`. The `\"Q\"=\"A\"` shape and\n * the optional `selected preview:` / `user notes:` suffixes are pinned by envelope tests.\n */\nexport function buildAnswerSegment(a: QuestionAnswer): string {\n\tconst parts: string[] = [`\"${a.question}\"=\"${formatAnswerScalar(a, \"envelope\")}\"`];\n\tif (a.preview && a.preview.length > 0) parts.push(`selected preview: ${a.preview}`);\n\tif (a.notes && a.notes.length > 0) parts.push(`user notes: ${a.notes}`);\n\treturn `${parts.join(\". \")}.`;\n}\n\nexport function buildToolResult(text: string, details: QuestionnaireResult, options?: { terminate?: boolean }) {\n\treturn {\n\t\tcontent: [{ type: \"text\" as const, text }],\n\t\tdetails,\n\t\t...(options?.terminate === true ? { terminate: true } : {}),\n\t};\n}\n"]}
|
|
@@ -84,7 +84,8 @@ export type QuestionParams = Static<typeof QuestionParamsSchema>;
|
|
|
84
84
|
* Variant semantics:
|
|
85
85
|
* - `option`: user picked one of the author-defined options. `answer` is the option's label.
|
|
86
86
|
* - `custom`: user typed free-text via the "Type something." row. `answer` is the typed text or null.
|
|
87
|
-
* - `chat`: user picked the chat sentinel. `answer` is
|
|
87
|
+
* - `chat`: user picked the chat sentinel. `answer` is either typed inline chat text or the legacy
|
|
88
|
+
* literal "Chat about this" when submitted empty / whitespace-only.
|
|
88
89
|
* - `multi`: user committed multi-select choices. `selected` carries chosen labels; `answer` is null.
|
|
89
90
|
*/
|
|
90
91
|
export interface QuestionAnswer {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/tool/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,iBAAiB,KAAK,CAAC;AACpC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe;;;;CAAiB,CAAC;AAE9C,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,eAAe,CAAC;AACxD,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,YAAY,CAAC,CAAC;AAEnE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,eAAe,YAC3B,OAAO,yBAIE,CAAC;AACX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,eAAO,MAAM,YAAY;;;;EAevB,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;EAsBzB,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;GAI1B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;EAE/B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,YAAY,CAAC,CAAC;AACrD,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC;AACzD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAEjE
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/tool/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,iBAAiB,KAAK,CAAC;AACpC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe;;;;CAAiB,CAAC;AAE9C,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,eAAe,CAAC;AACxD,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,YAAY,CAAC,CAAC;AAEnE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,eAAe,YAC3B,OAAO,yBAIE,CAAC;AACX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,eAAO,MAAM,YAAY;;;;EAevB,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;EAsBzB,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;GAI1B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;EAE/B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,YAAY,CAAC,CAAC;AACrD,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC;AACzD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAEjE;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAc;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAC7C,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,kBAAkB,GAC3B,OAAO,GACP,cAAc,GACd,eAAe,GACf,oBAAoB,GACpB,oBAAoB,GACpB,wBAAwB,GACxB,gBAAgB,CAAC;AAEpB,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC3B;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,mBAAmB,CAIlF","sourcesContent":["import { type Static, Type } from \"typebox\";\nimport { LABELS_BY_KIND, ROW_INTENT_META } from \"../state/row-intent.ts\";\n\nexport const MAX_QUESTIONS = 4;\nexport const MIN_OPTIONS = 2;\nexport const MAX_OPTIONS = 4;\nexport const MAX_HEADER_LENGTH = 16;\nexport const MAX_LABEL_LENGTH = 60;\n\n/**\n * User-facing labels for the three runtime sentinel rows, keyed by their\n * `WrappingSelectItem.kind` discriminator. Sourced from\n * `ROW_INTENT_META` via `LABELS_BY_KIND` (`row-intent.ts`) — single source of\n * truth. Adding a new sentinel requires extending the `WrappingSelectItem`\n * union AND adding an entry to `ROW_INTENT_META`; this map then auto-extends.\n */\nexport const SENTINEL_LABELS = LABELS_BY_KIND;\n\nexport type SentinelKind = keyof typeof SENTINEL_LABELS;\nexport type SentinelLabel = (typeof SENTINEL_LABELS)[SentinelKind];\n\n/**\n * Labels reserved for Pi-internal sentinels — authoring an option with any\n * of these labels triggers the `reserved_label` runtime guard. Three of the\n * four come from `ROW_INTENT_META` (the runtime kinds); `\"Other\"` is\n * reserved for CC parity only (the model is conditioned to reach for\n * \"Other\" in CC; we reject it so the runtime sentinel is the single source\n * of truth) and has no runtime kind.\n *\n * Reserved unconditionally — multiSelect questions also reject these labels\n * even though the runtime sentinel is suppressed there.\n *\n * Order is pinned by `types.test.ts:292` — keep the explicit\n * `[\"Other\", other, chat, next]` literal so consumers using\n * `RESERVED_LABELS[i]` indexing or `Set` membership see no behavior change.\n */\nexport const RESERVED_LABELS = [\n\t\"Other\",\n\tROW_INTENT_META.other.label,\n\tROW_INTENT_META.chat.label,\n\tROW_INTENT_META.next.label,\n] as const;\nexport type ReservedLabel = (typeof RESERVED_LABELS)[number];\n\nexport const OptionSchema = Type.Object({\n\tlabel: Type.String({\n\t\tmaxLength: MAX_LABEL_LENGTH,\n\t\tdescription: `MAX ${MAX_LABEL_LENGTH} CHARACTERS — hard limit, requests over the limit are rejected. The display text for this option that the user will see and select. Should be concise (1-5 words) and clearly describe the choice.`,\n\t}),\n\tdescription: Type.String({\n\t\tdescription:\n\t\t\t\"Explanation of what this option means or what will happen if chosen. Useful for providing context about trade-offs or implications.\",\n\t}),\n\tpreview: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Optional preview content rendered when this option is focused. Use for mockups, code snippets, or visual comparisons that help users compare options. See the tool description for the expected content format.\",\n\t\t}),\n\t),\n});\n\nexport const QuestionSchema = Type.Object({\n\tquestion: Type.String({\n\t\tdescription:\n\t\t\t'The complete question to ask the user. Should be clear, specific, and end with a question mark. Example: \"Which library should we use for date formatting?\" If multiSelect is true, phrase it accordingly, e.g. \"Which features do you want to enable?\"',\n\t}),\n\theader: Type.String({\n\t\tmaxLength: MAX_HEADER_LENGTH,\n\t\tdescription: `MAX ${MAX_HEADER_LENGTH} CHARACTERS — hard limit, requests over the limit are rejected. Very short chip/tag shown next to the question. Examples: \"Auth method\", \"Library\", \"Approach\".`,\n\t}),\n\toptions: Type.Array(OptionSchema, {\n\t\tminItems: MIN_OPTIONS,\n\t\tmaxItems: MAX_OPTIONS,\n\t\tdescription:\n\t\t\t\"The available choices for this question. Must have 2-4 options. Each option should be a distinct, mutually exclusive choice (unless multiSelect is enabled). The 'Type something.' row is appended automatically — do NOT author it.\",\n\t}),\n\tmultiSelect: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdefault: false,\n\t\t\tdescription:\n\t\t\t\t\"Set to true to allow the user to select multiple options instead of just one. Use when choices are not mutually exclusive.\",\n\t\t}),\n\t),\n});\n\nexport const QuestionsSchema = Type.Array(QuestionSchema, {\n\tminItems: 1,\n\tmaxItems: MAX_QUESTIONS,\n\tdescription: \"Questions to ask the user (1-4 questions)\",\n});\n\nexport const QuestionParamsSchema = Type.Object({\n\tquestions: QuestionsSchema,\n});\n\nexport type OptionData = Static<typeof OptionSchema>;\nexport type QuestionData = Static<typeof QuestionSchema>;\nexport type QuestionParams = Static<typeof QuestionParamsSchema>;\n\n/**\n * Answer-intent discriminated union. `kind` is the single discriminator —\n * pre-1.0.3 boolean flags have been removed (see `banned-flags.test.ts`).\n * Mirrors the row-side `WrappingSelectItem.kind` vocabulary where possible;\n * `multi` is the multi-select variant (no row-side analog).\n *\n * Variant semantics:\n * - `option`: user picked one of the author-defined options. `answer` is the option's label.\n * - `custom`: user typed free-text via the \"Type something.\" row. `answer` is the typed text or null.\n * - `chat`: user picked the chat sentinel. `answer` is either typed inline chat text or the legacy\n * literal \"Chat about this\" when submitted empty / whitespace-only.\n * - `multi`: user committed multi-select choices. `selected` carries chosen labels; `answer` is null.\n */\nexport interface QuestionAnswer {\n\tquestionIndex: number;\n\tquestion: string;\n\tkind: \"option\" | \"custom\" | \"chat\" | \"multi\";\n\tanswer: string | null;\n\tselected?: string[];\n\tnotes?: string;\n\t/**\n\t * Markdown text from the matched option's `preview` field, populated only\n\t * when the user lands on a single-select option carrying a `preview`.\n\t * Used by `buildQuestionnaireResponse` to echo `selected preview: <preview>`\n\t * into the LLM-facing envelope. Undefined for multi-select, custom-text\n\t * (`kind: \"custom\"`), and chat (`kind: \"chat\"`) answers.\n\t */\n\tpreview?: string;\n}\n\nexport type QuestionnaireError =\n\t| \"no_ui\"\n\t| \"no_questions\"\n\t| \"empty_options\"\n\t| \"too_many_questions\"\n\t| \"duplicate_question\"\n\t| \"duplicate_option_label\"\n\t| \"reserved_label\";\n\nexport interface QuestionnaireResult {\n\tanswers: QuestionAnswer[];\n\tcancelled: boolean;\n\terror?: QuestionnaireError;\n}\n\nexport function isQuestionnaireResult(value: unknown): value is QuestionnaireResult {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst v = value as Record<string, unknown>;\n\treturn Array.isArray(v.answers) && typeof v.cancelled === \"boolean\";\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/tool/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzE,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;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,cAAc,CAAC;AAK9C;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC9B,OAAO;IACP,eAAe,CAAC,KAAK,CAAC,KAAK;IAC3B,eAAe,CAAC,IAAI,CAAC,KAAK;IAC1B,eAAe,CAAC,IAAI,CAAC,KAAK;CACjB,CAAC;AAGX,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC;IACvC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC;QAClB,SAAS,EAAE,gBAAgB;QAC3B,WAAW,EAAE,OAAO,gBAAgB,oMAAoM;KACxO,CAAC;IACF,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC;QACxB,WAAW,EACV,qIAAqI;KACtI,CAAC;IACF,OAAO,EAAE,IAAI,CAAC,QAAQ,CACrB,IAAI,CAAC,MAAM,CAAC;QACX,WAAW,EACV,iNAAiN;KAClN,CAAC,CACF;CACD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC;IACzC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC;QACrB,WAAW,EACV,yPAAyP;KAC1P,CAAC;IACF,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;QACnB,SAAS,EAAE,iBAAiB;QAC5B,WAAW,EAAE,OAAO,iBAAiB,iKAAiK;KACtM,CAAC;IACF,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE;QACjC,QAAQ,EAAE,WAAW;QACrB,QAAQ,EAAE,WAAW;QACrB,WAAW,EACV,sOAAsO;KACvO,CAAC;IACF,WAAW,EAAE,IAAI,CAAC,QAAQ,CACzB,IAAI,CAAC,OAAO,CAAC;QACZ,OAAO,EAAE,KAAK;QACd,WAAW,EACV,4HAA4H;KAC7H,CAAC,CACF;CACD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE;IACzD,QAAQ,EAAE,CAAC;IACX,QAAQ,EAAE,aAAa;IACvB,WAAW,EAAE,2CAA2C;CACxD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC,MAAM,CAAC;IAC/C,SAAS,EAAE,eAAe;CAC1B,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/tool/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzE,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;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,cAAc,CAAC;AAK9C;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC9B,OAAO;IACP,eAAe,CAAC,KAAK,CAAC,KAAK;IAC3B,eAAe,CAAC,IAAI,CAAC,KAAK;IAC1B,eAAe,CAAC,IAAI,CAAC,KAAK;CACjB,CAAC;AAGX,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC;IACvC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC;QAClB,SAAS,EAAE,gBAAgB;QAC3B,WAAW,EAAE,OAAO,gBAAgB,oMAAoM;KACxO,CAAC;IACF,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC;QACxB,WAAW,EACV,qIAAqI;KACtI,CAAC;IACF,OAAO,EAAE,IAAI,CAAC,QAAQ,CACrB,IAAI,CAAC,MAAM,CAAC;QACX,WAAW,EACV,iNAAiN;KAClN,CAAC,CACF;CACD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC;IACzC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC;QACrB,WAAW,EACV,yPAAyP;KAC1P,CAAC;IACF,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;QACnB,SAAS,EAAE,iBAAiB;QAC5B,WAAW,EAAE,OAAO,iBAAiB,iKAAiK;KACtM,CAAC;IACF,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE;QACjC,QAAQ,EAAE,WAAW;QACrB,QAAQ,EAAE,WAAW;QACrB,WAAW,EACV,sOAAsO;KACvO,CAAC;IACF,WAAW,EAAE,IAAI,CAAC,QAAQ,CACzB,IAAI,CAAC,OAAO,CAAC;QACZ,OAAO,EAAE,KAAK;QACd,WAAW,EACV,4HAA4H;KAC7H,CAAC,CACF;CACD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE;IACzD,QAAQ,EAAE,CAAC;IACX,QAAQ,EAAE,aAAa;IACvB,WAAW,EAAE,2CAA2C;CACxD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC,MAAM,CAAC;IAC/C,SAAS,EAAE,eAAe;CAC1B,CAAC,CAAC;AAmDH,MAAM,UAAU,qBAAqB,CAAC,KAAc;IACnD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,CAAC,GAAG,KAAgC,CAAC;IAC3C,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC;AACrE,CAAC","sourcesContent":["import { type Static, Type } from \"typebox\";\nimport { LABELS_BY_KIND, ROW_INTENT_META } from \"../state/row-intent.ts\";\n\nexport const MAX_QUESTIONS = 4;\nexport const MIN_OPTIONS = 2;\nexport const MAX_OPTIONS = 4;\nexport const MAX_HEADER_LENGTH = 16;\nexport const MAX_LABEL_LENGTH = 60;\n\n/**\n * User-facing labels for the three runtime sentinel rows, keyed by their\n * `WrappingSelectItem.kind` discriminator. Sourced from\n * `ROW_INTENT_META` via `LABELS_BY_KIND` (`row-intent.ts`) — single source of\n * truth. Adding a new sentinel requires extending the `WrappingSelectItem`\n * union AND adding an entry to `ROW_INTENT_META`; this map then auto-extends.\n */\nexport const SENTINEL_LABELS = LABELS_BY_KIND;\n\nexport type SentinelKind = keyof typeof SENTINEL_LABELS;\nexport type SentinelLabel = (typeof SENTINEL_LABELS)[SentinelKind];\n\n/**\n * Labels reserved for Pi-internal sentinels — authoring an option with any\n * of these labels triggers the `reserved_label` runtime guard. Three of the\n * four come from `ROW_INTENT_META` (the runtime kinds); `\"Other\"` is\n * reserved for CC parity only (the model is conditioned to reach for\n * \"Other\" in CC; we reject it so the runtime sentinel is the single source\n * of truth) and has no runtime kind.\n *\n * Reserved unconditionally — multiSelect questions also reject these labels\n * even though the runtime sentinel is suppressed there.\n *\n * Order is pinned by `types.test.ts:292` — keep the explicit\n * `[\"Other\", other, chat, next]` literal so consumers using\n * `RESERVED_LABELS[i]` indexing or `Set` membership see no behavior change.\n */\nexport const RESERVED_LABELS = [\n\t\"Other\",\n\tROW_INTENT_META.other.label,\n\tROW_INTENT_META.chat.label,\n\tROW_INTENT_META.next.label,\n] as const;\nexport type ReservedLabel = (typeof RESERVED_LABELS)[number];\n\nexport const OptionSchema = Type.Object({\n\tlabel: Type.String({\n\t\tmaxLength: MAX_LABEL_LENGTH,\n\t\tdescription: `MAX ${MAX_LABEL_LENGTH} CHARACTERS — hard limit, requests over the limit are rejected. The display text for this option that the user will see and select. Should be concise (1-5 words) and clearly describe the choice.`,\n\t}),\n\tdescription: Type.String({\n\t\tdescription:\n\t\t\t\"Explanation of what this option means or what will happen if chosen. Useful for providing context about trade-offs or implications.\",\n\t}),\n\tpreview: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Optional preview content rendered when this option is focused. Use for mockups, code snippets, or visual comparisons that help users compare options. See the tool description for the expected content format.\",\n\t\t}),\n\t),\n});\n\nexport const QuestionSchema = Type.Object({\n\tquestion: Type.String({\n\t\tdescription:\n\t\t\t'The complete question to ask the user. Should be clear, specific, and end with a question mark. Example: \"Which library should we use for date formatting?\" If multiSelect is true, phrase it accordingly, e.g. \"Which features do you want to enable?\"',\n\t}),\n\theader: Type.String({\n\t\tmaxLength: MAX_HEADER_LENGTH,\n\t\tdescription: `MAX ${MAX_HEADER_LENGTH} CHARACTERS — hard limit, requests over the limit are rejected. Very short chip/tag shown next to the question. Examples: \"Auth method\", \"Library\", \"Approach\".`,\n\t}),\n\toptions: Type.Array(OptionSchema, {\n\t\tminItems: MIN_OPTIONS,\n\t\tmaxItems: MAX_OPTIONS,\n\t\tdescription:\n\t\t\t\"The available choices for this question. Must have 2-4 options. Each option should be a distinct, mutually exclusive choice (unless multiSelect is enabled). The 'Type something.' row is appended automatically — do NOT author it.\",\n\t}),\n\tmultiSelect: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdefault: false,\n\t\t\tdescription:\n\t\t\t\t\"Set to true to allow the user to select multiple options instead of just one. Use when choices are not mutually exclusive.\",\n\t\t}),\n\t),\n});\n\nexport const QuestionsSchema = Type.Array(QuestionSchema, {\n\tminItems: 1,\n\tmaxItems: MAX_QUESTIONS,\n\tdescription: \"Questions to ask the user (1-4 questions)\",\n});\n\nexport const QuestionParamsSchema = Type.Object({\n\tquestions: QuestionsSchema,\n});\n\nexport type OptionData = Static<typeof OptionSchema>;\nexport type QuestionData = Static<typeof QuestionSchema>;\nexport type QuestionParams = Static<typeof QuestionParamsSchema>;\n\n/**\n * Answer-intent discriminated union. `kind` is the single discriminator —\n * pre-1.0.3 boolean flags have been removed (see `banned-flags.test.ts`).\n * Mirrors the row-side `WrappingSelectItem.kind` vocabulary where possible;\n * `multi` is the multi-select variant (no row-side analog).\n *\n * Variant semantics:\n * - `option`: user picked one of the author-defined options. `answer` is the option's label.\n * - `custom`: user typed free-text via the \"Type something.\" row. `answer` is the typed text or null.\n * - `chat`: user picked the chat sentinel. `answer` is either typed inline chat text or the legacy\n * literal \"Chat about this\" when submitted empty / whitespace-only.\n * - `multi`: user committed multi-select choices. `selected` carries chosen labels; `answer` is null.\n */\nexport interface QuestionAnswer {\n\tquestionIndex: number;\n\tquestion: string;\n\tkind: \"option\" | \"custom\" | \"chat\" | \"multi\";\n\tanswer: string | null;\n\tselected?: string[];\n\tnotes?: string;\n\t/**\n\t * Markdown text from the matched option's `preview` field, populated only\n\t * when the user lands on a single-select option carrying a `preview`.\n\t * Used by `buildQuestionnaireResponse` to echo `selected preview: <preview>`\n\t * into the LLM-facing envelope. Undefined for multi-select, custom-text\n\t * (`kind: \"custom\"`), and chat (`kind: \"chat\"`) answers.\n\t */\n\tpreview?: string;\n}\n\nexport type QuestionnaireError =\n\t| \"no_ui\"\n\t| \"no_questions\"\n\t| \"empty_options\"\n\t| \"too_many_questions\"\n\t| \"duplicate_question\"\n\t| \"duplicate_option_label\"\n\t| \"reserved_label\";\n\nexport interface QuestionnaireResult {\n\tanswers: QuestionAnswer[];\n\tcancelled: boolean;\n\terror?: QuestionnaireError;\n}\n\nexport function isQuestionnaireResult(value: unknown): value is QuestionnaireResult {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst v = value as Record<string, unknown>;\n\treturn Array.isArray(v.answers) && typeof v.cancelled === \"boolean\";\n}\n"]}
|