micode 0.8.3 → 0.8.5
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/README.md +2 -2
- package/dist/index.js +21020 -0
- package/package.json +10 -6
- 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 +18 -1
- package/src/agents/implementer.ts +83 -6
- 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 +139 -49
- package/src/agents/probe.ts +152 -0
- package/src/agents/project-initializer.ts +0 -1
- package/src/agents/reviewer.ts +75 -5
- 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,114 @@
|
|
|
1
|
+
// src/tools/octto/session.ts
|
|
2
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
3
|
+
|
|
4
|
+
import type { SessionStore } from "../../octto/session";
|
|
5
|
+
import type { OcttoSessionTracker, OcttoTools } from "./types";
|
|
6
|
+
|
|
7
|
+
export function createSessionTools(sessions: SessionStore, tracker?: OcttoSessionTracker): OcttoTools {
|
|
8
|
+
const start_session = tool({
|
|
9
|
+
description: `Start an interactive octto session with initial questions.
|
|
10
|
+
Opens a browser window with questions already displayed - no waiting.
|
|
11
|
+
REQUIRED: You MUST provide at least 1 question. Will fail without questions.`,
|
|
12
|
+
args: {
|
|
13
|
+
title: tool.schema.string().optional().describe("Session title (shown in browser)"),
|
|
14
|
+
questions: tool.schema
|
|
15
|
+
.array(
|
|
16
|
+
tool.schema.object({
|
|
17
|
+
type: tool.schema
|
|
18
|
+
.enum([
|
|
19
|
+
"pick_one",
|
|
20
|
+
"pick_many",
|
|
21
|
+
"confirm",
|
|
22
|
+
"ask_text",
|
|
23
|
+
"ask_image",
|
|
24
|
+
"ask_file",
|
|
25
|
+
"ask_code",
|
|
26
|
+
"show_diff",
|
|
27
|
+
"show_plan",
|
|
28
|
+
"show_options",
|
|
29
|
+
"review_section",
|
|
30
|
+
"thumbs",
|
|
31
|
+
"slider",
|
|
32
|
+
"rank",
|
|
33
|
+
"rate",
|
|
34
|
+
"emoji_react",
|
|
35
|
+
])
|
|
36
|
+
.describe("Question type"),
|
|
37
|
+
|
|
38
|
+
config: tool.schema
|
|
39
|
+
.looseObject({
|
|
40
|
+
question: tool.schema.string().optional(),
|
|
41
|
+
context: tool.schema.string().optional(),
|
|
42
|
+
})
|
|
43
|
+
.describe("Question config (varies by type)"),
|
|
44
|
+
}),
|
|
45
|
+
)
|
|
46
|
+
.describe("REQUIRED: Initial questions to display when browser opens. Must have at least 1."),
|
|
47
|
+
},
|
|
48
|
+
execute: async (args, context) => {
|
|
49
|
+
// ENFORCE: questions are required
|
|
50
|
+
if (!args.questions || args.questions.length === 0) {
|
|
51
|
+
return `## ERROR: questions parameter is REQUIRED
|
|
52
|
+
|
|
53
|
+
start_session MUST include questions. Browser should open with questions ready.
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
\`\`\`
|
|
57
|
+
start_session(
|
|
58
|
+
title="Design Session",
|
|
59
|
+
questions=[
|
|
60
|
+
{type: "pick_one", config: {question: "What language?", options: [{id: "go", label: "Go"}]}},
|
|
61
|
+
{type: "ask_text", config: {question: "Any constraints?"}}
|
|
62
|
+
]
|
|
63
|
+
)
|
|
64
|
+
\`\`\`
|
|
65
|
+
|
|
66
|
+
Please call start_session again WITH your prepared questions.`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const result = await sessions.startSession({ title: args.title, questions: args.questions });
|
|
71
|
+
tracker?.onCreated?.(context.sessionID, result.session_id);
|
|
72
|
+
|
|
73
|
+
let output = `## Session Started
|
|
74
|
+
|
|
75
|
+
| Field | Value |
|
|
76
|
+
|-------|-------|
|
|
77
|
+
| Session ID | ${result.session_id} |
|
|
78
|
+
| URL | ${result.url} |
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
if (result.question_ids && result.question_ids.length > 0) {
|
|
82
|
+
output += `| Questions | ${result.question_ids.length} loaded |\n\n`;
|
|
83
|
+
output += `**Question IDs:** ${result.question_ids.join(", ")}\n\n`;
|
|
84
|
+
output += `Browser opened with ${result.question_ids.length} questions ready.\n`;
|
|
85
|
+
output += `Use get_next_answer(session_id, block=true) to get answers as user responds.`;
|
|
86
|
+
} else {
|
|
87
|
+
output += `\nBrowser opened. Use question tools to push questions.`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return output;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return `Failed to start session: ${error instanceof Error ? error.message : String(error)}`;
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const end_session = tool({
|
|
98
|
+
description: `End an interactive octto session.
|
|
99
|
+
Closes the browser window and cleans up resources.`,
|
|
100
|
+
args: {
|
|
101
|
+
session_id: tool.schema.string().describe("Session ID to end"),
|
|
102
|
+
},
|
|
103
|
+
execute: async (args, context) => {
|
|
104
|
+
const result = await sessions.endSession(args.session_id);
|
|
105
|
+
if (result.ok) {
|
|
106
|
+
tracker?.onEnded?.(context.sessionID, args.session_id);
|
|
107
|
+
return `Session ${args.session_id} ended successfully.`;
|
|
108
|
+
}
|
|
109
|
+
return `Failed to end session ${args.session_id}. It may not exist.`;
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return { start_session, end_session };
|
|
114
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/tools/octto/types.ts
|
|
2
|
+
|
|
3
|
+
import type { ToolContext } from "@opencode-ai/plugin/tool";
|
|
4
|
+
import type { createOpencodeClient } from "@opencode-ai/sdk";
|
|
5
|
+
|
|
6
|
+
// Using `any` to avoid exposing zod types in declaration files.
|
|
7
|
+
// The actual tools are typesafe via zod schemas.
|
|
8
|
+
export interface OcttoTool {
|
|
9
|
+
description: string;
|
|
10
|
+
args: any;
|
|
11
|
+
execute: (args: any, context: ToolContext) => Promise<string>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type OcttoTools = Record<string, OcttoTool>;
|
|
15
|
+
|
|
16
|
+
export type OpencodeClient = ReturnType<typeof createOpencodeClient>;
|
|
17
|
+
|
|
18
|
+
export interface OcttoSessionTracker {
|
|
19
|
+
onCreated?: (parentSessionId: string, octtoSessionId: string) => void;
|
|
20
|
+
onEnded?: (parentSessionId: string, octtoSessionId: string) => void;
|
|
21
|
+
}
|
package/src/tools/pty/manager.ts
CHANGED
|
@@ -20,13 +20,19 @@ export class PTYManager {
|
|
|
20
20
|
const env = { ...process.env, ...opts.env } as Record<string, string>;
|
|
21
21
|
const title = opts.title ?? (`${opts.command} ${args.join(" ")}`.trim() || `Terminal ${id.slice(-4)}`);
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
let ptyProcess: IPty;
|
|
24
|
+
try {
|
|
25
|
+
ptyProcess = spawn(opts.command, args, {
|
|
26
|
+
name: "xterm-256color",
|
|
27
|
+
cols: 120,
|
|
28
|
+
rows: 40,
|
|
29
|
+
cwd: workdir,
|
|
30
|
+
env,
|
|
31
|
+
});
|
|
32
|
+
} catch (e) {
|
|
33
|
+
const errorMsg = e instanceof Error ? e.message : String(e);
|
|
34
|
+
throw new Error(`Failed to spawn PTY for command "${opts.command}": ${errorMsg}`);
|
|
35
|
+
}
|
|
30
36
|
|
|
31
37
|
const buffer = new RingBuffer();
|
|
32
38
|
const session: PTYSession = {
|
package/src/tools/spawn-agent.ts
CHANGED
|
@@ -63,9 +63,7 @@ For parallel execution, call spawn_agent multiple times in ONE message.`,
|
|
|
63
63
|
|
|
64
64
|
// Find the last assistant message
|
|
65
65
|
const messages = messagesResp.data || [];
|
|
66
|
-
const lastAssistant = messages
|
|
67
|
-
.filter((m) => m.info?.role === "assistant")
|
|
68
|
-
.pop();
|
|
66
|
+
const lastAssistant = messages.filter((m) => m.info?.role === "assistant").pop();
|
|
69
67
|
|
|
70
68
|
const result =
|
|
71
69
|
lastAssistant?.parts
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// src/utils/config.ts
|
|
2
|
+
// Centralized configuration constants
|
|
3
|
+
// Organized by domain for easy discovery and maintenance
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Application configuration constants.
|
|
7
|
+
* All values are compile-time constants - no runtime configuration.
|
|
8
|
+
*/
|
|
9
|
+
export const config = {
|
|
10
|
+
/**
|
|
11
|
+
* Auto-compaction settings
|
|
12
|
+
*/
|
|
13
|
+
compaction: {
|
|
14
|
+
/** Trigger compaction when context usage exceeds this ratio */
|
|
15
|
+
threshold: 0.5,
|
|
16
|
+
/** Minimum time between compaction attempts (ms) */
|
|
17
|
+
cooldownMs: 30_000,
|
|
18
|
+
/** Maximum time to wait for compaction to complete (ms) */
|
|
19
|
+
timeoutMs: 120_000,
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Context window monitoring settings
|
|
24
|
+
*/
|
|
25
|
+
contextWindow: {
|
|
26
|
+
/** Show warning when context usage exceeds this ratio */
|
|
27
|
+
warningThreshold: 0.7,
|
|
28
|
+
/** Show critical warning when context usage exceeds this ratio */
|
|
29
|
+
criticalThreshold: 0.85,
|
|
30
|
+
/** Minimum time between warning toasts (ms) */
|
|
31
|
+
warningCooldownMs: 120_000,
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Token estimation settings
|
|
36
|
+
*/
|
|
37
|
+
tokens: {
|
|
38
|
+
/** Characters per token for estimation */
|
|
39
|
+
charsPerToken: 4,
|
|
40
|
+
/** Default context window limit (tokens) */
|
|
41
|
+
defaultContextLimit: 200_000,
|
|
42
|
+
/** Default max output tokens */
|
|
43
|
+
defaultMaxOutputTokens: 50_000,
|
|
44
|
+
/** Safety margin for output (ratio of remaining context) */
|
|
45
|
+
safetyMargin: 0.5,
|
|
46
|
+
/** Lines to preserve when truncating output */
|
|
47
|
+
preserveHeaderLines: 3,
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* File path patterns and directories
|
|
52
|
+
*/
|
|
53
|
+
paths: {
|
|
54
|
+
/** Directory for ledger files */
|
|
55
|
+
ledgerDir: "thoughts/ledgers",
|
|
56
|
+
/** Prefix for ledger filenames */
|
|
57
|
+
ledgerPrefix: "CONTINUITY_",
|
|
58
|
+
/** Context files to inject from project root */
|
|
59
|
+
rootContextFiles: ["ARCHITECTURE.md", "CODE_STYLE.md", "README.md"] as readonly string[],
|
|
60
|
+
/** Context files to collect when walking up directories */
|
|
61
|
+
dirContextFiles: ["README.md"] as readonly string[],
|
|
62
|
+
/** Pattern to match plan files */
|
|
63
|
+
planPattern: /thoughts\/shared\/plans\/.*\.md$/,
|
|
64
|
+
/** Pattern to match ledger files */
|
|
65
|
+
ledgerPattern: /thoughts\/ledgers\/CONTINUITY_.*\.md$/,
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Timeout settings
|
|
70
|
+
*/
|
|
71
|
+
timeouts: {
|
|
72
|
+
/** BTCA command timeout (ms) */
|
|
73
|
+
btcaMs: 120_000,
|
|
74
|
+
/** Success toast duration (ms) */
|
|
75
|
+
toastSuccessMs: 3000,
|
|
76
|
+
/** Warning toast duration (ms) */
|
|
77
|
+
toastWarningMs: 4000,
|
|
78
|
+
/** Error toast duration (ms) */
|
|
79
|
+
toastErrorMs: 5000,
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Various limits
|
|
84
|
+
*/
|
|
85
|
+
limits: {
|
|
86
|
+
/** File size threshold for triggering extraction (bytes) */
|
|
87
|
+
largeFileBytes: 100 * 1024,
|
|
88
|
+
/** Max lines to return without extraction */
|
|
89
|
+
maxLinesNoExtract: 200,
|
|
90
|
+
/** Max lines in PTY buffer */
|
|
91
|
+
ptyMaxBufferLines: 50_000,
|
|
92
|
+
/** Default read limit for PTY */
|
|
93
|
+
ptyDefaultReadLimit: 500,
|
|
94
|
+
/** Max line length for PTY output */
|
|
95
|
+
ptyMaxLineLength: 2000,
|
|
96
|
+
/** Max matches to show from ast-grep */
|
|
97
|
+
astGrepMaxMatches: 100,
|
|
98
|
+
/** Context cache TTL (ms) */
|
|
99
|
+
contextCacheTtlMs: 30_000,
|
|
100
|
+
/** Max entries in context cache */
|
|
101
|
+
contextCacheMaxSize: 100,
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Octto (browser-based brainstorming) settings
|
|
106
|
+
*/
|
|
107
|
+
octto: {
|
|
108
|
+
/** Answer timeout (ms) - 5 minutes */
|
|
109
|
+
answerTimeoutMs: 5 * 60 * 1000,
|
|
110
|
+
/** Review timeout (ms) - 10 minutes */
|
|
111
|
+
reviewTimeoutMs: 10 * 60 * 1000,
|
|
112
|
+
/** Max iterations in brainstorm loop */
|
|
113
|
+
maxIterations: 50,
|
|
114
|
+
/** Max follow-up questions per branch */
|
|
115
|
+
maxQuestions: 15,
|
|
116
|
+
/** State directory for brainstorm sessions */
|
|
117
|
+
stateDir: "thoughts/brainstorms",
|
|
118
|
+
/** Bind address for brainstorm server */
|
|
119
|
+
bindAddress: "127.0.0.1",
|
|
120
|
+
/** Allow overriding bind address for remote access */
|
|
121
|
+
allowRemoteBind: false,
|
|
122
|
+
},
|
|
123
|
+
} as const;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/utils/errors.ts
|
|
2
|
+
// Unified error handling utilities
|
|
3
|
+
// Used by tools and hooks for consistent error formatting and logging
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Safely extract error message from unknown error type.
|
|
7
|
+
* Handles Error instances, strings, and other types.
|
|
8
|
+
*/
|
|
9
|
+
export function extractErrorMessage(e: unknown): string {
|
|
10
|
+
if (e instanceof Error) {
|
|
11
|
+
return e.message;
|
|
12
|
+
}
|
|
13
|
+
return String(e);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Format error message for tool responses (LLM-facing).
|
|
18
|
+
* @param message - The error message
|
|
19
|
+
* @param context - Optional context about what operation failed
|
|
20
|
+
*/
|
|
21
|
+
export function formatToolError(message: string, context?: string): string {
|
|
22
|
+
if (context && context.trim()) {
|
|
23
|
+
return `Error (${context}): ${message}`;
|
|
24
|
+
}
|
|
25
|
+
return `Error: ${message}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Execute a function and log any errors without throwing.
|
|
30
|
+
* Use for non-critical operations that shouldn't fail the main flow.
|
|
31
|
+
* @param module - Module name for log prefix
|
|
32
|
+
* @param fn - Function to execute
|
|
33
|
+
* @returns Result or undefined if error occurred
|
|
34
|
+
*/
|
|
35
|
+
export function catchAndLog<T>(module: string, fn: () => T): T | undefined {
|
|
36
|
+
try {
|
|
37
|
+
return fn();
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.error(`[${module}] ${extractErrorMessage(e)}`);
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Async version of catchAndLog.
|
|
46
|
+
* @param module - Module name for log prefix
|
|
47
|
+
* @param fn - Async function to execute
|
|
48
|
+
* @returns Result or undefined if error occurred
|
|
49
|
+
*/
|
|
50
|
+
export async function catchAndLogAsync<T>(module: string, fn: () => Promise<T>): Promise<T | undefined> {
|
|
51
|
+
try {
|
|
52
|
+
return await fn();
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.error(`[${module}] ${extractErrorMessage(e)}`);
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/utils/logger.ts
|
|
2
|
+
// Standardized logging with module prefixes
|
|
3
|
+
// Used across hooks and tools for consistent log formatting
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Logger with standardized [module] prefix format.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* log.info("my-hook", "Processing file");
|
|
10
|
+
* log.error("my-tool", "Failed to read", error);
|
|
11
|
+
* log.debug("my-module", "Verbose info"); // Only when DEBUG env set
|
|
12
|
+
*/
|
|
13
|
+
export const log = {
|
|
14
|
+
/**
|
|
15
|
+
* Debug level - only outputs when DEBUG environment variable is set.
|
|
16
|
+
*/
|
|
17
|
+
debug(module: string, message: string): void {
|
|
18
|
+
if (process.env.DEBUG) {
|
|
19
|
+
console.log(`[${module}] ${message}`);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Info level - general informational messages.
|
|
25
|
+
*/
|
|
26
|
+
info(module: string, message: string): void {
|
|
27
|
+
console.log(`[${module}] ${message}`);
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Warning level - non-fatal issues.
|
|
32
|
+
*/
|
|
33
|
+
warn(module: string, message: string): void {
|
|
34
|
+
console.warn(`[${module}] ${message}`);
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Error level - errors that were caught and handled.
|
|
39
|
+
* @param module - Module name for prefix
|
|
40
|
+
* @param message - Error description
|
|
41
|
+
* @param error - Optional error object for additional context
|
|
42
|
+
*/
|
|
43
|
+
error(module: string, message: string, error?: unknown): void {
|
|
44
|
+
if (error !== undefined) {
|
|
45
|
+
console.error(`[${module}] ${message}`, error);
|
|
46
|
+
} else {
|
|
47
|
+
console.error(`[${module}] ${message}`);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|