agdi 3.3.0 → 3.3.4

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.
@@ -0,0 +1,45 @@
1
+ // src/core/event-bus.ts
2
+ import { EventEmitter } from "events";
3
+ var agentEventBus = new EventEmitter();
4
+ agentEventBus.setMaxListeners(50);
5
+ var thoughtFlushTimeout = null;
6
+ var pendingThought = null;
7
+ function emitAgentEvent(event) {
8
+ const fullEvent = {
9
+ ...event,
10
+ timestamp: Date.now()
11
+ };
12
+ if (event.type === "thought") {
13
+ pendingThought = fullEvent;
14
+ if (!thoughtFlushTimeout) {
15
+ thoughtFlushTimeout = setTimeout(() => {
16
+ if (pendingThought) {
17
+ agentEventBus.emit("agent_event", pendingThought);
18
+ pendingThought = null;
19
+ }
20
+ thoughtFlushTimeout = null;
21
+ }, 100);
22
+ }
23
+ } else {
24
+ agentEventBus.emit("agent_event", fullEvent);
25
+ }
26
+ }
27
+ function cleanupEventBus() {
28
+ agentEventBus.removeAllListeners();
29
+ if (thoughtFlushTimeout) {
30
+ clearTimeout(thoughtFlushTimeout);
31
+ }
32
+ }
33
+ process.on("exit", cleanupEventBus);
34
+ process.on("SIGINT", cleanupEventBus);
35
+ process.on("uncaughtException", (err) => {
36
+ console.error("Uncaught exception:", err);
37
+ cleanupEventBus();
38
+ process.exit(1);
39
+ });
40
+
41
+ export {
42
+ agentEventBus,
43
+ emitAgentEvent,
44
+ cleanupEventBus
45
+ };
@@ -0,0 +1,10 @@
1
+ import {
2
+ agentEventBus,
3
+ cleanupEventBus,
4
+ emitAgentEvent
5
+ } from "./chunk-A5UTYKKM.js";
6
+ export {
7
+ agentEventBus,
8
+ cleanupEventBus,
9
+ emitAgentEvent
10
+ };
package/dist/index.js CHANGED
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ agentEventBus,
4
+ emitAgentEvent
5
+ } from "./chunk-A5UTYKKM.js";
2
6
  import {
3
7
  loadConfig,
4
8
  saveConfig
@@ -339,9 +343,9 @@ var errorGradient = gradient([THEME.red, "#b91c1c"]);
339
343
  var goldGradient = gradient([THEME.yellow, "#fbbf24"]);
340
344
  async function renderBanner(version = "v2.6.0") {
341
345
  console.clear();
342
- const text = await new Promise((resolve) => {
346
+ const text = await new Promise((resolve2) => {
343
347
  figlet("AGDI", { font: "Slant" }, (err, data) => {
344
- resolve(data || "AGDI");
348
+ resolve2(data || "AGDI");
345
349
  });
346
350
  });
347
351
  console.log(brandGradient.multiline(text));
@@ -1572,7 +1576,7 @@ async function runProject(targetDir) {
1572
1576
  console.log(chalk6.yellow("\u{1F4E6} Installing dependencies...\n"));
1573
1577
  const installSpinner = ora3("Running npm install...").start();
1574
1578
  try {
1575
- await new Promise((resolve, reject) => {
1579
+ await new Promise((resolve2, reject) => {
1576
1580
  const install = spawn("npm", ["install"], {
1577
1581
  cwd: absoluteDir,
1578
1582
  stdio: "inherit",
@@ -1581,7 +1585,7 @@ async function runProject(targetDir) {
1581
1585
  install.on("close", (code) => {
1582
1586
  if (code === 0) {
1583
1587
  installSpinner.succeed("Dependencies installed!");
1584
- resolve();
1588
+ resolve2();
1585
1589
  } else {
1586
1590
  installSpinner.fail("npm install failed");
1587
1591
  reject(new Error(`npm install exited with code ${code}`));
@@ -2072,6 +2076,13 @@ var BaseAgent = class {
2072
2076
  * Log a message (if verbose)
2073
2077
  */
2074
2078
  log(message, type = "info") {
2079
+ emitAgentEvent({
2080
+ type: "log",
2081
+ agentName: this.name,
2082
+ role: this.role,
2083
+ message,
2084
+ metadata: { type }
2085
+ });
2075
2086
  if (!this.verbose) return;
2076
2087
  const prefix = `[${this.name}]`;
2077
2088
  switch (type) {
@@ -2090,10 +2101,29 @@ var BaseAgent = class {
2090
2101
  }
2091
2102
  /**
2092
2103
  * Generate a response from the LLM
2104
+ * Includes timeout protection to prevent indefinite hanging
2093
2105
  */
2094
- async think(prompt) {
2106
+ async think(prompt, timeoutMs = 12e4) {
2095
2107
  this.log("Thinking...", "info");
2096
- const response = await this.llm.generate(prompt, this.getSystemPrompt());
2108
+ emitAgentEvent({
2109
+ type: "thought",
2110
+ agentName: this.name,
2111
+ role: this.role,
2112
+ message: `Analyzing task...`
2113
+ });
2114
+ const response = await Promise.race([
2115
+ this.llm.generate(prompt, this.getSystemPrompt()),
2116
+ new Promise(
2117
+ (_, reject) => setTimeout(() => reject(new Error(`LLM request timed out after ${timeoutMs / 1e3}s`)), timeoutMs)
2118
+ )
2119
+ ]);
2120
+ const summary = response.text.split("\n")[0].substring(0, 80) + "...";
2121
+ emitAgentEvent({
2122
+ type: "thought",
2123
+ agentName: this.name,
2124
+ role: this.role,
2125
+ message: `Generated response: ${summary}`
2126
+ });
2097
2127
  this.collectSourcesFromText(response.text);
2098
2128
  return response.text;
2099
2129
  }
@@ -3124,12 +3154,17 @@ Requirements:
3124
3154
  }
3125
3155
  try {
3126
3156
  const prodFlag = production ? "--prod" : "";
3127
- const command = `npx vercel ${prodFlag} --yes --token ${this.vercelToken}`;
3128
- this.log(`Running: npx vercel ${prodFlag} --yes --token ***`, "info");
3157
+ const command = `npx vercel ${prodFlag} --yes`;
3158
+ this.log(`Running: npx vercel ${prodFlag} --yes`, "info");
3129
3159
  const { stdout, stderr } = await execAsync2(command, {
3130
3160
  cwd,
3131
- timeout: 3e5
3161
+ timeout: 3e5,
3132
3162
  // 5 minutes
3163
+ env: {
3164
+ ...process.env,
3165
+ VERCEL_TOKEN: this.vercelToken
3166
+ // Pass via env, not CLI (security)
3167
+ }
3133
3168
  });
3134
3169
  const output = stdout + stderr;
3135
3170
  const urlMatch = output.match(/https:\/\/[^\s]+\.vercel\.app/);
@@ -3179,10 +3214,15 @@ Requirements:
3179
3214
  try {
3180
3215
  const prodFlag = production ? "--prod" : "";
3181
3216
  const outputDir = await detectBuildOutputDir(cwd);
3182
- const command = `npx netlify deploy ${prodFlag} --auth ${this.netlifyToken} --dir=${outputDir}`;
3217
+ const command = `npx netlify deploy ${prodFlag} --dir=${outputDir}`;
3183
3218
  const { stdout, stderr } = await execAsync2(command, {
3184
3219
  cwd,
3185
- timeout: 3e5
3220
+ timeout: 3e5,
3221
+ env: {
3222
+ ...process.env,
3223
+ NETLIFY_AUTH_TOKEN: this.netlifyToken
3224
+ // Pass via env, not CLI (security)
3225
+ }
3186
3226
  });
3187
3227
  const output = stdout + (stderr || "");
3188
3228
  const urlMatch = output.match(/https:\/\/[^\s]+\.netlify\.app/);
@@ -3568,6 +3608,14 @@ ${qaResult.errors?.join("\n")}`,
3568
3608
  */
3569
3609
  async executeTask(task) {
3570
3610
  this.log(`[${task.assignee}] ${task.title}`, "task");
3611
+ const { emitAgentEvent: emitAgentEvent2 } = await import("./event-bus-Q3WCETQQ.js");
3612
+ emitAgentEvent2({
3613
+ type: "handoff",
3614
+ agentName: task.assignee,
3615
+ // e.g. "frontend"
3616
+ role: task.assignee,
3617
+ message: `Taking control for task: ${task.title}`
3618
+ });
3571
3619
  await this.appendTrace("task_start", {
3572
3620
  id: task.id,
3573
3621
  title: task.title,
@@ -3615,10 +3663,21 @@ ${qaResult.errors?.join("\n")}`,
3615
3663
  }
3616
3664
  /**
3617
3665
  * Write generated files to disk
3666
+ * SECURITY: All paths are validated to prevent directory traversal attacks
3618
3667
  */
3619
3668
  async writeFiles(files, filesCreated, filesModified) {
3669
+ const workspaceRoot = path5.resolve(this.config.workspaceRoot);
3620
3670
  for (const file of files) {
3621
- const fullPath = path5.join(this.config.workspaceRoot, file.path);
3671
+ const validationResult = this.validateFilePath(file.path, workspaceRoot);
3672
+ if (!validationResult.valid) {
3673
+ this.log(`\u{1F6E1}\uFE0F Blocked unsafe path: ${file.path} - ${validationResult.error}`, "error");
3674
+ await this.appendTrace("file_blocked", {
3675
+ path: file.path,
3676
+ reason: validationResult.error
3677
+ });
3678
+ continue;
3679
+ }
3680
+ const fullPath = validationResult.resolvedPath;
3622
3681
  const dir = path5.dirname(fullPath);
3623
3682
  try {
3624
3683
  await fs5.mkdir(dir, { recursive: true });
@@ -3633,7 +3692,7 @@ ${qaResult.errors?.join("\n")}`,
3633
3692
  }
3634
3693
  await fs5.writeFile(fullPath, file.content, "utf-8");
3635
3694
  const afterContent = file.content;
3636
- const outputPath = path5.join(this.runDir, "outputs", file.path);
3695
+ const outputPath = path5.join(this.runDir, "outputs", validationResult.normalizedPath);
3637
3696
  await fs5.mkdir(path5.dirname(outputPath), { recursive: true });
3638
3697
  await fs5.writeFile(outputPath, file.content, "utf-8");
3639
3698
  const beforeHash = beforeContent ? this.hashContent(beforeContent) : null;
@@ -3660,6 +3719,54 @@ ${qaResult.errors?.join("\n")}`,
3660
3719
  }
3661
3720
  }
3662
3721
  }
3722
+ /**
3723
+ * Validate a file path to prevent directory traversal attacks
3724
+ * SECURITY: Critical function - blocks paths that escape workspace root
3725
+ */
3726
+ validateFilePath(filePath, workspaceRoot) {
3727
+ if (path5.isAbsolute(filePath)) {
3728
+ return {
3729
+ valid: false,
3730
+ error: "Absolute paths not allowed"
3731
+ };
3732
+ }
3733
+ const normalizedPath = path5.normalize(filePath).replace(/^(\.\.[\\/])+/, "");
3734
+ const resolvedPath = path5.resolve(workspaceRoot, filePath);
3735
+ if (filePath.includes("..")) {
3736
+ if (!resolvedPath.startsWith(workspaceRoot + path5.sep) && resolvedPath !== workspaceRoot) {
3737
+ return {
3738
+ valid: false,
3739
+ error: "Path traversal blocked: cannot escape workspace root"
3740
+ };
3741
+ }
3742
+ }
3743
+ if (!resolvedPath.startsWith(workspaceRoot)) {
3744
+ return {
3745
+ valid: false,
3746
+ error: "Path traversal blocked: resolved path outside workspace"
3747
+ };
3748
+ }
3749
+ const sensitivePatterns = [
3750
+ /^\.git\//,
3751
+ /^node_modules\//,
3752
+ /^\.env$/,
3753
+ /^\.env\./,
3754
+ /\/\.git\//
3755
+ ];
3756
+ for (const pattern of sensitivePatterns) {
3757
+ if (pattern.test(normalizedPath)) {
3758
+ return {
3759
+ valid: false,
3760
+ error: `Blocked write to sensitive path: ${normalizedPath}`
3761
+ };
3762
+ }
3763
+ }
3764
+ return {
3765
+ valid: true,
3766
+ resolvedPath,
3767
+ normalizedPath
3768
+ };
3769
+ }
3663
3770
  async initializeRun(userPrompt) {
3664
3771
  this.runId = uuidv42();
3665
3772
  this.runDir = path5.join(this.config.workspaceRoot, "runs", this.runId);
@@ -4154,142 +4261,207 @@ import { render } from "ink";
4154
4261
  // src/ui/tui.tsx
4155
4262
  import { useState, useEffect } from "react";
4156
4263
  import { Box, Text, useApp } from "ink";
4157
- import BigText from "ink-big-text";
4158
4264
  import TextInput from "ink-text-input";
4159
4265
  import fs7 from "fs";
4160
4266
  import path7 from "path";
4267
+ import os from "os";
4161
4268
  import { jsx, jsxs } from "react/jsx-runtime";
4269
+ var THEME2 = {
4270
+ bg: "black",
4271
+ border: "gray",
4272
+ accent: "blue",
4273
+ text: "white",
4274
+ dim: "gray",
4275
+ success: "green"
4276
+ };
4162
4277
  var BootScreen = ({ onComplete }) => {
4163
4278
  const [progress, setProgress] = useState(0);
4164
- const [log, setLog] = useState("");
4165
- const logs = [
4166
- "Initializing core systems...",
4167
- "Loading neural modules...",
4168
- "Connecting to Agdi Cloud...",
4169
- "Verifying agent credentials...",
4170
- "Mounting virtual filesystem...",
4171
- "Establishing secure uplink...",
4172
- "Syncing global state...",
4173
- "Boot sequence complete."
4174
- ];
4175
4279
  useEffect(() => {
4176
4280
  const timer = setInterval(() => {
4177
4281
  setProgress((prev) => {
4178
- const next = prev + 2;
4282
+ const next = prev + 10;
4179
4283
  if (next >= 100) {
4180
4284
  clearInterval(timer);
4181
- setTimeout(onComplete, 500);
4285
+ setTimeout(onComplete, 100);
4182
4286
  return 100;
4183
4287
  }
4184
4288
  return next;
4185
4289
  });
4186
- const index = Math.floor(progress / 100 * logs.length);
4187
- setLog(logs[index] || logs[logs.length - 1]);
4188
- }, 30);
4290
+ }, 20);
4189
4291
  return () => clearInterval(timer);
4190
- }, [progress]);
4191
- const width = 60;
4192
- const filled = Math.floor(width * progress / 100);
4193
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
4194
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", height: 20, children: [
4195
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(BigText, { text: "AGDI OS", font: "block", align: "center", colors: ["cyan", "blue"] }) }),
4196
- /* @__PURE__ */ jsx(Box, { marginY: 1, children: /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
4197
- bar,
4198
- " ",
4199
- progress,
4200
- "%"
4201
- ] }) }),
4202
- /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
4203
- "\u279C ",
4204
- log
4292
+ }, []);
4293
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", height: 15, children: [
4294
+ /* @__PURE__ */ jsx(Text, { color: THEME2.accent, bold: true, children: "Initializing Agdi Workspace..." }),
4295
+ /* @__PURE__ */ jsxs(Text, { color: THEME2.dim, children: [
4296
+ "\u2588",
4297
+ "\u2588".repeat(progress / 5)
4205
4298
  ] })
4206
4299
  ] });
4207
4300
  };
4208
- var FileExplorer = ({ cwd }) => {
4209
- const [files, setFiles] = useState([]);
4210
- useEffect(() => {
4211
- try {
4212
- const list = fs7.readdirSync(cwd).filter((f) => !f.startsWith(".")).slice(0, 15);
4213
- setFiles(list);
4214
- } catch {
4215
- setFiles(["<Error reading dir>"]);
4216
- }
4217
- }, [cwd]);
4218
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", title: " FILESYSTEM ", width: "30%", children: [
4219
- /* @__PURE__ */ jsxs(Text, { color: "cyan", bold: true, children: [
4220
- " ",
4221
- cwd
4222
- ] }),
4223
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
4224
- files.map((f, i) => /* @__PURE__ */ jsxs(Text, { color: "white", children: [
4225
- fs7.statSync(path7.join(cwd, f)).isDirectory() ? "\u{1F4C1} " : "\u{1F4C4} ",
4226
- f
4227
- ] }, i)),
4228
- files.length === 0 && /* @__PURE__ */ jsx(Text, { color: "gray", children: " (empty)" })
4229
- ] })
4301
+ var Sidebar = ({ history }) => {
4302
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: 25, borderStyle: "single", borderColor: THEME2.border, paddingX: 1, children: [
4303
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, borderStyle: "single", borderColor: THEME2.accent, justifyContent: "center", children: /* @__PURE__ */ jsx(Text, { bold: true, children: "+ New task" }) }),
4304
+ /* @__PURE__ */ jsx(Text, { color: THEME2.dim, bold: true, children: "RECENTS" }),
4305
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 0, children: history.length === 0 ? /* @__PURE__ */ jsx(Text, { color: THEME2.dim, children: "No recent sessions" }) : history.map((h, i) => /* @__PURE__ */ jsx(Text, { color: THEME2.text, children: h }, i)) })
4230
4306
  ] });
4231
4307
  };
4232
- var LogPanel = ({ logs }) => {
4233
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", title: " AGENT LOGS ", width: "70%", marginLeft: 1, children: [
4234
- logs.slice(-10).map((log, i) => /* @__PURE__ */ jsxs(Text, { color: "green", children: [
4235
- /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
4236
- "[",
4237
- (/* @__PURE__ */ new Date()).toLocaleTimeString(),
4238
- "]"
4239
- ] }),
4240
- " ",
4241
- log
4242
- ] }, i)),
4243
- logs.length === 0 && /* @__PURE__ */ jsx(Text, { color: "gray", children: "Waiting for agent activity..." })
4308
+ var ContextPanel = ({ files, agentStatus }) => {
4309
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: 30, borderStyle: "single", borderColor: THEME2.border, paddingX: 1, children: [
4310
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
4311
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Context" }),
4312
+ /* @__PURE__ */ jsx(Text, { color: THEME2.dim, children: "WORKING FILES" }),
4313
+ files.length === 0 ? /* @__PURE__ */ jsx(Text, { color: THEME2.dim, children: "None yet." }) : files.slice(0, 5).map((f, i) => /* @__PURE__ */ jsxs(Text, { color: THEME2.accent, children: [
4314
+ "\u{1F4C4} ",
4315
+ f
4316
+ ] }, i))
4317
+ ] }),
4318
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, borderStyle: "single", borderColor: THEME2.dim, padding: 0, children: [
4319
+ /* @__PURE__ */ jsx(Text, { bold: true, children: " Plugins" }),
4320
+ /* @__PURE__ */ jsx(Text, { color: THEME2.success, children: "\u2022 Scheduler" }),
4321
+ /* @__PURE__ */ jsx(Text, { color: THEME2.dim, children: " Run scheduled jobs" })
4322
+ ] }),
4323
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
4324
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Status" }),
4325
+ /* @__PURE__ */ jsxs(Text, { color: agentStatus === "IDLE" ? THEME2.dim : THEME2.success, children: [
4326
+ "\u2022 ",
4327
+ agentStatus
4328
+ ] })
4329
+ ] })
4244
4330
  ] });
