@integrity-labs/agt-cli 0.28.421 → 0.28.423

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.
@@ -100,7 +100,7 @@ async function spawnPairSession(session) {
100
100
  return { ok: true };
101
101
  } catch {
102
102
  }
103
- const { resolveClaudeBinary } = await import("./persistent-session-2OYYXMM7.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-HWCKGPYF.js");
104
104
  const claudeBin = resolveClaudeBinary();
105
105
  const pairEnv = {
106
106
  ...process.env,
@@ -373,4 +373,4 @@ export {
373
373
  startClaudePair,
374
374
  submitClaudePairCode
375
375
  };
376
- //# sourceMappingURL=claude-pair-runtime-ZTZR3VCN.js.map
376
+ //# sourceMappingURL=claude-pair-runtime-3JHCWZZD.js.map
@@ -45,14 +45,14 @@ import {
45
45
  resolveEffectivePinRaw,
46
46
  safeWriteJsonAtomic,
47
47
  setConfigHash
48
- } from "../chunk-HVVEMQWZ.js";
48
+ } from "../chunk-4SEBGBAO.js";
49
49
  import {
50
50
  getProjectDir as getProjectDir2,
51
51
  getReadyTasks,
52
52
  loadSchedulerState,
53
53
  markTaskFired,
54
54
  syncTasksToScheduler
55
- } from "../chunk-EP6E6CIY.js";
55
+ } from "../chunk-3HLECXMI.js";
56
56
  import {
57
57
  AnchorSessionClient,
58
58
  CONVERSATION_FAILURE_CATEGORIES,
@@ -150,7 +150,7 @@ import {
150
150
  toOpencodeModel,
151
151
  transcriptActivityAgeSeconds,
152
152
  writeEgressAllowlist
153
- } from "../chunk-B4NX2T2H.js";
153
+ } from "../chunk-XFC7YVGS.js";
154
154
  import {
155
155
  reapOrphanChannelMcps
156
156
  } from "../chunk-XWVM4KPK.js";
@@ -9458,7 +9458,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
9458
9458
  var lastVersionCheckAt = 0;
9459
9459
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
9460
9460
  var lastResponsivenessProbeAt = 0;
