@integrity-labs/agt-cli 0.10.0 → 0.10.2

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.
@@ -30,7 +30,9 @@ function computeNextFire(kind, expr, every, at, timezone, afterMs) {
30
30
  }
31
31
  if (kind === "at" && at) {
32
32
  const ts = new Date(at).getTime();
33
- return isNaN(ts) ? null : ts;
33
+ if (isNaN(ts)) return null;
34
+ if (afterMs && ts <= afterMs) return null;
35
+ return ts;
34
36
  }
35
37
  return null;
36
38
  }
@@ -190,4 +192,4 @@ export {
190
192
  findTaskByTemplate,
191
193
  getProjectDir
192
194
  };
193
- //# sourceMappingURL=chunk-2TSCVXHE.js.map
195
+ //# sourceMappingURL=chunk-QU7FBXH3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/claude-scheduler.ts"],"sourcesContent":["/**\n * In-process scheduler for Claude Code agents.\n *\n * On each manager poll cycle, checks if any tasks are due and fires them\n * via `claude -p`. State persists to disk so nextFireAt/lastFireAt survive\n * manager restarts.\n */\n\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { Cron } from 'croner';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SchedulerTaskState {\n taskId: string;\n templateId: string;\n name: string;\n agentCodeName: string;\n agentId: string;\n scheduleKind: 'cron' | 'every' | 'at';\n scheduleExpr: string | null;\n scheduleEvery: string | null;\n scheduleAt: string | null;\n timezone: string;\n prompt: string;\n sessionTarget: string;\n deliveryMode: string;\n deliveryChannel: string | null;\n deliveryTo: string | null;\n enabled: boolean;\n triggeredAt: number | null; // epoch ms — manual trigger from webapp\n nextFireAt: number | null; // epoch ms, null = completed one-shot\n lastFireAt: number | null;\n lastStatus: 'ok' | 'error' | null;\n firedCount: number;\n}\n\nexport interface SchedulerState {\n version: 1;\n tasks: Record<string, SchedulerTaskState>;\n updatedAt: string;\n}\n\nexport interface SchedulerTaskInput {\n id: string;\n template_id: string;\n name: string;\n schedule_kind: 'cron' | 'every' | 'at';\n schedule_expr: string | null;\n schedule_every: string | null;\n schedule_at: string | null;\n timezone: string;\n prompt: string;\n session_target: string;\n delivery_mode: string;\n delivery_channel: string | null;\n delivery_to: string | null;\n enabled: boolean;\n triggered_at?: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Interval parsing (reused from Claude Code adapter)\n// ---------------------------------------------------------------------------\n\nfunction parseIntervalMs(scheduleEvery: string | null): number {\n if (!scheduleEvery) return 60 * 60_000; // 1hr default\n const match = scheduleEvery.match(/^(\\d+)\\s*(m|min|h|hr|d)$/i);\n if (!match) return 60 * 60_000;\n const value = parseInt(match[1]!, 10);\n const unit = match[2]!.toLowerCase();\n if (unit === 'h' || unit === 'hr') return value * 60 * 60_000;\n if (unit === 'd') return value * 24 * 60 * 60_000;\n return value * 60_000; // minutes\n}\n\n// ---------------------------------------------------------------------------\n// Next-fire computation\n// ---------------------------------------------------------------------------\n\nexport function computeNextFire(\n kind: 'cron' | 'every' | 'at',\n expr: string | null,\n every: string | null,\n at: string | null,\n timezone: string,\n afterMs?: number,\n): number | null {\n const now = afterMs ?? Date.now();\n\n if (kind === 'cron' && expr) {\n try {\n const cron = new Cron(expr, { timezone: timezone || undefined });\n const next = cron.nextRun(new Date(now));\n return next ? next.getTime() : null;\n } catch {\n return null;\n }\n }\n\n if (kind === 'every') {\n const intervalMs = parseIntervalMs(every);\n return now + intervalMs;\n }\n\n if (kind === 'at' && at) {\n const ts = new Date(at).getTime();\n if (isNaN(ts)) return null;\n // If the 'at' timestamp is in the past and afterMs is set (meaning we already\n // fired), return null to prevent re-firing on state rebuild.\n if (afterMs && ts <= afterMs) return null;\n return ts;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// State persistence\n// ---------------------------------------------------------------------------\n\nfunction getStateDir(codeName: string): string {\n return join(homedir(), '.augmented', codeName, 'claudecode');\n}\n\nfunction getStatePath(codeName: string): string {\n return join(getStateDir(codeName), 'scheduler-state.json');\n}\n\nexport function loadSchedulerState(codeName: string): SchedulerState {\n const path = getStatePath(codeName);\n if (existsSync(path)) {\n try {\n return JSON.parse(readFileSync(path, 'utf-8'));\n } catch { /* corrupted — start fresh */ }\n }\n return { version: 1, tasks: {}, updatedAt: new Date().toISOString() };\n}\n\nexport function saveSchedulerState(codeName: string, state: SchedulerState): void {\n const dir = getStateDir(codeName);\n mkdirSync(dir, { recursive: true });\n state.updatedAt = new Date().toISOString();\n const path = getStatePath(codeName);\n const tmpPath = path + '.tmp';\n writeFileSync(tmpPath, JSON.stringify(state, null, 2));\n renameSync(tmpPath, path);\n}\n\n// ---------------------------------------------------------------------------\n// Sync API tasks → scheduler state\n// ---------------------------------------------------------------------------\n\nexport function syncTasksToScheduler(\n codeName: string,\n agentId: string,\n tasks: SchedulerTaskInput[],\n): SchedulerState {\n const state = loadSchedulerState(codeName);\n const desiredIds = new Set(tasks.map((t) => t.id));\n\n // Remove tasks no longer in API\n for (const id of Object.keys(state.tasks)) {\n if (!desiredIds.has(id)) {\n delete state.tasks[id];\n }\n }\n\n // Add or update tasks\n for (const t of tasks) {\n const existing = state.tasks[t.id];\n if (existing) {\n // Only recompute nextFireAt if the schedule definition actually changed.\n // Without this guard, every sync cycle resets nextFireAt from \"now\",\n // preventing past-due tasks from ever being detected as ready.\n const scheduleChanged =\n existing.scheduleKind !== t.schedule_kind ||\n existing.scheduleExpr !== t.schedule_expr ||\n existing.scheduleEvery !== t.schedule_every ||\n existing.scheduleAt !== t.schedule_at ||\n existing.timezone !== t.timezone;\n\n // Update mutable fields, preserve fire history\n existing.name = t.name;\n existing.templateId = t.template_id;\n existing.scheduleKind = t.schedule_kind;\n existing.scheduleExpr = t.schedule_expr;\n existing.scheduleEvery = t.schedule_every;\n existing.scheduleAt = t.schedule_at;\n existing.timezone = t.timezone;\n existing.prompt = t.prompt;\n existing.sessionTarget = t.session_target;\n existing.deliveryMode = t.delivery_mode;\n existing.deliveryChannel = t.delivery_channel;\n existing.deliveryTo = t.delivery_to;\n existing.enabled = t.enabled;\n if (t.triggered_at) existing.triggeredAt = new Date(t.triggered_at).getTime();\n if (scheduleChanged) {\n existing.nextFireAt = computeNextFire(\n t.schedule_kind, t.schedule_expr, t.schedule_every, t.schedule_at,\n t.timezone, existing.lastFireAt ?? undefined,\n );\n }\n } else {\n // New task\n state.tasks[t.id] = {\n taskId: t.id,\n templateId: t.template_id,\n name: t.name,\n agentCodeName: codeName,\n agentId,\n scheduleKind: t.schedule_kind,\n scheduleExpr: t.schedule_expr,\n scheduleEvery: t.schedule_every,\n scheduleAt: t.schedule_at,\n timezone: t.timezone,\n prompt: t.prompt,\n sessionTarget: t.session_target,\n deliveryMode: t.delivery_mode,\n deliveryChannel: t.delivery_channel,\n deliveryTo: t.delivery_to,\n enabled: t.enabled,\n triggeredAt: t.triggered_at ? new Date(t.triggered_at).getTime() : null,\n nextFireAt: computeNextFire(\n t.schedule_kind, t.schedule_expr, t.schedule_every, t.schedule_at, t.timezone,\n ),\n lastFireAt: null,\n lastStatus: null,\n firedCount: 0,\n };\n }\n }\n\n saveSchedulerState(codeName, state);\n return state;\n}\n\n// ---------------------------------------------------------------------------\n// Ready-task detection\n// ---------------------------------------------------------------------------\n\nexport function getReadyTasks(state: SchedulerState): SchedulerTaskState[] {\n const now = Date.now();\n const ready = Object.values(state.tasks).filter((t) => {\n if (!t.enabled) return false;\n // Manual trigger: triggered_at is set and hasn't been fired yet\n if (t.triggeredAt) {\n const triggerReady = !t.lastFireAt || t.triggeredAt > t.lastFireAt;\n if (!triggerReady) {\n // Debug: trigger was already consumed\n }\n if (triggerReady) return true;\n }\n // Normal schedule\n return t.nextFireAt !== null && t.nextFireAt <= now;\n });\n // Deduplicate by templateId — only fire one task per template per cycle\n const seen = new Set<string>();\n return ready.filter((t) => {\n if (seen.has(t.templateId)) return false;\n seen.add(t.templateId);\n return true;\n });\n}\n\n// ---------------------------------------------------------------------------\n// Post-execution state update\n// ---------------------------------------------------------------------------\n\nexport function markTaskFired(\n codeName: string,\n taskId: string,\n status: 'ok' | 'error',\n): SchedulerState {\n const state = loadSchedulerState(codeName);\n const task = state.tasks[taskId];\n if (!task) return state;\n\n task.lastFireAt = Date.now();\n task.lastStatus = status;\n task.firedCount++;\n\n // Compute next fire\n if (task.scheduleKind === 'at') {\n // One-shot — mark as completed\n task.nextFireAt = null;\n } else {\n task.nextFireAt = computeNextFire(\n task.scheduleKind, task.scheduleExpr, task.scheduleEvery, task.scheduleAt,\n task.timezone, task.lastFireAt,\n );\n }\n\n saveSchedulerState(codeName, state);\n return state;\n}\n\n// ---------------------------------------------------------------------------\n// Find a task by template ID (for work triggers)\n// ---------------------------------------------------------------------------\n\nexport function findTaskByTemplate(state: SchedulerState, templateId: string): SchedulerTaskState | undefined {\n return Object.values(state.tasks).find(\n (t) => t.templateId === templateId && t.enabled,\n );\n}\n\nexport function getProjectDir(codeName: string): string {\n return join(homedir(), '.augmented', codeName, 'project');\n}\n"],"mappings":"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","names":[]}
@@ -215,6 +215,25 @@ var INTEGRATION_REGISTRY = [
215
215
  },
