micode 0.8.4 → 0.8.6
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/dist/index.js +21096 -0
- package/package.json +6 -5
- package/src/agents/artifact-searcher.ts +0 -1
- package/src/agents/bootstrapper.ts +164 -0
- package/src/agents/brainstormer.ts +140 -33
- package/src/agents/codebase-analyzer.ts +0 -1
- package/src/agents/codebase-locator.ts +0 -1
- package/src/agents/commander.ts +99 -10
- package/src/agents/executor.ts +133 -76
- package/src/agents/implementer.ts +126 -41
- package/src/agents/index.ts +29 -19
- package/src/agents/ledger-creator.ts +0 -1
- package/src/agents/octto.ts +132 -0
- package/src/agents/pattern-finder.ts +0 -1
- package/src/agents/planner.ts +227 -107
- package/src/agents/probe.ts +152 -0
- package/src/agents/project-initializer.ts +0 -1
- package/src/agents/reviewer.ts +89 -21
- package/src/config-loader.test.ts +226 -0
- package/src/config-loader.ts +132 -6
- package/src/hooks/artifact-auto-index.ts +2 -1
- package/src/hooks/auto-compact.ts +14 -21
- package/src/hooks/context-injector.ts +6 -13
- package/src/hooks/context-window-monitor.ts +8 -13
- package/src/hooks/ledger-loader.ts +4 -6
- package/src/hooks/token-aware-truncation.ts +11 -17
- package/src/index.ts +54 -22
- package/src/indexing/milestone-artifact-classifier.ts +26 -0
- package/src/indexing/milestone-artifact-ingest.ts +42 -0
- package/src/octto/constants.ts +20 -0
- package/src/octto/session/browser.ts +32 -0
- package/src/octto/session/index.ts +25 -0
- package/src/octto/session/server.ts +89 -0
- package/src/octto/session/sessions.ts +383 -0
- package/src/octto/session/types.ts +305 -0
- package/src/octto/session/utils.ts +25 -0
- package/src/octto/session/waiter.ts +139 -0
- package/src/octto/state/index.ts +5 -0
- package/src/octto/state/persistence.ts +65 -0
- package/src/octto/state/store.ts +161 -0
- package/src/octto/state/types.ts +51 -0
- package/src/octto/types.ts +376 -0
- package/src/octto/ui/bundle.ts +1650 -0
- package/src/octto/ui/index.ts +2 -0
- package/src/tools/artifact-index/index.ts +152 -3
- package/src/tools/artifact-index/schema.sql +21 -0
- package/src/tools/milestone-artifact-search.ts +48 -0
- package/src/tools/octto/brainstorm.ts +332 -0
- package/src/tools/octto/extractor.ts +95 -0
- package/src/tools/octto/factory.ts +89 -0
- package/src/tools/octto/formatters.ts +63 -0
- package/src/tools/octto/index.ts +27 -0
- package/src/tools/octto/processor.ts +165 -0
- package/src/tools/octto/questions.ts +508 -0
- package/src/tools/octto/responses.ts +135 -0
- package/src/tools/octto/session.ts +114 -0
- package/src/tools/octto/types.ts +21 -0
- package/src/tools/octto/utils.ts +4 -0
- package/src/tools/pty/manager.ts +13 -7
- package/src/tools/spawn-agent.ts +1 -3
- package/src/utils/config.ts +123 -0
- package/src/utils/errors.ts +57 -0
- package/src/utils/logger.ts +50 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
// src/tools/octto/questions.ts
|
|
2
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
3
|
+
|
|
4
|
+
import type { SessionStore } from "../../octto/session";
|
|
5
|
+
import type { ConfirmConfig, PickManyConfig, PickOneConfig, RankConfig, RateConfig } from "../../octto/types";
|
|
6
|
+
import { createQuestionToolFactory } from "./factory";
|
|
7
|
+
import type { OcttoTools } from "./types";
|
|
8
|
+
|
|
9
|
+
const optionsSchema = tool.schema
|
|
10
|
+
.array(
|
|
11
|
+
tool.schema.object({
|
|
12
|
+
id: tool.schema.string().describe("Unique option identifier"),
|
|
13
|
+
label: tool.schema.string().describe("Display label"),
|
|
14
|
+
description: tool.schema.string().optional().describe("Optional description"),
|
|
15
|
+
}),
|
|
16
|
+
)
|
|
17
|
+
.describe("Available options");
|
|
18
|
+
|
|
19
|
+
function requireOptions(args: { options?: unknown[] }): string | null {
|
|
20
|
+
if (!args.options || args.options.length === 0) return "options array must not be empty";
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createQuestionTools(sessions: SessionStore): OcttoTools {
|
|
25
|
+
const createTool = createQuestionToolFactory(sessions);
|
|
26
|
+
|
|
27
|
+
const pick_one = createTool<PickOneConfig & { session_id: string }>({
|
|
28
|
+
type: "pick_one",
|
|
29
|
+
description: `Ask user to select ONE option from a list.
|
|
30
|
+
Response format: { selected: string } where selected is the chosen option id.`,
|
|
31
|
+
args: {
|
|
32
|
+
question: tool.schema.string().describe("Question to display"),
|
|
33
|
+
options: optionsSchema,
|
|
34
|
+
recommended: tool.schema.string().optional().describe("Recommended option id (highlighted)"),
|
|
35
|
+
allowOther: tool.schema.boolean().optional().describe("Allow custom 'other' input"),
|
|
36
|
+
},
|
|
37
|
+
validate: requireOptions,
|
|
38
|
+
toConfig: (args) => ({
|
|
39
|
+
question: args.question,
|
|
40
|
+
options: args.options,
|
|
41
|
+
recommended: args.recommended,
|
|
42
|
+
allowOther: args.allowOther,
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const pick_many = createTool<PickManyConfig & { session_id: string }>({
|
|
47
|
+
type: "pick_many",
|
|
48
|
+
description: `Ask user to select MULTIPLE options from a list.
|
|
49
|
+
Response format: { selected: string[] } where selected is array of chosen option ids.`,
|
|
50
|
+
args: {
|
|
51
|
+
question: tool.schema.string().describe("Question to display"),
|
|
52
|
+
options: optionsSchema,
|
|
53
|
+
recommended: tool.schema.array(tool.schema.string()).optional().describe("Recommended option ids"),
|
|
54
|
+
min: tool.schema.number().optional().describe("Minimum selections required"),
|
|
55
|
+
max: tool.schema.number().optional().describe("Maximum selections allowed"),
|
|
56
|
+
allowOther: tool.schema.boolean().optional().describe("Allow custom 'other' input"),
|
|
57
|
+
},
|
|
58
|
+
validate: (args) => {
|
|
59
|
+
if (!args.options || args.options.length === 0) return "options array must not be empty";
|
|
60
|
+
if (args.min !== undefined && args.max !== undefined && args.min > args.max) {
|
|
61
|
+
return `min (${args.min}) cannot be greater than max (${args.max})`;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
},
|
|
65
|
+
toConfig: (args) => ({
|
|
66
|
+
question: args.question,
|
|
67
|
+
options: args.options,
|
|
68
|
+
recommended: args.recommended,
|
|
69
|
+
min: args.min,
|
|
70
|
+
max: args.max,
|
|
71
|
+
allowOther: args.allowOther,
|
|
72
|
+
}),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const confirm = createTool<ConfirmConfig & { session_id: string }>({
|
|
76
|
+
type: "confirm",
|
|
77
|
+
description: `Ask user for Yes/No confirmation.
|
|
78
|
+
Response format: { choice: "yes" | "no" | "cancel" }`,
|
|
79
|
+
args: {
|
|
80
|
+
question: tool.schema.string().describe("Question to display"),
|
|
81
|
+
context: tool.schema.string().optional().describe("Additional context/details"),
|
|
82
|
+
yesLabel: tool.schema.string().optional().describe("Custom label for yes button"),
|
|
83
|
+
noLabel: tool.schema.string().optional().describe("Custom label for no button"),
|
|
84
|
+
allowCancel: tool.schema.boolean().optional().describe("Show cancel option"),
|
|
85
|
+
},
|
|
86
|
+
toConfig: (args) => ({
|
|
87
|
+
question: args.question,
|
|
88
|
+
context: args.context,
|
|
89
|
+
yesLabel: args.yesLabel,
|
|
90
|
+
noLabel: args.noLabel,
|
|
91
|
+
allowCancel: args.allowCancel,
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const rank = createTool<RankConfig & { session_id: string }>({
|
|
96
|
+
type: "rank",
|
|
97
|
+
description: `Ask user to rank/order items by dragging.
|
|
98
|
+
Response format: { ranked: string[] } where ranked is array of option ids in user's order (first = highest).`,
|
|
99
|
+
args: {
|
|
100
|
+
question: tool.schema.string().describe("Question to display"),
|
|
101
|
+
options: optionsSchema.describe("Items to rank"),
|
|
102
|
+
context: tool.schema.string().optional().describe("Instructions/context"),
|
|
103
|
+
},
|
|
104
|
+
validate: requireOptions,
|
|
105
|
+
toConfig: (args) => ({
|
|
106
|
+
question: args.question,
|
|
107
|
+
options: args.options,
|
|
108
|
+
context: args.context,
|
|
109
|
+
}),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const rate = createTool<RateConfig & { session_id: string }>({
|
|
113
|
+
type: "rate",
|
|
114
|
+
description: `Ask user to rate items on a numeric scale.
|
|
115
|
+
Response format: { ratings: Record<string, number> } where key is option id, value is rating.`,
|
|
116
|
+
args: {
|
|
117
|
+
question: tool.schema.string().describe("Question to display"),
|
|
118
|
+
options: optionsSchema.describe("Items to rate"),
|
|
119
|
+
min: tool.schema.number().optional().describe("Minimum rating value (default: 1)"),
|
|
120
|
+
max: tool.schema.number().optional().describe("Maximum rating value (default: 5)"),
|
|
121
|
+
step: tool.schema.number().optional().describe("Rating step (default: 1)"),
|
|
122
|
+
labels: tool.schema
|
|
123
|
+
.object({
|
|
124
|
+
min: tool.schema.string().optional().describe("Label for minimum value"),
|
|
125
|
+
max: tool.schema.string().optional().describe("Label for maximum value"),
|
|
126
|
+
})
|
|
127
|
+
.optional()
|
|
128
|
+
.describe("Optional labels for min/max"),
|
|
129
|
+
},
|
|
130
|
+
validate: (args) => {
|
|
131
|
+
if (!args.options || args.options.length === 0) return "options array must not be empty";
|
|
132
|
+
const min = args.min ?? 1;
|
|
133
|
+
const max = args.max ?? 5;
|
|
134
|
+
if (min >= max) return `min (${min}) must be less than max (${max})`;
|
|
135
|
+
return null;
|
|
136
|
+
},
|
|
137
|
+
toConfig: (args) => ({
|
|
138
|
+
question: args.question,
|
|
139
|
+
options: args.options,
|
|
140
|
+
min: args.min ?? 1,
|
|
141
|
+
max: args.max ?? 5,
|
|
142
|
+
step: args.step,
|
|
143
|
+
labels: args.labels,
|
|
144
|
+
}),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Import remaining tools from other files
|
|
148
|
+
const inputTools = createInputTools(sessions);
|
|
149
|
+
const presentationTools = createPresentationTools(sessions);
|
|
150
|
+
const quickTools = createQuickTools(sessions);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
pick_one,
|
|
154
|
+
pick_many,
|
|
155
|
+
confirm,
|
|
156
|
+
rank,
|
|
157
|
+
rate,
|
|
158
|
+
...inputTools,
|
|
159
|
+
...presentationTools,
|
|
160
|
+
...quickTools,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Input tools using factory
|
|
165
|
+
function createInputTools(sessions: SessionStore): OcttoTools {
|
|
166
|
+
const createTool = createQuestionToolFactory(sessions);
|
|
167
|
+
|
|
168
|
+
interface TextConfig {
|
|
169
|
+
session_id: string;
|
|
170
|
+
question: string;
|
|
171
|
+
placeholder?: string;
|
|
172
|
+
context?: string;
|
|
173
|
+
multiline?: boolean;
|
|
174
|
+
minLength?: number;
|
|
175
|
+
maxLength?: number;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const ask_text = createTool<TextConfig>({
|
|
179
|
+
type: "ask_text",
|
|
180
|
+
description: `Ask user for text input (single or multi-line).
|
|
181
|
+
Response format: { text: string }`,
|
|
182
|
+
args: {
|
|
183
|
+
question: tool.schema.string().describe("Question to display"),
|
|
184
|
+
placeholder: tool.schema.string().optional().describe("Placeholder text"),
|
|
185
|
+
context: tool.schema.string().optional().describe("Instructions/context"),
|
|
186
|
+
multiline: tool.schema.boolean().optional().describe("Multi-line input (default: false)"),
|
|
187
|
+
minLength: tool.schema.number().optional().describe("Minimum text length"),
|
|
188
|
+
maxLength: tool.schema.number().optional().describe("Maximum text length"),
|
|
189
|
+
},
|
|
190
|
+
toConfig: (args) => ({
|
|
191
|
+
question: args.question,
|
|
192
|
+
placeholder: args.placeholder,
|
|
193
|
+
context: args.context,
|
|
194
|
+
multiline: args.multiline,
|
|
195
|
+
minLength: args.minLength,
|
|
196
|
+
maxLength: args.maxLength,
|
|
197
|
+
}),
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
interface ImageConfig {
|
|
201
|
+
session_id: string;
|
|
202
|
+
question: string;
|
|
203
|
+
context?: string;
|
|
204
|
+
multiple?: boolean;
|
|
205
|
+
maxImages?: number;
|
|
206
|
+
accept?: string[];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const ask_image = createTool<ImageConfig>({
|
|
210
|
+
type: "ask_image",
|
|
211
|
+
description: "Ask user to upload/paste image(s).",
|
|
212
|
+
args: {
|
|
213
|
+
question: tool.schema.string().describe("Question to display"),
|
|
214
|
+
context: tool.schema.string().optional().describe("Instructions/context"),
|
|
215
|
+
multiple: tool.schema.boolean().optional().describe("Allow multiple images"),
|
|
216
|
+
maxImages: tool.schema.number().optional().describe("Maximum number of images"),
|
|
217
|
+
accept: tool.schema.array(tool.schema.string()).optional().describe("Allowed image types"),
|
|
218
|
+
},
|
|
219
|
+
toConfig: (args) => ({
|
|
220
|
+
question: args.question,
|
|
221
|
+
context: args.context,
|
|
222
|
+
multiple: args.multiple,
|
|
223
|
+
maxImages: args.maxImages,
|
|
224
|
+
accept: args.accept,
|
|
225
|
+
}),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
interface FileConfig {
|
|
229
|
+
session_id: string;
|
|
230
|
+
question: string;
|
|
231
|
+
context?: string;
|
|
232
|
+
multiple?: boolean;
|
|
233
|
+
maxFiles?: number;
|
|
234
|
+
accept?: string[];
|
|
235
|
+
maxSize?: number;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const ask_file = createTool<FileConfig>({
|
|
239
|
+
type: "ask_file",
|
|
240
|
+
description: "Ask user to upload file(s).",
|
|
241
|
+
args: {
|
|
242
|
+
question: tool.schema.string().describe("Question to display"),
|
|
243
|
+
context: tool.schema.string().optional().describe("Instructions/context"),
|
|
244
|
+
multiple: tool.schema.boolean().optional().describe("Allow multiple files"),
|
|
245
|
+
maxFiles: tool.schema.number().optional().describe("Maximum number of files"),
|
|
246
|
+
accept: tool.schema.array(tool.schema.string()).optional().describe("Allowed file types"),
|
|
247
|
+
maxSize: tool.schema.number().optional().describe("Maximum file size in bytes"),
|
|
248
|
+
},
|
|
249
|
+
toConfig: (args) => ({
|
|
250
|
+
question: args.question,
|
|
251
|
+
context: args.context,
|
|
252
|
+
multiple: args.multiple,
|
|
253
|
+
maxFiles: args.maxFiles,
|
|
254
|
+
accept: args.accept,
|
|
255
|
+
maxSize: args.maxSize,
|
|
256
|
+
}),
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
interface CodeConfig {
|
|
260
|
+
session_id: string;
|
|
261
|
+
question: string;
|
|
262
|
+
context?: string;
|
|
263
|
+
language?: string;
|
|
264
|
+
placeholder?: string;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const ask_code = createTool<CodeConfig>({
|
|
268
|
+
type: "ask_code",
|
|
269
|
+
description: "Ask user for code input with syntax highlighting.",
|
|
270
|
+
args: {
|
|
271
|
+
question: tool.schema.string().describe("Question to display"),
|
|
272
|
+
context: tool.schema.string().optional().describe("Instructions/context"),
|
|
273
|
+
language: tool.schema.string().optional().describe("Programming language for highlighting"),
|
|
274
|
+
placeholder: tool.schema.string().optional().describe("Placeholder code"),
|
|
275
|
+
},
|
|
276
|
+
toConfig: (args) => ({
|
|
277
|
+
question: args.question,
|
|
278
|
+
context: args.context,
|
|
279
|
+
language: args.language,
|
|
280
|
+
placeholder: args.placeholder,
|
|
281
|
+
}),
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
return { ask_text, ask_image, ask_file, ask_code };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Presentation tools using factory
|
|
288
|
+
function createPresentationTools(sessions: SessionStore): OcttoTools {
|
|
289
|
+
const createTool = createQuestionToolFactory(sessions);
|
|
290
|
+
|
|
291
|
+
interface DiffConfig {
|
|
292
|
+
session_id: string;
|
|
293
|
+
question: string;
|
|
294
|
+
before: string;
|
|
295
|
+
after: string;
|
|
296
|
+
filePath?: string;
|
|
297
|
+
language?: string;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const show_diff = createTool<DiffConfig>({
|
|
301
|
+
type: "show_diff",
|
|
302
|
+
description: "Show a diff and ask user to approve/reject/edit.",
|
|
303
|
+
args: {
|
|
304
|
+
question: tool.schema.string().describe("Title/description of the change"),
|
|
305
|
+
before: tool.schema.string().describe("Original content"),
|
|
306
|
+
after: tool.schema.string().describe("Modified content"),
|
|
307
|
+
filePath: tool.schema.string().optional().describe("File path for context"),
|
|
308
|
+
language: tool.schema.string().optional().describe("Language for syntax highlighting"),
|
|
309
|
+
},
|
|
310
|
+
toConfig: (args) => ({
|
|
311
|
+
question: args.question,
|
|
312
|
+
before: args.before,
|
|
313
|
+
after: args.after,
|
|
314
|
+
filePath: args.filePath,
|
|
315
|
+
language: args.language,
|
|
316
|
+
}),
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const sectionSchema = tool.schema.array(
|
|
320
|
+
tool.schema.object({
|
|
321
|
+
id: tool.schema.string().describe("Section identifier"),
|
|
322
|
+
title: tool.schema.string().describe("Section title"),
|
|
323
|
+
content: tool.schema.string().describe("Section content (markdown)"),
|
|
324
|
+
}),
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
interface PlanConfig {
|
|
328
|
+
session_id: string;
|
|
329
|
+
question: string;
|
|
330
|
+
sections?: Array<{ id: string; title: string; content: string }>;
|
|
331
|
+
markdown?: string;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const show_plan = createTool<PlanConfig>({
|
|
335
|
+
type: "show_plan",
|
|
336
|
+
description: `Show a plan/document for user review with annotations.
|
|
337
|
+
Response format: { approved: boolean, annotations?: Record<sectionId, string> }`,
|
|
338
|
+
args: {
|
|
339
|
+
question: tool.schema.string().describe("Plan title"),
|
|
340
|
+
sections: sectionSchema.optional().describe("Plan sections"),
|
|
341
|
+
markdown: tool.schema.string().optional().describe("Full markdown (alternative to sections)"),
|
|
342
|
+
},
|
|
343
|
+
toConfig: (args) => ({
|
|
344
|
+
question: args.question,
|
|
345
|
+
sections: args.sections,
|
|
346
|
+
markdown: args.markdown,
|
|
347
|
+
}),
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const prosConsOptionSchema = tool.schema.array(
|
|
351
|
+
tool.schema.object({
|
|
352
|
+
id: tool.schema.string().describe("Unique option identifier"),
|
|
353
|
+
label: tool.schema.string().describe("Display label"),
|
|
354
|
+
description: tool.schema.string().optional().describe("Optional description"),
|
|
355
|
+
pros: tool.schema.array(tool.schema.string()).optional().describe("Advantages"),
|
|
356
|
+
cons: tool.schema.array(tool.schema.string()).optional().describe("Disadvantages"),
|
|
357
|
+
}),
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
interface ShowOptionsConfig {
|
|
361
|
+
session_id: string;
|
|
362
|
+
question: string;
|
|
363
|
+
options: Array<{ id: string; label: string; description?: string; pros?: string[]; cons?: string[] }>;
|
|
364
|
+
recommended?: string;
|
|
365
|
+
allowFeedback?: boolean;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const show_options = createTool<ShowOptionsConfig>({
|
|
369
|
+
type: "show_options",
|
|
370
|
+
description: `Show options with pros/cons for user to select.
|
|
371
|
+
Response format: { selected: string, feedback?: string } where selected is the chosen option id.`,
|
|
372
|
+
args: {
|
|
373
|
+
question: tool.schema.string().describe("Question to display"),
|
|
374
|
+
options: prosConsOptionSchema.describe("Options with pros/cons"),
|
|
375
|
+
recommended: tool.schema.string().optional().describe("Recommended option id"),
|
|
376
|
+
allowFeedback: tool.schema.boolean().optional().describe("Allow text feedback with selection"),
|
|
377
|
+
},
|
|
378
|
+
validate: (args) => {
|
|
379
|
+
if (!args.options || args.options.length === 0) return "options array must not be empty";
|
|
380
|
+
return null;
|
|
381
|
+
},
|
|
382
|
+
toConfig: (args) => ({
|
|
383
|
+
question: args.question,
|
|
384
|
+
options: args.options,
|
|
385
|
+
recommended: args.recommended,
|
|
386
|
+
allowFeedback: args.allowFeedback,
|
|
387
|
+
}),
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
interface ReviewConfig {
|
|
391
|
+
session_id: string;
|
|
392
|
+
question: string;
|
|
393
|
+
content: string;
|
|
394
|
+
context?: string;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const review_section = createTool<ReviewConfig>({
|
|
398
|
+
type: "review_section",
|
|
399
|
+
description: "Show content section for user review with inline feedback.",
|
|
400
|
+
args: {
|
|
401
|
+
question: tool.schema.string().describe("Section title"),
|
|
402
|
+
content: tool.schema.string().describe("Section content (markdown)"),
|
|
403
|
+
context: tool.schema.string().optional().describe("Context about what to review"),
|
|
404
|
+
},
|
|
405
|
+
toConfig: (args) => ({
|
|
406
|
+
question: args.question,
|
|
407
|
+
content: args.content,
|
|
408
|
+
context: args.context,
|
|
409
|
+
}),
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
return { show_diff, show_plan, show_options, review_section };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Quick tools using factory
|
|
416
|
+
function createQuickTools(sessions: SessionStore): OcttoTools {
|
|
417
|
+
const createTool = createQuestionToolFactory(sessions);
|
|
418
|
+
|
|
419
|
+
interface ThumbsConfig {
|
|
420
|
+
session_id: string;
|
|
421
|
+
question: string;
|
|
422
|
+
context?: string;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const thumbs = createTool<ThumbsConfig>({
|
|
426
|
+
type: "thumbs",
|
|
427
|
+
description: `Ask user for quick thumbs up/down feedback.
|
|
428
|
+
Response format: { choice: "up" | "down" }`,
|
|
429
|
+
args: {
|
|
430
|
+
question: tool.schema.string().describe("Question to display"),
|
|
431
|
+
context: tool.schema.string().optional().describe("Context to show"),
|
|
432
|
+
},
|
|
433
|
+
toConfig: (args) => ({
|
|
434
|
+
question: args.question,
|
|
435
|
+
context: args.context,
|
|
436
|
+
}),
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
interface EmojiConfig {
|
|
440
|
+
session_id: string;
|
|
441
|
+
question: string;
|
|
442
|
+
context?: string;
|
|
443
|
+
emojis?: string[];
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const emoji_react = createTool<EmojiConfig>({
|
|
447
|
+
type: "emoji_react",
|
|
448
|
+
description: "Ask user to react with an emoji.",
|
|
449
|
+
args: {
|
|
450
|
+
question: tool.schema.string().describe("Question to display"),
|
|
451
|
+
context: tool.schema.string().optional().describe("Context to show"),
|
|
452
|
+
emojis: tool.schema.array(tool.schema.string()).optional().describe("Available emoji options"),
|
|
453
|
+
},
|
|
454
|
+
toConfig: (args) => ({
|
|
455
|
+
question: args.question,
|
|
456
|
+
context: args.context,
|
|
457
|
+
emojis: args.emojis,
|
|
458
|
+
}),
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
interface SliderConfig {
|
|
462
|
+
session_id: string;
|
|
463
|
+
question: string;
|
|
464
|
+
min: number;
|
|
465
|
+
max: number;
|
|
466
|
+
step?: number;
|
|
467
|
+
defaultValue?: number;
|
|
468
|
+
context?: string;
|
|
469
|
+
labels?: { min?: string; max?: string; mid?: string };
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const slider = createTool<SliderConfig>({
|
|
473
|
+
type: "slider",
|
|
474
|
+
description: `Ask user to select a value on a numeric slider.
|
|
475
|
+
Response format: { value: number }`,
|
|
476
|
+
args: {
|
|
477
|
+
question: tool.schema.string().describe("Question to display"),
|
|
478
|
+
min: tool.schema.number().describe("Minimum value"),
|
|
479
|
+
max: tool.schema.number().describe("Maximum value"),
|
|
480
|
+
step: tool.schema.number().optional().describe("Step size (default: 1)"),
|
|
481
|
+
defaultValue: tool.schema.number().optional().describe("Default value"),
|
|
482
|
+
context: tool.schema.string().optional().describe("Instructions/context"),
|
|
483
|
+
labels: tool.schema
|
|
484
|
+
.object({
|
|
485
|
+
min: tool.schema.string().optional().describe("Label for minimum value"),
|
|
486
|
+
max: tool.schema.string().optional().describe("Label for maximum value"),
|
|
487
|
+
mid: tool.schema.string().optional().describe("Label for middle value"),
|
|
488
|
+
})
|
|
489
|
+
.optional()
|
|
490
|
+
.describe("Optional labels for the slider"),
|
|
491
|
+
},
|
|
492
|
+
validate: (args) => {
|
|
493
|
+
if (args.min >= args.max) return `min (${args.min}) must be less than max (${args.max})`;
|
|
494
|
+
return null;
|
|
495
|
+
},
|
|
496
|
+
toConfig: (args) => ({
|
|
497
|
+
question: args.question,
|
|
498
|
+
min: args.min,
|
|
499
|
+
max: args.max,
|
|
500
|
+
step: args.step,
|
|
501
|
+
defaultValue: args.defaultValue,
|
|
502
|
+
context: args.context,
|
|
503
|
+
labels: args.labels,
|
|
504
|
+
}),
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
return { thumbs, emoji_react, slider };
|
|
508
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// src/tools/octto/responses.ts
|
|
2
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
3
|
+
|
|
4
|
+
import { type SessionStore, STATUSES } from "../../octto/session";
|
|
5
|
+
|
|
6
|
+
import type { OcttoTools } from "./types";
|
|
7
|
+
|
|
8
|
+
export function createResponseTools(sessions: SessionStore): OcttoTools {
|
|
9
|
+
const get_answer = tool({
|
|
10
|
+
description: `Get the answer to a SPECIFIC question.
|
|
11
|
+
By default returns immediately with current status.
|
|
12
|
+
Set block=true to wait for user response (with optional timeout).
|
|
13
|
+
NOTE: Prefer get_next_answer for better flow - it returns whichever question user answers first.`,
|
|
14
|
+
args: {
|
|
15
|
+
question_id: tool.schema.string().describe("Question ID from a question tool"),
|
|
16
|
+
block: tool.schema.boolean().optional().describe("Wait for response (default: false)"),
|
|
17
|
+
timeout: tool.schema
|
|
18
|
+
.number()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Max milliseconds to wait if blocking (default: 300000 = 5 min)"),
|
|
21
|
+
},
|
|
22
|
+
execute: async (args) => {
|
|
23
|
+
const result = await sessions.getAnswer({
|
|
24
|
+
question_id: args.question_id,
|
|
25
|
+
block: args.block,
|
|
26
|
+
timeout: args.timeout,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (result.completed) {
|
|
30
|
+
return `## Answer Received
|
|
31
|
+
|
|
32
|
+
**Status:** ${result.status}
|
|
33
|
+
|
|
34
|
+
**Response:**
|
|
35
|
+
\`\`\`json
|
|
36
|
+
${JSON.stringify(result.response, null, 2)}
|
|
37
|
+
\`\`\``;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return `## Waiting for Answer
|
|
41
|
+
|
|
42
|
+
**Status:** ${result.status}
|
|
43
|
+
**Reason:** ${result.reason}
|
|
44
|
+
|
|
45
|
+
${result.status === STATUSES.PENDING ? "User has not answered yet. Call again with block=true to wait." : ""}`;
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const get_next_answer = tool({
|
|
50
|
+
description: `Wait for ANY question to be answered. Returns whichever question the user answers first.
|
|
51
|
+
This is the PREFERRED way to get answers - lets user answer in any order.
|
|
52
|
+
Push multiple questions, then call this repeatedly to get answers as they come.`,
|
|
53
|
+
args: {
|
|
54
|
+
session_id: tool.schema.string().describe("Session ID from start_session"),
|
|
55
|
+
block: tool.schema.boolean().optional().describe("Wait for response (default: false)"),
|
|
56
|
+
timeout: tool.schema
|
|
57
|
+
.number()
|
|
58
|
+
.optional()
|
|
59
|
+
.describe("Max milliseconds to wait if blocking (default: 300000 = 5 min)"),
|
|
60
|
+
},
|
|
61
|
+
execute: async (args) => {
|
|
62
|
+
const result = await sessions.getNextAnswer({
|
|
63
|
+
session_id: args.session_id,
|
|
64
|
+
block: args.block,
|
|
65
|
+
timeout: args.timeout,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (result.completed) {
|
|
69
|
+
return `## Answer Received
|
|
70
|
+
|
|
71
|
+
**Question ID:** ${result.question_id}
|
|
72
|
+
**Question Type:** ${result.question_type}
|
|
73
|
+
**Status:** ${result.status}
|
|
74
|
+
|
|
75
|
+
**Response:**
|
|
76
|
+
\`\`\`json
|
|
77
|
+
${JSON.stringify(result.response, null, 2)}
|
|
78
|
+
\`\`\``;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (result.status === STATUSES.NONE_PENDING) {
|
|
82
|
+
return `## No Pending Questions
|
|
83
|
+
|
|
84
|
+
All questions have been answered or there are no questions in the queue.
|
|
85
|
+
Push more questions or end the session.`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return `## Waiting for Answer
|
|
89
|
+
|
|
90
|
+
**Status:** ${result.status}
|
|
91
|
+
${result.reason === STATUSES.TIMEOUT ? "Timed out waiting for response." : "No answer yet."}`;
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const list_questions = tool({
|
|
96
|
+
description: `List all questions and their status for a session.`,
|
|
97
|
+
args: {
|
|
98
|
+
session_id: tool.schema.string().optional().describe("Session ID (omit for all sessions)"),
|
|
99
|
+
},
|
|
100
|
+
execute: async (args) => {
|
|
101
|
+
const result = sessions.listQuestions(args.session_id);
|
|
102
|
+
|
|
103
|
+
if (result.questions.length === 0) {
|
|
104
|
+
return "No questions found.";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let output = "## Questions\n\n";
|
|
108
|
+
output += "| ID | Type | Status | Created | Answered |\n";
|
|
109
|
+
output += "|----|------|--------|---------|----------|\n";
|
|
110
|
+
|
|
111
|
+
for (const q of result.questions) {
|
|
112
|
+
output += `| ${q.id} | ${q.type} | ${q.status} | ${q.createdAt} | ${q.answeredAt || "-"} |\n`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return output;
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const cancel_question = tool({
|
|
120
|
+
description: `Cancel a pending question.
|
|
121
|
+
The question will be removed from the user's queue.`,
|
|
122
|
+
args: {
|
|
123
|
+
question_id: tool.schema.string().describe("Question ID to cancel"),
|
|
124
|
+
},
|
|
125
|
+
execute: async (args) => {
|
|
126
|
+
const result = sessions.cancelQuestion(args.question_id);
|
|
127
|
+
if (result.ok) {
|
|
128
|
+
return `Question ${args.question_id} cancelled.`;
|
|
129
|
+
}
|
|
130
|
+
return `Could not cancel question ${args.question_id}. It may already be answered or not exist.`;
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return { get_answer, get_next_answer, list_questions, cancel_question };
|
|
135
|
+
}
|