@integrity-labs/agt-cli 0.9.10 → 0.10.1

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":[]}
@@ -7,7 +7,7 @@ import {
7
7
  markTaskFired,
8
8
  saveSchedulerState,
9
9
  syncTasksToScheduler
10
- } from "./chunk-2TSCVXHE.js";
10
+ } from "./chunk-QU7FBXH3.js";
11
11
  export {
12
12
  computeNextFire,
13
13
  findTaskByTemplate,
@@ -18,4 +18,4 @@ export {
18
18
  saveSchedulerState,
19
19
  syncTasksToScheduler
20
20
  };
21
- //# sourceMappingURL=claude-scheduler-VFBZFE6U.js.map
21
+ //# sourceMappingURL=claude-scheduler-7PVWQHWU.js.map
@@ -9,7 +9,7 @@ import {
9
9
  provisionStopHook,
10
10
  requireHost,
11
11
  resolveChannels
12
- } from "../chunk-IT34G2Y2.js";
12
+ } from "../chunk-N7TRKQMT.js";
13
13
  import {
14
14
  findTaskByTemplate,
15
15
  getProjectDir,
@@ -17,7 +17,7 @@ import {
17
17
  loadSchedulerState,
18
18
  markTaskFired,
19
19
  syncTasksToScheduler
20
- } from "../chunk-2TSCVXHE.js";
20
+ } from "../chunk-QU7FBXH3.js";
21
21
  import {
22
22
  getProjectDir as getProjectDir2,
23
23
  injectMessage,
@@ -1591,10 +1591,11 @@ async function processAgent(agent, agentStates) {
1591
1591
  const fw = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
1592
1592
  if (fw === "claude-code" && isSessionHealthy(agent.code_name)) {
1593
1593
  const names = integrations.map((i) => i.display_name || i.definition_id).join(", ");
1594
- injectMessage(agent.code_name, "system", `Your integrations have been updated. You now have access to: ${names}. Re-read your CLAUDE.md for details.`, {
1594
+ injectMessage(agent.code_name, "system", `Your integrations have been updated. You now have access to: ${names}. Your .env.integrations and CLAUDE.md have been refreshed \u2014 credentials are available immediately via environment variables.`, {
1595
1595
  task_name: "integration-update"
1596
1596
  }, log).catch(() => {
1597
1597
  });
1598
+ log(`[hot-reload] Notified '${agent.code_name}' about integration update: ${names}`);
1598
1599
  }
1599
1600
  const managedIntegrations = integrations.filter((i) => i.auth_type === "managed");
1600
1601
  if (managedIntegrations.length > 0 && frameworkAdapter.writeMcpServer) {
@@ -1636,6 +1637,25 @@ async function processAgent(agent, agentStates) {
1636
1637
  }
1637
1638
  }
1638
1639
  needsGatewayRestart = true;
1640
+ if (managedIntegrations.length > 0 && fw === "claude-code" && isSessionHealthy(agent.code_name)) {
1641
+ const mcpNames = managedIntegrations.map((i) => i.display_name || i.definition_id).join(", ");
1642
+ log(`[hot-reload] MCP servers changed for '${agent.code_name}': ${mcpNames} \u2014 restarting session`);
1643
+ const delivered = await injectMessage(
1644
+ agent.code_name,
1645
+ "system",
1646
+ `New MCP tool servers have been configured: ${mcpNames}. Note: MCP servers require a session restart to connect. Your manager will restart your session shortly.`,
1647
+ { task_name: "mcp-update" },
1648
+ log
1649
+ ).catch(() => false);
1650
+ const delay = delivered ? 8e3 : 3e3;
1651
+ if (!delivered) {
1652
+ log(`[hot-reload] Inject notification unconfirmed for '${agent.code_name}' \u2014 proceeding with shorter delay`);
1653
+ }
1654
+ setTimeout(() => {
1655
+ stopPersistentSession(agent.code_name, log);
1656
+ log(`[hot-reload] Session stopped for '${agent.code_name}' \u2014 will respawn with new MCP servers`);
1657
+ }, delay);
1658
+ }
1639
1659
  }
1640
1660
  const resolvedDefIds = new Set(integrations.map((i) => i.definition_id));
1641
1661
  for (const capId of resolvedDefIds) {
@@ -1652,15 +1672,30 @@ async function processAgent(agent, agentStates) {
1652
1672
  const skillKey = `${agent.agent_id}:${capId}`;
1653
1673
  const prevSkillHash = knownSkillHashes.get(skillKey);
1654
1674
  if (skillHash !== prevSkillHash) {
1675
+ const installedSkillNames = [];
1655
1676
  if (frameworkAdapter.installSkillFiles && capData.skills) {
1656
1677
  for (const skill of capData.skills) {
1657
1678
  if (skill.files.length > 0) {
1658
1679
  frameworkAdapter.installSkillFiles(agent.code_name, skill.id, skill.files);
1680
+ installedSkillNames.push(skill.name || skill.id);
1659
1681
  log(`Installed skill '${skill.id}' for '${agent.code_name}' (${skill.files.length} file(s))`);
1660
1682
  }
1661
1683
  }
1662
1684
  }
1663
1685
  knownSkillHashes.set(skillKey, skillHash);
1686
+ const agentFw2 = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
1687
+ if (agentFw2 === "claude-code" && installedSkillNames.length > 0 && isSessionHealthy(agent.code_name)) {
1688
+ const names = installedSkillNames.join(", ");
1689
+ injectMessage(
1690
+ agent.code_name,
1691
+ "system",
1692
+ `New skills installed: ${names}. These are available immediately \u2014 Claude Code loads skills on demand from .claude/skills/.`,
1693
+ { task_name: "skill-update" },
1694
+ log
1695
+ ).catch(() => {
1696
+ });
1697
+ log(`[hot-reload] Notified '${agent.code_name}' about new skills: ${names}`);
1698
+ }
1664
1699
  }
1665
1700
  const ALLOWED_CLI_PACKAGES = /* @__PURE__ */ new Set([
1666
1701
  "xero-cli",
@@ -2274,6 +2309,12 @@ async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
2274
2309
  });
2275
2310
  const updated = markTaskFired(codeName, task.taskId, "ok");
2276
2311
  claudeSchedulerStates.set(codeName, updated);
2312
+ if (task.scheduleKind === "at") {
2313
+ api.post("/host/schedules/disable", { agent_id: agentId, task_id: task.taskId }).catch(
2314
+ (err) => log(`[claude-scheduler] Failed to disable one-off task '${task.name}': ${err.message}`)
2315
+ );
2316
+ log(`[claude-scheduler] One-off task '${task.name}' fired and disabled for '${codeName}'`);
2317
+ }
2277
2318
  } catch (err) {
2278
2319
  const errMsg = err instanceof Error ? err.message : String(err);
2279
2320
  log(`[claude-scheduler] Task '${task.name}' failed for '${codeName}': ${errMsg}`);
@@ -2683,7 +2724,7 @@ async function processDirectChatMessage(agent, msg) {
2683
2724
  try {
2684
2725
  let reply;
2685
2726
  if (fw === "claude-code") {
2686
- const { getProjectDir: ccProjectDir } = await import("../claude-scheduler-VFBZFE6U.js");
2727
+ const { getProjectDir: ccProjectDir } = await import("../claude-scheduler-7PVWQHWU.js");
2687
2728
  const projDir = ccProjectDir(agent.codeName);
2688
2729
  const mcpConfigPath = join(projDir, ".mcp.json");
2689
2730
  const channelsConfigPath = join(projDir, ".mcp-channels.json");
@@ -3397,6 +3438,32 @@ function generateArtifacts(agent, refreshData, adapter) {
3397
3438
  }
3398
3439
  const resolvedChannels = resolveChannels(agentChannelPolicy, orgChannelPolicy);
3399
3440
  const effectiveChannels = agent.status === "paused" ? [] : resolvedChannels;
3441
+ const tasks = refreshData.scheduled_tasks;
3442
+ const taskTimezones = [...new Set(
3443
+ (tasks ?? []).map((t) => typeof t.timezone === "string" ? t.timezone.trim() : "").filter((tz) => tz.length > 0)
3444
+ )];
3445
+ const teamTimezoneRaw = refreshData.team?.timezone;
3446
+ const teamTimezone = typeof teamTimezoneRaw === "string" && teamTimezoneRaw.trim().length > 0 ? teamTimezoneRaw.trim() : void 0;
3447
+ const agentTimezone = (taskTimezones.length === 1 ? taskTimezones[0] : void 0) ?? teamTimezone ?? "UTC";
3448
+ const orgDefaults = refreshData.model_defaults;
3449
+ const personalitySeed = orgDefaults?.org?.settings?.personality_seed;
3450
+ let reportsTo;
3451
+ const agentData = refreshData.agent;
3452
+ const reportsToId = agentData.reports_to;
3453
+ const reportsToType = agentData.reports_to_type ?? "agent";
3454
+ if (reportsToId) {
3455
+ const reportsToName = agentData.reports_to_name;
3456
+ const reportsToTitle = agentData.reports_to_title;
3457
+ const reportsToDesc = agentData.reports_to_description;
3458
+ if (reportsToName) {
3459
+ reportsTo = {
3460
+ name: reportsToName,
3461
+ type: reportsToType,
3462
+ title: reportsToTitle,
3463
+ description: reportsToDesc
3464
+ };
3465
+ }
3466
+ }
3400
3467
  const provisionInput = {
3401
3468
  agent: refreshData.agent,
3402
3469
  charterFrontmatter,
@@ -3406,7 +3473,10 @@ function generateArtifacts(agent, refreshData, adapter) {
3406
3473
  resolvedChannels: effectiveChannels,
3407
3474
  deploymentTarget: "local_docker",
3408
3475
  gatewayPort: 9e3,
3409
- team: refreshData.team ?? void 0
3476
+ team: refreshData.team ?? void 0,
3477
+ timezone: agentTimezone,
3478
+ reportsTo,
3479
+ personalitySeed
3410
3480
  };
3411
3481
  const provisionOutput = provision(provisionInput, adapter.id);
3412
3482
  return provisionOutput.artifacts;