@integrity-labs/agt-cli 0.15.38 → 0.16.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.
@@ -22,7 +22,7 @@ import {
22
22
  resolveChannels,
23
23
  resolveDmTarget,
24
24
  wrapScheduledTaskPrompt
25
- } from "../chunk-XJSQKRFV.js";
25
+ } from "../chunk-ITLXAEXI.js";
26
26
  import {
27
27
  findTaskByTemplate,
28
28
  getProjectDir,
@@ -35,14 +35,17 @@ import {
35
35
  buildAllowedTools,
36
36
  getProjectDir as getProjectDir2,
37
37
  injectMessage,
38
+ isAgentIdle,
38
39
  isSessionHealthy,
40
+ isStaleForToday,
41
+ peekCurrentSession,
39
42
  resetRestartCount,
40
43
  resolveClaudeBinary,
41
44
  sanitizeMcpJson,
42
45
  startPersistentSession,
43
46
  stopAllSessionsAndWait,
44
47
  stopPersistentSession
45
- } from "../chunk-AFUG4KD3.js";
48
+ } from "../chunk-QYG5LUTP.js";
46
49
 
47
50
  // src/lib/manager-worker.ts
48
51
  import { createHash } from "crypto";
@@ -53,7 +56,7 @@ import { join as join3, dirname } from "path";
53
56
  import { homedir as homedir3 } from "os";
54
57
  import { fileURLToPath } from "url";
55
58
 
