agentlas 0.4.0 → 0.5.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.
Files changed (41) hide show
  1. package/README.md +112 -23
  2. package/bin/agentlas.cjs +55 -8
  3. package/engine/agentlas-api-agent.cjs +1 -1
  4. package/engine/agentlas-banner.cjs +66 -51
  5. package/engine/agentlas-capabilities.cjs +3 -0
  6. package/engine/agentlas-cloud-runtime.cjs +65 -11
  7. package/engine/agentlas-composer.cjs +109 -44
  8. package/engine/agentlas-doctor.cjs +65 -14
  9. package/engine/agentlas-i18n.cjs +132 -12
  10. package/engine/agentlas-input.cjs +123 -19
  11. package/engine/agentlas-native-host.cjs +381 -83
  12. package/engine/agentlas-parity.cjs +373 -53
  13. package/engine/agentlas-permissions.cjs +90 -0
  14. package/engine/agentlas-repl.cjs +149 -47
  15. package/engine/agentlas-tasks.cjs +111 -0
  16. package/engine/agentlas-tools.cjs +174 -12
  17. package/engine/agentlas-ui.cjs +349 -24
  18. package/engine/agentlas.cjs +3074 -379
  19. package/engine/architecture.data.json +5 -1
  20. package/engine/semver.cjs +64 -0
  21. package/package.json +1 -1
  22. package/test/bootstrap-race.cjs +47 -0
  23. package/test/capture-runtime-guard.cjs +122 -0
  24. package/test/cloud-asset-restore.cjs +423 -0
  25. package/test/cloud-cas-client.cjs +333 -0
  26. package/test/cloud-owner-restore.cjs +183 -0
  27. package/test/cloud-runtime-paths.cjs +40 -0
  28. package/test/cloud-save-publish.cjs +453 -0
  29. package/test/credential-env-regression.cjs +52 -0
  30. package/test/login-loopback-security.cjs +115 -0
  31. package/test/mcp-config-isolation.cjs +36 -0
  32. package/test/permission-mapping.cjs +180 -0
  33. package/test/run-api-regression.cjs +322 -0
  34. package/test/runtime-env-protection.cjs +45 -0
  35. package/test/semver-precedence.cjs +39 -0
  36. package/test/smoke.sh +33 -0
  37. package/test/sqlite-driver-probe.cjs +22 -0
  38. package/test/terminal-ui-regression.cjs +454 -0
  39. package/test/timeout-regression.cjs +218 -0
  40. package/test/tool-workspace-boundary.cjs +165 -0
  41. package/test/update-safety.cjs +376 -0
@@ -11,6 +11,7 @@ const fs = require("node:fs");
11
11
  const os = require("node:os");
12
12
  const path = require("node:path");
13
13
  const readline = require("node:readline");
14
+ const i18n = require("./agentlas-i18n.cjs");
14
15
 