9461
- var agtCliVersion = true ? "0.28.421" : "dev";
9461
+ var agtCliVersion = true ? "0.28.423" : "dev";
9462
9462
  function resolveBrewPath(execFileSync2) {
9463
9463
  try {
9464
9464
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -10606,7 +10606,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
10606
10606
  if (codeNames.length === 0) return;
10607
10607
  void (async () => {
10608
10608
  try {
10609
- const { collectDiagnostics } = await import("../persistent-session-2OYYXMM7.js");
10609
+ const { collectDiagnostics } = await import("../persistent-session-HWCKGPYF.js");
10610
10610
  await api.post("/host/heartbeat", {
10611
10611
  host_id: hostId,
10612
10612
  agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor)
@@ -10713,7 +10713,7 @@ async function pollCycle() {
10713
10713
  }
10714
10714
  try {
10715
10715
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
10716
- const { collectDiagnostics } = await import("../persistent-session-2OYYXMM7.js");
10716
+ const { collectDiagnostics } = await import("../persistent-session-HWCKGPYF.js");
10717
10717
  const diagCodeNames = [...agentState.persistentSessionAgents];
10718
10718
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor) : void 0;
10719
10719
  let tailscaleHostname;
@@ -10827,7 +10827,7 @@ async function pollCycle() {
10827
10827
  collectResponsivenessProbes,
10828
10828
  collectPanelessActivityProbes,
10829
10829
  getResponsivenessIntervalMs
10830
- } = await import("../responsiveness-probe-7OOA57BP.js");
10830
+ } = await import("../responsiveness-probe-4CGMUXOH.js");
10831
10831
  const probeIntervalMs = getResponsivenessIntervalMs();
10832
10832
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
10833
10833
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -10866,8 +10866,8 @@ async function pollCycle() {
10866
10866
  collectResponsivenessProbes,
10867
10867
  livePendingInboundOldestAgeSeconds,
10868
10868
  parkPendingInbound
10869
- } = await import("../responsiveness-probe-7OOA57BP.js");
10870
- const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-HYOOQV4A.js");
10869
+ } = await import("../responsiveness-probe-4CGMUXOH.js");
10870
+ const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-NDP36U7O.js");
10871
10871
  const wedgeNow = /* @__PURE__ */ new Date();
10872
10872
  const liveAgents = agentState.persistentSessionAgents;
10873
10873
  for (const tracked of consecutiveWedgeCycles.keys()) {
@@ -14177,7 +14177,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
14177
14177
  void api.post("/host/restart-ack", { host_id: hostId, agent_id: agentId, restart_requested_at: requestedAt }).catch((err) => log(`[restart-lane] ack failed for '${codeName}': ${err.message}`));
14178
14178
  void (async () => {
14179
14179
  try {
14180
- const { collectDiagnostics } = await import("../persistent-session-2OYYXMM7.js");
14180
+ const { collectDiagnostics } = await import("../persistent-session-HWCKGPYF.js");
14181
14181
  await api.post("/host/heartbeat", {
14182
14182
  host_id: hostId,
14183
14183
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor)
@@ -14227,7 +14227,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
14227
14227
  }
14228
14228
  try {
14229
14229
  const hostId = await getHostId();
14230
- const { collectDiagnostics } = await import("../persistent-session-2OYYXMM7.js");
14230
+ const { collectDiagnostics } = await import("../persistent-session-HWCKGPYF.js");
14231
14231
  await api.post("/host/heartbeat", {
14232
14232
  host_id: hostId,
14233
14233
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor)
@@ -14680,7 +14680,7 @@ async function processClaudePairSessions(agents) {
14680
14680
  killPairSession,
14681
14681
  pairTmuxSession,
14682
14682
  finalizeClaudePairOnboarding
14683
- } = await import("../claude-pair-runtime-ZTZR3VCN.js");
14683
+ } = await import("../claude-pair-runtime-3JHCWZZD.js");
14684
14684
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
14685
14685
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
14686
14686
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -34681,7 +34681,14 @@ var FLAG_REGISTRY = [
34681
34681
  envVar: "AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE",
34682
34682
  // enforce REFUSES a send the agent asked to make - a visible availability
34683
34683
  // change, so flipping toward it is deliberate (ADR-0022 sensitive-flag confirm).
34684
- sensitive: true
34684
+ sensitive: true,
34685
+ // ENG-8149: the agt-cli that first carries resolveSlackScheduledChannelGuardMode
34686
+ // (published from the ENG-8137 merge c6ff71cd). Without this the flip-reach modal
34687
+ // reports FULL reach, because an undefined `since` means "every host can honour
34688
+ // it" - true for flags that predate the reach work, wrong for a NEW host-read
34689
+ // flag whose reader older hosts simply do not have. Excluded from
34690
+ // projectDefinition, so setting it does not roll FLAGS_SCHEMA_VERSION.
34691
+ since: "0.28.421"
34685
34692
  },
34686
34693
  {
34687
34694
  key: "slack-hot-thread-guard",
@@ -35293,6 +35300,17 @@ var FLAG_REGISTRY = [
35293
35300
  // direction - a flag-DB read error must not resurrect the mis-threading/noise the kill
35294
35301
  // fixes. Net-new gate, registry-only (ADR-0022); API-evaluated, no env materialization.
35295
35302
  defaultValue: false
35303
+ },
35304
+ {
35305
+ key: "onboarding-msteams-channel",
35306
+ description: `Offer Microsoft Teams as a selectable channel on onboarding step 3 ("which channels should your org use?", ENG-8146). OFF (default) hides the Teams option entirely: it is not rendered, not selectable, and not named in the plan-upgrade note. Turn it ON per org to restore the option for a customer who genuinely runs on Teams. Resolved SERVER-SIDE in the step-3 server component for the URL's orgId (webapp lib/feature-flags-org-server.ts), NOT via the browser public map \u2014 the client flag path evaluates at the active-org cookie, which onboarding deliberately does not pin to the org being onboarded, so a client read would resolve the wrong org for a multi-org user. Hiding the option never revokes a channel: an org that already saved msteams keeps it (step 3 carries hidden-but-saved channels through the write). Ships dark.`,
35307
+ flagType: "boolean",
35308
+ // Declared safe value is `false`: Teams hidden. `false` is also the fail-closed
35309
+ // direction - a flag-DB read error must show the narrower channel set, never
35310
+ // surface an option the operator has not deliberately turned on. Net-new gate,
35311
+ // registry-only (ADR-0022). NOT `public`: it is resolved server-side only, so it
35312
+ // must never be serialized into the browser map.
35313
+ defaultValue: false
35296
35314
  }
35297
35315
  ];
35298
35316
  var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
@@ -40297,7 +40297,14 @@ var FLAG_REGISTRY = [
40297
40297
  envVar: "AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE",
40298
40298
  // enforce REFUSES a send the agent asked to make - a visible availability
40299
40299
  // change, so flipping toward it is deliberate (ADR-0022 sensitive-flag confirm).
40300
- sensitive: true
40300
+ sensitive: true,
40301
+ // ENG-8149: the agt-cli that first carries resolveSlackScheduledChannelGuardMode
40302
+ // (published from the ENG-8137 merge c6ff71cd). Without this the flip-reach modal
40303
+ // reports FULL reach, because an undefined `since` means "every host can honour
40304
+ // it" - true for flags that predate the reach work, wrong for a NEW host-read
40305
+ // flag whose reader older hosts simply do not have. Excluded from
40306
+ // projectDefinition, so setting it does not roll FLAGS_SCHEMA_VERSION.
40307
+ since: "0.28.421"
40301
40308
  },
40302
40309
  {
40303
40310
  key: "slack-hot-thread-guard",
@@ -40909,6 +40916,17 @@ var FLAG_REGISTRY = [
40909
40916
  // direction - a flag-DB read error must not resurrect the mis-threading/noise the kill
40910
40917
  // fixes. Net-new gate, registry-only (ADR-0022); API-evaluated, no env materialization.
40911
40918
  defaultValue: false
40919
+ },
40920
+ {
40921
+ key: "onboarding-msteams-channel",
40922
+ description: `Offer Microsoft Teams as a selectable channel on onboarding step 3 ("which channels should your org use?", ENG-8146). OFF (default) hides the Teams option entirely: it is not rendered, not selectable, and not named in the plan-upgrade note. Turn it ON per org to restore the option for a customer who genuinely runs on Teams. Resolved SERVER-SIDE in the step-3 server component for the URL's orgId (webapp lib/feature-flags-org-server.ts), NOT via the browser public map \u2014 the client flag path evaluates at the active-org cookie, which onboarding deliberately does not pin to the org being onboarded, so a client read would resolve the wrong org for a multi-org user. Hiding the option never revokes a channel: an org that already saved msteams keeps it (step 3 carries hidden-but-saved channels through the write). Ships dark.`,
40923
+ flagType: "boolean",
40924
+ // Declared safe value is `false`: Teams hidden. `false` is also the fail-closed
40925
+ // direction - a flag-DB read error must show the narrower channel set, never
40926
+ // surface an option the operator has not deliberately turned on. Net-new gate,
40927
+ // registry-only (ADR-0022). NOT `public`: it is resolved server-side only, so it
40928
+ // must never be serialized into the browser map.
40929
+ defaultValue: false
40912
40930
  }
40913
40931
  ];
40914
40932
  var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
@@ -34264,7 +34264,14 @@ var FLAG_REGISTRY = [
34264
34264
  envVar: "AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE",
34265
34265
  // enforce REFUSES a send the agent asked to make - a visible availability
34266
34266
  // change, so flipping toward it is deliberate (ADR-0022 sensitive-flag confirm).
34267
- sensitive: true
34267
+ sensitive: true,
34268
+ // ENG-8149: the agt-cli that first carries resolveSlackScheduledChannelGuardMode
34269
+ // (published from the ENG-8137 merge c6ff71cd). Without this the flip-reach modal
34270
+ // reports FULL reach, because an undefined `since` means "every host can honour
34271
+ // it" - true for flags that predate the reach work, wrong for a NEW host-read
34272
+ // flag whose reader older hosts simply do not have. Excluded from
34273
+ // projectDefinition, so setting it does not roll FLAGS_SCHEMA_VERSION.
34274
+ since: "0.28.421"
34268
34275
  },
34269
34276
  {
34270
34277
  key: "slack-hot-thread-guard",
@@ -34876,6 +34883,17 @@ var FLAG_REGISTRY = [
34876
34883
  // direction - a flag-DB read error must not resurrect the mis-threading/noise the kill
34877
34884
  // fixes. Net-new gate, registry-only (ADR-0022); API-evaluated, no env materialization.
34878
34885
  defaultValue: false
34886
+ },
34887
+ {
34888
+ key: "onboarding-msteams-channel",
34889
+ description: `Offer Microsoft Teams as a selectable channel on onboarding step 3 ("which channels should your org use?", ENG-8146). OFF (default) hides the Teams option entirely: it is not rendered, not selectable, and not named in the plan-upgrade note. Turn it ON per org to restore the option for a customer who genuinely runs on Teams. Resolved SERVER-SIDE in the step-3 server component for the URL's orgId (webapp lib/feature-flags-org-server.ts), NOT via the browser public map \u2014 the client flag path evaluates at the active-org cookie, which onboarding deliberately does not pin to the org being onboarded, so a client read would resolve the wrong org for a multi-org user. Hiding the option never revokes a channel: an org that already saved msteams keeps it (step 3 carries hidden-but-saved channels through the write). Ships dark.`,
34890
+ flagType: "boolean",
34891
+ // Declared safe value is `false`: Teams hidden. `false` is also the fail-closed
34892
+ // direction - a flag-DB read error must show the narrower channel set, never
34893
+ // surface an option the operator has not deliberately turned on. Net-new gate,
34894
+ // registry-only (ADR-0022). NOT `public`: it is resolved server-side only, so it
34895
+ // must never be serialized into the browser map.
34896
+ defaultValue: false
34879
34897
  }
34880
34898
  ];
34881
34899
  var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
@@ -34566,7 +34566,14 @@ var FLAG_REGISTRY = [
34566
34566
  envVar: "AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE",
34567
34567
  // enforce REFUSES a send the agent asked to make - a visible availability
34568
34568
  // change, so flipping toward it is deliberate (ADR-0022 sensitive-flag confirm).
34569
- sensitive: true
34569
+ sensitive: true,
34570
+ // ENG-8149: the agt-cli that first carries resolveSlackScheduledChannelGuardMode
34571
+ // (published from the ENG-8137 merge c6ff71cd). Without this the flip-reach modal
34572
+ // reports FULL reach, because an undefined `since` means "every host can honour
34573
+ // it" - true for flags that predate the reach work, wrong for a NEW host-read
34574
+ // flag whose reader older hosts simply do not have. Excluded from
34575
+ // projectDefinition, so setting it does not roll FLAGS_SCHEMA_VERSION.
34576
+ since: "0.28.421"
34570
34577
  },
34571
34578
  {
34572
34579
  key: "slack-hot-thread-guard",
@@ -35178,6 +35185,17 @@ var FLAG_REGISTRY = [
35178
35185
  // direction - a flag-DB read error must not resurrect the mis-threading/noise the kill
35179
35186
  // fixes. Net-new gate, registry-only (ADR-0022); API-evaluated, no env materialization.
35180
35187
  defaultValue: false
35188
+ },
35189
+ {
35190
+ key: "onboarding-msteams-channel",
35191
+ description: `Offer Microsoft Teams as a selectable channel on onboarding step 3 ("which channels should your org use?", ENG-8146). OFF (default) hides the Teams option entirely: it is not rendered, not selectable, and not named in the plan-upgrade note. Turn it ON per org to restore the option for a customer who genuinely runs on Teams. Resolved SERVER-SIDE in the step-3 server component for the URL's orgId (webapp lib/feature-flags-org-server.ts), NOT via the browser public map \u2014 the client flag path evaluates at the active-org cookie, which onboarding deliberately does not pin to the org being onboarded, so a client read would resolve the wrong org for a multi-org user. Hiding the option never revokes a channel: an org that already saved msteams keeps it (step 3 carries hidden-but-saved channels through the write). Ships dark.`,
35192
+ flagType: "boolean",
35193
+ // Declared safe value is `false`: Teams hidden. `false` is also the fail-closed
35194
+ // direction - a flag-DB read error must show the narrower channel set, never
35195
+ // surface an option the operator has not deliberately turned on. Net-new gate,
35196
+ // registry-only (ADR-0022). NOT `public`: it is resolved server-side only, so it
35197
+ // must never be serialized into the browser map.
35198
+ defaultValue: false
35181
35199
  }
35182
35200
  ];
35183
35201
  var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
@@ -36,7 +36,7 @@ import {
36
36
  writeDirectChatSessionState,
37
37
  writeEgressAllowlist,
38
38
  writePersistentClaudeWrapper
39
- } from "./chunk-B4NX2T2H.js";
39
+ } from "./chunk-XFC7YVGS.js";
40
40
  import "./chunk-XWVM4KPK.js";
41
41
  export {
42
42
  EGRESS_BASELINE_DOMAINS,
@@ -77,4 +77,4 @@ export {
77
77
  writeEgressAllowlist,
78
78
  writePersistentClaudeWrapper
79
79
  };
80
- //# sourceMappingURL=persistent-session-2OYYXMM7.js.map
80
+ //# sourceMappingURL=persistent-session-HWCKGPYF.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  paneLogPath
3
- } from "./chunk-B4NX2T2H.js";
3
+ } from "./chunk-XFC7YVGS.js";
4
4
  import "./chunk-XWVM4KPK.js";
5
5
 
6
6
  // src/lib/responsiveness-probe.ts
@@ -471,4 +471,4 @@ export {
471
471
  readAndResetSlackReplyBindingClassifications,
472
472
  readAndResetSlackReplyTargetClassifications
473
473
  };
474
- //# sourceMappingURL=responsiveness-probe-7OOA57BP.js.map
474
+ //# sourceMappingURL=responsiveness-probe-4CGMUXOH.js.map
@@ -7,7 +7,7 @@ import {
7
7
  markTaskFired,
8
8
  saveSchedulerState,
9
9
  syncTasksToScheduler
10
- } from "./chunk-EP6E6CIY.js";
10
+ } from "./chunk-3HLECXMI.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=scheduler-engine-HYOOQV4A.js.map
21
+ //# sourceMappingURL=scheduler-engine-NDP36U7O.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.28.421",
3
+ "version": "0.28.423",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/scheduler-engine.ts"],"sourcesContent":["/**\n * Framework-agnostic scheduled-task state machine.\n *\n * The pure scheduling engine: schedule definitions -> next-fire computation ->\n * ready-task detection -> post-fire state advance, with on-disk persistence so\n * nextFireAt/lastFireAt survive manager restarts. It owns the WHEN, not the HOW:\n * each framework supplies its own firing leaf and calls back into\n * `markTaskFired`.\n * - Claude Code fires via the in-session kanban todo-card route\n * (manager/scheduler/execution.ts + kanban-route.ts).\n * - opencode fires via an HTTP inject into `opencode serve`\n * (manager/opencode-scheduler.ts).\n * Both share this engine verbatim, so cadence, one-shot completion,\n * manual-trigger, and cross-restart recovery are identical across frameworks.\n * (Named claude-scheduler.ts historically, when the claude `-p` oneshot was the\n * only consumer.)\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 /** ENG-6107: always | conditional | never — whether results deliver.\n * Optional: states persisted by older CLIs lack it; absent = 'always'. */\n deliveryPolicy?: string | null;\n deliveryChannel: string | null;\n /** ENG-4422 §4: JSONB DeliveryTarget (or null). Legacy string form is gone\n * after the migration; in-memory shape is the structured object. */\n deliveryTo: unknown | 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 /** ENG-6107: absent from older APIs — treated as 'always'. */\n delivery_policy?: string | null;\n delivery_channel: string | null;\n /** ENG-4422 §4: JSONB DeliveryTarget or null (post-migration). */\n delivery_to: unknown | 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 // ENG-4418: unified agent dir — scheduler state lives at the agent root\n // alongside other runtime state files (plugins.json, schedules.json,\n // registration.json). Legacy ~/.augmented/<codeName>/claudecode/scheduler-state.json\n // is migrated up by claudeCodeAdapter.getAgentDir on first poll.\n return join(homedir(), '.augmented', codeName);\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.deliveryPolicy = t.delivery_policy ?? 'always';\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 deliveryPolicy: t.delivery_policy ?? 'always',\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\n// ENG-4675: pass `inFlightTaskIds` so the manager's poll loop doesn't\n// re-detect tasks that already have a `claude -p` subprocess running.\n// Without this, every supervisor tick (~17s) saw the manual trigger as\n// still ready (because lastFireAt is only set on completion, not on\n// fire start), spamming \"N ready task(s)\" + \"Firing task\" log lines that\n// looked like duplicate fires. The in-flight guard at the spawn site\n// prevented actual duplicate subprocesses, but the log was misleading.\nexport function getReadyTasks(\n state: SchedulerState,\n inFlightTaskIds?: ReadonlySet<string>,\n): SchedulerTaskState[] {\n const now = Date.now();\n const ready = Object.values(state.tasks).filter((t) => {\n if (!t.enabled) return false;\n if (inFlightTaskIds?.has(t.taskId)) 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) 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 // Persisting is best-effort: the SchedulerState the caller stores in memory\n // (claudeSchedulerStates) is the running source of truth — getReadyTasks\n // reads it, not the disk file. A disk-write failure (ENOSPC/EACCES) must NOT\n // throw, or the caller's in-memory state would never advance and the task\n // would be treated as ready again next poll (ENG-5599: a duplicate\n // scheduled-task card materialised every tick). The mutated `state` is\n // returned regardless; cross-restart recovery is a separate concern.\n try {\n saveSchedulerState(codeName, state);\n } catch (err) {\n console.error(\n `[scheduler-engine] markTaskFired: failed to persist state for '${codeName}': ${(err as Error).message}`,\n );\n }\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":[]}