@integrity-labs/agt-cli 0.27.4 → 0.27.7-test.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,208 @@
1
+ import {
2
+ CLAUDE_AGENT_PROCESS_PATTERN,
3
+ parsePsRows
4
+ } from "./chunk-XWVM4KPK.js";
5
+
6
+ // src/lib/claude-code-updater.ts
7
+ import { execFile, execFileSync } from "child_process";
8
+ import { existsSync, readFileSync } from "fs";
9
+ import { homedir } from "os";
10
+ import { promisify } from "util";
11
+ var execFileAsync = promisify(execFile);
12
+ var NPM_PKG = "@anthropic-ai/claude-code";
13
+ var DEFAULT_INTERVAL_MS = 6 * 60 * 60 * 1e3;
14
+ var DEFAULT_VERIFY_MISMATCH_THRESHOLD = 5;
15
+ function intervalMs() {
16
+ const raw = Number.parseInt(process.env["AGT_CLAUDE_UPDATE_INTERVAL_MS"] ?? "", 10);
17
+ return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_INTERVAL_MS;
18
+ }
19
+ function verifyMismatchThreshold() {
20
+ const raw = Number.parseInt(process.env["AGT_CLAUDE_UPDATE_VERIFY_THRESHOLD"] ?? "", 10);
21
+ return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_VERIFY_MISMATCH_THRESHOLD;
22
+ }
23
+ function parseVersion(output) {
24
+ const m = output.match(/\d+\.\d+\.\d+[A-Za-z0-9.+-]*/);
25
+ return m ? m[0] : null;
26
+ }
27
+ function isConcreteVersion(spec) {
28
+ return /^\d+\.\d+\.\d+[A-Za-z0-9.+-]*$/.test(spec.trim());
29
+ }
30
+ var WELL_KNOWN_CLAUDE_PATHS = [
31
+ "/usr/bin/claude",
32
+ // Anthropic native autoupdater (Linux deb/rpm)
33
+ "/usr/local/bin/claude",
34
+ // npm global on macOS Intel + Linux without nvm
35
+ "/opt/homebrew/bin/claude",
36
+ // Homebrew on macOS Apple Silicon
37
+ "/home/linuxbrew/.linuxbrew/bin/claude"
38
+ // Linuxbrew (used on agt-aws-* hosts)
39
+ ];
40
+ var lastCheckAt = 0;
41
+ var multiInstallWarned = false;
42
+ var verifyMismatchState = null;
43
+ function _resetClaudeCodeUpdaterState() {
44
+ lastCheckAt = 0;
45
+ multiInstallWarned = false;
46
+ verifyMismatchState = null;
47
+ }
48
+ function defaultPsSnapshot() {
49
+ return execFileSync("ps", ["-eo", "pid,ppid,args"], { encoding: "utf-8", maxBuffer: 16 * 1024 * 1024 });
50
+ }
51
+ function defaultReadClaudeJson(path) {
52
+ try {
53
+ return readFileSync(path, "utf-8");
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+ function hostHasLiveAgent(psOutput) {
59
+ return parsePsRows(psOutput).some((r) => CLAUDE_AGENT_PROCESS_PATTERN.test(r.args));
60
+ }
61
+ function readsAsNativeInstall(raw) {
62
+ if (!raw) return false;
63
+ try {
64
+ const parsed = JSON.parse(raw);
65
+ return parsed.installMethod === "native";
66
+ } catch {
67
+ return false;
68
+ }
69
+ }
70
+ async function resolveInstallTargetPath(run) {
71
+ try {
72
+ const { stdout } = await run("npm", ["prefix", "-g"]);
73
+ const prefix = stdout.trim();
74
+ if (!prefix) return null;
75
+ return `${prefix}/bin/claude`;
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+ async function probeClaudeInstalls(run, fileExists, home) {
81
+ const candidates = new Set(WELL_KNOWN_CLAUDE_PATHS);
82
+ candidates.add(`${home}/.claude/local/claude`);
83
+ try {
84
+ const { stdout } = await run("which", ["claude"]);
85
+ const p = stdout.trim().split("\n")[0]?.trim();
86
+ if (p) candidates.add(p);
87
+ } catch {
88
+ }
89
+ const results = [];
90
+ for (const p of candidates) {
91
+ if (!fileExists(p)) continue;
92
+ let version = null;
93
+ try {
94
+ const { stdout } = await run(p, ["--version"]);
95
+ version = parseVersion(stdout);
96
+ } catch {
97
+ }
98
+ results.push({ path: p, version });
99
+ }
100
+ return results;
101
+ }
102
+ async function maybeUpdateClaudeCode(deps) {
103
+ const now = deps.now ? deps.now() : Date.now();
104
+ const run = deps.runCommand ?? ((cmd, args) => execFileAsync(cmd, args, { maxBuffer: 16 * 1024 * 1024 }));
105
+ const ps = deps.psSnapshot ?? defaultPsSnapshot;
106
+ const fileExists = deps.fileExists ?? existsSync;
107
+ const readClaudeJson = deps.readClaudeJson ?? defaultReadClaudeJson;
108
+ const home = (deps.homeDir ?? homedir)();
109
+ const { log } = deps;
110
+ if (now - lastCheckAt < intervalMs()) {
111
+ return { action: "throttled" };
112
+ }
113
+ lastCheckAt = now;
114
+ try {
115
+ const spec = (deps.desiredVersion || "latest").trim();
116
+ if (!multiInstallWarned) {
117
+ const installs = await probeClaudeInstalls(run, fileExists, home);
118
+ if (installs.length > 1) {
119
+ const summary = installs.map((i) => `${i.path}=${i.version ?? "unknown"}`).join(", ");
120
+ log(`[claude-code-updater] multiple claude installs detected: ${summary}`);
121
+ }
122
+ multiInstallWarned = true;
123
+ }
124
+ if (readsAsNativeInstall(readClaudeJson(`${home}/.claude.json`))) {
125
+ log("[claude-code-updater] native install detected, deferring to Anthropic autoupdater");
126
+ verifyMismatchState = null;
127
+ return { action: "skipped_native_install", target: spec };
128
+ }
129
+ let target;
130
+ if (isConcreteVersion(spec)) {
131
+ target = spec;
132
+ } else {
133
+ const { stdout } = await run("npm", ["view", `${NPM_PKG}@${spec}`, "version"]);
134
+ const resolved = parseVersion(stdout);
135
+ if (!resolved) {
136
+ log(`[claude-code-updater] could not resolve ${NPM_PKG}@${spec} to a version; skipping`);
137
+ return { action: "failed", reason: "unresolved_target" };
138
+ }
139
+ target = resolved;
140
+ }
141
+ let current = deps.currentVersion ?? null;
142
+ if (!current) {
143
+ try {
144
+ const { stdout } = await run("claude", ["--version"]);
145
+ current = parseVersion(stdout);
146
+ } catch {
147
+ current = null;
148
+ }
149
+ }
150
+ if (current && current === target) {
151
+ if (verifyMismatchState && verifyMismatchState.target === target) {
152
+ verifyMismatchState = null;
153
+ }
154
+ return { action: "up_to_date", version: target };
155
+ }
156
+ if (hostHasLiveAgent(ps())) {
157
+ log(`[claude-code-updater] host busy (live agent) \u2014 deferring update to ${target} to a later cycle`);
158
+ return { action: "deferred_busy", target };
159
+ }
160
+ const installTargetPath = await resolveInstallTargetPath(run);
161
+ log(`[claude-code-updater] updating Claude Code ${current ?? "(none)"} \u2192 ${target}`);
162
+ await run("npm", ["install", "-g", `${NPM_PKG}@${target}`]);
163
+ const verifyTarget = installTargetPath ?? "claude";
164
+ let installed = null;
165
+ try {
166
+ const { stdout } = await run(verifyTarget, ["--version"]);
167
+ installed = parseVersion(stdout);
168
+ } catch {
169
+ installed = null;
170
+ }
171
+ if (installed !== target) {
172
+ log(
173
+ `[claude-code-updater] post-install verify mismatch: expected ${target}, got ${installed ?? "unknown"} from ${verifyTarget}`
174
+ );
175
+ if (!verifyMismatchState || verifyMismatchState.target !== target) {
176
+ verifyMismatchState = { target, count: 1, alarmed: false };
177
+ } else {
178
+ verifyMismatchState.count += 1;
179
+ }
180
+ const threshold = verifyMismatchThreshold();
181
+ if (verifyMismatchState.count >= threshold && !verifyMismatchState.alarmed) {
182
+ verifyMismatchState.alarmed = true;
183
+ log(
184
+ `[claude-code-updater] ERROR verify_mismatch_stuck: ${verifyMismatchState.count} consecutive verify failures for target ${target} (verify path: ${verifyTarget}) \u2014 install is not taking effect, investigate host install slots`
185
+ );
186
+ return { action: "failed", reason: "verify_mismatch_threshold" };
187
+ }
188
+ return { action: "failed", reason: "verify_mismatch" };
189
+ }
190
+ verifyMismatchState = null;
191
+ log(`[claude-code-updater] updated Claude Code ${current ?? "(none)"} \u2192 ${installed}`);
192
+ return { action: "updated", from: current, to: installed };
193
+ } catch (err) {
194
+ log(`[claude-code-updater] update attempt failed: ${err instanceof Error ? err.message : String(err)}`);
195
+ return { action: "failed", reason: "exception" };
196
+ }
197
+ }
198
+ export {
199
+ WELL_KNOWN_CLAUDE_PATHS,
200
+ _resetClaudeCodeUpdaterState,
201
+ hostHasLiveAgent,
202
+ isConcreteVersion,
203
+ maybeUpdateClaudeCode,
204
+ parseVersion,
205
+ probeClaudeInstalls,
206
+ readsAsNativeInstall
207
+ };
208
+ //# sourceMappingURL=claude-code-updater-4E5T2X3Z.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/claude-code-updater.ts"],"sourcesContent":["// ENG-5672: idle-gated, server-pinned Claude Code auto-updater.\n// ENG-5798: multi-install drift handling — detect coexisting claude binaries,\n// short-circuit on native autoupdater hosts, verify against absolute\n// install path, hard-error after N consecutive verify mismatches.\n//\n// Hosts install @anthropic-ai/claude-code once at bootstrap (host-bootstrap.ts)\n// and never refresh it, so a long-lived host runs a stale Claude Code forever.\n// This module brings a host to its *desired* version, which the server pins via\n// /host/refresh (per-host override, else fleet default, else 'latest').\n//\n// Safety properties (this runs npm install -g on live prod hosts):\n// • Idle-gated — we never install while a `claude --name agt-*` agent process\n// is running on the host (a running process holds the old binary anyway;\n// replacing it under a live session is pointless and risky). Busy → defer\n// to a later cycle.\n// • No-op when already on target — a concrete target equal to the installed\n// version short-circuits; a dist-tag ('latest') is resolved to a concrete\n// version via `npm view` and compared, so we don't reinstall every cycle.\n// • Host-global throttle — at most one check per interval (default 6h), so the\n// manager's ~10-30s poll doesn't shell out to npm/ps constantly.\n// • Log-and-continue — every failure is logged and swallowed; an error here\n// must never break the manager poll cycle.\n// • Multi-install awareness (ENG-5798) — on hosts where Anthropic's native\n// autoupdater owns /usr/bin/claude, this updater short-circuits cleanly\n// instead of fighting it. On hosts where multiple binaries coexist (e.g. a\n// brew cask alongside the npm global), we log every path+version once so\n// drift is visible in manager.log without spamming every cycle.\n//\n// The *installed* version is reported separately as hosts.framework_version\n// (the claude-code framework adapter's getVersion() == `claude --version`), so\n// this module does not report it itself.\n\nimport { execFile, execFileSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { promisify } from 'node:util';\nimport { parsePsRows, CLAUDE_AGENT_PROCESS_PATTERN } from './orphan-channel-mcp-reaper.js';\n\nconst execFileAsync = promisify(execFile);\n\nconst NPM_PKG = '@anthropic-ai/claude-code';\n\n/** Default host-global check interval. Overridable via env for tuning. */\nconst DEFAULT_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6h\n\n/**\n * ENG-5798: After this many consecutive verify mismatches against the same\n * target version, emit a distinct one-shot error code that the\n * synthetic-probe / CloudWatch alarm pipeline can latch onto, instead of\n * silently re-failing forever. Overridable via env for testing.\n */\nconst DEFAULT_VERIFY_MISMATCH_THRESHOLD = 5;\n\nfunction intervalMs(): number {\n const raw = Number.parseInt(process.env['AGT_CLAUDE_UPDATE_INTERVAL_MS'] ?? '', 10);\n return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_INTERVAL_MS;\n}\n\nfunction verifyMismatchThreshold(): number {\n const raw = Number.parseInt(process.env['AGT_CLAUDE_UPDATE_VERIFY_THRESHOLD'] ?? '', 10);\n return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_VERIFY_MISMATCH_THRESHOLD;\n}\n\n/** Extract a version string (leading semver) from `claude --version` / npm output. */\nexport function parseVersion(output: string): string | null {\n const m = output.match(/\\d+\\.\\d+\\.\\d+[A-Za-z0-9.+-]*/);\n return m ? m[0] : null;\n}\n\n/** A target spec is \"concrete\" when it already names an exact version. */\nexport function isConcreteVersion(spec: string): boolean {\n return /^\\d+\\.\\d+\\.\\d+[A-Za-z0-9.+-]*$/.test(spec.trim());\n}\n\n/**\n * Well-known absolute paths where a `claude` binary may live on managed hosts.\n * Probed alongside the PATH-resolved binary so we can see all install slots,\n * even ones not on the manager systemd unit's PATH.\n */\nexport const WELL_KNOWN_CLAUDE_PATHS: readonly string[] = [\n '/usr/bin/claude', // Anthropic native autoupdater (Linux deb/rpm)\n '/usr/local/bin/claude', // npm global on macOS Intel + Linux without nvm\n '/opt/homebrew/bin/claude', // Homebrew on macOS Apple Silicon\n '/home/linuxbrew/.linuxbrew/bin/claude', // Linuxbrew (used on agt-aws-* hosts)\n];\n\nexport interface ClaudeCodeUpdaterDeps {\n /** Server-pinned target: a concrete version or an npm dist-tag like 'latest'. */\n desiredVersion: string;\n /** Currently-installed version, if the caller already knows it (e.g. the\n * manager's cachedFrameworkVersion). When omitted we run `claude --version`. */\n currentVersion?: string | null;\n log: (msg: string) => void;\n /** Override \"now\" for tests. */\n now?: () => number;\n /** Run a command; injected in tests. Defaults to execFile. */\n runCommand?: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>;\n /** Raw `ps -eo pid,ppid,args` snapshot; injected in tests. */\n psSnapshot?: () => string;\n /** Sync existence probe for binary detection; injected in tests. */\n fileExists?: (path: string) => boolean;\n /** Read ~/.claude.json contents (raw); returns null when absent. Injected in tests. */\n readClaudeJson?: (path: string) => string | null;\n /** Resolve the user's home directory; injected in tests. */\n homeDir?: () => string;\n}\n\nexport type UpdaterOutcome =\n | { action: 'throttled' }\n | { action: 'up_to_date'; version: string }\n | { action: 'deferred_busy'; target: string }\n | { action: 'skipped_native_install'; target: string }\n | { action: 'updated'; from: string | null; to: string }\n | { action: 'failed'; reason: string };\n\n// ── Host-global state (module-level; one updater per host process) ──────────\nlet lastCheckAt = 0;\n/** Per-process one-shot guard for the multi-install warn line. */\nlet multiInstallWarned = false;\n/**\n * Sticky counter for consecutive `post-install verify mismatch` against the\n * same target version. Reset on success, on target change, or after the\n * threshold error has been emitted (so we emit it exactly once per stuck run).\n */\nlet verifyMismatchState: { target: string; count: number; alarmed: boolean } | null = null;\n\n/** Test helper — reset module state. Not for production use. */\nexport function _resetClaudeCodeUpdaterState(): void {\n lastCheckAt = 0;\n multiInstallWarned = false;\n verifyMismatchState = null;\n}\n\nfunction defaultPsSnapshot(): string {\n return execFileSync('ps', ['-eo', 'pid,ppid,args'], { encoding: 'utf-8', maxBuffer: 16 * 1024 * 1024 });\n}\n\nfunction defaultReadClaudeJson(path: string): string | null {\n try {\n return readFileSync(path, 'utf-8');\n } catch {\n return null;\n }\n}\n\n/** True when at least one `claude --name agt-*` agent process is live. */\nexport function hostHasLiveAgent(psOutput: string): boolean {\n return parsePsRows(psOutput).some((r) => CLAUDE_AGENT_PROCESS_PATTERN.test(r.args));\n}\n\n/**\n * ENG-5798: returns true when ~/.claude.json reports `installMethod: \"native\"`,\n * meaning Anthropic's native autoupdater owns the on-disk binary. In that case\n * the brew/npm update path is actively harmful — it installs a second binary\n * the host's PATH never resolves to, and verify reads from yet a third.\n */\nexport function readsAsNativeInstall(raw: string | null): boolean {\n if (!raw) return false;\n try {\n const parsed = JSON.parse(raw) as { installMethod?: unknown };\n return parsed.installMethod === 'native';\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve the absolute filesystem path that `npm install -g <pkg>` will write\n * the `claude` binary to. Returns null when `npm prefix -g` fails or is empty,\n * in which case the caller should fall back to PATH-based verify.\n *\n * We deliberately use `npm prefix -g` rather than `which claude` because the\n * latter resolves through PATH, which on agt-aws-* hosts can land on\n * /usr/bin/claude (the native autoupdater slot) — exactly the misalignment\n * ENG-5798 is fixing.\n */\nasync function resolveInstallTargetPath(\n run: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>,\n): Promise<string | null> {\n try {\n const { stdout } = await run('npm', ['prefix', '-g']);\n const prefix = stdout.trim();\n if (!prefix) return null;\n return `${prefix}/bin/claude`;\n } catch {\n return null;\n }\n}\n\n/**\n * Enumerate every `claude` binary visible to the host: PATH-resolved + the\n * well-known absolute slots + `~/.claude/local/claude`. Returns one entry per\n * unique resolved path with the version each reports (or null if it failed to\n * execute). Used purely for the one-shot drift warn line.\n */\nexport async function probeClaudeInstalls(\n run: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>,\n fileExists: (path: string) => boolean,\n home: string,\n): Promise<Array<{ path: string; version: string | null }>> {\n const candidates = new Set<string>(WELL_KNOWN_CLAUDE_PATHS);\n candidates.add(`${home}/.claude/local/claude`);\n // PATH-resolved. `which` is portable enough across the Linux + macOS hosts\n // we target; errors here just mean \"no claude on PATH\", which is fine.\n try {\n const { stdout } = await run('which', ['claude']);\n const p = stdout.trim().split('\\n')[0]?.trim();\n if (p) candidates.add(p);\n } catch {\n /* no-op */\n }\n\n const results: Array<{ path: string; version: string | null }> = [];\n for (const p of candidates) {\n if (!fileExists(p)) continue;\n let version: string | null = null;\n try {\n const { stdout } = await run(p, ['--version']);\n version = parseVersion(stdout);\n } catch {\n /* binary exists but failed to execute — record path with null version */\n }\n results.push({ path: p, version });\n }\n return results;\n}\n\n/**\n * Bring Claude Code to the desired version if the host is idle and not already\n * on target. Safe to call every manager poll — internally throttled. Returns an\n * outcome describing what happened (for logging/observability/tests).\n */\nexport async function maybeUpdateClaudeCode(deps: ClaudeCodeUpdaterDeps): Promise<UpdaterOutcome> {\n const now = deps.now ? deps.now() : Date.now();\n const run = deps.runCommand ?? ((cmd, args) => execFileAsync(cmd, args, { maxBuffer: 16 * 1024 * 1024 }));\n const ps = deps.psSnapshot ?? defaultPsSnapshot;\n const fileExists = deps.fileExists ?? existsSync;\n const readClaudeJson = deps.readClaudeJson ?? defaultReadClaudeJson;\n const home = (deps.homeDir ?? homedir)();\n const { log } = deps;\n\n // 1. Host-global throttle.\n if (now - lastCheckAt < intervalMs()) {\n return { action: 'throttled' };\n }\n lastCheckAt = now;\n\n try {\n const spec = (deps.desiredVersion || 'latest').trim();\n\n // 2. ENG-5798 multi-install drift detection. Emit one warn line per\n // process listing every claude binary + version we can see. This is\n // independent of the install decision below — even on hosts where we\n // proceed normally, surfacing drift is the difference between a 3-day\n // silent loop and a one-line lead.\n if (!multiInstallWarned) {\n const installs = await probeClaudeInstalls(run, fileExists, home);\n if (installs.length > 1) {\n const summary = installs\n .map((i) => `${i.path}=${i.version ?? 'unknown'}`)\n .join(', ');\n log(`[claude-code-updater] multiple claude installs detected: ${summary}`);\n }\n multiInstallWarned = true;\n }\n\n // 3. ENG-5798 native-install short-circuit. When Anthropic's native\n // autoupdater owns the host binary, the brew/npm update path is\n // actively harmful (it installs a second binary the manager's PATH\n // never resolves to, then verifies against yet a third). Defer.\n if (readsAsNativeInstall(readClaudeJson(`${home}/.claude.json`))) {\n // One-shot info line per process — same throttle as the warn above, but\n // we re-use the throttle window to keep this from logging every poll.\n log('[claude-code-updater] native install detected, deferring to Anthropic autoupdater');\n // Reset any sticky verify-mismatch state — it can't apply once we stop\n // attempting installs.\n verifyMismatchState = null;\n return { action: 'skipped_native_install', target: spec };\n }\n\n // 4. Resolve the target to a concrete version (read-only). A concrete spec\n // is its own target; a dist-tag is resolved via `npm view`.\n let target: string;\n if (isConcreteVersion(spec)) {\n target = spec;\n } else {\n const { stdout } = await run('npm', ['view', `${NPM_PKG}@${spec}`, 'version']);\n const resolved = parseVersion(stdout);\n if (!resolved) {\n log(`[claude-code-updater] could not resolve ${NPM_PKG}@${spec} to a version; skipping`);\n return { action: 'failed', reason: 'unresolved_target' };\n }\n target = resolved;\n }\n\n // 5. Determine the installed version (prefer the caller's cached value).\n let current = deps.currentVersion ?? null;\n if (!current) {\n try {\n const { stdout } = await run('claude', ['--version']);\n current = parseVersion(stdout);\n } catch {\n current = null; // claude missing/broken — treat as \"needs install\".\n }\n }\n\n // 6. Already on target → nothing to do.\n if (current && current === target) {\n // Successful no-op — clear sticky verify mismatch state for this target.\n if (verifyMismatchState && verifyMismatchState.target === target) {\n verifyMismatchState = null;\n }\n return { action: 'up_to_date', version: target };\n }\n\n // 7. Idle gate — never replace the binary under a live agent session.\n if (hostHasLiveAgent(ps())) {\n log(`[claude-code-updater] host busy (live agent) — deferring update to ${target} to a later cycle`);\n return { action: 'deferred_busy', target };\n }\n\n // 8. Install the target, then verify against the *absolute* path of the\n // binary npm just wrote — not via PATH. PATH resolution is the original\n // ENG-5798 bug: on hosts with multiple claude installs, PATH lands on a\n // different binary than the one npm overwrites, so verify never agrees.\n const installTargetPath = await resolveInstallTargetPath(run);\n log(`[claude-code-updater] updating Claude Code ${current ?? '(none)'} → ${target}`);\n await run('npm', ['install', '-g', `${NPM_PKG}@${target}`]);\n\n // Prefer the absolute install target; fall back to PATH only when\n // `npm prefix -g` failed (in which case verify is best-effort).\n const verifyTarget = installTargetPath ?? 'claude';\n let installed: string | null = null;\n try {\n const { stdout } = await run(verifyTarget, ['--version']);\n installed = parseVersion(stdout);\n } catch {\n installed = null;\n }\n\n if (installed !== target) {\n log(\n `[claude-code-updater] post-install verify mismatch: expected ${target}, got ${installed ?? 'unknown'} from ${verifyTarget}`,\n );\n\n // ENG-5798: hard-error after N consecutive mismatches against the same\n // target. Reset the counter when the target changes — a new pin is a\n // new event, not a continuation of the old stuck run.\n if (!verifyMismatchState || verifyMismatchState.target !== target) {\n verifyMismatchState = { target, count: 1, alarmed: false };\n } else {\n verifyMismatchState.count += 1;\n }\n\n const threshold = verifyMismatchThreshold();\n if (verifyMismatchState.count >= threshold && !verifyMismatchState.alarmed) {\n verifyMismatchState.alarmed = true;\n log(\n `[claude-code-updater] ERROR verify_mismatch_stuck: ${verifyMismatchState.count} consecutive verify failures for target ${target} (verify path: ${verifyTarget}) — install is not taking effect, investigate host install slots`,\n );\n return { action: 'failed', reason: 'verify_mismatch_threshold' };\n }\n return { action: 'failed', reason: 'verify_mismatch' };\n }\n\n // Successful install — clear sticky state.\n verifyMismatchState = null;\n log(`[claude-code-updater] updated Claude Code ${current ?? '(none)'} → ${installed}`);\n return { action: 'updated', from: current, to: installed };\n } catch (err) {\n // Log-and-continue: an updater failure must never break the manager tick.\n log(`[claude-code-updater] update attempt failed: ${err instanceof Error ? err.message : String(err)}`);\n return { action: 'failed', reason: 'exception' };\n }\n}\n"],"mappings":";;;;;;AAgCA,SAAS,UAAU,oBAAoB;AACvC,SAAS,YAAY,oBAAoB;AACzC,SAAS,eAAe;AACxB,SAAS,iBAAiB;AAG1B,IAAM,gBAAgB,UAAU,QAAQ;AAExC,IAAM,UAAU;AAGhB,IAAM,sBAAsB,IAAI,KAAK,KAAK;AAQ1C,IAAM,oCAAoC;AAE1C,SAAS,aAAqB;AAC5B,QAAM,MAAM,OAAO,SAAS,QAAQ,IAAI,+BAA+B,KAAK,IAAI,EAAE;AAClF,SAAO,OAAO,SAAS,GAAG,KAAK,MAAM,IAAI,MAAM;AACjD;AAEA,SAAS,0BAAkC;AACzC,QAAM,MAAM,OAAO,SAAS,QAAQ,IAAI,oCAAoC,KAAK,IAAI,EAAE;AACvF,SAAO,OAAO,SAAS,GAAG,KAAK,MAAM,IAAI,MAAM;AACjD;AAGO,SAAS,aAAa,QAA+B;AAC1D,QAAM,IAAI,OAAO,MAAM,8BAA8B;AACrD,SAAO,IAAI,EAAE,CAAC,IAAI;AACpB;AAGO,SAAS,kBAAkB,MAAuB;AACvD,SAAO,iCAAiC,KAAK,KAAK,KAAK,CAAC;AAC1D;AAOO,IAAM,0BAA6C;AAAA,EACxD;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AAgCA,IAAI,cAAc;AAElB,IAAI,qBAAqB;AAMzB,IAAI,sBAAkF;AAG/E,SAAS,+BAAqC;AACnD,gBAAc;AACd,uBAAqB;AACrB,wBAAsB;AACxB;AAEA,SAAS,oBAA4B;AACnC,SAAO,aAAa,MAAM,CAAC,OAAO,eAAe,GAAG,EAAE,UAAU,SAAS,WAAW,KAAK,OAAO,KAAK,CAAC;AACxG;AAEA,SAAS,sBAAsB,MAA6B;AAC1D,MAAI;AACF,WAAO,aAAa,MAAM,OAAO;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,iBAAiB,UAA2B;AAC1D,SAAO,YAAY,QAAQ,EAAE,KAAK,CAAC,MAAM,6BAA6B,KAAK,EAAE,IAAI,CAAC;AACpF;AAQO,SAAS,qBAAqB,KAA6B;AAChE,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,WAAO,OAAO,kBAAkB;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAYA,eAAe,yBACb,KACwB;AACxB,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM,IAAI,OAAO,CAAC,UAAU,IAAI,CAAC;AACpD,UAAM,SAAS,OAAO,KAAK;AAC3B,QAAI,CAAC,OAAQ,QAAO;AACpB,WAAO,GAAG,MAAM;AAAA,EAClB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,eAAsB,oBACpB,KACA,YACA,MAC0D;AAC1D,QAAM,aAAa,IAAI,IAAY,uBAAuB;AAC1D,aAAW,IAAI,GAAG,IAAI,uBAAuB;AAG7C,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM,IAAI,SAAS,CAAC,QAAQ,CAAC;AAChD,UAAM,IAAI,OAAO,KAAK,EAAE,MAAM,IAAI,EAAE,CAAC,GAAG,KAAK;AAC7C,QAAI,EAAG,YAAW,IAAI,CAAC;AAAA,EACzB,QAAQ;AAAA,EAER;AAEA,QAAM,UAA2D,CAAC;AAClE,aAAW,KAAK,YAAY;AAC1B,QAAI,CAAC,WAAW,CAAC,EAAG;AACpB,QAAI,UAAyB;AAC7B,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,MAAM,IAAI,GAAG,CAAC,WAAW,CAAC;AAC7C,gBAAU,aAAa,MAAM;AAAA,IAC/B,QAAQ;AAAA,IAER;AACA,YAAQ,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC;AAAA,EACnC;AACA,SAAO;AACT;AAOA,eAAsB,sBAAsB,MAAsD;AAChG,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,KAAK,IAAI;AAC7C,QAAM,MAAM,KAAK,eAAe,CAAC,KAAK,SAAS,cAAc,KAAK,MAAM,EAAE,WAAW,KAAK,OAAO,KAAK,CAAC;AACvG,QAAM,KAAK,KAAK,cAAc;AAC9B,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,iBAAiB,KAAK,kBAAkB;AAC9C,QAAM,QAAQ,KAAK,WAAW,SAAS;AACvC,QAAM,EAAE,IAAI,IAAI;AAGhB,MAAI,MAAM,cAAc,WAAW,GAAG;AACpC,WAAO,EAAE,QAAQ,YAAY;AAAA,EAC/B;AACA,gBAAc;AAEd,MAAI;AACF,UAAM,QAAQ,KAAK,kBAAkB,UAAU,KAAK;AAOpD,QAAI,CAAC,oBAAoB;AACvB,YAAM,WAAW,MAAM,oBAAoB,KAAK,YAAY,IAAI;AAChE,UAAI,SAAS,SAAS,GAAG;AACvB,cAAM,UAAU,SACb,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,IAAI,EAAE,WAAW,SAAS,EAAE,EAChD,KAAK,IAAI;AACZ,YAAI,4DAA4D,OAAO,EAAE;AAAA,MAC3E;AACA,2BAAqB;AAAA,IACvB;AAMA,QAAI,qBAAqB,eAAe,GAAG,IAAI,eAAe,CAAC,GAAG;AAGhE,UAAI,mFAAmF;AAGvF,4BAAsB;AACtB,aAAO,EAAE,QAAQ,0BAA0B,QAAQ,KAAK;AAAA,IAC1D;AAIA,QAAI;AACJ,QAAI,kBAAkB,IAAI,GAAG;AAC3B,eAAS;AAAA,IACX,OAAO;AACL,YAAM,EAAE,OAAO,IAAI,MAAM,IAAI,OAAO,CAAC,QAAQ,GAAG,OAAO,IAAI,IAAI,IAAI,SAAS,CAAC;AAC7E,YAAM,WAAW,aAAa,MAAM;AACpC,UAAI,CAAC,UAAU;AACb,YAAI,2CAA2C,OAAO,IAAI,IAAI,yBAAyB;AACvF,eAAO,EAAE,QAAQ,UAAU,QAAQ,oBAAoB;AAAA,MACzD;AACA,eAAS;AAAA,IACX;AAGA,QAAI,UAAU,KAAK,kBAAkB;AACrC,QAAI,CAAC,SAAS;AACZ,UAAI;AACF,cAAM,EAAE,OAAO,IAAI,MAAM,IAAI,UAAU,CAAC,WAAW,CAAC;AACpD,kBAAU,aAAa,MAAM;AAAA,MAC/B,QAAQ;AACN,kBAAU;AAAA,MACZ;AAAA,IACF;AAGA,QAAI,WAAW,YAAY,QAAQ;AAEjC,UAAI,uBAAuB,oBAAoB,WAAW,QAAQ;AAChE,8BAAsB;AAAA,MACxB;AACA,aAAO,EAAE,QAAQ,cAAc,SAAS,OAAO;AAAA,IACjD;AAGA,QAAI,iBAAiB,GAAG,CAAC,GAAG;AAC1B,UAAI,2EAAsE,MAAM,mBAAmB;AACnG,aAAO,EAAE,QAAQ,iBAAiB,OAAO;AAAA,IAC3C;AAMA,UAAM,oBAAoB,MAAM,yBAAyB,GAAG;AAC5D,QAAI,8CAA8C,WAAW,QAAQ,WAAM,MAAM,EAAE;AACnF,UAAM,IAAI,OAAO,CAAC,WAAW,MAAM,GAAG,OAAO,IAAI,MAAM,EAAE,CAAC;AAI1D,UAAM,eAAe,qBAAqB;AAC1C,QAAI,YAA2B;AAC/B,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,MAAM,IAAI,cAAc,CAAC,WAAW,CAAC;AACxD,kBAAY,aAAa,MAAM;AAAA,IACjC,QAAQ;AACN,kBAAY;AAAA,IACd;AAEA,QAAI,cAAc,QAAQ;AACxB;AAAA,QACE,gEAAgE,MAAM,SAAS,aAAa,SAAS,SAAS,YAAY;AAAA,MAC5H;AAKA,UAAI,CAAC,uBAAuB,oBAAoB,WAAW,QAAQ;AACjE,8BAAsB,EAAE,QAAQ,OAAO,GAAG,SAAS,MAAM;AAAA,MAC3D,OAAO;AACL,4BAAoB,SAAS;AAAA,MAC/B;AAEA,YAAM,YAAY,wBAAwB;AAC1C,UAAI,oBAAoB,SAAS,aAAa,CAAC,oBAAoB,SAAS;AAC1E,4BAAoB,UAAU;AAC9B;AAAA,UACE,sDAAsD,oBAAoB,KAAK,2CAA2C,MAAM,kBAAkB,YAAY;AAAA,QAChK;AACA,eAAO,EAAE,QAAQ,UAAU,QAAQ,4BAA4B;AAAA,MACjE;AACA,aAAO,EAAE,QAAQ,UAAU,QAAQ,kBAAkB;AAAA,IACvD;AAGA,0BAAsB;AACtB,QAAI,6CAA6C,WAAW,QAAQ,WAAM,SAAS,EAAE;AACrF,WAAO,EAAE,QAAQ,WAAW,MAAM,SAAS,IAAI,UAAU;AAAA,EAC3D,SAAS,KAAK;AAEZ,QAAI,gDAAgD,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AACtG,WAAO,EAAE,QAAQ,UAAU,QAAQ,YAAY;AAAA,EACjD;AACF;","names":[]}
@@ -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-KUKHF33M.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-ICYFLUAM.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-IWSCXRZ7.js.map
376
+ //# sourceMappingURL=claude-pair-runtime-ZBQKBBMT.js.map
@@ -1,5 +1,7 @@
1
1
  import {
2
2
  ApiError,
3
+ INTEGRATIONS_SECTION_END,
4
+ INTEGRATIONS_SECTION_START,
3
5
  SUPERVISOR_RESTART_EXIT_CODE,
4
6
  api,
5
7
  estimateActiveTasksTokens,
@@ -13,7 +15,7 @@ import {
13
15
  provisionOrientHook,
14
16
  provisionStopHook,
15
17
  requireHost
16
- } from "../chunk-44OJXXLF.js";
18
+ } from "../chunk-WCY7QM3R.js";
17
19
  import {
18
20
  getProjectDir as getProjectDir2,
19
21
  getReadyTasks,
@@ -44,7 +46,7 @@ import {
44
46
  stopAllSessionsAndWait,
45
47
  stopPersistentSession,
46
48
  takeZombieDetection
47
- } from "../chunk-U37Y32S5.js";
49
+ } from "../chunk-GN4XPQWJ.js";
48
50
  import {
49
51
  KANBAN_CHECK_COMMAND,
50
52
  appendDmFooter,
@@ -67,7 +69,7 @@ import {
67
69
  resolveConnectivityProbe,
68
70
  resolveDmTarget,
69
71
  wrapScheduledTaskPrompt
70
- } from "../chunk-JTZZ6YB2.js";
72
+ } from "../chunk-YSBGIXJG.js";
71
73
  import {
72
74
  parsePsRows,
73
75
  reapOrphanChannelMcps
@@ -2772,6 +2774,9 @@ function stopRealtimeChat() {
2772
2774
  }
2773
2775
 
2774
2776
  // src/lib/manager-worker.ts
2777
+ function escapeRegExp(s) {
2778
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2779
+ }
2775
2780
  function applyRestartAcks(args) {
2776
2781
  const { agentStates, priorAgents, restartAcks } = args;
2777
2782
  if (agentStates.length === 0) return false;
@@ -3161,7 +3166,7 @@ var cachedFrameworkVersion = null;
3161
3166
  var lastVersionCheckAt = 0;
3162
3167
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
3163
3168
  var lastResponsivenessProbeAt = 0;
3164
- var agtCliVersion = true ? "0.27.4" : "dev";
3169
+ var agtCliVersion = true ? "0.27.7-test.5" : "dev";
3165
3170
  function resolveBrewPath(execFileSync4) {
3166
3171
  try {
3167
3172
  const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -3331,10 +3336,42 @@ function runAsync(cmd, args, opts) {
3331
3336
  }).catch(reject);
3332
3337
  });
3333
3338
  }
3339
+ function claudeManagedSettingsPath() {
3340
+ return process.platform === "darwin" ? "/Library/Application Support/ClaudeCode/managed-settings.json" : "/etc/claude-code/managed-settings.json";
3341
+ }
3342
+ function ensureClaudeManagedSettings(path = claudeManagedSettingsPath()) {
3343
+ try {
3344
+ let settings = {};
3345
+ if (existsSync4(path)) {
3346
+ const raw = readFileSync5(path, "utf-8").trim();
3347
+ if (raw) {
3348
+ let parsed;
3349
+ try {
3350
+ parsed = JSON.parse(raw);
3351
+ } catch (err) {
3352
+ log(`[managed-settings] ${path} is not valid JSON (${err.message}) \u2014 leaving as-is; Claude Code channels may be blocked`);
3353
+ return;
3354
+ }
3355
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3356
+ settings = parsed;
3357
+ }
3358
+ }
3359
+ }
3360
+ if (settings.channelsEnabled === true) return;
3361
+ settings.channelsEnabled = true;
3362
+ mkdirSync2(dirname(path), { recursive: true });
3363
+ writeFileSync3(path, `${JSON.stringify(settings, null, 2)}
3364
+ `);
3365
+ log(`[managed-settings] set channelsEnabled:true in ${path} (ENG-5786 \u2014 unblocks Claude Code channels)`);
3366
+ } catch (err) {
3367
+ log(`[managed-settings] could not ensure ${path}: ${err.message} \u2014 Claude Code channels may be blocked; set channelsEnabled:true manually`);
3368
+ }
3369
+ }
3334
3370
  async function ensureFrameworkBinary(frameworkId) {
3335
3371
  if (frameworkId !== "claude-code") return;
3336
3372
  if (frameworkBinaryChecked.has(frameworkId)) return;
3337
3373
  frameworkBinaryChecked.add(frameworkId);
3374
+ ensureClaudeManagedSettings();
3338
3375
  const { execFileSync: execFileSync4 } = await import("child_process");
3339
3376
  const brewPath = resolveBrewPath(execFileSync4);
3340
3377
  if (!brewPath) {
@@ -3547,7 +3584,19 @@ async function checkAndUpdateCliViaNpm() {
3547
3584
  log(`[self-update] npm registry fetch failed: ${err.message}`);
3548
3585
  return;
3549
3586
  }
3550
- const shouldUpdate = channel === "latest" ? isNewerSemver(agtCliVersion, latest) : latest !== agtCliVersion;
3587
+ let shouldUpdate;
3588
+ if (channel === "latest") {
3589
+ shouldUpdate = isNewerSemver(agtCliVersion, latest);
3590
+ } else if (latest === agtCliVersion) {
3591
+ shouldUpdate = false;
3592
+ } else if (isOlderSemverTuple(agtCliVersion, latest)) {
3593
+ log(
3594
+ `[self-update] refusing to downgrade: installed ${agtCliVersion} is newer than channel=${channel} target ${latest}. Bump the channel tag forward to resume tracking.`
3595
+ );
3596
+ shouldUpdate = false;
3597
+ } else {
3598
+ shouldUpdate = true;
3599
+ }
3551
3600
  if (!shouldUpdate) {
3552
3601
  if (!selfUpdateUpToDateLogged) {
3553
3602
  log(`[self-update] agt CLI is up to date (npm, channel=${channel}, ${agtCliVersion})`);
@@ -3591,6 +3640,17 @@ function isNewerSemver(local, remote) {
3591
3640
  }
3592
3641
  return false;
3593
3642
  }
3643
+ function isOlderSemverTuple(local, remote) {
3644
+ const parse = (v) => v.replace(/^v/, "").split(/[-+]/)[0].split(".").map((p) => Number(p));
3645
+ const l = parse(local);
3646
+ const r = parse(remote);
3647
+ for (let i = 0; i < 3; i++) {
3648
+ const lv = l[i] ?? 0;
3649
+ const rv = r[i] ?? 0;
3650
+ if (rv !== lv) return rv < lv;
3651
+ }
3652
+ return false;
3653
+ }
3594
3654
  async function checkClaudeAuth() {
3595
3655
  try {
3596
3656
  const report = await detectClaudeAuth();
@@ -4120,7 +4180,7 @@ async function pollCycle() {
4120
4180
  }
4121
4181
  try {
4122
4182
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
4123
- const { collectDiagnostics } = await import("../persistent-session-KUKHF33M.js");
4183
+ const { collectDiagnostics } = await import("../persistent-session-ICYFLUAM.js");
4124
4184
  const diagCodeNames = [...agentState.persistentSessionAgents];
4125
4185
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
4126
4186
  let tailscaleHostname;
@@ -4172,7 +4232,7 @@ async function pollCycle() {
4172
4232
  realtime_socket_connected: isRealtimeSocketConnected()
4173
4233
  });
4174
4234
  try {
4175
- const { maybeUpdateClaudeCode } = await import("../claude-code-updater-7BGQ6JCV.js");
4235
+ const { maybeUpdateClaudeCode } = await import("../claude-code-updater-4E5T2X3Z.js");
4176
4236
  await maybeUpdateClaudeCode({
4177
4237
  desiredVersion: hbResp?.desired_claude_code_version ?? "latest",
4178
4238
  currentVersion: cachedFrameworkVersion,
@@ -4188,7 +4248,7 @@ async function pollCycle() {
4188
4248
  const {
4189
4249
  collectResponsivenessProbes,
4190
4250
  getResponsivenessIntervalMs
4191
- } = await import("../responsiveness-probe-WYQS3YID.js");
4251
+ } = await import("../responsiveness-probe-WZNQ2762.js");
4192
4252
  const probeIntervalMs = getResponsivenessIntervalMs();
4193
4253
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
4194
4254
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -4691,7 +4751,14 @@ async function processAgent(agent, agentStates) {
4691
4751
  if (artifact.relativePath === "CLAUDE.md") {
4692
4752
  const stripDynamicSections = (s) => {
4693
4753
  let out = s.replace(new RegExp(`${SKILLS_INDEX_START}[\\s\\S]*?${SKILLS_INDEX_END}`), "");
4694
- out = out.replace(/## Integrations[\s\S]*?(?=## Rules)/, "");
4754
+ if (out.includes(INTEGRATIONS_SECTION_START)) {
4755
+ out = out.replace(
4756
+ new RegExp(`${escapeRegExp(INTEGRATIONS_SECTION_START)}[\\s\\S]*?${escapeRegExp(INTEGRATIONS_SECTION_END)}\\n*`),
4757
+ ""
4758
+ );
4759
+ } else {
4760
+ out = out.replace(/## Integrations[\s\S]*?(?=## Rules)/, "");
4761
+ }
4695
4762
  return out.trimEnd();
4696
4763
  };
4697
4764
  newHash = sha256(stripDynamicSections(artifact.content));
@@ -8129,7 +8196,7 @@ async function processClaudePairSessions(agents) {
8129
8196
  killPairSession,
8130
8197
  pairTmuxSession,
8131
8198
  finalizeClaudePairOnboarding
8132
- } = await import("../claude-pair-runtime-IWSCXRZ7.js");
8199
+ } = await import("../claude-pair-runtime-ZBQKBBMT.js");
8133
8200
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
8134
8201
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
8135
8202
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -8917,7 +8984,9 @@ export {
8917
8984
  buildDoneCardNotification,
8918
8985
  claudeCodeUpgradeMarkerPath,
8919
8986
  claudeCodeUpgradeThrottled,
8987
+ claudeManagedSettingsPath,
8920
8988
  deliverScheduledCardResult,
8989
+ ensureClaudeManagedSettings,
8921
8990
  extractCharterSlackPeers,
8922
8991
  extractCharterTelegramPeers,
8923
8992
  formatBoardForPrompt,
@@ -8927,6 +8996,7 @@ export {
8927
8996
  isHybridActionable,
8928
8997
  isKanbanHybridDryRun,
8929
8998
  isKanbanHybridEnabled,
8999
+ isOlderSemverTuple,
8930
9000
  isPlainScheduledTemplate,
8931
9001
  isScheduledCardTracked,
8932
9002
  isScheduledViaKanbanEnabled,