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.
Files changed (63) hide show
  1. package/dist/index.js +21096 -0
  2. package/package.json +6 -5
  3. package/src/agents/artifact-searcher.ts +0 -1
  4. package/src/agents/bootstrapper.ts +164 -0
  5. package/src/agents/brainstormer.ts +140 -33
  6. package/src/agents/codebase-analyzer.ts +0 -1
  7. package/src/agents/codebase-locator.ts +0 -1
  8. package/src/agents/commander.ts +99 -10
  9. package/src/agents/executor.ts +133 -76
  10. package/src/agents/implementer.ts +126 -41
  11. package/src/agents/index.ts +29 -19
  12. package/src/agents/ledger-creator.ts +0 -1
  13. package/src/agents/octto.ts +132 -0
  14. package/src/agents/pattern-finder.ts +0 -1
  15. package/src/agents/planner.ts +227 -107
  16. package/src/agents/probe.ts +152 -0
  17. package/src/agents/project-initializer.ts +0 -1
  18. package/src/agents/reviewer.ts +89 -21
  19. package/src/config-loader.test.ts +226 -0
  20. package/src/config-loader.ts +132 -6
  21. package/src/hooks/artifact-auto-index.ts +2 -1
  22. package/src/hooks/auto-compact.ts +14 -21
  23. package/src/hooks/context-injector.ts +6 -13
  24. package/src/hooks/context-window-monitor.ts +8 -13
  25. package/src/hooks/ledger-loader.ts +4 -6
  26. package/src/hooks/token-aware-truncation.ts +11 -17
  27. package/src/index.ts +54 -22
  28. package/src/indexing/milestone-artifact-classifier.ts +26 -0
  29. package/src/indexing/milestone-artifact-ingest.ts +42 -0
  30. package/src/octto/constants.ts +20 -0
  31. package/src/octto/session/browser.ts +32 -0
  32. package/src/octto/session/index.ts +25 -0
  33. package/src/octto/session/server.ts +89 -0
  34. package/src/octto/session/sessions.ts +383 -0
  35. package/src/octto/session/types.ts +305 -0
  36. package/src/octto/session/utils.ts +25 -0
  37. package/src/octto/session/waiter.ts +139 -0
  38. package/src/octto/state/index.ts +5 -0
  39. package/src/octto/state/persistence.ts +65 -0
  40. package/src/octto/state/store.ts +161 -0
  41. package/src/octto/state/types.ts +51 -0
  42. package/src/octto/types.ts +376 -0
  43. package/src/octto/ui/bundle.ts +1650 -0
  44. package/src/octto/ui/index.ts +2 -0
  45. package/src/tools/artifact-index/index.ts +152 -3
  46. package/src/tools/artifact-index/schema.sql +21 -0
  47. package/src/tools/milestone-artifact-search.ts +48 -0
  48. package/src/tools/octto/brainstorm.ts +332 -0
  49. package/src/tools/octto/extractor.ts +95 -0
  50. package/src/tools/octto/factory.ts +89 -0
  51. package/src/tools/octto/formatters.ts +63 -0
  52. package/src/tools/octto/index.ts +27 -0
  53. package/src/tools/octto/processor.ts +165 -0
  54. package/src/tools/octto/questions.ts +508 -0
  55. package/src/tools/octto/responses.ts +135 -0
  56. package/src/tools/octto/session.ts +114 -0
  57. package/src/tools/octto/types.ts +21 -0
  58. package/src/tools/octto/utils.ts +4 -0
  59. package/src/tools/pty/manager.ts +13 -7
  60. package/src/tools/spawn-agent.ts +1 -3
  61. package/src/utils/config.ts +123 -0
  62. package/src/utils/errors.ts +57 -0
  63. package/src/utils/logger.ts +50 -0