4245
4331
  };
4246
- var ChatPanel = ({ history, onSend }) => {
4332
+ var ChatArea = ({ history, onSend, placeholder }) => {
4247
4333
  const [query, setQuery] = useState("");
4248
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", title: " MISSION CONTROL ", height: 12, children: [
4249
- /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, justifyContent: "flex-end", children: history.slice(-5).map((msg, i) => /* @__PURE__ */ jsxs(Box, { marginY: 0, children: [
4250
- /* @__PURE__ */ jsx(Text, { color: msg.role === "user" ? "cyan" : "magenta", bold: true, children: msg.role === "user" ? "YOU \u203A " : "AGDI \u203A " }),
4251
- /* @__PURE__ */ jsx(Text, { children: msg.text })
4334
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, borderStyle: "single", borderColor: THEME2.border, marginLeft: 0, marginRight: 0, children: [
4335
+ /* @__PURE__ */ jsx(Box, { justifyContent: "center", borderStyle: "single", borderBottomColor: THEME2.border, borderTop: false, borderLeft: false, borderRight: false, paddingBottom: 0, children: /* @__PURE__ */ jsx(Text, { bold: true, children: "Start a conversation" }) }),
4336
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, padding: 1, justifyContent: "flex-end", children: history.length === 0 ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", height: 10, children: [
4337
+ /* @__PURE__ */ jsx(Text, { color: THEME2.dim, children: "Describe what you want to do," }),
4338
+ /* @__PURE__ */ jsx(Text, { color: THEME2.dim, children: "and Agdi will take it from there." })
4339
+ ] }) : history.slice(-8).map((msg, i) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
4340
+ /* @__PURE__ */ jsx(Text, { bold: true, color: msg.role === "user" ? THEME2.text : THEME2.accent, children: msg.role === "user" ? "You" : "Agdi" }),
4341
+ /* @__PURE__ */ jsx(Text, { color: THEME2.text, children: msg.text })
4252
4342
  ] }, i)) }),
