astrabot 0.1.0

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 (47) hide show
  1. package/README.md +411 -0
  2. package/ai/ai.config.ts +27 -0
  3. package/ai/auto-retry.ts +117 -0
  4. package/ai/config-loader.ts +132 -0
  5. package/ai/index.ts +4 -0
  6. package/ai/retry-prompt.ts +30 -0
  7. package/bin/astra +2 -0
  8. package/core/retry/error-classifier.ts +208 -0
  9. package/core/retry/index.ts +29 -0
  10. package/core/retry/retry-config.ts +142 -0
  11. package/core/retry/retry-engine.ts +215 -0
  12. package/game/index.html +573 -0
  13. package/game/neon-breaker.html +1037 -0
  14. package/index.ts +140 -0
  15. package/modes/agent/action-tracker.ts +47 -0
  16. package/modes/agent/agent-tools.ts +338 -0
  17. package/modes/agent/approval.ts +184 -0
  18. package/modes/agent/diff-view.ts +34 -0
  19. package/modes/agent/orchestrator.ts +234 -0
  20. package/modes/agent/tool-executor.ts +993 -0
  21. package/modes/agent/types.ts +68 -0
  22. package/modes/ask/orchestrator.ts +230 -0
  23. package/modes/auto.ts +88 -0
  24. package/modes/cli.ts +43 -0
  25. package/modes/multi/agent-pool-manager.ts +337 -0
  26. package/modes/multi/examples.ts +441 -0
  27. package/modes/multi/message-broker.ts +179 -0
  28. package/modes/multi/multi-agent-orchestrator.ts +891 -0
  29. package/modes/multi/orchestrator.ts +414 -0
  30. package/modes/multi/types.ts +245 -0
  31. package/modes/multi/workflow-builder.ts +569 -0
  32. package/modes/plan/orchestrator.ts +198 -0
  33. package/modes/plan/planner.ts +121 -0
  34. package/modes/plan/selection.ts +43 -0
  35. package/modes/plan/types.ts +13 -0
  36. package/modes/plan/web-tools.ts +132 -0
  37. package/modes/setup.ts +210 -0
  38. package/package.json +62 -0
  39. package/session/index.ts +45 -0
  40. package/session/session-context.ts +188 -0
  41. package/session/session-manager.ts +374 -0
  42. package/session/session-tools.ts +109 -0
  43. package/session/store.ts +278 -0
  44. package/tsconfig.json +30 -0
  45. package/tui/spinner.ts +182 -0
  46. package/tui/terminal-md.ts +17 -0
  47. package/tui/wakeup.ts +231 -0