@@ -0,0 +1,89 @@
1
+ // src/tools/octto/factory.ts
2
+
3
+ import { tool } from "@opencode-ai/plugin/tool";
4
+
5
+ import type { BaseConfig, QuestionType, SessionStore } from "../../octto/session";
6
+ import type { OcttoTool, OcttoTools } from "./types";
7
+
8
+ type ArgsSchema = Parameters<typeof tool>[0]["args"];
9
+
10
+ interface QuestionToolConfig<T> {
11
+ type: QuestionType;
12
+ description: string;
13
+ args: ArgsSchema;
14
+ validate?: (args: T) => string | null;
15
+ toConfig: (args: T) => BaseConfig;
16
+ }
17
+
18
+ export function createQuestionToolFactory(sessions: SessionStore) {
19
+ return function createQuestionTool<T extends { session_id: string }>(config: QuestionToolConfig<T>): OcttoTool {
20
+ return tool({
21
+ description: `${config.description}
22
+ Returns immediately with question_id. Use get_answer to retrieve response.`,
23
+ args: {
24
+ session_id: tool.schema.string().describe("Session ID from start_session"),
25
+ ...config.args,
26
+ },
27
+ execute: async (args) => {
28
+ const validationError = config.validate?.(args as unknown as T);
29
+ if (validationError) return `Failed: ${validationError}`;
30
+
31
+ try {
32
+ const questionConfig = config.toConfig(args as unknown as T);
33
+ const result = sessions.pushQuestion(args.session_id, config.type, questionConfig);
34
+ return `Question pushed: ${result.question_id}\nUse get_answer("${result.question_id}") to retrieve response.`;
35
+ } catch (error) {
36
+ return `Failed: ${error instanceof Error ? error.message : String(error)}`;
37
+ }
38
+ },
39
+ });
40
+ };
41
+ }
42
+
43
+ export function createPushQuestionTool(sessions: SessionStore): OcttoTools {
44
+ const push_question = tool({
45
+ description: `Push a question to the session queue. This is the generic tool for adding any question type.
46
+ The question will appear in the browser for the user to answer.`,
47
+ args: {
48
+ session_id: tool.schema.string().describe("Session ID from start_session"),
49
+ type: tool.schema
50
+ .enum([
51
+ "pick_one",
52
+ "pick_many",
53
+ "confirm",
54
+ "ask_text",
55
+ "ask_image",
56
+ "ask_file",
57
+ "ask_code",
58
+ "show_diff",
59
+ "show_plan",
60
+ "show_options",
61
+ "review_section",
62
+ "thumbs",
63
+ "slider",
64
+ "rank",
65
+ "rate",
66
+ "emoji_react",
67
+ ])
68
+ .describe("Question type"),
69
+ config: tool.schema
70
+ .looseObject({
71
+ question: tool.schema.string().optional(),
72
+ context: tool.schema.string().optional(),
73
+ })
74
+ .describe("Question configuration (varies by type)"),
75
+ },
76
+ execute: async (args) => {
77
+ try {
78
+ const result = sessions.pushQuestion(args.session_id, args.type, args.config);
79
+ return `Question pushed: ${result.question_id}
80
+ Type: ${args.type}
81
+ Use get_next_answer(session_id, block=true) to wait for the user's response.`;
82
+ } catch (error) {
83
+ return `Failed to push question: ${error instanceof Error ? error.message : String(error)}`;
84
+ }
85
+ },
86
+ });
87
+
88
+ return { push_question };
89
+ }
@@ -0,0 +1,63 @@
1
+ // src/tools/octto/formatters.ts
2
+
3
+ import type { Answer } from "../../octto/session";
4
+ import type { BrainstormState, Branch, BranchQuestion } from "../../octto/state";
5
+ import { extractAnswerSummary } from "./extractor";
6
+
7
+ function escapeXml(str: string): string {
8
+ return str
9
+ .replace(/&/g, "&amp;")
10
+ .replace(/</g, "&lt;")
11
+ .replace(/>/g, "&gt;")
12
+ .replace(/"/g, "&quot;")
13
+ .replace(/'/g, "&apos;");
14
+ }
15
+
16
+ export function formatBranchFinding(branch: Branch): string {
17
+ return `<branch id="${branch.id}">
18
+ <scope>${escapeXml(branch.scope)}</scope>
19
+ <finding>${escapeXml(branch.finding || "no finding")}</finding>
20
+ </branch>`;
21
+ }
22
+
23
+ export function formatBranchStatus(branch: Branch): string {
24
+ return `<branch id="${branch.id}" status="${branch.status}">
25
+ <scope>${escapeXml(branch.scope)}</scope>
26
+ <finding>${escapeXml(branch.finding || "pending")}</finding>
27
+ </branch>`;
28
+ }
29
+
30
+ export function formatFindings(state: BrainstormState): string {
31
+ const branches = state.branch_order.map((id) => formatBranchFinding(state.branches[id])).join("\n");
32
+ return `<findings>\n${branches}\n</findings>`;
33
+ }
34
+
35
+ export function formatFindingsList(state: BrainstormState): string {
36
+ const items = state.branch_order
37
+ .map((id) => {
38
+ const b = state.branches[id];
39
+ return ` <finding scope="${escapeXml(b.scope)}">${escapeXml(b.finding || "no finding")}</finding>`;
40
+ })
41
+ .join("\n");
42
+ return `<findings>\n${items}\n</findings>`;
43
+ }
44
+
45
+ export function formatQASummary(branch: Branch): string {
46
+ const answered = branch.questions.filter((q): q is BranchQuestion & { answer: Answer } => q.answer !== undefined);
47
+
48
+ if (answered.length === 0) {
49
+ return "<qa_summary>no questions answered</qa_summary>";
50
+ }
51
+
52
+ const qas = answered
53
+ .map((q) => {
54
+ const answerText = extractAnswerSummary(q.type, q.answer);
55
+ return ` <qa>
56
+ <question>${escapeXml(q.text)}</question>
57
+ <answer>${escapeXml(answerText)}</answer>
58
+ </qa>`;
59
+ })
60
+ .join("\n");
61
+
62
+ return qas;
63
+ }
@@ -0,0 +1,27 @@
1
+ // src/tools/octto/index.ts
2
+
3
+ import type { SessionStore } from "../../octto/session";
4
+ import { createBrainstormTools } from "./brainstorm";
5
+ import { createPushQuestionTool } from "./factory";
6
+ import { createQuestionTools } from "./questions";
7
+ import { createResponseTools } from "./responses";
8
+ import { createSessionTools } from "./session";
9
+ import type { OcttoSessionTracker, OcttoTools, OpencodeClient } from "./types";
10
+
11
+ export type { SessionStore } from "../../octto/session";
12
+ export { createSessionStore } from "../../octto/session";
13
+ export type { OcttoSessionTracker, OcttoTools, OpencodeClient } from "./types";
14
+
15
+ export function createOcttoTools(
16
+ sessions: SessionStore,
17
+ client: OpencodeClient,
18
+ tracker?: OcttoSessionTracker,
19
+ ): OcttoTools {
20
+ return {
21
+ ...createSessionTools(sessions, tracker),
22
+ ...createQuestionTools(sessions),
23
+ ...createResponseTools(sessions),
24
+ ...createPushQuestionTool(sessions),
25
+ ...createBrainstormTools(sessions, client, tracker),
26
+ };
27
+ }
@@ -0,0 +1,165 @@
1
+ // src/tools/octto/processor.ts
2
+
3
+ import type { Answer, QuestionType, SessionStore } from "../../octto/session";
4
+ import { BRANCH_STATUSES, type BrainstormState, type StateStore } from "../../octto/state";
5
+ import { log } from "../../utils/logger";
6
+
7
+ import type { OpencodeClient } from "./types";
8
+
9
+ // Agent name constant - matches the agent exported from src/agents/probe.ts
10
+ const PROBE_AGENT = "probe";
11
+
12
+ interface ProbeResult {
13
+ done: boolean;
14
+ finding?: string;
15
+ question?: {
16
+ type: QuestionType;
17
+ config: Record<string, unknown>;
18
+ };
19
+ }
20
+
21
+ function formatBranchContext(state: BrainstormState, branchId: string): string {
22
+ const lines: string[] = [];
23
+
24
+ lines.push(`<original_request>${state.request}</original_request>`);
25
+ lines.push("");
26
+ lines.push("<branches>");
27
+
28
+ for (const [id, branch] of Object.entries(state.branches)) {
29
+ const isCurrent = id === branchId;
30
+ lines.push(`<branch id="${id}" scope="${branch.scope}"${isCurrent ? ' current="true"' : ""}>`);
31
+
32
+ for (const q of branch.questions) {
33
+ lines.push(` <question type="${q.type}">${q.text}</question>`);
34
+ if (q.answer) {
35
+ lines.push(` <answer>${JSON.stringify(q.answer)}</answer>`);
36
+ }
37
+ }
38
+
39
+ if (branch.status === BRANCH_STATUSES.DONE && branch.finding) {
40
+ lines.push(` <finding>${branch.finding}</finding>`);
41
+ }
42
+
43
+ lines.push("</branch>");
44
+ }
45
+
46
+ lines.push("</branches>");
47
+ lines.push("");
48
+ lines.push(`Evaluate the branch "${branchId}" and decide: ask another question or complete with a finding.`);
49
+
50
+ return lines.join("\n");
51
+ }
52
+
53
+ async function runProbeAgent(client: OpencodeClient, state: BrainstormState, branchId: string): Promise<ProbeResult> {
54
+ const sessionResult = await client.session.create({
55
+ body: { title: `probe-${branchId}` },
56
+ });
57
+
58
+ if (!sessionResult.data) {
59
+ throw new Error("Failed to create probe session");
60
+ }
61
+
62
+ const probeSessionId = sessionResult.data.id;
63
+
64
+ try {
65
+ const promptResult = await client.session.prompt({
66
+ path: { id: probeSessionId },
67
+ body: {
68
+ agent: PROBE_AGENT,
69
+ tools: {},
70
+ parts: [{ type: "text", text: formatBranchContext(state, branchId) }],
71
+ },
72
+ });
73
+
74
+ if (!promptResult.data) {
75
+ throw new Error("Failed to get probe response");
76
+ }
77
+
78
+ let responseText = "";
79
+ for (const part of promptResult.data.parts) {
80
+ if (part.type === "text" && "text" in part) {
81
+ responseText += (part as { text: string }).text;
82
+ }
83
+ }
84
+
85
+ const jsonMatch = responseText.match(/\{[\s\S]*\}/);
86
+ if (!jsonMatch) {
87
+ return { done: true, finding: "Could not parse probe response" };
88
+ }
89
+
90
+ return JSON.parse(jsonMatch[0]) as ProbeResult;
91
+ } finally {
92
+ await client.session.delete({ path: { id: probeSessionId } }).catch(() => {});
93
+ }
94
+ }
95
+
96
+ export async function processAnswer(
97
+ stateStore: StateStore,
98
+ sessions: SessionStore,
99
+ sessionId: string,
100
+ browserSessionId: string,
101
+ questionId: string,
102
+ answer: Answer,
103
+ client: OpencodeClient,
104
+ ): Promise<void> {
105
+ const state = await stateStore.getSession(sessionId);
106
+ if (!state) return;
107
+
108
+ // Find which branch this question belongs to
109
+ let branchId: string | null = null;
110
+ for (const [id, branch] of Object.entries(state.branches)) {
111
+ if (branch.questions.some((q) => q.id === questionId)) {
112
+ branchId = id;
113
+ break;
114
+ }
115
+ }
116
+
117
+ if (!branchId) return;
118
+ if (state.branches[branchId].status === BRANCH_STATUSES.DONE) return;
119
+
120
+ // Record the answer
121
+ try {
122
+ await stateStore.recordAnswer(sessionId, questionId, answer);
123
+ } catch (error) {
124
+ log.error("octto", `Failed to record answer for ${questionId}`, error);
125
+ throw error;
126
+ }
127
+
128
+ // Get fresh state after recording
129
+ const updatedState = await stateStore.getSession(sessionId);
130
+ if (!updatedState) return;
131
+
132
+ const branch = updatedState.branches[branchId];
133
+ if (!branch || branch.status === BRANCH_STATUSES.DONE) return;
134
+
135
+ // Evaluate branch using probe agent
136
+ const result = await runProbeAgent(client, updatedState, branchId);
137
+
138
+ if (result.done) {
139
+ await stateStore.completeBranch(sessionId, branchId, result.finding || "No finding");
140
+ return;
141
+ }
142
+
143
+ if (result.question) {
144
+ const config = result.question.config as { question?: string; context?: string };
145
+ const questionText = config.question ?? "Follow-up question";
146
+ const existingContext = config.context ?? "";
147
+ const configWithContext = {
148
+ ...config,
149
+ context: `[${branch.scope}] ${existingContext}`.trim(),
150
+ };
151
+
152
+ const { question_id: newQuestionId } = sessions.pushQuestion(
153
+ browserSessionId,
154
+ result.question.type,
155
+ configWithContext,
156
+ );
157
+
158
+ await stateStore.addQuestionToBranch(sessionId, branchId, {
159
+ id: newQuestionId,
160
+ type: result.question.type,
161
+ text: questionText,
162
+ config: configWithContext,
163
+ });
164
+ }
165
+ }