56
- // src/lib/plugin-context-render.ts
59
+ // src/lib/integration-context-render.ts
57
60
  var PLUGIN_CONTEXT_PLACEHOLDER_RE = /\{\{\s*context\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
58
61
  var TEAM_OVERRIDES_HEADER = "## Team Overrides\n\n> **The following overrides anything you've read above.** If any rule here conflicts with earlier instructions in this skill, follow what is written here. These are user-supplied directives that take precedence over the plugin's default guidance.\n";
59
62
  function formatContextValue(value) {
@@ -66,7 +69,7 @@ function formatContextValue(value) {
66
69
  return "";
67
70
  }
68
71
  }
69
- function renderPluginSkillContent(raw, values, overrides, warn = () => {
72
+ function renderIntegrationSkillContent(raw, values, overrides, warn = () => {
70
73
  }) {
71
74
  const substituted = raw.replace(PLUGIN_CONTEXT_PLACEHOLDER_RE, (_match, fieldName) => {
72
75
  if (!(fieldName in values)) {
@@ -85,7 +88,7 @@ ${trimmedOverrides}
85
88
  `;
86
89
  }
87
90
 
88
- // src/lib/plugin-skill-layout.ts
91
+ // src/lib/integration-skill-layout.ts
89
92
  function extractDescription(content) {
90
93
  const match = content.match(/^---\s*([\s\S]*?)---/);
91
94
  if (!match) return null;
@@ -98,20 +101,20 @@ function extractDescription(content) {
98
101
  }
99
102
  return descMatch[2]?.trim() ?? null;
100
103
  }
101
- function sanitizeScopeSlug(skillId, pluginSlug) {
104
+ function sanitizeScopeSlug(skillId, integrationSlug) {
102
105
  const normalized = skillId.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-");
103
- const prefix = pluginSlug.replace(/-/g, "");
106
+ const prefix = integrationSlug.replace(/-/g, "");
104
107
  const stripped = normalized.startsWith(`${prefix}-`) ? normalized.slice(prefix.length + 1) : normalized;
105
108
  return stripped.replace(/^-|-$/g, "") || normalized || "scope";
106
109
  }
107
- function buildPluginBundle(skills) {
110
+ function buildIntegrationBundle(skills) {
108
111
  if (skills.length === 0) {
109
- throw new Error("buildPluginBundle: empty skills list");
112
+ throw new Error("buildIntegrationBundle: empty skills list");
110
113
  }
111
- const pluginSlug = skills[0].plugin_slug;
114
+ const integrationSlug = skills[0].plugin_slug;
112
115
  const ordered = [...skills].sort((a, b) => a.skill_id.localeCompare(b.skill_id));
113
116
  const entries = ordered.map((s) => {
114
- const scopeSlug = sanitizeScopeSlug(s.skill_id, pluginSlug);
117
+ const scopeSlug = sanitizeScopeSlug(s.skill_id, integrationSlug);
115
118
  const description = extractDescription(s.content);
116
119
  return {
117
120
  skillId: s.skill_id,
@@ -132,11 +135,11 @@ function buildPluginBundle(skills) {
132
135
  if (group.length > 1) {
133
136
  const conflicts = group.map((e) => `${e.skillId} (${e.skillName})`).join(", ");
134
137
  throw new Error(
135
- `buildPluginBundle: duplicate scope path '${scopePath}' for plugin '${pluginSlug}' \u2014 conflicting skills: ${conflicts}`
138
+ `buildIntegrationBundle: duplicate scope path '${scopePath}' for integration '${integrationSlug}' \u2014 conflicting skills: ${conflicts}`
136
139
  );
137
140
  }
138
141
  }
139
- const pluginName = slugToTitle(pluginSlug);
142
+ const integrationName = slugToTitle(integrationSlug);
140
143
  const umbrellaDescription = entries.map((e) => e.description ?? `Use for ${e.skillName}.`).join(" ");
141
144
  const scopeList = entries.map((e) => {
142
145
  const label = e.skillName || slugToTitle(e.scopeSlug);
@@ -145,13 +148,13 @@ function buildPluginBundle(skills) {
145
148
  }).join("\n");
146
149
  const umbrella = [
147
150
  "---",
148
- `name: "${pluginName}"`,
151
+ `name: "${integrationName}"`,
149
152
  `description: "${escapeYamlDouble(umbrellaDescription)}"`,
150
153
  "---",
151
154
  "",
152
- `# ${pluginName}`,
155
+ `# ${integrationName}`,
153
156
  "",
154
- `This skill bundles ${entries.length === 1 ? "one scope" : `${entries.length} scopes`} for the ${pluginSlug} plugin. Each scope has a dedicated reference under \`scopes/\` that you should load on demand when the user's intent maps to it.`,
157
+ `This skill bundles ${entries.length === 1 ? "one scope" : `${entries.length} scopes`} for the ${integrationSlug} integration. Each scope has a dedicated reference under \`scopes/\` that you should load on demand when the user's intent maps to it.`,
155
158
  "",
156
159
  "## Scopes",
157
160
  "",
@@ -170,7 +173,7 @@ function buildPluginBundle(skills) {
170
173
  }))
171
174
  ];
172
175
  return {
173
- pluginSlug,
176
+ integrationSlug,
174
177
  files,
175
178
  scopePaths: entries.map((e) => e.scopePath)
176
179
  };
@@ -181,7 +184,7 @@ function slugToTitle(slug) {
181
184
  function escapeYamlDouble(value) {
182
185
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
183
186
  }
184
- function groupSkillsByPlugin(skills) {
187
+ function groupSkillsByIntegration(skills) {
185
188
  const map = /* @__PURE__ */ new Map();
186
189
  for (const s of skills) {
187
190
  const bucket = map.get(s.plugin_slug);
@@ -794,6 +797,44 @@ async function sweepChannelProcesses(opts) {
794
797
  dryRun
795
798
  };
796
799
  }
800
+ function pickTeardownTargets(processes, codeName) {
801
+ return processes.filter((p) => p.codeName === codeName);
802
+ }
803
+ function defaultKillSignal(pid, signal) {
804
+ try {
805
+ process.kill(pid, signal);
806
+ } catch {
807
+ }
808
+ }
809
+ function defaultPs() {
810
+ return execFileSync("ps", ["eww", "-o", "pid=,ppid=,etime=,command="], {
811
+ encoding: "utf-8",
812
+ timeout: 5e3,
813
+ maxBuffer: 10 * 1024 * 1024
814
+ });
815
+ }
816
+ function killAgentChannelProcesses(codeName, opts) {
817
+ const { log: log2, dryRun = false } = opts;
818
+ const kill = opts.killFn ?? defaultKillSignal;
819
+ const ps = opts.psFn ?? defaultPs;
820
+ let psOutput = "";
821
+ try {
822
+ psOutput = ps();
823
+ } catch (err) {
824
+ log2(`[channel-teardown] ps failed for '${codeName}': ${err.message}`);
825
+ return [];
826
+ }
827
+ const targets = pickTeardownTargets(parsePsOutput(psOutput), codeName);
828
+ if (targets.length === 0) return [];
829
+ const pids = targets.map((t) => t.pid);
830
+ log2(
831
+ `[channel-teardown]${dryRun ? "[dry-run]" : ""} de-provision '${codeName}': killing ${targets.length} channel MCP(s) \u2014 ` + targets.map((t) => `${t.channelType}#${t.pid}`).join(", ")
832
+ );
833
+ if (!dryRun) {
834
+ for (const pid of pids) kill(pid, "SIGTERM");
835
+ }
836
+ return pids;
837
+ }
797
838
 
798
839
  // src/lib/delivery-hint.ts
799
840
  var DEFAULT_PROBABILITY = 0.1;
@@ -1033,7 +1074,7 @@ var driftChannel = null;
1033
1074
  var assignChannel = null;
1034
1075
  var configChannel = null;
1035
1076
  var kanbanChannel = null;
1036
- var pluginContextChannel = null;
1077
+ var integrationContextChannel = null;
1037
1078
  var connected = false;
1038
1079
  var tearingDown = false;
1039
1080
  function ensureClient(config2) {
@@ -1078,7 +1119,7 @@ function startRealtimeChat(config2) {
1078
1119
  assignChannel = null;
1079
1120
  configChannel = null;
1080
1121
  kanbanChannel = null;
1081
- pluginContextChannel = null;
1122
+ integrationContextChannel = null;
1082
1123
  if (client) {
1083
1124
  try {
1084
1125
  client.removeAllChannels();
@@ -1251,12 +1292,12 @@ function startRealtimeKanban(config2) {
1251
1292
  log2(`[realtime] Subscribing to agent_kanban_items for ${agentIds.length} agent(s)`);
1252
1293
  void formatActorId;
1253
1294
  }
1254
- function startRealtimePluginContext(config2) {
1295
+ function startRealtimeIntegrationContext(config2) {
1255
1296
  const { agentIds, onContextChange, log: log2 } = config2;
1256
1297
  if (agentIds.length === 0) return;
1257
1298
  const sb = ensureClient(config2);
1258
1299
  const filterStr = agentIds.length === 1 ? `agent_id=eq.${agentIds[0]}` : `agent_id=in.(${agentIds.join(",")})`;
1259
- pluginContextChannel = sb.channel("plugin-context-realtime").on("postgres_changes", {
1300
+ integrationContextChannel = sb.channel("plugin-context-realtime").on("postgres_changes", {
1260
1301
  event: "INSERT",
1261
1302
  schema: "public",
1262
1303
  table: "plugin_context",
@@ -1276,9 +1317,9 @@ function startRealtimePluginContext(config2) {
1276
1317
  onContextChange(row);
1277
1318
  }).subscribe((status) => {
1278
1319
  if (status === "SUBSCRIBED") {
1279
- log2("[realtime] Plugin context channel connected");
1320
+ log2("[realtime] Integration context channel connected");
1280
1321
  } else if (status === "CLOSED" || status === "CHANNEL_ERROR") {
1281
- log2(`[realtime] Plugin context channel: ${status}`);
1322
+ log2(`[realtime] Integration context channel: ${status}`);
1282
1323
  }
1283
1324
  });
1284
1325
  log2(`[realtime] Subscribing to plugin_context for ${agentIds.length} agent(s)`);
@@ -1325,12 +1366,12 @@ function stopRealtimeChat() {
1325
1366
  }
1326
1367
  kanbanChannel = null;
1327
1368
  }
1328
- if (pluginContextChannel) {
1369
+ if (integrationContextChannel) {
1329
1370
  try {
1330
- pluginContextChannel.unsubscribe();
1371
+ integrationContextChannel.unsubscribe();
1331
1372
  } catch {
1332
1373
  }
1333
- pluginContextChannel = null;
1374
+ integrationContextChannel = null;
1334
1375
  }
1335
1376
  if (client) {
1336
1377
  try {
@@ -1435,15 +1476,21 @@ function clearAgentCaches(agentId, codeName) {
1435
1476
  var cachedFrameworkVersion = null;
1436
1477
  var lastVersionCheckAt = 0;
1437
1478
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
1438
- var agtCliVersion = true ? "0.15.38" : "dev";
1479
+ var agtCliVersion = true ? "0.16.1" : "dev";
1439
1480
  function resolveBrewPath(execFileSync2) {
1440
1481
  try {
1441
1482
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
1442
1483
  if (out) return out;
1443
1484
  } catch {
1444
1485
  }
1445
- const fallback = "/home/linuxbrew/.linuxbrew/bin/brew";
1446
- if (existsSync2(fallback)) return fallback;
1486
+ const fallbacks = [
1487
+ "/home/linuxbrew/.linuxbrew/bin/brew",
1488
+ "/opt/homebrew/bin/brew",
1489
+ "/usr/local/bin/brew"
1490
+ ];
1491
+ for (const path of fallbacks) {
1492
+ if (existsSync2(path)) return path;
1493
+ }
1447
1494
  return null;
1448
1495
  }
1449
1496
  var toolkitCliEnsured = /* @__PURE__ */ new Set();
@@ -1656,44 +1703,51 @@ var UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
1656
1703
  var selfUpdateUpToDateLogged = false;
1657
1704
  var restartAfterUpgrade = false;
1658
1705
  var pendingUpgradeVersion = null;
1706
+ var selfUpdateInFlight = false;
1659
1707
  async function checkAndUpdateCli() {
1660
- const cliPath = process.argv[1] ?? "";
1661
- const isDevMode = cliPath.includes("/src/") || cliPath.includes("tsx");
1662
- if (isDevMode) return;
1663
- let resolvedPath = cliPath;
1708
+ if (selfUpdateInFlight) return;
1709
+ selfUpdateInFlight = true;
1664
1710
  try {
1665
- const { realpathSync } = await import("fs");
1666
- resolvedPath = realpathSync(cliPath);
1667
- } catch {
1668
- }
1669
- const isBrewFormula = /\/Cellar\/[^/]+\//.test(resolvedPath);
1670
- const isNpmGlobal = !isBrewFormula && resolvedPath.includes("node_modules");
1671
- if (!isBrewFormula && !isNpmGlobal) return;
1672
- const { readFileSync: readF, writeFileSync: writeF } = await import("fs");
1673
- const markerPath = join3(homedir3(), ".augmented", ".last-update-check");
1674
- try {
1675
- const lastCheck = parseInt(readF(markerPath, "utf-8").trim(), 10);
1676
- if (Date.now() - lastCheck < UPDATE_CHECK_INTERVAL_MS) return;
1677
- } catch {
1678
- }
1679
- if (isBrewFormula) {
1680
- await checkAndUpdateCliViaBrew();
1681
- } else {
1682
- await checkAndUpdateCliViaNpm();
1683
- }
1684
- try {
1685
- writeF(markerPath, String(Date.now()));
1686
- } catch {
1711
+ const cliPath = process.argv[1] ?? "";
1712
+ const isDevMode = cliPath.includes("/src/") || cliPath.includes("tsx");
1713
+ if (isDevMode) return;
1714
+ let resolvedPath = cliPath;
1715
+ try {
1716
+ const { realpathSync } = await import("fs");
1717
+ resolvedPath = realpathSync(cliPath);
1718
+ } catch {
1719
+ }
1720
+ const isBrewFormula = /\/Cellar\/[^/]+\//.test(resolvedPath);
1721
+ const isNpmGlobal = !isBrewFormula && resolvedPath.includes("node_modules");
1722
+ if (!isBrewFormula && !isNpmGlobal) return;
1723
+ const { readFileSync: readF, writeFileSync: writeF } = await import("fs");
1724
+ const markerPath = join3(homedir3(), ".augmented", ".last-update-check");
1725
+ try {
1726
+ const lastCheck = parseInt(readF(markerPath, "utf-8").trim(), 10);
1727
+ if (Date.now() - lastCheck < UPDATE_CHECK_INTERVAL_MS) return;
1728
+ } catch {
1729
+ }
1730
+ try {
1731
+ writeF(markerPath, String(Date.now()));
1732
+ } catch {
1733
+ }
1734
+ if (isBrewFormula) {
1735
+ await checkAndUpdateCliViaBrew();
1736
+ } else {
1737
+ await checkAndUpdateCliViaNpm();
1738
+ }
1739
+ try {
1740
+ writeF(markerPath, String(Date.now()));
1741
+ } catch {
1742
+ }
1743
+ } finally {
1744
+ selfUpdateInFlight = false;
1687
1745
  }
1688
1746
  }
1689
1747
  async function checkAndUpdateCliViaBrew() {
1690
1748
  const { execFileSync: execFileSync2 } = await import("child_process");
1691
- let brewPath;
1692
- try {
1693
- brewPath = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
1694
- } catch {
1695
- return;
1696
- }
1749
+ const brewPath = resolveBrewPath(execFileSync2);
1750
+ if (!brewPath) return;
1697
1751
  try {
1698
1752
  execFileSync2(brewPath, ["update", "--quiet"], { timeout: 6e4, stdio: "pipe" });
1699
1753
  } catch (err) {
@@ -2299,7 +2353,7 @@ async function pollCycle() {
2299
2353
  }
2300
2354
  try {
2301
2355
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
2302
- const { collectDiagnostics } = await import("../persistent-session-VRS3MFQ3.js");
2356
+ const { collectDiagnostics } = await import("../persistent-session-RZWMTEZY.js");
2303
2357
  const diagCodeNames = [...persistentSessionAgents];
2304
2358
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
2305
2359
  let tailscaleHostname;
@@ -2399,6 +2453,13 @@ async function pollCycle() {
2399
2453
  log(`Agent '${prev.codeName}' removed from host (deleted or unassigned)`);
2400
2454
  const adapter = resolveAgentFramework(prev.codeName);
2401
2455
  await stopGatewayIfRunning(prev.codeName, adapter);
2456
+ stopPersistentSession(prev.codeName, log);
2457
+ try {
2458
+ const { execSync: es } = await import("child_process");
2459
+ es(`tmux kill-session -t agt-${prev.codeName} 2>/dev/null`, { stdio: "ignore" });
2460
+ } catch {
2461
+ }
2462
+ killAgentChannelProcesses(prev.codeName, { log });
2402
2463
  freePort(prev.codeName);
2403
2464
  const agentDir = join3(adapter.getAgentDir(prev.codeName), "provision");
2404
2465
  await cleanupAgentFiles(prev.codeName, agentDir);
@@ -2442,7 +2503,7 @@ async function pollCycle() {
2442
2503
  ensureRealtimeAssignStarted(agentStates);
2443
2504
  ensureRealtimeConfigStarted(agentStates);
2444
2505
  ensureRealtimeKanbanStarted(agentStates);
2445
- ensureRealtimePluginContextStarted(agentStates);
2506
+ ensureRealtimeIntegrationContextStarted(agentStates);
2446
2507
  try {
2447
2508
  const spawnData = await api.post("/host/kanban/recurring/spawn");
2448
2509
  if (spawnData.spawned > 0) {
@@ -2526,6 +2587,7 @@ async function processAgent(agent, agentStates) {
2526
2587
  es(`tmux kill-session -t agt-${agent.code_name} 2>/dev/null`, { stdio: "ignore" });
2527
2588
  } catch {
2528
2589
  }
2590
+ killAgentChannelProcesses(agent.code_name, { log });
2529
2591
  freePort(agent.code_name);
2530
2592
  await cleanupAgentFiles(agent.code_name, agentDir);
2531
2593
  clearAgentCaches(agent.agent_id, agent.code_name);
@@ -3123,42 +3185,46 @@ async function processAgent(agent, agentStates) {
3123
3185
  }
3124
3186
  }
3125
3187
  if (frameworkAdapter.installSkillFiles) {
3126
- const currentPluginSkillIds = /* @__PURE__ */ new Set();
3127
- const installedPluginSkills = [];
3188
+ const currentIntegrationSkillIds = /* @__PURE__ */ new Set();
3189
+ const installedIntegrationSkills = [];
3128
3190
  const { createHash: createHash2 } = await import("crypto");
3191
+ const refreshAny = refreshData;
3192
+ const contexts = refreshAny.integration_contexts ?? refreshAny.plugin_contexts ?? [];
3129
3193
  const contextBySlug = /* @__PURE__ */ new Map();
3130
- for (const ctx of refreshData.plugin_contexts ?? []) {
3131
- contextBySlug.set(ctx.plugin_slug, { values: ctx.values ?? {}, overrides: (ctx.overrides ?? "").trim() });
3194
+ for (const ctx of contexts) {
3195
+ const slug = ctx.integration_slug ?? ctx.plugin_slug;
3196
+ if (!slug) continue;
3197
+ contextBySlug.set(slug, { values: ctx.values ?? {}, overrides: (ctx.overrides ?? "").trim() });
3132
3198
  }
3133
- const pluginGroups = groupSkillsByPlugin(
3134
- refreshData.plugin_skills ?? []
3199
+ const integrationGroups = groupSkillsByIntegration(
3200
+ refreshAny.integration_skills ?? refreshAny.plugin_skills ?? []
3135
3201
  );
3136
- for (const [pluginSlug, scopes] of pluginGroups) {
3202
+ for (const [integrationSlug, scopes] of integrationGroups) {
3137
3203
  try {
3138
- const pluginSkillId = `integration-${pluginSlug}`.replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
3139
- currentPluginSkillIds.add(pluginSkillId);
3140
- const ctx = contextBySlug.get(pluginSlug);
3204
+ const integrationSkillId = `integration-${integrationSlug}`.replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
3205
+ currentIntegrationSkillIds.add(integrationSkillId);
3206
+ const ctx = contextBySlug.get(integrationSlug);
3141
3207
  const renderedScopes = scopes.map((s) => ({
3142
3208
  plugin_slug: s.plugin_slug,
3143
3209
  skill_id: s.skill_id,
3144
3210
  skill_name: s.skill_name,
3145
- content: renderPluginSkillContent(
3211
+ content: renderIntegrationSkillContent(
3146
3212
  s.content,
3147
3213
  ctx?.values ?? {},
3148
3214
  ctx?.overrides ?? "",
3149
3215
  (warning) => log(`[plugin-context] ${s.plugin_slug}/${s.skill_id}: ${warning}`)
3150
3216
  )
3151
3217
  }));
3152
- const bundle = buildPluginBundle(renderedScopes);
3218
+ const bundle = buildIntegrationBundle(renderedScopes);
3153
3219
  const contentHash = createHash2("sha256").update(bundleFingerprint(bundle.files)).digest("hex").slice(0, 12);
3154
- const hashKey = `plugin-skill:${agent.agent_id}:${pluginSkillId}`;
3220
+ const hashKey = `plugin-skill:${agent.agent_id}:${integrationSkillId}`;
3155
3221
  if (knownSkillHashes.get(hashKey) === contentHash) continue;
3156
- frameworkAdapter.installSkillFiles(agent.code_name, pluginSkillId, bundle.files);
3222
+ frameworkAdapter.installSkillFiles(agent.code_name, integrationSkillId, bundle.files);
3157
3223
  knownSkillHashes.set(hashKey, contentHash);
3158
- for (const s of scopes) installedPluginSkills.push(s.skill_name);
3159
- log(`Installed plugin '${pluginSkillId}' for '${agent.code_name}' (${scopes.length} scope(s))`);
3224
+ for (const s of scopes) installedIntegrationSkills.push(s.skill_name);
3225
+ log(`Installed integration skill bundle '${integrationSkillId}' for '${agent.code_name}' (${scopes.length} scope(s))`);
3160
3226
  } catch (err) {
3161
- log(`Plugin install failed for '${agent.code_name}' / '${pluginSlug}': ${err.message}`);
3227
+ log(`Integration skill install failed for '${agent.code_name}' / '${integrationSlug}': ${err.message}`);
3162
3228
  }
3163
3229
  }
3164
3230
  try {
@@ -3198,12 +3264,12 @@ async function processAgent(agent, agentStates) {
3198
3264
  log(`Removed ${reason} '${entry}' for '${agent.code_name}' (framework=${frameworkId2})`);
3199
3265
  };
3200
3266
  for (const entry of discoveredEntries) {
3201
- if (!currentPluginSkillIds.has(entry)) {
3267
+ if (!currentIntegrationSkillIds.has(entry)) {
3202
3268
  removeSkillFolder(entry, "orphaned skill folder");
3203
3269
  }
3204
3270
  }
3205
3271
  } catch (err) {
3206
- log(`Plugin skill cleanup failed for '${agent.code_name}': ${err.message}`);
3272
+ log(`Integration skill cleanup failed for '${agent.code_name}': ${err.message}`);
3207
3273
  }
3208
3274
  try {
3209
3275
  const agentFwForIndex = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
@@ -3213,40 +3279,44 @@ async function processAgent(agent, agentStates) {
3213
3279
  } catch (err) {
3214
3280
  log(`Skills index refresh failed for '${agent.code_name}': ${err.message}`);
3215
3281
  }
3216
- if (frameworkAdapter.executePluginHook && refreshData.plugin_install_hooks?.length) {
3217
- for (const hook of refreshData.plugin_install_hooks) {
3282
+ const installHooks = refreshAny.integration_install_hooks ?? refreshAny.plugin_install_hooks ?? [];
3283
+ if (frameworkAdapter.executePluginHook && installHooks.length) {
3284
+ for (const hook of installHooks) {
3285
+ const slug = hook.integration_slug ?? hook.plugin_slug;
3286
+ if (!slug) continue;
3218
3287
  try {
3219
3288
  const scriptHash = createHash2("sha256").update(hook.script).digest("hex").slice(0, 12);
3220
- const hookKey = `${agent.agent_id}:${frameworkAdapter.id}:plugin-hook:${hook.plugin_slug}:on_install`;
3289
+ const hookKey = `${agent.agent_id}:${frameworkAdapter.id}:plugin-hook:${slug}:on_install`;
3221
3290
  if (knownSkillHashes.get(hookKey) === scriptHash) continue;
3222
3291
  const result = await frameworkAdapter.executePluginHook({
3223
3292
  codeName: agent.code_name,
3224
- pluginSlug: hook.plugin_slug,
3293
+ pluginSlug: slug,
3225
3294
  hookName: "on_install",
3226
3295
  script: hook.script
3227
3296
  });
3228
3297
  if (result.exitCode === 0) {
3229
3298
  knownSkillHashes.set(hookKey, scriptHash);
3230
- log(`Plugin hook on_install '${hook.plugin_slug}' succeeded for '${agent.code_name}' (${result.durationMs}ms)`);
3299
+ log(`Integration hook on_install '${slug}' succeeded for '${agent.code_name}' (${result.durationMs}ms)`);
3231
3300
  } else if (result.timedOut) {
3232
- log(`Plugin hook on_install '${hook.plugin_slug}' TIMED OUT for '${agent.code_name}' after ${result.durationMs}ms`);
3301
+ log(`Integration hook on_install '${slug}' TIMED OUT for '${agent.code_name}' after ${result.durationMs}ms`);
3233
3302
  } else {
3234
3303
  const stderrHash = createHash2("sha256").update(result.stderr).digest("hex").slice(0, 12);
3235
3304
  const missingCmd = result.exitCode === 127 ? extractCommandNotFound(result.stderr) : null;
3236
3305
  const missingCmdHash = missingCmd ? createHash2("sha256").update(missingCmd).digest("hex").slice(0, 8) : null;
3237
3306
  log(
3238
- `Plugin hook on_install '${hook.plugin_slug}' exited ${result.exitCode} for '${agent.code_name}' ` + (missingCmdHash ? `[missing_command_hash=${missingCmdHash}] ` : "") + `[stderr_hash=${stderrHash} stderr_len=${result.stderr.length}]`
3307
+ `Integration hook on_install '${slug}' exited ${result.exitCode} for '${agent.code_name}' ` + (missingCmdHash ? `[missing_command_hash=${missingCmdHash}] ` : "") + `[stderr_hash=${stderrHash} stderr_len=${result.stderr.length}]`
3239
3308
  );
3240
3309
  }
3241
3310
  } catch (err) {
3242
- log(`Plugin hook on_install failed for '${agent.code_name}' / '${hook.plugin_slug}': ${err.message}`);
3311
+ log(`Integration hook on_install failed for '${agent.code_name}' / '${slug}': ${err.message}`);
3243
3312
  }
3244
3313
  }
3245
3314
  }
3246
3315
  const agentFwForToolkits = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
3247
- if (agentFwForToolkits === "claude-code" && refreshData.plugin_toolkits?.length) {
3316
+ const toolkitsList = refreshAny.integration_toolkits ?? refreshAny.plugin_toolkits ?? [];
3317
+ if (agentFwForToolkits === "claude-code" && toolkitsList.length) {
3248
3318
  const toolkitUnion = /* @__PURE__ */ new Set();
3249
- for (const { toolkits } of refreshData.plugin_toolkits) {
3319
+ for (const { toolkits } of toolkitsList) {
3250
3320
  for (const t of toolkits) toolkitUnion.add(t);
3251
3321
  }
3252
3322
  for (const toolkitSlug of toolkitUnion) {
@@ -3254,17 +3324,17 @@ async function processAgent(agent, agentStates) {
3254
3324
  }
3255
3325
  }
3256
3326
  const agentFw2 = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
3257
- if (agentFw2 === "claude-code" && installedPluginSkills.length > 0 && isSessionHealthy(agent.code_name)) {
3258
- const names = installedPluginSkills.join(", ");
3327
+ if (agentFw2 === "claude-code" && installedIntegrationSkills.length > 0 && isSessionHealthy(agent.code_name)) {
3328
+ const names = installedIntegrationSkills.join(", ");
3259
3329
  injectMessage(
3260
3330
  agent.code_name,
3261
3331
  "system",
3262
- `New plugin skills installed: ${names}. These are available immediately \u2014 Claude Code loads skills on demand from .claude/skills/.`,
3332
+ `New integration skills installed: ${names}. These are available immediately \u2014 Claude Code loads skills on demand from .claude/skills/.`,
3263
3333
  { task_name: "plugin-skill-update" },
3264
3334
  log
3265
3335
  ).catch(() => {
3266
3336
  });
3267
- log(`[hot-reload] Notified '${agent.code_name}' about new plugin skills: ${names}`);
3337
+ log(`[hot-reload] Notified '${agent.code_name}' about new integration skills: ${names}`);
3268
3338
  }
3269
3339
  }
3270
3340
  }
@@ -4040,6 +4110,23 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
4040
4110
  stopPersistentSession(codeName, log);
4041
4111
  persistentSessionAgents.delete(codeName);
4042
4112
  }
4113
+ if (isStaleForToday(codeName) && isSessionHealthy(codeName)) {
4114
+ const current = peekCurrentSession(codeName);
4115
+ if (current) {
4116
+ const idle = isAgentIdle(projectDir, current.sessionId);
4117
+ if (idle) {
4118
+ log(
4119
+ `[persistent-session] Day rollover for '${codeName}' (yesterday=${current.date}) \u2014 agent idle, restarting to mint fresh session`
4120
+ );
4121
+ stopPersistentSession(codeName, log);
4122
+ persistentSessionAgents.delete(codeName);
4123
+ } else {
4124
+ log(
4125
+ `[persistent-session] Day rollover for '${codeName}' deferred \u2014 agent still active on session ${current.sessionId} (will retry next tick)`
4126
+ );
4127
+ }
4128
+ }
4129
+ }
4043
4130
  if (!isSessionHealthy(codeName)) {
4044
4131
  if (persistentSessionAgents.has(codeName)) {
4045
4132
  log(`[persistent-session] Session for '${codeName}' is unhealthy, will restart`);
@@ -4155,7 +4242,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
4155
4242
  var realtimeStarted = false;
4156
4243
  var realtimeDriftStarted = false;
4157
4244
  var realtimeKanbanStarted = false;
4158
- var realtimePluginContextStarted = false;
4245
+ var realtimeIntegrationContextStarted = false;
4159
4246
  var realtimeAssignStarted = false;
4160
4247
  var realtimeConfigStarted = false;
4161
4248
  var realtimeSubscribedAgentIds = /* @__PURE__ */ new Set();
@@ -4174,7 +4261,7 @@ function ensureRealtimeStarted(agentStates) {
4174
4261
  realtimeAssignStarted = false;
4175
4262
  realtimeConfigStarted = false;
4176
4263
  realtimeKanbanStarted = false;
4177
- realtimePluginContextStarted = false;
4264
+ realtimeIntegrationContextStarted = false;
4178
4265
  }
4179
4266
  const activeAgentIds = agentStates.filter((a) => a.status === "active").map((a) => a.agentId);
4180
4267
  if (activeAgentIds.length === 0) return;
@@ -4214,6 +4301,7 @@ function ensureRealtimeStarted(agentStates) {
4214
4301
  realtimeAssignStarted = false;
4215
4302
  realtimeConfigStarted = false;
4216
4303
  realtimeKanbanStarted = false;
4304
+ realtimeIntegrationContextStarted = false;
4217
4305
  }
4218
4306
  },
4219
4307
  log
@@ -4353,15 +4441,15 @@ function ensureRealtimeKanbanStarted(agentStates) {
4353
4441
  log(`[realtime] Kanban subscription failed: ${err.message}`);
4354
4442
  });
4355
4443
  }
4356
- function ensureRealtimePluginContextStarted(agentStates) {
4357
- if (realtimePluginContextStarted) return;
4444
+ function ensureRealtimeIntegrationContextStarted(agentStates) {
4445
+ if (realtimeIntegrationContextStarted) return;
4358
4446
  const activeAgentIds = agentStates.filter((a) => a.status === "active").map((a) => a.agentId);
4359
4447
  if (activeAgentIds.length === 0) return;
4360
4448
  const apiKey = process.env["AGT_API_KEY"];
4361
4449
  if (!apiKey) return;
4362
4450
  void exchangeApiKey(apiKey).then((exchange) => {
4363
4451
  if (!exchange.supabaseUrl || !exchange.supabaseAnonKey) return;
4364
- startRealtimePluginContext({
4452
+ startRealtimeIntegrationContext({
4365
4453
  supabaseUrl: exchange.supabaseUrl,
4366
4454
  supabaseAnonKey: exchange.supabaseAnonKey,
4367
4455
  token: exchange.token,
@@ -4375,14 +4463,14 @@ function ensureRealtimePluginContextStarted(agentStates) {
4375
4463
  }
4376
4464
  }
4377
4465
  }
4378
- triggerEarlyPoll(`plugin context changed for agent ${payload.agent_id}`);
4466
+ triggerEarlyPoll(`integration context changed for agent ${payload.agent_id}`);
4379
4467
  },
4380
4468
  log
4381
4469
  });
4382
- realtimePluginContextStarted = true;
4383
- log(`[realtime] Plugin context subscription started for ${activeAgentIds.length} agent(s)`);
4470
+ realtimeIntegrationContextStarted = true;
4471
+ log(`[realtime] Integration context subscription started for ${activeAgentIds.length} agent(s)`);
4384
4472
  }).catch((err) => {
4385
- log(`[realtime] Plugin context subscription failed: ${err.message}`);
4473
+ log(`[realtime] Integration context subscription failed: ${err.message}`);
4386
4474
  });
4387
4475
  }
4388
4476
  function triggerEarlyPoll(reason) {
@@ -5352,8 +5440,9 @@ async function processClaudePairSessions(agents) {
5352
5440
  submitClaudePairCode,
5353
5441
  spawnPairSession,
5354
5442
  killPairSession,
5355
- pairTmuxSession
5356
- } = await import("../claude-pair-runtime-GLO2D7WP.js");
5443
+ pairTmuxSession,
5444
+ finalizeClaudePairOnboarding
5445
+ } = await import("../claude-pair-runtime-FEUHTZXZ.js");
5357
5446
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
5358
5447
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
5359
5448
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -5414,10 +5503,11 @@ async function processClaudePairSessions(agents) {
5414
5503
  });
5415
5504
  } else {
5416
5505
  const errKind = result.error.kind;
5506
+ const errMessage = "message" in result.error ? result.error.message : void 0;
5417
5507
  await reportAndCleanup(session.pair_id, {
5418
5508
  status: errKind === "no-session" ? "session_missing" : "failure",
5419
5509
  error_code: errKind,
5420
- error_message: errKind === "unknown" ? result.error.message : void 0
5510
+ error_message: errMessage
5421
5511
  });
5422
5512
  }
5423
5513
  } else if (session.status === "code_submitted" && session.code) {
@@ -5427,6 +5517,10 @@ async function processClaudePairSessions(agents) {
5427
5517
  code: session.code
5428
5518
  });
5429
5519
  if (result.kind === "success") {
5520
+ const finalize = await finalizeClaudePairOnboarding(pairSession, log);
5521
+ if (!finalize.finalized) {
5522
+ log(`[claude-pair] WARN: ~/.claude.json was not updated during onboarding (pair ${session.pair_id.slice(0, 8)}) \u2014 first agent launch may show the login picker`);
5523
+ }
5430
5524
  await reportAndCleanup(session.pair_id, { status: "success" });
5431
5525
  } else if (result.kind === "failure") {
5432
5526
  await reportAndCleanup(session.pair_id, {
@@ -5442,10 +5536,11 @@ async function processClaudePairSessions(agents) {
5442
5536
  });
5443
5537
  } else {
5444
5538
  const errKind = result.error.kind;
5539
+ const errMessage = "message" in result.error ? result.error.message : void 0;
5445
5540
  await reportAndCleanup(session.pair_id, {
5446
5541
  status: errKind === "no-session" ? "session_missing" : "failure",
5447
5542
  error_code: errKind,
5448
- error_message: errKind === "unknown" ? result.error.message : void 0
5543
+ error_message: errMessage
5449
5544
  });
5450
5545
  }
5451
5546
  }