@@ -0,0 +1,68 @@
1
+ export type ActionType =
2
+ | 'file_create'
3
+ | 'file_modify'
4
+ | 'file_delete'
5
+ | 'folder_create'
6
+ | 'code_analysis'
7
+ | 'tool_execute'
8
+
9
+ export type ActionStatus = 'pending' | 'executed' | 'approved' | 'rejected'
10
+
11
+ export interface ActionLog {
12
+ id: string;
13
+ timestamp: Date;
14
+ type: ActionType;
15
+ path: string;
16
+ details: {
17
+ before?: string;
18
+ after?: string;
19
+ toolName?: string;
20
+ toolResult?: string;
21
+ error?: string;
22
+ command?: string;
23
+ };
24
+ status: ActionStatus;
25
+ userApproved?: boolean;
26
+ }
27
+
28
+ export interface AgentConfig {
29
+ codebasePath: string;
30
+ maxFileSizeToRead: number;
31
+ excludePatterns: string[];
32
+ tools: {
33
+ allowShellExecution: boolean;
34
+ allowFileModification: boolean;
35
+ allowFileCreation: boolean;
36
+ allowFolderCreation: boolean;
37
+ }
38
+ }
39
+
40
+ export const defaultAgentConfig = (): AgentConfig => ({
41
+ codebasePath: process.cwd(),
42
+ maxFileSizeToRead: 1024*1024,
43
+ excludePatterns: [
44
+ 'node_modules',
45
+ '.git',
46
+ 'dist',
47
+ 'build',
48
+ '.next',
49
+ '*.log',
50
+ '.env*'
51
+ ],
52
+ tools: {
53
+ allowShellExecution: true,
54
+ allowFileModification: true,
55
+ allowFileCreation: true,
56
+ allowFolderCreation: true,
57
+ }
58
+ })
59
+
60
+ export function isMutationType(t: ActionType): boolean {
61
+ return (
62
+ t==='file_create' ||
63
+ t==='file_modify' ||
64
+ t==='file_delete' ||
65
+ t==='folder_create' ||
66
+ t==='tool_execute'
67
+ )
68
+ }
@@ -0,0 +1,230 @@
1
+ import z from "zod";
2
+ import chalk from "chalk";
3
+ import { confirm, isCancel, text } from "@clack/prompts";
4
+ import { ToolLoopAgent, stepCountIs } from "ai";
5
+ import { getAgentModel } from "../../ai/ai.config";
6
+ import { ActionTracker } from "../agent/action-tracker";
7
+ import { ToolExecutor } from "../agent/tool-executor";
8
+ import { createAgentTools } from "../agent/agent-tools";
9
+ import { defaultAgentConfig } from "../agent/types";
10
+ import { runApprovalFlow } from "../agent/approval";
11
+ import { renderTerminalMarkdown } from "../../tui/terminal-md";
12
+ import { createWebTools } from "../plan/web-tools";
13
+ import { withSpinner } from "../../tui/spinner";
14
+ import { beginSession, endSession, markSessionInterrupted } from "../../session";
15
+ import { createSessionTools } from "../../session/session-tools";
16
+ import { promptToRetryAiCall } from "../../ai/retry-prompt";
17
+
18
+ function createReadOnlyTools(executor: ToolExecutor) {
19
+ const all = createAgentTools(executor);
20
+ const {
21
+ create_file: _cf,
22
+ modify_file: _mf,
23
+ delete_file: _df,
24
+ create_folder: _cfo,
25
+ replace_in_file: _rif,
26
+ append_to_file: _atf,
27
+ insert_at_line: _ial,
28
+ run_command: _rc,
29
+ run_background_command: _rbc,
30
+ execute_shell: _es,
31
+ run_tests: _rt,
32
+ run_test_file: _rtf,
33
+ lint_project: _lp,
34
+ format_project: _fp,
35
+ ...readOnly
36
+ } = all;
37
+ return readOnly;
38
+ }
39
+
40
+ function asMd(questions: string, answer: string): string {
41
+ return `## Question\n\n${questions.trim()}\n\n## Answer\n\n${answer.trim()}\n`;
42
+ }
43
+
44
+ /**
45
+ * Exponential backoff with jitter.
46
+ * Base 1s, max 30s: 1s → 2s → 4s → 8s → 16s → 30s (capped)
47
+ */
48
+ async function backoffDelay(attemptNumber: number): Promise<void> {
49
+ const baseDelayMs = 1000;
50
+ const maxDelayMs = 30000;
51
+ const jitterMs = Math.random() * 500; // ±0ms to 500ms
52
+ const delayMs = Math.min(maxDelayMs, (1 << attemptNumber) * baseDelayMs + jitterMs);
53
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
54
+ }
55
+
56
+ export async function runAskMode(preCapturedGoal?: string) {
57
+ console.log(chalk.bold("\nAsk Mode\n"));
58
+
59
+ const goal = preCapturedGoal ?? await text({
60
+ message: "What would you like the agent to do for you?",
61
+ placeholder: "Concrete task for this codebase...",
62
+ });
63
+
64
+ if (isCancel(goal) || !goal.trim()) return;
65
+
66
+ const config = defaultAgentConfig();
67
+ config.tools.allowShellExecution = false;
68
+ config.tools.allowFileModification = false;
69
+ config.tools.allowFolderCreation = false;
70
+ config.tools.allowFileCreation = false;
71
+
72
+ const tracker = new ActionTracker();
73
+ const executor = new ToolExecutor(tracker, config);
74
+
75
+ const { entry: sessionEntry } = beginSession({
76
+ workspacePath: config.codebasePath,
77
+ mode: "ask",
78
+ goal: goal.trim(),
79
+ });
80
+
81
+ const agent = new ToolLoopAgent({
82
+ model: getAgentModel(),
83
+ stopWhen: stepCountIs(25),
84
+ tools: {
85
+ ...createReadOnlyTools(executor),
86
+ ...createWebTools(tracker),
87
+ ...createSessionTools(config.codebasePath),
88
+ },
89
+ });
90
+
91
+ // ── System Prompt Injection for Organic Identity ──────────────────────
92
+ const systemDirective =
93
+ "You are Astra, an AI-native development CLI companion tool built to help " +
94
+ "the user navigate, analyze, and build within their workspace codebase. If the user asks " +
95
+ "who you are, what your name is, or what model you are running on, you must always identify " +
96
+ "yourself exclusively as Astra. Do not mention your underlying model architecture or provider.";
97
+
98
+ const combinedPrompt = `${systemDirective}\n\nUser Question: ${goal.trim()}`;
99
+
100
+ // ── Retry loop with bounded attempt count and exponential backoff ──────
101
+ const MAX_RETRIES = 5;
102
+ let result;
103
+ let attemptCount = 0;
104
+
105
+ while (attemptCount < MAX_RETRIES) {
106
+ try {
107
+ result = await withSpinner(
108
+ {
109
+ message: "Thinking...",
110
+ doneMessage: "here's the answer",
111
+ failMessage: "couldn't get an answer",
112
+ },
113
+ () =>
114
+ agent.generate({
115
+ prompt: combinedPrompt,
116
+ onStepFinish: ({ toolCalls }) => {
117
+ for (const tc of toolCalls) {
118
+ const preview = JSON.stringify(tc.input).slice(0, 160);
119
+ console.log(
120
+ chalk.cyan(" -"),
121
+ chalk.bold(String(tc.toolName)),
122
+ chalk.dim(preview + (preview.length > 160 ? "..." : "")),
123
+ );
124
+ }
125
+ },
126
+ }),
127
+ );
128
+ break; // Success — exit retry loop
129
+ } catch (error) {
130
+ attemptCount++;
131
+ const attemptsRemaining = MAX_RETRIES - attemptCount;
132
+
133
+ // If out of retries, fail gracefully
134
+ if (attemptsRemaining <= 0) {
135
+ console.log(
136
+ chalk.red(`\n✗ AI provider error after ${MAX_RETRIES} attempts. Giving up.\n`)
137
+ );
138
+ markSessionInterrupted(sessionEntry.id);
139
+ await endSession(
140
+ sessionEntry.id,
141
+ tracker,
142
+ `Provider error after ${MAX_RETRIES} retries.`,
143
+ );
144
+ executor.discardChanges();
145
+ return;
146
+ }
147
+
148
+ // Ask if user wants to retry
149
+ const retry = await promptToRetryAiCall(
150
+ `The answer request hit a provider error (attempt ${attemptCount}/${MAX_RETRIES}).`,
151
+ error,
152
+ );
153
+
154
+ if (!retry) {
155
+ markSessionInterrupted(sessionEntry.id);
156
+ await endSession(
157
+ sessionEntry.id,
158
+ tracker,
159
+ "User cancelled after provider error.",
160
+ );
161
+ executor.discardChanges();
162
+ return;
163
+ }
164
+
165
+ // Apply backoff before next attempt
166
+ console.log(
167
+ chalk.dim(
168
+ ` Waiting before retry ${attemptCount + 1}/${MAX_RETRIES}...`
169
+ )
170
+ );
171
+ await backoffDelay(attemptCount - 1);
172
+ }
173
+ }
174
+
175
+ const answer = result!.text.trim() || "(agent returned empty response)";
176
+
177
+ console.log("\n" + renderTerminalMarkdown(answer) + "\n");
178
+
179
+ const wantsSave = await confirm({
180
+ message: "Do you want to save this response to a .md file in the current directory?",
181
+ initialValue: false,
182
+ });
183
+
184
+ if (isCancel(wantsSave) || !wantsSave) {
185
+ await endSession(sessionEntry.id, tracker, answer);
186
+ return;
187
+ }
188
+
189
+ const filename = await text({
190
+ message: "Filename",
191
+ initialValue: "response.md",
192
+ validate: (v) => {
193
+ const s = (v ?? "").trim();
194
+ if (!s) return "Required";
195
+ if (s.includes("..") || s.includes("/") || s.includes("\\"))
196
+ return "Do not specify path in filename";
197
+ if (!s.toLocaleLowerCase().endsWith(".md"))
198
+ return "Must end with .md";
199
+ },
200
+ });
201
+
202
+ if (isCancel(filename)) {
203
+ await endSession(sessionEntry.id, tracker, answer);
204
+ return;
205
+ }
206
+
207
+ config.tools.allowFileCreation = true;
208
+ executor.createFile(filename, asMd(goal, answer));
209
+ config.tools.allowFileCreation = false;
210
+
211
+ const ok = await runApprovalFlow(tracker);
212
+ if (!ok) {
213
+ await endSession(sessionEntry.id, tracker, answer);
214
+ return executor.discardChanges();
215
+ }
216
+
217
+ await withSpinner(
218
+ {
219
+ message: "Saving response...",
220
+ doneMessage: "response saved",
221
+ failMessage: "save failed",
222
+ },
223
+ async () => {
224
+ executor.applyApprovedFromTracker();
225
+ },
226
+ );
227
+
228
+ await endSession(sessionEntry.id, tracker, answer);
229
+ executor.discardChanges();
230
+ }
package/modes/auto.ts ADDED
@@ -0,0 +1,88 @@
1
+ import { text } from "@clack/prompts";
2
+ import { generateText, stepCountIs } from "ai";
3
+ import chalk from "chalk";
4
+ import { getAgentModel } from "../ai";
5
+ import { runAgentMode } from "./agent/orchestrator";
6
+ import { runAskMode } from "./ask/orchestrator";
7
+ import { runPlanMode } from "./plan/orchestrator";
8
+ import { runMultiAgentMode } from "./multi/orchestrator";
9
+ import { withSpinner } from "../tui/spinner";
10
+ import { beginSession, endSession } from "../session";
11
+ import { ActionTracker } from "./agent/action-tracker"; // Adjust path if ActionTracker is located elsewhere
12
+
13
+ export async function runAutoMode() {
14
+ console.log(chalk.bold("\n ✨ Auto-Routing Session\n"));
15
+
16
+ const goal = await text({
17
+ message: "What would you like to do?",
18
+ placeholder: "Type anything (e.g., 'fix the bug in store.ts' or 'explain how this app works')...",
19
+ });
20
+
21
+ if (!goal || typeof goal !== "string" || !goal.trim()) return;
22
+
23
+ const trimmedGoal = goal.trim();
24
+
25
+ // 1. Initialize and register the "auto" session in your database store
26
+ const { entry: sessionEntry } = beginSession({
27
+ workspacePath: process.cwd(),
28
+ mode: "auto",
29
+ goal: trimmedGoal,
30
+ });
31
+
32
+ // Create a generic tracker instance to satisfy endSession requirements
33
+ const autoTracker = new ActionTracker();
34
+ let routedMode: "agent" | "ask" | "plan" | "multi" = "agent";
35
+
36
+ try {
37
+ routedMode = await withSpinner(
38
+ {
39
+ message: "Analysing request intent...",
40
+ hideTime: true,
41
+ },
42
+ async () => {
43
+ const result = await generateText({
44
+ model: getAgentModel(),
45
+ stopWhen: stepCountIs(1),
46
+ prompt: [
47
+ "You are an intent classification router for a development workflow environment.",
48
+ "Classify the following user request into exactly one of these 4 options:",
49
+ "- 'ask': User is asking questions, needs code explanations, or conceptual help without needing file edits.",
50
+ "- 'plan': User wants structural architectural layout designs or step-by-step checklists before writing code.",
51
+ "- 'multi': User explicitly requests multi-agent swarms or concurrent pipeline workers.",
52
+ "- 'agent': User wants to write code, modify files, delete files, refactor items, or run workspace terminal tasks.",
53
+ "",
54
+ `Request: "${trimmedGoal}"`,
55
+ "",
56
+ "Respond with exactly one word from the choices: ask, plan, multi, agent. Do not include markdown formatting or punctuation.",
57
+ ].join("\n"),
58
+ });
59
+
60
+ const word = result.text.trim().toLowerCase();
61
+ if (["ask", "plan", "multi", "agent"].includes(word)) {
62
+ return word as "agent" | "ask" | "plan" | "multi";
63
+ }
64
+ return "agent";
65
+ }
66
+ );
67
+ } catch {
68
+ routedMode = "agent";
69
+ }
70
+
71
+ // 2. Finalize and log the router's decision phase into the store history trail
72
+ await endSession(
73
+ sessionEntry.id,
74
+ autoTracker,
75
+ `Auto-router successfully mapped intent to down-stream [${routedMode}] engine.`
76
+ );
77
+
78
+ // 3. Execute the target handler, forwarding the pre-captured prompt input string
79
+ if (routedMode === "agent") {
80
+ await runAgentMode(trimmedGoal);
81
+ } else if (routedMode === "ask") {
82
+ await runAskMode(trimmedGoal);
83
+ } else if (routedMode === "plan") {
84
+ await runPlanMode(trimmedGoal);
85
+ } else if (routedMode === "multi") {
86
+ await runMultiAgentMode(trimmedGoal);
87
+ }
88
+ }
package/modes/cli.ts ADDED
@@ -0,0 +1,43 @@
1
+ import chalk from "chalk";
2
+ import {select, isCancel} from "@clack/prompts"
3
+ import { runAgentMode } from "./agent/orchestrator";
4
+ import { runAskMode } from "./ask/orchestrator";
5
+ import { runPlanMode } from "./plan/orchestrator";
6
+ import { runMultiAgentMode } from "./multi/orchestrator";
7
+ import { runAutoMode } from "./auto";
8
+
9
+ export async function runCliMode () {
10
+ while(true){
11
+ const mode = await select({
12
+ message: "Choose CLI mode:",
13
+ options: [
14
+ {value: "auto", label:"Auto Mode"},
15
+ {value: "agent", label:"Agent Mode"},
16
+ {value: "plan", label:"Plan Mode"},
17
+ {value: "ask", label:"Ask Mode"},
18
+ {value: "multi", label: "Multi-Agent Mode"},
19
+ {value: "back", label:" ⬅ Back to main menu"},
20
+ ]
21
+ })
22
+ if(isCancel(mode) || mode === "back") return
23
+
24
+ if(mode==="agent"){
25
+ await runAgentMode()
26
+ }
27
+ else if(mode==="plan"){
28
+ await runPlanMode()
29
+ }
30
+ else if(mode==="ask"){
31
+ await runAskMode()
32
+ }
33
+ else if(mode==="multi"){
34
+ await runMultiAgentMode()
35
+ }
36
+ else if(mode==="auto"){
37
+ await runAutoMode()
38
+ }
39
+ if(mode!=="agent" && mode!=="plan" && mode!=="ask" && mode!=="multi" && mode!="auto"){
40
+ console.log(chalk.yellow('\n This mode is not implemented yet. \n'))
41
+ }
42
+ }
43
+ }