fix: question tool 'factory is not a function' error - #67
Conversation
The OCGS question extension called ctx.ui.custom({type:'picker',...})
with a plain config object, but Pi's custom() API expects a factory
function (tui, theme, kb, done) => component as the first argument.
This threw 'factory is not a function' at runtime.
Rewrote using Pi's factory pattern from examples/extensions/question.ts:
- Factory function returns { render, handleInput, invalidate }
- Options list with keyboard navigation (↑↓ enter esc)
- Inline editor for custom typed answers
- Description display per option
- renderCall and renderResult for proper TUI rendering
- Preserved OCGS features: header param, decision logging,
promptSnippet/promptGuidelines
PR Review:
|
| Issue | Line(s) | Severity | Suggestion |
|---|---|---|---|
| Redundant ternary | 329 | Medium | Remove the variable and pass result.answer directly to logDecision, or make the distinction meaningful |
logDecision not wrapped in try-catch |
29-36 | Low | If production/session-logs/ is unwritable, the entire execute throws instead of gracefully degrading |
renderResult accesses result.content[0] without length guard |
389 | Low | Add result.content?.length check before indexing to be defensive against empty arrays |
editor.onSubmit mixed arrow+function style |
153 | Low | Consistent style preference — the rest of the file uses named functions |
_kb param unused in factory |
136 | Info | Already prefixed with _, which is correct. No change needed |
| No tests for the TUI component | N/A | Info | Consider adding a story-level test for the non-TUI fallback path at minimum |
What's Done Well
- Factory pattern is correct: The return value
{ render, handleInput, invalidate }matches Pi's expected contract - Keyboard navigation: up/down/enter/esc are handled correctly with clamping on
optionIndex - Inline editor integration: properly pairs
Editorwith Pi's TUI, delegates input viaeditor.handleInput, and esc returns to option selection cachedLinesinvalidation: avoids unnecessary re-renders while staying responsive to input- Decision logging: writes to
production/session-logs/agent-decisions.jsonlwith timestamps — good provenance renderCallandrenderResult: properly implemented, showing numbered options and clean result display- Fallback for non-TUI mode: degrades gracefully with a text summary
Suggested Changes (in priority order)
-
Line 329: Remove the dead ternary — replace with
result.answer:logDecision({ question: params.question, header: params.header, options: simpleOptions, answer: result.answer, wasCustom: result.wasCustom, });
-
Wrap
logDecisionin try-catch to prevent filesystem errors from crashing the extension. -
(Optional) Guard
renderResultagainst empty content arrays.
Verdict
Approved with minor fix required — the core issue (factory pattern mismatch) is correctly resolved, the component is well-structured, and the bug on line 329 is trivial to fix. Ship after addressing the dead ternary.
PR Review:
|
| # | Issue | Line(s) | Suggestion |
|---|---|---|---|
| 1 | logDecision can throw |
29–36 | Wrap in try-catch — if production/session-logs/ is unwritable (e.g. read-only FS in CI), the entire execute throws instead of gracefully falling back |
| 2 | renderResult accesses result.content[0] without guard |
387 | Add result.content?.length check — if content array is empty, this silently returns undefined text |
🟢 Minor / Nitpicks
- Line 153:
editor.onSubmit = (value) => { ... }uses an arrow function while the rest of the file uses namedfunctiondeclarations. Inconsistent style. - Line 258: If
isOther && editMode, the label shows✎— nice touch.
✅ What's Good
- Factory pattern returns
{ render, handleInput, invalidate }— matches Pi's expected contract correctly. - Keyboard navigation is proper: up/down clamping, enter/esc routing.
cachedLinesinvalidation avoids unnecessary re-renders.logDecisionwrites toagent-decisions.jsonlwith timestamps — good provenance tracking.- Non-TUI fallback degrades gracefully with text summary.
renderCallandrenderResultproduce clean formatted output.
Verdict
Changes requested — the answerText ReferenceError on line 333 must be fixed before this can ship. It's a one-line fix (answerText → result.answer). Recommend fixing before merge.
PR #67 introduced a ReferenceError: 'answerText' is not defined on line 333 of the question extension. The variable was never declared or assigned -- every other reference in the same function uses result.answer correctly.


Bug
The OCGS question extension called
ctx.ui.custom({type:'picker',...})with a plain config object, but Pi'scustom()API expects a factory function(tui, theme, kb, done) => componentas the first argument. This threwfactory is not a functionat runtime.Fix
Rewrote the TUI path using Pi's factory pattern from
examples/extensions/question.ts:{ render, handleInput, invalidate }renderCallandrenderResultfor proper TUI renderingheaderparam, decision logging toproduction/session-logs/,promptSnippet/promptGuidelinesCloses #(bug-report)