@rmrdeveloper/sideroom-pi 7.0.0 → 7.0.1

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.
@@ -1,7 +1,12 @@
1
1
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
2
  import { Text } from '@earendil-works/pi-tui';
3
3
  import { executeAsk } from './execute.ts';
4
- import { AskParamsSchema, type AskResult, TOOL_NAME } from './model.ts';
4
+ import {
5
+ AskParamsSchema,
6
+ type AskResult,
7
+ prepareAskArguments,
8
+ TOOL_NAME,
9
+ } from './model.ts';
5
10
 
6
11
  export const ASK_DESCRIPTION =
7
12
  'Ask the user one or more questions in the language they are speaking. Use for clarifying requirements, getting preferences, or confirming decisions. Each question must include a recommended option. For a single question, shows a simple option list. For multiple questions, shows a tab-based interface.';
@@ -27,6 +32,7 @@ export default function registerAsk(pi: ExtensionAPI): void {
27
32
  promptSnippet: ASK_PROMPT_SNIPPET,
28
33
  promptGuidelines: ASK_PROMPT_GUIDELINES,
29
34
  parameters: AskParamsSchema,
35
+ prepareArguments: prepareAskArguments,
30
36
  executionMode: 'sequential',
31
37
  execute: async (_toolCallId, params, _signal, _onUpdate, ctx) =>
32
38
  executeAsk(params, ctx),
@@ -79,13 +79,118 @@ export type ParseAskResult =
79
79
  | { readonly ok: true; readonly questions: readonly AskQuestion[] }
80
80
  | { readonly ok: false; readonly message: string };
81
81
 
82
+ interface JsonWireObject {
83
+ readonly [key: string]: JsonWireValue | undefined;
84
+ }
85
+
86
+ type JsonWireValue =
87
+ | string
88
+ | number
89
+ | boolean
90
+ | null
91
+ | readonly JsonWireValue[]
92
+ | JsonWireObject;
93
+
94
+ export function prepareAskArguments(args: unknown): AskParams {
95
+ return decodeAskWireArgs(args) as AskParams;
96
+ }
97
+
98
+ function decodeAskWireArgs(args: unknown): JsonWireValue {
99
+ if (!isJsonWireObject(args)) {
100
+ return toJsonWireValue(args);
101
+ }
102
+
103
+ const questions = decodeAskQuestions(args.questions);
104
+ if (questions === args.questions) {
105
+ return args;
106
+ }
107
+
108
+ return { ...args, questions };
109
+ }
110
+
111
+ function decodeAskQuestions(
112
+ value: JsonWireValue | undefined,
113
+ ): JsonWireValue | undefined {
114
+ if (value === undefined) {
115
+ return value;
116
+ }
117
+
118
+ const parsed = decodeJsonWireField(value);
119
+ if (!Array.isArray(parsed)) {
120
+ return parsed;
121
+ }
122
+
123
+ let changed = parsed !== value;
124
+ const questions = parsed.map((question) => {
125
+ const decoded = decodeAskQuestion(question);
126
+ if (decoded !== question) {
127
+ changed = true;
128
+ }
129
+ return decoded;
130
+ });
131
+ if (!changed) {
132
+ return value;
133
+ }
134
+ return questions;
135
+ }
136
+
137
+ function decodeAskQuestion(question: JsonWireValue): JsonWireValue {
138
+ if (!isJsonWireObject(question)) {
139
+ return question;
140
+ }
141
+
142
+ const options = decodeJsonWireField(question.options);
143
+ if (options === question.options) {
144
+ return question;
145
+ }
146
+
147
+ return { ...question, options };
148
+ }
149
+
150
+ function decodeJsonWireField(
151
+ value: JsonWireValue | undefined,
152
+ ): JsonWireValue | undefined {
153
+ if (typeof value !== 'string') {
154
+ return value;
155
+ }
156
+
157
+ try {
158
+ return JSON.parse(value) as JsonWireValue;
159
+ } catch {
160
+ return value;
161
+ }
162
+ }
163
+
164
+ function isJsonWireObject(value: unknown): value is JsonWireObject {
165
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
166
+ }
167
+
168
+ function toJsonWireValue(value: unknown): JsonWireValue {
169
+ if (
170
+ value === null ||
171
+ typeof value === 'string' ||
172
+ typeof value === 'number' ||
173
+ typeof value === 'boolean'
174
+ ) {
175
+ return value;
176
+ }
177
+ if (Array.isArray(value)) {
178
+ return value.map(toJsonWireValue);
179
+ }
180
+ if (isJsonWireObject(value)) {
181
+ return value;
182
+ }
183
+ return null;
184
+ }
185
+
82
186
  export function parseAskParams(value: unknown): ParseAskResult {
83
- if (!Check(AskParamsSchema, value)) {
84
- return { ok: false, message: schemaErrorMessage(value) };
187
+ const decoded = decodeAskWireArgs(value);
188
+ if (!Check(AskParamsSchema, decoded)) {
189
+ return { ok: false, message: schemaErrorMessage(decoded) };
85
190
  }
86
191
 
87
192
  const seen = new Set<string>();
88
- for (const question of value.questions) {
193
+ for (const question of decoded.questions) {
89
194
  if (seen.has(question.id)) {
90
195
  return {
91
196
  ok: false,
@@ -101,7 +206,7 @@ export function parseAskParams(value: unknown): ParseAskResult {
101
206
  }
102
207
  }
103
208
 
104
- return { ok: true, questions: normalizeQuestions(value.questions) };
209
+ return { ok: true, questions: normalizeQuestions(decoded.questions) };
105
210
  }
106
211
 
107
212
  export function normalizeQuestions(
@@ -2,7 +2,7 @@ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
2
  import { Text } from '@earendil-works/pi-tui';
3
3
  import { executeTodo, type TodoToolDetails } from './execute.ts';
4
4
  import { registerGuardEvents } from './guards.ts';
5
- import { TOOL_NAME, TodoParamsSchema } from './model.ts';
5
+ import { prepareTodoArguments, TOOL_NAME, TodoParamsSchema } from './model.ts';
6
6
  import {
7
7
  type BoardLifecycle,
8
8
  commitBoard,
@@ -60,6 +60,7 @@ function registerTodoTool(
60
60
  promptSnippet: TODO_PROMPT_SNIPPET,
61
61
  promptGuidelines: TODO_PROMPT_GUIDELINES,
62
62
  parameters: TodoParamsSchema,
63
+ prepareArguments: prepareTodoArguments,
63
64
  executionMode: 'sequential',
64
65
  execute: async (_toolCallId, params, _signal, _onUpdate, ctx) => {
65
66
  const result = executeTodo(params, ctx, store.items);
@@ -115,13 +115,84 @@ const STATUS_MARKS: Readonly<Record<TodoStatus, string>> = {
115
115
  const BOARD_INSTRUCTIONS =
116
116
  'Call sideroom_todo update before the next item. While any item is pending, exactly one must be in_progress. Complete the current item and start the next in the same update. propose replaces the list; update patches by id.';
117
117
 
118
+ interface JsonWireObject {
119
+ readonly [key: string]: JsonWireValue | undefined;
120
+ }
121
+
122
+ type JsonWireValue =
123
+ | string
124
+ | number
125
+ | boolean
126
+ | null
127
+ | readonly JsonWireValue[]
128
+ | JsonWireObject;
129
+
130
+ export function prepareTodoArguments(args: unknown): TodoParams {
131
+ return decodeTodoWireArgs(args) as TodoParams;
132
+ }
133
+
134
+ function decodeTodoWireArgs(args: unknown): JsonWireValue {
135
+ if (!isJsonWireObject(args)) {
136
+ return toJsonWireValue(args);
137
+ }
138
+
139
+ const items = decodeJsonWireField(args.items);
140
+ const patches = decodeJsonWireField(args.patches);
141
+ if (items === args.items && patches === args.patches) {
142
+ return args;
143
+ }
144
+
145
+ return {
146
+ ...args,
147
+ ...('items' in args ? { items } : {}),
148
+ ...('patches' in args ? { patches } : {}),
149
+ };
150
+ }
151
+
152
+ function decodeJsonWireField(
153
+ value: JsonWireValue | undefined,
154
+ ): JsonWireValue | undefined {
155
+ if (typeof value !== 'string') {
156
+ return value;
157
+ }
158
+
159
+ try {
160
+ return JSON.parse(value) as JsonWireValue;
161
+ } catch {
162
+ return value;
163
+ }
164
+ }
165
+
166
+ function isJsonWireObject(value: unknown): value is JsonWireObject {
167
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
168
+ }
169
+
170
+ function toJsonWireValue(value: unknown): JsonWireValue {
171
+ if (
172
+ value === null ||
173
+ typeof value === 'string' ||
174
+ typeof value === 'number' ||
175
+ typeof value === 'boolean'
176
+ ) {
177
+ return value;
178
+ }
179
+ if (Array.isArray(value)) {
180
+ return value.map(toJsonWireValue);
181
+ }
182
+ if (isJsonWireObject(value)) {
183
+ return value;
184
+ }
185
+ return null;
186
+ }
187
+
118
188
  export function parseTodoParams(value: unknown): ParseTodoResult {
119
- if (!Check(TodoParamsSchema, value)) {
189
+ const decoded = decodeTodoWireArgs(value);
190
+ if (!Check(TodoParamsSchema, decoded)) {
120
191
  return { ok: false, message: 'Error: Invalid sideroom_todo parameters' };
121
192
  }
122
193
 
123
- if (value.action === TODO_ACTION.propose) {
124
- const items = normalizeItems(value.items);
194
+ if (decoded.action === TODO_ACTION.propose) {
195
+ const items = normalizeItems(decoded.items);
125
196
  if (!items.ok) {
126
197
  return { ok: false, message: items.message };
127
198
  }
@@ -135,7 +206,7 @@ export function parseTodoParams(value: unknown): ParseTodoResult {
135
206
  };
136
207
  }
137
208
 
138
- const patches = normalizePatches(value.patches);
209
+ const patches = normalizePatches(decoded.patches);
139
210
  if (!patches.ok) {
140
211
  return { ok: false, message: patches.message };
141
212
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmrdeveloper/sideroom-pi",
3
- "version": "7.0.0",
3
+ "version": "7.0.1",
4
4
  "description": "A global Pi package with a questionnaire, work board, edited-file view, and coding guidelines for parent agents.",
5
5
  "type": "module",
6
6
  "files": [