216
216
  docs_url: "https://github.com/Pika-Labs/Pika-Skills"
217
217
  },
218
+ {
219
+ id: "claude-code",
220
+ name: "Claude Code",
221
+ category: "code",
222
+ description: "Claude Code AI agent runtime \u2014 code editing, task execution, file management, and development workflows",
223
+ supported_auth_types: ["api_key", "none"],
224
+ capabilities: [
225
+ { id: "claude-code:edit-code", name: "Edit Code", description: "Read, write, and edit source files", access: "write" },
226
+ { id: "claude-code:run-tasks", name: "Run Tasks", description: "Execute bash commands and development tasks", access: "write" },
227
+ { id: "claude-code:search", name: "Search Code", description: "Search files and grep codebase", access: "read" },
228
+ { id: "claude-code:git", name: "Git Operations", description: "Commit, branch, push, and manage version control", access: "write" }
229
+ ],
230
+ cli_tool: {
231
+ package: "@anthropic-ai/claude-code",
232
+ binary: "claude",
233
+ env_key: "ANTHROPIC_API_KEY"
234
+ },
235
+ docs_url: "https://docs.anthropic.com/en/docs/claude-code"
236
+ },
218
237
  {
219
238
  id: "custom",
220
239
  name: "Custom Integration",
@@ -464,12 +483,30 @@ you need context about who you work for or how to operate.
464
483
 
465
484
  ${body}`;
466
485
  }
486
+ function buildReportsToSection(reportsTo) {
487
+ if (!reportsTo)
488
+ return "";
489
+ const typeLabel = reportsTo.type === "agent" ? "Agent" : "Person";
490
+ let section = `
491
+ ## Reports To
492
+
493
+ - **${reportsTo.name}** (${typeLabel})`;
494
+ if (reportsTo.title)
495
+ section += `
496
+ - Title: ${reportsTo.title}`;
497
+ if (reportsTo.description)
498
+ section += `
499
+ - ${reportsTo.description}`;
500
+ section += "\n";
501
+ return section;
502
+ }
467
503
  function generateSoulMd(input) {
468
- const { frontmatter, role, description, resolvedChannels, team, knowledge } = input;
504
+ const { frontmatter, role, description, resolvedChannels, team, knowledge, reportsTo } = input;
469
505
  const channelList = resolvedChannels?.length ? resolvedChannels.join(", ") : "none";
470
506
  const roleDisplay = role ?? "Agent";
471
507
  const desc = description?.trim();
472
508
  const knowledgeSection = buildKnowledgeSection(knowledge);
509
+ const reportsToSection = buildReportsToSection(reportsTo);
473
510
  return `# ${frontmatter.display_name}
474
511
 
475
512
  You are **${frontmatter.display_name}**, **${roleDisplay}**${team ? ` at **${team.name}**` : ""}.
@@ -479,7 +516,7 @@ ${desc}
479
516
  - Owner: ${frontmatter.owner.name}
480
517
  - Environment: ${frontmatter.environment}
481
518
  - Channels: ${channelList}
482
- ${knowledgeSection}`;
519
+ ${reportsToSection}${knowledgeSection}`;
483
520
  }