4253
- /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: "gray", marginTop: 1, children: [
4254
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u279C " }),
4255
- /* @__PURE__ */ jsx(
4256
- TextInput,
4257
- {
4258
- value: query,
4259
- onChange: setQuery,
4260
- onSubmit: (val) => {
4261
- onSend(val);
4262
- setQuery("");
4263
- }
4264
- }
4265
- )
4266
- ] })
4343
+ /* @__PURE__ */ jsx(Box, { borderStyle: "round", borderColor: THEME2.dim, paddingX: 1, marginX: 1, marginBottom: 1, children: /* @__PURE__ */ jsx(
4344
+ TextInput,
4345
+ {
4346
+ value: query,
4347
+ onChange: setQuery,
4348
+ onSubmit: (val) => {
4349
+ onSend(val);
4350
+ setQuery("");
4351
+ },
4352
+ placeholder
4353
+ }
4354
+ ) })
4267
4355
  ] });
4268
4356
  };
4269
4357
  var Dashboard = () => {
4270
4358
  const { exit } = useApp();
4271
- const [logs, setLogs] = useState(["System ready.", "Waiting for instructions."]);
4359
+ const [cwd, setCwd] = useState(process.cwd());
4272
4360
  const [chatHistory, setChatHistory] = useState([]);
4273
- const addLog = (msg) => setLogs((prev) => [...prev, msg]);
4274
- const handleCommand = (cmd) => {
4361
+ const [step, setStep] = useState("safety");
4362
+ const [pendingAction, setPendingAction] = useState(null);
4363
+ const [activeFiles, setActiveFiles] = useState([]);
4364
+ const [agentStatus, setAgentStatus] = useState("IDLE");
4365
+ useEffect(() => {
4366
+ const handleEvent = (event) => {
4367
+ if (event.type === "handoff") {
4368
+ setAgentStatus(event.role.toUpperCase());
4369
+ }
4370
+ if (event.type === "thought") {
4371
+ if (!event.message.startsWith("Analyzing")) {
4372
+ setChatHistory((prev) => [...prev, { role: "system", text: `[${event.agentName}] ${event.message}` }]);
4373
+ }
4374
+ }
4375
+ if (event.message.includes("Created:")) {
4376
+ const filename = event.message.split("Created:")[1].trim();
4377
+ setActiveFiles((prev) => [filename, ...prev].slice(0, 5));
4378
+ }
4379
+ };
4380
+ agentEventBus.on("agent_event", handleEvent);
4381
+ return () => {
4382
+ agentEventBus.off("agent_event", handleEvent);
4383
+ };
4384
+ }, []);
4385
+ useEffect(() => {
4386
+ if (step === "safety") {
4387
+ const home = os.homedir();
4388
+ const root = path7.parse(cwd).root;
4389
+ const isUnsafe = cwd === home || cwd === root;
4390
+ if (isUnsafe) {
4391
+ setChatHistory([
4392
+ { role: "system", text: `\u26A0\uFE0F Safety Check: Running in ${cwd}` },
4393
+ { role: "ai", text: "This directory is too broad. Should I create a new project folder? (yes/no)" }
4394
+ ]);
4395
+ setPendingAction("safety_confirm");
4396
+ } else {
4397
+ setStep("prompt");
4398
+ }
4399
+ }
4400
+ }, []);
4401
+ const handleCommand = async (cmd) => {
4275
4402
  if (cmd === "/exit") exit();
4276
4403
  setChatHistory((prev) => [...prev, { role: "user", text: cmd }]);
4277
- addLog(`Processing command: ${cmd}`);
4278
- setTimeout(() => {
4279
- setChatHistory((prev) => [...prev, { role: "ai", text: `I'm analyzing your request to "${cmd}"...` }]);
4280
- addLog("Agent [Manager] started planning phase.");
4281
- }, 800);
4404
+ if (step === "safety" && pendingAction === "safety_confirm") {
4405
+ if (cmd.toLowerCase().startsWith("y")) {
4406
+ setChatHistory((prev) => [...prev, { role: "ai", text: "Name your project:" }]);
4407
+ setPendingAction("create_folder");
4408
+ } else {
4409
+ setChatHistory((prev) => [...prev, { role: "ai", text: "Proceeding in current directory." }]);
4410
+ setStep("prompt");
4411
+ setPendingAction(null);
4412
+ }
4413
+ return;
4414
+ }
4415
+ if (step === "safety" && pendingAction === "create_folder") {
4416
+ const newPath = path7.join(cwd, cmd.replace(/[^a-z0-9-_]/gi, "-"));
4417
+ try {
4418
+ if (!fs7.existsSync(newPath)) fs7.mkdirSync(newPath);
4419
+ process.chdir(newPath);
4420
+ setCwd(newPath);
4421
+ setChatHistory((prev) => [...prev, { role: "system", text: `\u{1F4C2} Switched to ${newPath}` }]);
4422
+ setStep("prompt");
4423
+ setPendingAction(null);
4424
+ } catch (e) {
4425
+ setChatHistory((prev) => [...prev, { role: "system", text: `Error: ${e}` }]);
4426
+ }
4427
+ return;
4428
+ }
4429
+ if (step === "prompt") {
4430
+ setStep("active");
4431
+ setAgentStatus("MANAGER");
4432
+ const activeConfig = getActiveProvider();
4433
+ if (!activeConfig) {
4434
+ setChatHistory((prev) => [...prev, { role: "system", text: '\u274C No API key found. Run "agdi auth" first.' }]);
4435
+ return;
4436
+ }
4437
+ const llm = createLLMProvider(activeConfig.provider, {
4438
+ apiKey: activeConfig.apiKey,
4439
+ model: activeConfig.model
4440
+ });
4441
+ runSquadCommand(cmd, llm, {
4442
+ output: cwd,
4443
+ verbose: false,
4444
+ deploy: false
4445
+ }).then(() => {
4446
+ setAgentStatus("IDLE");
4447
+ setChatHistory((prev) => [...prev, { role: "ai", text: "Task completed." }]);
4448
+ }).catch((err) => {
4449
+ setAgentStatus("ERROR");
4450
+ setChatHistory((prev) => [...prev, { role: "system", text: `Error: ${err.message}` }]);
4451
+ });
4452
+ }
4282
4453
  };
4283
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, height: "100%", children: [
4284
- /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", marginBottom: 1, children: [
4285
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "AGDI v3.3.0 [ONLINE]" }),
4286
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "CPU: 12% | MEM: 450MB | NET: CONNECTED" })
4287
- ] }),
4288
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", flexGrow: 1, children: [
4289
- /* @__PURE__ */ jsx(FileExplorer, { cwd: process.cwd() }),
4290
- /* @__PURE__ */ jsx(LogPanel, { logs })
4291
- ] }),
4292
- /* @__PURE__ */ jsx(Box, { marginTop: 0, children: /* @__PURE__ */ jsx(ChatPanel, { history: chatHistory, onSend: handleCommand }) })
4454
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", height: 30, padding: 1, children: [
4455
+ /* @__PURE__ */ jsx(Sidebar, { history: ["New session - " + (/* @__PURE__ */ new Date()).toLocaleDateString()] }),
4456
+ /* @__PURE__ */ jsx(
4457
+ ChatArea,
4458
+ {
4459
+ history: chatHistory,
4460
+ onSend: handleCommand,
4461
+ placeholder: step === "prompt" ? "Ask Agdi..." : "Reply..."
4462
+ }
4463
+ ),
4464
+ /* @__PURE__ */ jsx(ContextPanel, { files: activeFiles, agentStatus })
4293
4465
  ] });