15
16
  function userDataDir() {
16
17
  const override = process.env.AGENTLAS_USER_DATA_DIR;
@@ -75,6 +76,7 @@ const SLASH_COMMAND_META = [
75
76
  { command: "/help", description: "Show Agentlas terminal commands", category: "Help", usage: "/help", detail: "Open the command reference, shortcuts, and common flows." },
76
77
  { command: "/status", description: "Show model/runtime, agent, permission, and directory", category: "Session", usage: "/status", detail: "Print the current runtime, active agent or company, permission level, and cwd." },
77
78
  { command: "/skills", description: "List available Agentlas terminal skills", category: "Discovery", usage: "/skills", detail: "Show the slash-command skills Agentlas can run inside this terminal." },
79
+ { command: "/career-graph", description: "Show or add Career Graph source refs", category: "Knowledge", usage: "/career-graph add ./docs", detail: "Career Graph routes agents to source Markdown, JSONL ledgers, sitemap, and code map before broad scans.", examples: ["/career-graph status", "/career-graph add ./docs", "/career-graph open"] },
78
80
  { command: "/ontology", description: "Turn on, list, or add project ontology sources", category: "Knowledge", usage: "/ontology add ./docs", detail: "Also understands natural text like /ontology use ./docs as company knowledge.", examples: ["/ontology list", "/ontology use ./docs as company knowledge", "/ontology open"] },
79
81
  { command: "/agents", description: "List installed agents", category: "Routing", usage: "/agents", detail: "Show local agents and their routed runtime." },
80
82
  { command: "/team", description: "View or pin each agent runtime", category: "Routing", usage: "/team <agent> <runtime|auto>", detail: "Pin one agent to claude-code, codex, gemini, or automatic routing." },
@@ -91,7 +93,7 @@ const SLASH_COMMAND_META = [
91
93
  { command: "/memory", description: "Show the memory injected into this run", category: "Context", usage: "/memory", detail: "Print the project memory that Agentlas adds to agent turns." },
92
94
  { command: "/side", description: "Ask a side question without saving it to chat context", category: "Context", usage: "/side <question>", detail: "Runs a one-off answer using current context, then returns without appending to chat history.", aliases: ["/btw"] },
93
95
  { command: "/multimodal", description: "Show or set image, video, and audio fallback providers", category: "Settings", usage: "/multimodal", detail: "Inspect or change fallback providers for media work." },
94
- { command: "/mcp", description: "List configured MCP servers", category: "Settings", usage: "/mcp", detail: "Show MCP servers and which enabled stdio servers the terminal wires into write/full turns." },
96
+ { command: "/mcp", description: "List configured MCP servers", category: "Settings", usage: "/mcp", detail: "Show MCP servers available only during explicit full-access turns." },
95
97
  { command: "/diff", description: "Show the current git diff", category: "Files", usage: "/diff", detail: "Print the working-tree diff for the current cwd." },
96
98
  { command: "/history", description: "Show recent inputs", category: "Session", usage: "/history", detail: "Show persisted terminal input history." },
97
99
  { command: "/resume", description: "Resume a recent runtime session", category: "Session", usage: "/resume [n]", detail: "List recent agent/runtime sessions and continue one (restores the native session thread)." },
@@ -101,12 +103,16 @@ const SLASH_COMMAND_META = [
101
103
  { command: "/clear", description: "Clear the chat and redraw", category: "Session", usage: "/clear", detail: "Clear local conversation state and redraw the Agentlas banner." },
102
104
  { command: "/import", description: "Import a local agent or team folder", category: "Files", usage: "/import <path>", detail: "Install a local agent or team into Agentlas." },
103
105
  { command: "/marketplace", description: "Browse/install marketplace agents", category: "Routing", usage: "/marketplace", detail: "Show how to install agents from the Agentlas cloud marketplace or a local folder.", aliases: ["/market"] },
104
- { command: "/install", description: "Install a cloud agent by slug", category: "Routing", usage: "/install <slug>", detail: "Download and install an agent from the Agentlas cloud marketplace by slug." },
105
106
  { command: "/storm", description: "Run a force-robust Stormbreaker pipeline on a goal", category: "Engine", usage: "/storm <goal> [--research]", detail: "Route the goal through Hephaestus Stormbreaker and execute the verified pipeline; --research grounds it with Research Engine evidence." },
106
107
  { command: "/swarm", description: "Fan out an emergent agent swarm on a goal", category: "Engine", usage: "/swarm <goal> [--parallel N]", detail: "Parallel workers share a blackboard and spawn subtasks with ## Spawn; a synthesizer merges results into one answer." },
107
108
  { command: "/build", description: "Build/repair/package an agent or team (Hephaestus)", category: "Engine", usage: "/build <what to build>", detail: "Runs Hephaestus hep-build natively — deep interview, scaffolding, packaging." },
108
109
  { command: "/route", description: "Preview which agent/pipeline would take a request", category: "Engine", usage: "/route <request>", detail: "Runs the Hephaestus router without executing — shows the selected agent, candidates, and reasons." },
109
110
  { command: "/research", description: "Run the Hephaestus Research Engine", category: "Engine", usage: "/research search \"query\"", detail: "status|gather|search|read|plan — evidence-grade web research from the terminal." },
111
+ { command: "/search", description: "Discover agents in the Hub", category: "Hub", usage: "/search <what you need>", detail: "Search the Agentlas Hub + local for an agent that fits the task (hep-search)." },
112
+ { command: "/install", description: "Install an agent from the Hub by slug", category: "Hub", usage: "/install <slug>", detail: "Install a marketplace agent into this terminal (hep-cloud)." },
113
+ { command: "/network", description: "Decompose a request into an A2A task force", category: "Engine", usage: "/network <request>", detail: "Runs Hephaestus hep-network — splits a composite request across agents.", aliases: ["/taskforce"] },
114
+ { command: "/browser", description: "Real browser execution hardpoint", category: "Engine", usage: "/browser [sub]", detail: "Runs the Agentlas browser hardpoint (hep-browser)." },
115
+ { command: "/connect", description: "Wire Telegram / platforms to an agent team", category: "Hub", usage: "/connect", detail: "Runs Hephaestus hep-connect for platform integration." },
110
116
  { command: "/doctor", description: "Check runtimes and local data", category: "Health", usage: "/doctor", detail: "Run local checks for runtimes, data, credentials, and setup." },
111
117
  { command: "/exit", description: "Quit Agentlas", category: "Session", usage: "/exit", detail: "Close the terminal session.", aliases: ["/quit"] },
112
118
  ];
@@ -114,19 +120,79 @@ const SLASH_COMMANDS = SLASH_COMMAND_META.flatMap((entry) => [entry.command].con
114
120
  const RUNTIME_SPECS = ["claude-code", "codex", "gemini", "anthropic", "openai", "google", "ollama", "upstage"];
115
121
  const PERM_LEVELS = ["read", "write", "full"];
116
122
 
123
+ const HELP_KEY_BY_COMMAND = {
124
+ "/help": "help.help",
125
+ "/status": "help.status",
126
+ "/skills": "help.skills",
127
+ "/career-graph": "help.careerGraph",
128
+ "/ontology": "help.ontology",
129
+ "/agents": "help.agents",
130
+ "/team": "help.team",
131
+ "/agent": "help.agent",
132
+ "/firms": "help.firms",
133
+ "/firm": "help.firms",
134
+ "/runtime": "help.runtime",
135
+ "/model": "help.model",
136
+ "/effort": "help.effort",
137
+ "/permission": "help.permission",
138
+ "/permissions": "help.permissions",
139
+ "/setup": "help.setup",
140
+ "/cwd": "help.cwd",
141
+ "/memory": "help.memory",
142
+ "/side": "help.side",
143
+ "/multimodal": "help.multimodal",
144
+ "/mcp": "help.mcp",
145
+ "/diff": "help.diff",
146
+ "/history": "help.history",
147
+ "/resume": "help.resume",
148
+ "/compact": "help.compact",
149
+ "/cost": "help.cost",
150
+ "/keybindings": "help.keybindings",
151
+ "/clear": "help.clear",
152
+ "/import": "help.import",
153
+ "/marketplace": "help.market",
154
+ "/install": "help.install",
155
+ "/storm": "help.storm",
156
+ "/swarm": "help.swarm",
157
+ "/build": "help.build",
158
+ "/route": "help.route",
159
+ "/research": "help.research",
160
+ "/search": "help.search",
161
+ "/network": "help.network",
162
+ "/browser": "help.browser",
163
+ "/connect": "help.connect",
164
+ "/doctor": "help.doctor",
165
+ "/exit": "help.exit",
166
+ };
167
+
117
168
  function uniqStartsWith(cands, token) {
118
169
  const hits = cands.filter((c) => c.startsWith(token));
119
170
  return hits.length ? hits : cands;
120
171
  }
121
172
 
122
- function slashCommandEntries() {
173
+ function localizeSlashEntry(entry, lang) {
174
+ const helpKey = HELP_KEY_BY_COMMAND[entry.command];
175
+ const description = helpKey ? i18n.t(lang, helpKey) : entry.description;
176
+ const category = entry.category ? i18n.t(lang, `category.${entry.category}`) : entry.category;
177
+ return {
178
+ ...entry,
179
+ description,
180
+ category,
181
+ // English keeps the longer authored detail. Other languages must not fall back to
182
+ // an English paragraph under an otherwise-localized command palette.
183
+ detail: lang === "en" ? entry.detail : description,
184
+ };
185
+ }
186
+
187
+ function slashCommandEntries(lang = "en") {
123
188
  const rows = [];
124
- for (const entry of SLASH_COMMAND_META) {
189
+ for (const rawEntry of SLASH_COMMAND_META) {
190
+ const entry = localizeSlashEntry(rawEntry, lang);
125
191
  rows.push({ ...entry, aliasOf: null });
126
192
  for (const alias of entry.aliases || []) {
127
193
  rows.push({
128
194
  command: alias,
129
- description: `Alias for ${entry.command}`,
195
+ description: lang === "ko" ? `${entry.command} 별칭` : `Alias for ${entry.command}`,
130
196
  category: entry.category,
131
197
  usage: alias + (entry.usage && entry.usage.includes(" ") ? entry.usage.slice(entry.usage.indexOf(" ")) : ""),
132
198
  detail: entry.detail,
@@ -146,11 +212,11 @@ function slashCommandQuery(line) {
146
212
  return value;
147
213
  }
148
214
 
149
- function slashCommandSuggestions(line, limit = 12) {
215
+ function slashCommandSuggestions(line, limit = 12, lang = "en") {
150
216
  const query = slashCommandQuery(line);
151
217
  if (query == null) return [];
152
218
  const q = query.toLowerCase();
153
- const entries = slashCommandEntries();
219
+ const entries = slashCommandEntries(lang);
154
220
  const starts = entries.filter((entry) => entry.command.toLowerCase().startsWith(q));
155
221
  const contains = entries.filter(
156
222
  (entry) =>
@@ -161,9 +227,9 @@ function slashCommandSuggestions(line, limit = 12) {
161
227
  }
162
228
 
163
229
  function padVisible(value, width) {
164
- const clean = stripAnsiLite(value);
165
- if (clean.length >= width) return value;
166
- return value + " ".repeat(width - clean.length);
230
+ const current = visibleWidthLite(value);
231
+ if (current >= width) return value;
232
+ return value + " ".repeat(width - current);
167
233
  }
168
234
 
169
235
  function stripAnsiLite(value) {
@@ -173,8 +239,40 @@ function stripAnsiLite(value) {
173
239
 
174
240
  function truncateVisible(value, width) {
175
241
  const clean = stripAnsiLite(value);
176
- if (clean.length <= width) return value;
177
- return clean.slice(0, Math.max(0, width - 1)) + "";
242
+ if (visibleWidthLite(clean) <= width) return value;
243
+ let out = "";
244
+ let used = 0;
245
+ const room = Math.max(0, width - 1);
246
+ for (const ch of clean) {
247
+ const cells = cellWidthLite(ch);
248
+ if (used + cells > room) break;
249
+ out += ch;
250
+ used += cells;
251
+ }
252
+ return out + "…";
253
+ }
254
+
255
+ function cellWidthLite(ch) {
256
+ const cp = ch.codePointAt(0);
257
+ if (cp < 0x20) return 0;
258
+ return (
259
+ (cp >= 0x1100 && cp <= 0x115f) ||
260
+ (cp >= 0x2e80 && cp <= 0x303e) ||
261
+ (cp >= 0x3041 && cp <= 0x33ff) ||
262
+ (cp >= 0x3400 && cp <= 0x4dbf) ||
263
+ (cp >= 0x4e00 && cp <= 0x9fff) ||
264
+ (cp >= 0xac00 && cp <= 0xd7a3) ||
265
+ (cp >= 0xf900 && cp <= 0xfaff) ||
266
+ (cp >= 0xfe30 && cp <= 0xfe4f) ||
267
+ (cp >= 0xff00 && cp <= 0xff60) ||
268
+ (cp >= 0x1f300 && cp <= 0x1faff)
269
+ ) ? 2 : 1;
270
+ }
271
+
272
+ function visibleWidthLite(value) {
273
+ let width = 0;
274
+ for (const ch of stripAnsiLite(value)) width += cellWidthLite(ch);
275
+ return width;
178
276
  }
179
277
 
180
278
  function renderSlashPalette(rows, selectedIndex, opts = {}) {
@@ -188,32 +286,35 @@ function renderSlashPalette(rows, selectedIndex, opts = {}) {
188
286
  inverse: (s) => String(s),
189
287
  };
190
288
  const c = { ...fallbackColors, ...(opts.colors || {}) };
289
+ const lang = opts.lang || "en";
191
290
  const commandWidth = Math.min(24, Math.max(16, rows.reduce((n, row) => Math.max(n, row.command.length), 0) + 2));
192
291
  const descWidth = Math.max(12, columns - commandWidth - 8);
193
292
  const lineWidth = Math.min(columns - 1, commandWidth + descWidth + 5);
194
293
  const selected = rows[Math.max(0, Math.min(selectedIndex, rows.length - 1))] || rows[0];
195
294
  const out = [
196
- c.faint("Slash commands") + c.dim(" type to search"),
295
+ c.faint(truncateVisible(`${i18n.t(lang, "palette.title")} ${i18n.t(lang, "palette.search")}`, lineWidth)),
197
296
  c.faint("─".repeat(lineWidth)),
198
297
  ];
199
298
  rows.forEach((row, index) => {
200
299
  const command = padVisible(row.command, commandWidth);
201
300
  const desc = truncateVisible(row.description, descWidth);
202
301
  const body = " " + c.blue(command) + c.text(desc);
203
- out.push(index === selectedIndex ? c.inverse(body.padEnd(lineWidth)) : body);
302
+ out.push(index === selectedIndex ? c.inverse(padVisible(body, lineWidth)) : body);
204
303
  });
205
304
  out.push(c.faint("─".repeat(lineWidth)));
206
305
  if (selected) {
207
306
  const usage = truncateVisible(selected.usage || selected.command, lineWidth - 2);
208
307
  const detail = truncateVisible(selected.detail || selected.description || "", lineWidth - 2);
209
- const category = selected.category ? `category: ${selected.category}` : "";
210
- out.push(" " + c.text(usage) + (category ? c.dim(" " + category) : ""));
308
+ const category = selected.category ? i18n.t(lang, "palette.category", selected.category) : "";
309
+ const categoryRoom = Math.max(0, lineWidth - visibleWidthLite(usage) - 3);
310
+ const categoryText = categoryRoom > 0 ? truncateVisible(category, categoryRoom) : "";
311
+ out.push(" " + c.text(usage) + (categoryText ? c.dim(" " + categoryText) : ""));
211
312
  if (detail) out.push(" " + c.dim(detail));
212
313
  if (selected.examples && selected.examples.length) {
213
- out.push(" " + c.dim("examples: " + selected.examples.slice(0, 2).join(" | ")));
314
+ out.push(c.dim(truncateVisible(" " + i18n.t(lang, "palette.examples", selected.examples.slice(0, 2).join(" | ")), lineWidth)));
214
315
  }
215
316
  }
216
- out.push(c.dim(" ↑↓ move Enter run Tab complete Esc close"));
317
+ out.push(c.dim(truncateVisible(" " + i18n.t(lang, "palette.controls"), lineWidth)));
217
318
  return out.join("\n");
218
319
  }
219
320
 
@@ -238,7 +339,7 @@ function attachSlashPalette(rl, opts = {}) {
238
339
 
239
340
  function rows() {
240
341
  if (!state.enabled) return [];
241
- return slashCommandSuggestions(rl.line || "");
342
+ return slashCommandSuggestions(rl.line || "", 12, opts.lang || (opts.ui && opts.ui.lang) || "en");
242
343
  }
243
344
  function active() {
244
345
  return rows().length > 0 && state.dismissedForLine !== (rl.line || "");
@@ -270,6 +371,7 @@ function attachSlashPalette(rl, opts = {}) {
270
371
  const body = renderSlashPalette(list, state.selected, {
271
372
  columns: stream.columns || process.stdout.columns || 88,
272
373
  colors,
374
+ lang: opts.lang || (opts.ui && opts.ui.lang) || "en",
273
375
  });
274
376
  stream.write("\x1b7\x1b[E\x1b[0J" + body + "\x1b8");
275
377
  state.visible = true;
@@ -359,6 +461,7 @@ function isAbsolutePathTask(line) {
359
461
  const value = String(line || "").trim();
360
462
  if (!value.startsWith("/")) return false;
361
463
  const first = value.split(/\s+/)[0] || "";
464
+ if (first === "/") return false;
362
465
  if (!first || SLASH_COMMANDS.includes(first)) return false;
363
466
  if (!path.isAbsolute(first)) return false;
364
467
  if (fs.existsSync(first)) return true;
@@ -406,6 +509,7 @@ function makeCompleter(ctx) {
406
509
  return [uniqStartsWith(RUNTIME_SPECS.concat(["auto"]), last), last];
407
510
  case "/cwd":
408
511
  case "/import":
512
+ case "/career-graph":
409
513
  case "/ontology":
410
514
  return [completePath(last, getCwd(), ""), last];
411
515
  default: