fullcourtdefense-cli 1.36.4 → 1.37.0

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.
Files changed (90) hide show
  1. package/dist/askDialog.d.ts +1 -0
  2. package/dist/askDialog.js +20 -3
  3. package/dist/callerEnv.d.ts +29 -2
  4. package/dist/callerEnv.js +48 -2
  5. package/dist/commands/daemon.d.ts +10 -0
  6. package/dist/commands/daemon.js +348 -170
  7. package/dist/commands/desktopChatGuard.js +2 -0
  8. package/dist/commands/discoverPaths.d.ts +12 -9
  9. package/dist/commands/discoverPaths.js +30 -0
  10. package/dist/commands/ephemeralStack.js +7 -0
  11. package/dist/commands/flushSpool.d.ts +2 -0
  12. package/dist/commands/flushSpool.js +9 -2
  13. package/dist/commands/hook.d.ts +14 -0
  14. package/dist/commands/hook.js +302 -101
  15. package/dist/commands/httpGatewayCommand.d.ts +1 -0
  16. package/dist/commands/httpGatewayCommand.js +7 -1
  17. package/dist/commands/installClaudeHook.js +32 -10
  18. package/dist/commands/installCursorHook.js +20 -9
  19. package/dist/commands/login.js +8 -1
  20. package/dist/commands/mcpGateway.d.ts +21 -0
  21. package/dist/commands/mcpGateway.js +90 -32
  22. package/dist/commands/refreshPolicyCache.d.ts +28 -0
  23. package/dist/commands/refreshPolicyCache.js +59 -0
  24. package/dist/commands/watchdog.d.ts +2 -0
  25. package/dist/commands/watchdog.js +30 -5
  26. package/dist/config.d.ts +36 -0
  27. package/dist/config.js +78 -12
  28. package/dist/daemonDetectorBoot.d.ts +1 -0
  29. package/dist/daemonDetectorBoot.js +12 -0
  30. package/dist/daemonForensics.d.ts +14 -1
  31. package/dist/daemonForensics.js +32 -0
  32. package/dist/detectorInProcess.d.ts +18 -0
  33. package/dist/detectorInProcess.js +234 -0
  34. package/dist/detectorInThread.d.ts +15 -0
  35. package/dist/detectorInThread.js +129 -0
  36. package/dist/distress.d.ts +2 -0
  37. package/dist/distress.js +2 -0
  38. package/dist/fileWriteCanon.d.ts +17 -0
  39. package/dist/fileWriteCanon.js +141 -0
  40. package/dist/gateReports.d.ts +144 -0
  41. package/dist/gateReports.js +488 -0
  42. package/dist/hookIo.d.ts +59 -0
  43. package/dist/hookIo.js +319 -9
  44. package/dist/hookSlim.d.ts +15 -8
  45. package/dist/hookSlim.js +187 -25
  46. package/dist/housekeeping.d.ts +64 -0
  47. package/dist/housekeeping.js +384 -0
  48. package/dist/housekeepingOps.d.ts +150 -0
  49. package/dist/housekeepingOps.js +218 -0
  50. package/dist/housekeepingWorker.d.ts +1 -0
  51. package/dist/housekeepingWorker.js +113 -0
  52. package/dist/index.d.ts +1 -1
  53. package/dist/index.js +19 -0
  54. package/dist/localDetectionData.d.ts +10 -0
  55. package/dist/localDetectionData.js +48 -0
  56. package/dist/localDetectionUpdates.d.ts +2 -2
  57. package/dist/localDetectionUpdates.js +4 -4
  58. package/dist/localSafetySnapshot.d.ts +48 -2
  59. package/dist/localSafetySnapshot.js +241 -25
  60. package/dist/machineIdentity.d.ts +7 -0
  61. package/dist/machineIdentity.js +20 -2
  62. package/dist/monitorFastPath.d.ts +191 -0
  63. package/dist/monitorFastPath.js +1198 -0
  64. package/dist/nativeHook.js +9 -2
  65. package/dist/pendingDrain.d.ts +82 -0
  66. package/dist/pendingDrain.js +215 -0
  67. package/dist/pendingDrainer.d.ts +30 -0
  68. package/dist/pendingDrainer.js +217 -0
  69. package/dist/perfSnapshot.d.ts +1 -1
  70. package/dist/perfSnapshot.js +28 -4
  71. package/dist/policyCacheFiles.d.ts +73 -0
  72. package/dist/policyCacheFiles.js +320 -0
  73. package/dist/policyGateHealth.d.ts +8 -0
  74. package/dist/policyGateHealth.js +17 -0
  75. package/dist/policyRefresh.d.ts +28 -0
  76. package/dist/policyRefresh.js +69 -0
  77. package/dist/protectionMaintenance.d.ts +15 -0
  78. package/dist/protectionMaintenance.js +43 -1
  79. package/dist/runtimeConfig.d.ts +77 -20
  80. package/dist/runtimeConfig.js +278 -57
  81. package/dist/selfTest.d.ts +6 -1
  82. package/dist/selfTest.js +11 -4
  83. package/dist/telemetry.d.ts +136 -4
  84. package/dist/telemetry.js +493 -96
  85. package/dist/verdictIpc.d.ts +60 -0
  86. package/dist/verdictIpc.js +504 -19
  87. package/dist/verdictIpcClient.d.ts +175 -18
  88. package/dist/verdictIpcClient.js +277 -59
  89. package/dist/version.json +1 -1
  90. package/package.json +2 -2
@@ -96,6 +96,7 @@ export declare function resolveAskWithDialog(verdict: HookVerdict, options?: {
96
96
  clientLabel?: string;
97
97
  timeoutMs?: number;
98
98
  dialog?: DialogFn;
99
+ deadlineAt?: number;
99
100
  }): Promise<HookVerdict & {
100
101
  resolution?: AskResolution;
101
102
  envelope?: AskEnvelope;
package/dist/askDialog.js CHANGED
@@ -36,6 +36,8 @@ const callerEnv_1 = require("./callerEnv");
36
36
  exports.ASK_ENVELOPE_FIELD = 'fcdAsk';
37
37
  /** Default wait for the person. Cursor's hook window is 90 s, Claude's 60 s by default; stay inside both. */
38
38
  exports.ASK_DIALOG_TIMEOUT_MS = 55_000;
39
+ /** Less time than this before the IDE's cutoff is not enough to read and answer a dialog. */
40
+ const MIN_DIALOG_TIMEOUT_MS = 5_000;
39
41
  /** Which channel asks the developer. `dialog` (default) = our OS dialog, `ide` = the IDE's own card. */
40
42
  function askChannel() {
41
43
  return ((0, callerEnv_1.callerEnv)().FCD_ASK_CHANNEL || '').trim().toLowerCase() === 'ide' ? 'ide' : 'dialog';
@@ -97,9 +99,20 @@ function rewriteDecision(decision, allowed, userMsg, agentMsg) {
97
99
  * written as-is with the envelope removed. Then the process exits.
98
100
  */
99
101
  async function emitVerdictForIde(args, result, entry) {
102
+ // This process ends with the verdict below: an upload the dialog's outcome
103
+ // asks for (allow, decline, timeout) starts after stdout, at exit — never
104
+ // while the IDE still waits for the answer.
105
+ try {
106
+ (0, telemetry_1.deferFlushUntilExit)();
107
+ }
108
+ catch { /* best-effort */ }
100
109
  let final = result;
101
110
  try {
102
- const resolved = await resolveAskWithDialog(result, { clientLabel: args.agentClient });
111
+ // This is the per-event process the IDE started: its clock started with
112
+ // the process, so the dialog must end before the IDE's hook timeout does
113
+ // (a killed hook runs the action without anyone's answer).
114
+ const processStartedAt = Date.now() - Math.round(process.uptime() * 1000);
115
+ const resolved = await resolveAskWithDialog(result, { clientLabel: args.agentClient, deadlineAt: (0, callerEnv_1.ideWaitCutoff)(processStartedAt) });
103
116
  if (resolved.resolution)
104
117
  (0, hookIo_1.dbg)({ phase: 'ask_dialog', event: args.event, entry, resolution: resolved.resolution, tool: resolved.envelope?.toolName });
105
118
  final = resolved;
@@ -137,7 +150,11 @@ async function resolveAskWithDialog(verdict, options = {}) {
137
150
  const clean = { ...verdict, stdout: taken.stdout, envelope };
138
151
  // An injected dialog (tests, embedding hosts) only needs a person; the OS dialog also needs a display.
139
152
  const canAsk = options.dialog ? (0, devConfirm_1.isHumanPresent)() : (0, devConfirm_1.nativeDialogAvailable)();
140
- if (askChannel() === 'ide' || !canAsk)
153
+ // Wait = min(configured, time left before the IDE's cutoff). Too little time
154
+ // left for a person to read and answer: the IDE's own ask card takes the
155
+ // question instead, so the hook still answers inside the IDE's window.
156
+ const dialogTimeoutMs = Math.min(options.timeoutMs ?? exports.ASK_DIALOG_TIMEOUT_MS, options.deadlineAt === undefined ? Number.POSITIVE_INFINITY : options.deadlineAt - Date.now());
157
+ if (askChannel() === 'ide' || !canAsk || dialogTimeoutMs < MIN_DIALOG_TIMEOUT_MS)
141
158
  return { ...clean, resolution: 'ide' };
142
159
  const grantHash = envelope.ruleHash || envelope.policyHash;
143
160
  const grantLimits = envelope.grantLimits === null ? undefined : (envelope.grantLimits || { ttlMs: devConfirm_1.GRANT_TTL_MS });
@@ -163,7 +180,7 @@ async function resolveAskWithDialog(verdict, options = {}) {
163
180
  let outcome = 'unavailable';
164
181
  let latencyMs = 0;
165
182
  try {
166
- ({ outcome, latencyMs } = await dialog(reason, options.timeoutMs ?? exports.ASK_DIALOG_TIMEOUT_MS, { allowAlways, alwaysScopeLabel: scopeLabel, alwaysLimitsLabel: limitsLabel, alwaysCovers, alwaysNotOffered, subject }));
183
+ ({ outcome, latencyMs } = await dialog(reason, dialogTimeoutMs, { allowAlways, alwaysScopeLabel: scopeLabel, alwaysLimitsLabel: limitsLabel, alwaysCovers, alwaysNotOffered, subject }));
167
184
  }
168
185
  catch {
169
186
  outcome = 'unavailable';
@@ -11,10 +11,23 @@
11
11
  *
12
12
  * Node built-ins only: this leaf is on the slim hook's warm-path module graph.
13
13
  */
14
- export declare const CALLER_ENV_KEYS: readonly ["FCD_HUMAN_PRESENT", "FCD_EPHEMERAL", "FULLCOURTDEFENSE_EPHEMERAL", "CI", "GITHUB_ACTIONS", "GITLAB_CI", "TF_BUILD", "JENKINS_URL", "BUILDKITE", "CIRCLECI", "DISPLAY", "WAYLAND_DISPLAY", "FCD_ASK_CHANNEL", "FCD_SESSION_ID", "CLAUDE_CODE_SESSION_ID", "CODEX_THREAD_ID", "FCD_RUN_ID", "GITHUB_RUN_ID", "CI_PIPELINE_ID", "FCD_WORKLOAD_INSTANCE_ID", "CLAUDECODE", "CLAUDE_PROJECT_DIR", "VSCODE_PID", "TERM_PROGRAM", "WORKSPACE_PATH", "FCD_HOOK_IDE_TIMEOUT_MS", "FCD_TAINT_APPROVE_ONCE_DISABLED", "FCD_TAINT_APPROVAL_TIMEOUT_MS", "CLOUDSDK_CORE_PROJECT", "CLOUDSDK_ACTIVE_CONFIG_NAME", "CLOUDSDK_CONFIG", "APPDATA", "AWS_PROFILE", "AWS_DEFAULT_PROFILE", "AWS_REGION", "AWS_DEFAULT_REGION", "AWS_CONFIG_FILE", "AWS_SHARED_CREDENTIALS_FILE", "AWS_ACCESS_KEY_ID", "KUBECONFIG", "HELM_KUBECONTEXT", "HELM_NAMESPACE", "HELM_KUBEAPISERVER", "HELM_KUBETOKEN", "HELM_KUBEASUSER", "DOCKER_HOST", "DOCKER_CONTEXT", "AZURE_CONFIG_DIR", "AZURE_DEFAULTS_GROUP", "TF_WORKSPACE", "TF_DATA_DIR", "PGHOST", "PGDATABASE", "PGSERVICE", "MYSQL_HOST"];
14
+ export declare const CALLER_ENV_KEYS: readonly ["FCD_HUMAN_PRESENT", "FCD_EPHEMERAL", "FULLCOURTDEFENSE_EPHEMERAL", "CI", "GITHUB_ACTIONS", "GITLAB_CI", "TF_BUILD", "JENKINS_URL", "BUILDKITE", "CIRCLECI", "DISPLAY", "WAYLAND_DISPLAY", "FCD_ASK_CHANNEL", "FCD_SESSION_ID", "CLAUDE_CODE_SESSION_ID", "CODEX_THREAD_ID", "FCD_RUN_ID", "GITHUB_RUN_ID", "CI_PIPELINE_ID", "FCD_WORKLOAD_INSTANCE_ID", "CLAUDECODE", "CLAUDE_PROJECT_DIR", "VSCODE_PID", "TERM_PROGRAM", "WORKSPACE_PATH", "FCD_HOOK_IDE_TIMEOUT_MS", "FCD_TAINT_APPROVE_ONCE_DISABLED", "FCD_TAINT_APPROVAL_TIMEOUT_MS", "FCD_HOOK_DEBUG", "CLOUDSDK_CORE_PROJECT", "CLOUDSDK_ACTIVE_CONFIG_NAME", "CLOUDSDK_CONFIG", "APPDATA", "AWS_PROFILE", "AWS_DEFAULT_PROFILE", "AWS_REGION", "AWS_DEFAULT_REGION", "AWS_CONFIG_FILE", "AWS_SHARED_CREDENTIALS_FILE", "AWS_ACCESS_KEY_ID", "KUBECONFIG", "HELM_KUBECONTEXT", "HELM_NAMESPACE", "HELM_KUBEAPISERVER", "HELM_KUBETOKEN", "HELM_KUBEASUSER", "DOCKER_HOST", "DOCKER_CONTEXT", "AZURE_CONFIG_DIR", "AZURE_DEFAULTS_GROUP", "TF_WORKSPACE", "TF_DATA_DIR", "PGHOST", "PGDATABASE", "PGSERVICE", "MYSQL_HOST"];
15
15
  export type CallerEnvSnapshot = Partial<Record<(typeof CALLER_ENV_KEYS)[number], string>>;
16
+ /**
17
+ * Keys a decision reads only for WHETHER they are set (cloudGrantTools: static
18
+ * AWS keys beat every profile; a helm API server / token / impersonation makes
19
+ * the target unknown) — and some are credentials (HELM_KUBETOKEN is a bearer
20
+ * token). A snapshot that is written to disk keeps only that they are set.
21
+ */
22
+ export declare const PRESENCE_ONLY_CALLER_ENV_KEYS: readonly (typeof CALLER_ENV_KEYS)[number][];
16
23
  /** The keys above that are set in `env` — what the thin client sends. */
17
24
  export declare function snapshotCallerEnv(env?: NodeJS.ProcessEnv): CallerEnvSnapshot;
25
+ /**
26
+ * snapshotCallerEnv for a snapshot that is STORED (the monitor fast path's
27
+ * local queue): the same decision facts, without credential values — a
28
+ * presence-only key keeps '1' when set (non-blank), '' when blank.
29
+ */
30
+ export declare function storableCallerEnv(env?: NodeJS.ProcessEnv): CallerEnvSnapshot;
18
31
  /** The environment decision facts are read from: the requesting caller's inside a daemon request, else this process's. */
19
32
  export declare function callerEnv(): NodeJS.ProcessEnv;
20
33
  /**
@@ -26,11 +39,25 @@ export declare function withCallerEnv<T>(snapshot: unknown, fn: () => T): T;
26
39
  /**
27
40
  * How long the IDE lets an approval-capable hook run before it kills the hook
28
41
  * (and runs the action): Claude Code / Copilot / VS Code and Cursor: 90s.
29
- * FCD_HOOK_IDE_TIMEOUT_MS narrows it for drills.
42
+ * FCD_HOOK_IDE_TIMEOUT_MS narrows it — per hook entry (applyIdeTimeoutArg) and
43
+ * for drills.
30
44
  */
31
45
  export declare const IDE_HOOK_TIMEOUT_MS = 90000;
32
46
  export declare const IDE_TIMEOUT_MARGIN_MS = 15000;
33
47
  export declare function ideHookTimeoutMs(env?: NodeJS.ProcessEnv): number;
48
+ /**
49
+ * The installer writes each hook entry's real IDE timeout into its command
50
+ * (`--ide-timeout <seconds>`, the same number as the entry's `timeout`): Claude
51
+ * Code PostToolUse 10s, UserPromptSubmit 30s, PreToolUse 90s; Cursor per event.
52
+ * A hook the IDE kills runs the action, so every wait must end before THAT
53
+ * timeout, not before a fixed 90s. Applied, in the hook's own process only, as
54
+ * its FCD_HOOK_IDE_TIMEOUT_MS — a caller-env key, so the daemon evaluating the
55
+ * call applies it too (older daemons already honour it). It only ever narrows
56
+ * the 90s default: an entry configured longer keeps today's waits. An explicit
57
+ * FCD_HOOK_IDE_TIMEOUT_MS (drills) wins; a command line without the argument
58
+ * (older installs) keeps the default.
59
+ */
60
+ export declare function applyIdeTimeoutArg(seconds: string | undefined, env?: NodeJS.ProcessEnv): void;
34
61
  /** Absolute time every wait inside a hook must end by, leaving the margin before the IDE's timeout. */
35
62
  export declare function ideWaitCutoff(startedAt: number, ideTimeoutMs?: number): number;
36
63
  /** CI / pipeline environments — no human at a keyboard. Shared by hooks and discovery. */
package/dist/callerEnv.js CHANGED
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.IDE_TIMEOUT_MARGIN_MS = exports.IDE_HOOK_TIMEOUT_MS = exports.CALLER_ENV_KEYS = void 0;
3
+ exports.IDE_TIMEOUT_MARGIN_MS = exports.IDE_HOOK_TIMEOUT_MS = exports.PRESENCE_ONLY_CALLER_ENV_KEYS = exports.CALLER_ENV_KEYS = void 0;
4
4
  exports.snapshotCallerEnv = snapshotCallerEnv;
5
+ exports.storableCallerEnv = storableCallerEnv;
5
6
  exports.callerEnv = callerEnv;
6
7
  exports.withCallerEnv = withCallerEnv;
7
8
  exports.ideHookTimeoutMs = ideHookTimeoutMs;
9
+ exports.applyIdeTimeoutArg = applyIdeTimeoutArg;
8
10
  exports.ideWaitCutoff = ideWaitCutoff;
9
11
  exports.isCiEnvironment = isCiEnvironment;
10
12
  exports.isHumanPresent = isHumanPresent;
@@ -34,6 +36,8 @@ exports.CALLER_ENV_KEYS = [
34
36
  'CLAUDECODE', 'CLAUDE_PROJECT_DIR', 'VSCODE_PID', 'TERM_PROGRAM', 'WORKSPACE_PATH',
35
37
  // Human-wait budget and approve-once switches.
36
38
  'FCD_HOOK_IDE_TIMEOUT_MS', 'FCD_TAINT_APPROVE_ONCE_DISABLED', 'FCD_TAINT_APPROVAL_TIMEOUT_MS',
39
+ // Support diagnostics: per-phase hook.log lines for this caller's verdicts (never a decision fact).
40
+ 'FCD_HOOK_DEBUG',
37
41
  // Which GCP project a gcloud line targets when it names none (cloudGrant.activeGcloudProject).
38
42
  'CLOUDSDK_CORE_PROJECT', 'CLOUDSDK_ACTIVE_CONFIG_NAME', 'CLOUDSDK_CONFIG', 'APPDATA',
39
43
  // Which aws profile / region and which kube context an aws / kubectl / helm line targets
@@ -49,6 +53,15 @@ exports.CALLER_ENV_KEYS = [
49
53
  // Which database host / name a psql / mysql line falls back to (cloudGrantTools).
50
54
  'PGHOST', 'PGDATABASE', 'PGSERVICE', 'MYSQL_HOST',
51
55
  ];
56
+ /**
57
+ * Keys a decision reads only for WHETHER they are set (cloudGrantTools: static
58
+ * AWS keys beat every profile; a helm API server / token / impersonation makes
59
+ * the target unknown) — and some are credentials (HELM_KUBETOKEN is a bearer
60
+ * token). A snapshot that is written to disk keeps only that they are set.
61
+ */
62
+ exports.PRESENCE_ONLY_CALLER_ENV_KEYS = [
63
+ 'AWS_ACCESS_KEY_ID', 'HELM_KUBEAPISERVER', 'HELM_KUBETOKEN', 'HELM_KUBEASUSER',
64
+ ];
52
65
  /** The keys above that are set in `env` — what the thin client sends. */
53
66
  function snapshotCallerEnv(env = process.env) {
54
67
  const snapshot = {};
@@ -59,6 +72,20 @@ function snapshotCallerEnv(env = process.env) {
59
72
  }
60
73
  return snapshot;
61
74
  }
75
+ /**
76
+ * snapshotCallerEnv for a snapshot that is STORED (the monitor fast path's
77
+ * local queue): the same decision facts, without credential values — a
78
+ * presence-only key keeps '1' when set (non-blank), '' when blank.
79
+ */
80
+ function storableCallerEnv(env = process.env) {
81
+ const snapshot = snapshotCallerEnv(env);
82
+ for (const key of exports.PRESENCE_ONLY_CALLER_ENV_KEYS) {
83
+ const value = snapshot[key];
84
+ if (typeof value === 'string')
85
+ snapshot[key] = value.trim() ? '1' : '';
86
+ }
87
+ return snapshot;
88
+ }
62
89
  const requestEnv = new async_hooks_1.AsyncLocalStorage();
63
90
  /** The environment decision facts are read from: the requesting caller's inside a daemon request, else this process's. */
64
91
  function callerEnv() {
@@ -85,7 +112,8 @@ function withCallerEnv(snapshot, fn) {
85
112
  /**
86
113
  * How long the IDE lets an approval-capable hook run before it kills the hook
87
114
  * (and runs the action): Claude Code / Copilot / VS Code and Cursor: 90s.
88
- * FCD_HOOK_IDE_TIMEOUT_MS narrows it for drills.
115
+ * FCD_HOOK_IDE_TIMEOUT_MS narrows it — per hook entry (applyIdeTimeoutArg) and
116
+ * for drills.
89
117
  */
90
118
  exports.IDE_HOOK_TIMEOUT_MS = 90_000;
91
119
  exports.IDE_TIMEOUT_MARGIN_MS = 15_000;
@@ -93,6 +121,24 @@ function ideHookTimeoutMs(env = callerEnv()) {
93
121
  const override = Number(env.FCD_HOOK_IDE_TIMEOUT_MS);
94
122
  return override > 0 ? override : exports.IDE_HOOK_TIMEOUT_MS;
95
123
  }
124
+ /**
125
+ * The installer writes each hook entry's real IDE timeout into its command
126
+ * (`--ide-timeout <seconds>`, the same number as the entry's `timeout`): Claude
127
+ * Code PostToolUse 10s, UserPromptSubmit 30s, PreToolUse 90s; Cursor per event.
128
+ * A hook the IDE kills runs the action, so every wait must end before THAT
129
+ * timeout, not before a fixed 90s. Applied, in the hook's own process only, as
130
+ * its FCD_HOOK_IDE_TIMEOUT_MS — a caller-env key, so the daemon evaluating the
131
+ * call applies it too (older daemons already honour it). It only ever narrows
132
+ * the 90s default: an entry configured longer keeps today's waits. An explicit
133
+ * FCD_HOOK_IDE_TIMEOUT_MS (drills) wins; a command line without the argument
134
+ * (older installs) keeps the default.
135
+ */
136
+ function applyIdeTimeoutArg(seconds, env = process.env) {
137
+ const ms = Math.round(Number(seconds) * 1000);
138
+ if (!Number.isFinite(ms) || ms <= 0 || Number(env.FCD_HOOK_IDE_TIMEOUT_MS) > 0)
139
+ return;
140
+ env.FCD_HOOK_IDE_TIMEOUT_MS = String(Math.min(ms, exports.IDE_HOOK_TIMEOUT_MS));
141
+ }
96
142
  /** Absolute time every wait inside a hook must end by, leaving the margin before the IDE's timeout. */
97
143
  function ideWaitCutoff(startedAt, ideTimeoutMs = ideHookTimeoutMs()) {
98
144
  return startedAt + ideTimeoutMs - Math.min(exports.IDE_TIMEOUT_MARGIN_MS, ideTimeoutMs / 4);
@@ -1,5 +1,6 @@
1
1
  import { BotGuardConfig } from '../config';
2
2
  import { ProtectAllArgs } from './mcpGateway';
3
+ import '../askDialog';
3
4
  import { windowsTaskRunsInteractive, windowsTaskRunsVisibly } from '../windowsTaskState';
4
5
  export interface DaemonArgs extends ProtectAllArgs {
5
6
  install?: string;
@@ -97,6 +98,15 @@ export declare function registerHiddenTask(taskName: string, xml: string | strin
97
98
  * (the pre-1.22.1 VBS launcher) — the migration trigger. */
98
99
  export declare function windowsTaskReferencesScriptHost(taskName: string): boolean;
99
100
  export { windowsTaskRunsInteractive, windowsTaskRunsVisibly };
101
+ /**
102
+ * Boot-time self-heal for the Windows autostart chain. Recreates a missing
103
+ * daemon logon entry, refreshes the launcher scripts (paths move on upgrade),
104
+ * and re-points the watchdog task at the `watchdog` launcher (older versions
105
+ * pointed it at daemon.vbs, which restarts but never beacons). Creating
106
+ * per-user MINUTE tasks needs no elevation; ONLOGON does — fall back to the
107
+ * per-user Run key exactly like installWindows().
108
+ */
109
+ export declare function ensureWindowsAutostartHealthy(logFn: (message: string) => void): void;
100
110
  /** Snapshot of the resident daemon for out-of-process callers (watchdog/status). */
101
111
  export interface DaemonRuntimeState {
102
112
  alive: boolean;