484
521
  function generateAgentsMd(input) {
485
522
  const { frontmatter, role, description, team } = input;
@@ -688,7 +725,8 @@ var openclawAdapter = {
688
725
  description: input.agent.description,
689
726
  resolvedChannels: input.resolvedChannels,
690
727
  team: input.team,
691
- knowledge: knowledgeRefs.length > 0 ? knowledgeRefs : void 0
728
+ knowledge: knowledgeRefs.length > 0 ? knowledgeRefs : void 0,
729
+ reportsTo: input.reportsTo
692
730
  };
693
731
  const artifacts = [
694
732
  { relativePath: "openclaw.json5", content: serializeOpenClawConfig(config) },
@@ -1314,6 +1352,38 @@ ${entry.content}`
1314
1352
  return changed;
1315
1353
  }, codeName);
1316
1354
  },
1355
+ executePluginHook(ctx) {
1356
+ const agentDir = join(AUGMENTED_DIR, ctx.codeName);
1357
+ const projectDir = join(agentDir, "project");
1358
+ mkdirSync(agentDir, { recursive: true });
1359
+ const startedAt = Date.now();
1360
+ return new Promise((resolve3) => {
1361
+ const child = execFile("bash", ["-c", ctx.script], {
1362
+ cwd: agentDir,
1363
+ timeout: 6e4,
1364
+ maxBuffer: 1024 * 1024,
1365
+ env: {
1366
+ ...process.env,
1367
+ AGENT_CODE_NAME: ctx.codeName,
1368
+ AGENT_DIR: agentDir,
1369
+ AGENT_PROJECT_DIR: projectDir,
1370
+ AGENT_FRAMEWORK: "openclaw"
1371
+ }
1372
+ }, (error, stdout, stderr) => {
1373
+ const durationMs = Date.now() - startedAt;
1374
+ const timedOut = !!error && error.code === "ETIMEDOUT";
1375
+ resolve3({
1376
+ exitCode: error ? typeof error.code === "number" ? error.code : 1 : 0,
1377
+ stdout: stdout?.toString() ?? "",
1378
+ stderr: stderr?.toString() ?? "",
1379
+ durationMs,
1380
+ timedOut
1381
+ });
1382
+ });
1383
+ child.on("error", () => {
1384
+ });
1385
+ });
1386
+ },
1317
1387
  readGatewayToken(codeName) {
1318
1388
  const homeDir = getHomeDir();
1319
1389
  try {
@@ -1792,6 +1862,7 @@ registerFramework(nemoClawAdapter);
1792
1862
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync3, chmodSync as chmodSync3 } from "fs";
1793
1863
  import { join as join3, relative } from "path";
1794
1864
  import { homedir as homedir2 } from "os";
1865
+ import { execFile as execFile3 } from "child_process";
1795
1866
 
1796
1867
  // ../../packages/core/dist/provisioning/frameworks/claudecode/identity.js
1797
1868
  function buildMemorySection(hasQmd) {
@@ -1916,8 +1987,29 @@ ${seed.trim()}
1916
1987
 
1917
1988
  `;
1918
1989
  }
1990
+ function buildReportsToSection2(reportsTo) {
1991
+ if (!reportsTo)
1992
+ return "";
1993
+ const typeLabel = reportsTo.type === "agent" ? "Agent" : "Person";
1994
+ let section = `## Reports To
1995
+
1996
+ - **${reportsTo.name}** (${typeLabel})`;
1997
+ if (reportsTo.title)
1998
+ section += `
1999
+ - Title: ${reportsTo.title}`;
2000
+ if (reportsTo.description)
2001
+ section += `
2002
+ - ${reportsTo.description}`;
2003
+ section += `
2004
+
2005
+ Escalate blockers, questions, and important decisions to your manager.
2006
+ When your manager sends you a message, prioritize it.
2007
+
2008
+ `;
2009
+ return section;
2010
+ }
1919
2011
  function generateClaudeMd(input) {
1920
- const { frontmatter, role, description, resolvedChannels, team, consoleUrl, hasQmd, integrations, knowledge, timezone, personalitySeed } = input;
2012
+ const { frontmatter, role, description, resolvedChannels, team, consoleUrl, hasQmd, integrations, knowledge, timezone, reportsTo, personalitySeed } = input;
1921
2013
  const channelList = resolvedChannels?.length ? resolvedChannels.join(", ") : "none";
1922
2014
  const roleDisplay = role ?? "Agent";
1923
2015
  const desc = description?.trim();
@@ -1926,6 +2018,7 @@ function generateClaudeMd(input) {
1926
2018
  const integrationsSection = buildIntegrationsSection(integrations);
1927
2019
  const knowledgeSection = buildKnowledgeSection2(knowledge);
1928
2020
  const personalitySection = buildPersonalitySection(personalitySeed);
2021
+ const reportsToSection = buildReportsToSection2(reportsTo);
1929
2022
  return `# ${frontmatter.display_name}
1930
2023
 
1931
2024
  You are **${frontmatter.display_name}**, **${roleDisplay}**${team ? ` at **${team.name}**` : ""}.
@@ -1981,7 +2074,7 @@ first to load your recent board state. This gives you context about completed an
1981
2074
  in-progress items so you can answer accurately.
1982
2075
 
1983
2076
  ${memorySection}
1984
- ${integrationsSection}${knowledgeSection}## Development Workflow
2077
+ ${reportsToSection}${integrationsSection}${knowledgeSection}## Development Workflow
1985
2078
 
1986
2079
  ### Repository Management
1987
2080
 
@@ -2149,6 +2242,88 @@ function provisionStopHook(codeName) {
2149
2242
  settings["hooks"] = hooks;
2150
2243
  writeFileSync3(settingsPath, JSON.stringify(settings, null, 2));
2151
2244
  }
2245
+ function provisionIsolationHook(codeName) {
2246
+ const projectDir = getProjectDir(codeName);
2247
+ const claudeDir = join3(projectDir, ".claude");
2248
+ mkdirSync3(claudeDir, { recursive: true });
2249
+ const homeDir = getHomeDir3();
2250
+ const augmentedBase = join3(homeDir, ".augmented");
2251
+ const ownAgentDir = join3(augmentedBase, codeName);
2252
+ const logFile = join3(ownAgentDir, "isolation.log");
2253
+ const hookScriptPath = join3(claudeDir, "agt-isolation-hook.sh");
2254
+ const hookScript = [
2255
+ "#!/bin/bash",
2256
+ "# Auto-generated by Augmented \u2014 prevents cross-agent file access.",
2257
+ "# Exit 0 = allow, Exit 2 = block (with stderr message shown to agent)",
2258
+ "set -euo pipefail",
2259
+ "INPUT=$(cat)",
2260
+ `TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')`,
2261
+ "",
2262
+ "# Only check file-access tools",
2263
+ 'case "$TOOL" in',
2264
+ " Read|Edit|Write|Glob|Grep|MultiEdit) ;;",
2265
+ " Bash)",
2266
+ " # For Bash, we can't reliably parse arbitrary commands \u2014 rely on allowedDirectories.",
2267
+ " # But block obvious attempts to read other agent dirs.",
2268
+ ` CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')`,
2269
+ ` if echo "$CMD" | grep -qE '${augmentedBase}/[^/]+/' 2>/dev/null; then`,
2270
+ ` MATCH=$(echo "$CMD" | grep -oE '${augmentedBase}/[^/]+' | head -1)`,
2271
+ ` AGENT_DIR=$(basename "$MATCH")`,
2272
+ ` if [ "$AGENT_DIR" != "${codeName}" ] && [ "$AGENT_DIR" != "_mcp" ]; then`,
2273
+ ` echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) BLOCKED bash targeting $MATCH" >> "${logFile}"`,
2274
+ ` echo "Access denied: you cannot access other agents' directories." >&2`,
2275
+ " exit 2",
2276
+ " fi",
2277
+ " fi",
2278
+ " exit 0 ;;",
2279
+ " *) exit 0 ;;",
2280
+ "esac",
2281
+ "",
2282
+ "# Extract file_path from tool input",
2283
+ `FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty')`,
2284
+ '[ -z "$FILE_PATH" ] && exit 0',
2285
+ "",
2286
+ "# Resolve to absolute path",
2287
+ 'if [[ "$FILE_PATH" != /* ]]; then',
2288
+ ` CWD=$(echo "$INPUT" | jq -r '.cwd // empty')`,
2289
+ ' [ -n "$CWD" ] && FILE_PATH="$CWD/$FILE_PATH"',
2290
+ "fi",
2291
+ "",
2292
+ "# Check if path targets another agent's directory",
2293
+ `AUGMENTED_BASE="${augmentedBase}"`,
2294
+ 'case "$FILE_PATH" in',
2295
+ ' "$AUGMENTED_BASE"/*/*) ',
2296
+ ' AGENT_DIR=$(echo "$FILE_PATH" | sed "s|$AUGMENTED_BASE/||" | cut -d/ -f1)',
2297
+ ` if [ "$AGENT_DIR" != "${codeName}" ] && [ "$AGENT_DIR" != "_mcp" ]; then`,
2298
+ ` echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) BLOCKED $TOOL on $FILE_PATH" >> "${logFile}"`,
2299
+ ` echo "Access denied: you cannot access other agents' directories." >&2`,
2300
+ " exit 2",
2301
+ " fi ;;",
2302
+ "esac",
2303
+ "",
2304
+ "exit 0"
2305
+ ].join("\n") + "\n";
2306
+ writeFileSync3(hookScriptPath, hookScript, { mode: 493 });
2307
+ const settingsPath = join3(claudeDir, "settings.local.json");
2308
+ let settings = {};
2309
+ try {
2310
+ settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
2311
+ } catch {
2312
+ }
2313
+ const hooks = settings["hooks"] ?? {};
2314
+ hooks["PreToolUse"] = [
2315
+ {
2316
+ hooks: [
2317
+ {
2318
+ type: "command",
2319
+ command: hookScriptPath
2320
+ }
2321
+ ]
2322
+ }
2323
+ ];
2324
+ settings["hooks"] = hooks;
2325
+ writeFileSync3(settingsPath, JSON.stringify(settings, null, 2));
2326
+ }
2152
2327
  function modifyJsonConfig(filePath, fn) {
2153
2328
  let originalContent;
2154
2329
  let config;
@@ -2184,6 +2359,19 @@ function buildSettingsJson(input) {
2184
2359
  if (agent.primary_model) {
2185
2360
  settings["model"] = agent.primary_model;
2186
2361
  }
2362
+ const projectDir = getProjectDir(agent.code_name);
2363
+ const agentDir = getAgentDir(agent.code_name);
2364
+ const homeDir = getHomeDir3();
2365
+ settings["allowedDirectories"] = [
2366
+ projectDir,
2367
+ // Agent's project dir (CLAUDE.md, settings.json, etc.)
2368
+ agentDir,
2369
+ // Agent's config dir (.env, schedules, registration)
2370
+ join3(homeDir, ".augmented", "_mcp"),
2371
+ // Shared MCP binaries
2372
+ "/tmp"
2373
+ // Temp files
2374
+ ];
2187
2375
  return settings;
2188
2376
  }
2189
2377
  function buildMcpJson(input) {
@@ -2384,9 +2572,9 @@ ${entry.content}`
2384
2572
  // so ensureGatewayRunning() returns early with running=false
2385
2573
  async getVersion() {
2386
2574
  try {
2387
- const { execFile: execFile3 } = await import("child_process");
2575
+ const { execFile: execFile4 } = await import("child_process");
2388
2576
  return new Promise((resolve3) => {
2389
- execFile3("claude", ["--version"], { timeout: 5e3 }, (err, stdout) => {
2577
+ execFile4("claude", ["--version"], { timeout: 5e3 }, (err, stdout) => {
2390
2578
  if (err) {
2391
2579
  resolve3(null);
2392
2580
  return;
@@ -2634,6 +2822,23 @@ ${entry.content}`
2634
2822
  args: ["-y", "@composio/mcp", "start", "--url", config.url],
2635
2823
  env: config.headers ?? {}
2636
2824
  };
2825
+ } else if (config.url.includes("mcp.pipedream.net")) {
2826
+ const pdUrl = new URL(config.url);
2827
+ const pathParts = pdUrl.pathname.split("/").filter(Boolean);
2828
+ const externalUserId = decodeURIComponent(pathParts[0] ?? "");
2829
+ const appSlug = decodeURIComponent(pathParts[1] ?? "");
2830
+ const headers = config.headers ?? {};
2831
+ const authToken = (headers["Authorization"] ?? "").replace("Bearer ", "");
2832
+ serverEntry = {
2833
+ command: "npx",
2834
+ args: ["-y", "@pipedream/mcp", "stdio", "--app", appSlug, "--external-user-id", externalUserId],
2835
+ env: {
2836
+ PIPEDREAM_PROJECT_ID: headers["x-pd-project-id"] ?? process.env["PIPEDREAM_PROJECT_ID"] ?? "",
2837
+ PIPEDREAM_CLIENT_ID: headers["x-pd-client-id"] ?? process.env["PIPEDREAM_CLIENT_ID"] ?? "",
2838
+ PIPEDREAM_CLIENT_SECRET: headers["x-pd-client-secret"] ?? process.env["PIPEDREAM_CLIENT_SECRET"] ?? "",
2839
+ PIPEDREAM_PROJECT_ENVIRONMENT: headers["x-pd-environment"] ?? process.env["PIPEDREAM_ENVIRONMENT"] ?? "development"
2840
+ }
2841
+ };
2637
2842
  } else {
2638
2843
  serverEntry = {
2639
2844
  command: "npx",
@@ -2707,6 +2912,40 @@ ${entry.content}`
2707
2912
  };
2708
2913
  writeFileSync3(pluginsJsonPath, JSON.stringify(pluginsConfig, null, 2));
2709
2914
  },
2915
+ executePluginHook(ctx) {
2916
+ assertValidCodeName(ctx.codeName);
2917
+ const agentRootDir = join3(getHomeDir3(), ".augmented", ctx.codeName);
2918
+ const projectDir = getProjectDir(ctx.codeName);
2919
+ mkdirSync3(agentRootDir, { recursive: true });
2920
+ mkdirSync3(projectDir, { recursive: true });
2921
+ const startedAt = Date.now();
2922
+ return new Promise((resolve3) => {
2923
+ const child = execFile3("bash", ["-c", ctx.script], {
2924
+ cwd: agentRootDir,
2925
+ timeout: 6e4,
2926
+ maxBuffer: 1024 * 1024,
2927
+ env: {
2928
+ ...process.env,
2929
+ AGENT_CODE_NAME: ctx.codeName,
2930
+ AGENT_DIR: agentRootDir,
2931
+ AGENT_PROJECT_DIR: projectDir,
2932
+ AGENT_FRAMEWORK: "claude-code"
2933
+ }
2934
+ }, (error, stdout, stderr) => {
2935
+ const durationMs = Date.now() - startedAt;
2936
+ const timedOut = !!error && error.code === "ETIMEDOUT";
2937
+ resolve3({
2938
+ exitCode: error ? typeof error.code === "number" ? error.code : 1 : 0,
2939
+ stdout: stdout?.toString() ?? "",
2940
+ stderr: stderr?.toString() ?? "",
2941
+ durationMs,
2942
+ timedOut
2943
+ });
2944
+ });
2945
+ child.on("error", () => {
2946
+ });
2947
+ });
2948
+ },
2710
2949
  writeTokenFile(codeName, integrations) {
2711
2950
  const agentDir = getAgentDir(codeName);
2712
2951
  mkdirSync3(agentDir, { recursive: true });
@@ -2798,12 +3037,16 @@ function setActiveTeam(slug) {
2798
3037
  config.active_team = slug;
2799
3038
  saveConfig(config);
2800
3039
  }
3040
+ function getHost() {
3041
+ return process.env["AGT_HOST"];
3042
+ }
2801
3043
  var AGT_HOST = process.env["AGT_HOST"];
2802
3044
  function requireHost() {
2803
- if (!AGT_HOST) {
3045
+ const host = getHost();
3046
+ if (!host) {
2804
3047
  throw new Error("AGT_HOST is not set. Export it to point at the Augmented API (e.g. export AGT_HOST=https://your-api.example.com)");
2805
3048
  }
2806
- return AGT_HOST;
3049
+ return host;
2807
3050
  }
2808
3051
 
2809
3052
  // src/lib/api-client.ts
@@ -2843,6 +3086,11 @@ async function doExchange(rawKey, retried) {
2843
3086
  if (!res.ok) {
2844
3087
  const body = await res.json().catch(() => ({}));
2845
3088
  const errorMsg = String(body["error"] ?? res.statusText);
3089
+ const host = requireHost();
3090
+ const obfuscated = rawKey.length > 12 ? `${rawKey.slice(0, 8)}${"*".repeat(rawKey.length - 12)}${rawKey.slice(-4)}` : rawKey.slice(0, 4) + "****";
3091
+ if (res.status >= 502 && res.status <= 504) {
3092
+ throw new Error(`API unreachable (${res.status}): ${host} \u2014 is the API server running?`);
3093
+ }
2846
3094
  if (errorMsg.includes("revoked") && !retried) {
2847
3095
  reloadFromShellProfile();
2848
3096
  const freshKey = getApiKey();
@@ -2850,8 +3098,6 @@ async function doExchange(rawKey, retried) {
2850
3098
  return doExchange(freshKey, true);
2851
3099
  }
2852
3100
  }
2853
- const host = requireHost();
2854
- const obfuscated = rawKey.length > 12 ? `${rawKey.slice(0, 8)}${"*".repeat(rawKey.length - 12)}${rawKey.slice(-4)}` : rawKey.slice(0, 4) + "****";
2855
3101
  throw new Error(`API key exchange failed: ${errorMsg} (host=${host}, key=${obfuscated})`);
2856
3102
  }
2857
3103
  const data = await res.json();
@@ -4501,7 +4747,12 @@ var ROLE_PERMISSIONS = {
4501
4747
  "audit_log.view",
4502
4748
  "host.create",
4503
4749
  "host.manage",
4504
- "host.view"
4750
+ "host.view",
4751
+ "plugin.view",
4752
+ "plugin.install",
4753
+ "plugin.configure",
4754
+ "plugin.manage_scopes",
4755
+ "plugin.approve_requests"
4505
4756
  ],
4506
4757
  admin: [
4507
4758
  "team.manage_members",
@@ -4515,7 +4766,12 @@ var ROLE_PERMISSIONS = {
4515
4766
  "audit_log.view",
4516
4767
  "host.create",
4517
4768
  "host.manage",
4518
- "host.view"
4769
+ "host.view",
4770
+ "plugin.view",
4771
+ "plugin.install",
4772
+ "plugin.configure",
4773
+ "plugin.manage_scopes",
4774
+ "plugin.approve_requests"
4519
4775
  ],
4520
4776
  member: [
4521
4777
  "agent.create",
@@ -4523,12 +4779,15 @@ var ROLE_PERMISSIONS = {
4523
4779
  "agent.deploy",
4524
4780
  "agent.view",
4525
4781
  "audit_log.view",
4526
- "host.view"
4782
+ "host.view",
4783
+ "plugin.install",
4784
+ "plugin.view"
4527
4785
  ],
4528
4786
  viewer: [
4529
4787
  "agent.view",
4530
4788
  "audit_log.view",
4531
- "host.view"
4789
+ "host.view",
4790
+ "plugin.view"
4532
4791
  ]
4533
4792
  };
4534
4793
  var permissionSets = new Map(Object.entries(ROLE_PERMISSIONS).map(([role, actions]) => [role, new Set(actions)]));
@@ -4635,6 +4894,129 @@ function getTemplate(id) {
4635
4894
  return DEPLOYMENT_TEMPLATES.find((t) => t.id === id);
4636
4895
  }
4637
4896
 
4897
+ // ../../packages/core/dist/plugins/context-validator.js
4898
+ import Ajv20202 from "ajv/dist/2020.js";
4899
+ import addFormats2 from "ajv-formats";
4900
+
4901
+ // ../../packages/core/dist/plugins/context-meta-schema.json
4902
+ var context_meta_schema_default = {
4903
+ $schema: "https://json-schema.org/draft/2020-12/schema",
4904
+ $id: "https://augmented.dev/schemas/plugin-context.meta.schema.json",
4905
+ title: "Plugin Context Schema (meta)",
4906
+ description: "Meta-schema for the constrained subset of JSON Schema that plugin authors may declare for their plugin context. Anything outside this subset is rejected at PUT time. See ENG-4341 / docs/plugins/plugin-context-rfc.md.",
4907
+ type: "object",
4908
+ required: ["type", "properties"],
4909
+ additionalProperties: false,
4910
+ properties: {
4911
+ $schema: {
4912
+ type: "string"
4913
+ },
4914
+ type: {
4915
+ type: "string",
4916
+ const: "object"
4917
+ },
4918
+ properties: {
4919
+ type: "object",
4920
+ minProperties: 0,
4921
+ additionalProperties: {
4922
+ $ref: "#/$defs/field"
4923
+ }
4924
+ },
4925
+ required: {
4926
+ type: "array",
4927
+ items: { type: "string" },
4928
+ uniqueItems: true
4929
+ }
4930
+ },
4931
+ $defs: {
4932
+ field: {
4933
+ oneOf: [
4934
+ { $ref: "#/$defs/stringField" },
4935
+ { $ref: "#/$defs/booleanField" },
4936
+ { $ref: "#/$defs/stringArrayField" },
4937
+ { $ref: "#/$defs/stringMapField" }
4938
+ ]
4939
+ },
4940
+ stringField: {
4941
+ type: "object",
4942
+ required: ["type"],
4943
+ additionalProperties: false,
4944
+ properties: {
4945
+ type: { const: "string" },
4946
+ title: { type: "string" },
4947
+ description: { type: "string" },
4948
+ enum: {
4949
+ type: "array",
4950
+ items: { type: "string" },
4951
+ minItems: 1,
4952
+ uniqueItems: true
4953
+ },
4954
+ default: { type: "string" }
4955
+ }
4956
+ },
4957
+ booleanField: {
4958
+ type: "object",
4959
+ required: ["type"],
4960
+ additionalProperties: false,
4961
+ properties: {
4962
+ type: { const: "boolean" },
4963
+ title: { type: "string" },
4964
+ description: { type: "string" },
4965
+ default: { type: "boolean" }
4966
+ }
4967
+ },
4968
+ stringArrayField: {
4969
+ type: "object",
4970
+ required: ["type", "items"],
4971
+ additionalProperties: false,
4972
+ properties: {
4973
+ type: { const: "array" },
4974
+ items: {
4975
+ type: "object",
4976
+ required: ["type"],
4977
+ additionalProperties: false,
4978
+ properties: {
4979
+ type: { const: "string" }
4980
+ }
4981
+ },
4982
+ title: { type: "string" },
4983
+ description: { type: "string" },
4984
+ default: {
4985
+ type: "array",
4986
+ items: { type: "string" }
4987
+ }
4988
+ }
4989
+ },
4990
+ stringMapField: {
4991
+ type: "object",
4992
+ required: ["type", "additionalProperties"],
4993
+ additionalProperties: false,
4994
+ properties: {
4995
+ type: { const: "object" },
4996
+ additionalProperties: {
4997
+ type: "object",
4998
+ required: ["type"],
4999
+ additionalProperties: false,
5000
+ properties: {
5001
+ type: { const: "string" }
5002
+ }
5003
+ },
5004
+ title: { type: "string" },
5005
+ description: { type: "string" },
5006
+ default: {
5007
+ type: "object",
5008
+ additionalProperties: { type: "string" }
5009
+ }
5010
+ }
5011
+ }
5012
+ }
5013
+ };
5014
+
5015
+ // ../../packages/core/dist/plugins/context-validator.js
5016
+ var ajv2 = new Ajv20202({ allErrors: true, strict: false });
5017
+ addFormats2(ajv2);
5018
+ var compiledMetaSchema = ajv2.compile(context_meta_schema_default);
5019
+
4638
5020
  // ../../packages/core/dist/drift/comparators.js
4639
5021
  function compareToolPolicy(expected, actual) {
4640
5022
  const findings = [];
@@ -4831,10 +5213,11 @@ export {
4831
5213
  getChannel,
4832
5214
  getAllChannelIds,
4833
5215
  provisionStopHook,
5216
+ provisionIsolationHook,
4834
5217
  getApiKey,
4835
5218
  getActiveTeam,
4836
5219
  setActiveTeam,
4837
- AGT_HOST,
5220
+ getHost,
4838
5221
  requireHost,
4839
5222
  exchangeApiKey,
4840
5223
  ApiError,
@@ -4861,4 +5244,4 @@ export {
4861
5244
  detectDrift,
4862
5245
  provision
4863
5246
  };
4864
- //# sourceMappingURL=chunk-UWH2MMKY.js.map
5247
+ //# sourceMappingURL=chunk-VCKY6MN2.js.map