4294
4466
  };
4295
4467
  var App = () => {
@@ -4314,7 +4486,7 @@ ${chalk14.cyan(`/_/ |_|\\_, /\\__,_//_/ `)}
4314
4486
  ${chalk14.cyan(` /____/ `)}
4315
4487
  `;
4316
4488
  var program = new Command();
4317
- program.name("agdi").description(chalk14.cyan("\u{1F9B8} The Autonomous AI Employee")).version("3.3.0").option("-y, --yes", "Auto-approve all prompts (headless/CI mode)").option("-m, --minimal", "Generate only the requested file(s), not a full app").option("-d, --dry-run", "Show what would be created without writing files").option("--saas", "Generate a production SaaS blueprint (Next.js + Prisma + Postgres + Stripe)");
4489
+ program.name("agdi").description(chalk14.cyan("\u{1F9B8} The Autonomous AI Employee")).version("3.3.4").option("-y, --yes", "Auto-approve all prompts (headless/CI mode)").option("-m, --minimal", "Generate only the requested file(s), not a full app").option("-d, --dry-run", "Show what would be created without writing files").option("--saas", "Generate a production SaaS blueprint (Next.js + Prisma + Postgres + Stripe)");
4318
4490
  program.hook("preAction", (thisCommand) => {
4319
4491
  const opts = thisCommand.opts();
4320
4492
  if (opts.yes) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agdi",
3
- "version": "3.3.0",
3
+ "version": "3.3.4",
4
4
  "description": "AI-powered app generator - build full-stack apps from natural language in your terminal",
5
5
  "type": "module",
6
6
  "bin": {