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.
- package/dist/askDialog.d.ts +1 -0
- package/dist/askDialog.js +20 -3
- package/dist/callerEnv.d.ts +29 -2
- package/dist/callerEnv.js +48 -2
- package/dist/commands/daemon.d.ts +10 -0
- package/dist/commands/daemon.js +348 -170
- package/dist/commands/desktopChatGuard.js +2 -0
- package/dist/commands/discoverPaths.d.ts +12 -9
- package/dist/commands/discoverPaths.js +30 -0
- package/dist/commands/ephemeralStack.js +7 -0
- package/dist/commands/flushSpool.d.ts +2 -0
- package/dist/commands/flushSpool.js +9 -2
- package/dist/commands/hook.d.ts +14 -0
- package/dist/commands/hook.js +302 -101
- package/dist/commands/httpGatewayCommand.d.ts +1 -0
- package/dist/commands/httpGatewayCommand.js +7 -1
- package/dist/commands/installClaudeHook.js +32 -10
- package/dist/commands/installCursorHook.js +20 -9
- package/dist/commands/login.js +8 -1
- package/dist/commands/mcpGateway.d.ts +21 -0
- package/dist/commands/mcpGateway.js +90 -32
- package/dist/commands/refreshPolicyCache.d.ts +28 -0
- package/dist/commands/refreshPolicyCache.js +59 -0
- package/dist/commands/watchdog.d.ts +2 -0
- package/dist/commands/watchdog.js +30 -5
- package/dist/config.d.ts +36 -0
- package/dist/config.js +78 -12
- package/dist/daemonDetectorBoot.d.ts +1 -0
- package/dist/daemonDetectorBoot.js +12 -0
- package/dist/daemonForensics.d.ts +14 -1
- package/dist/daemonForensics.js +32 -0
- package/dist/detectorInProcess.d.ts +18 -0
- package/dist/detectorInProcess.js +234 -0
- package/dist/detectorInThread.d.ts +15 -0
- package/dist/detectorInThread.js +129 -0
- package/dist/distress.d.ts +2 -0
- package/dist/distress.js +2 -0
- package/dist/fileWriteCanon.d.ts +17 -0
- package/dist/fileWriteCanon.js +141 -0
- package/dist/gateReports.d.ts +144 -0
- package/dist/gateReports.js +488 -0
- package/dist/hookIo.d.ts +59 -0
- package/dist/hookIo.js +319 -9
- package/dist/hookSlim.d.ts +15 -8
- package/dist/hookSlim.js +187 -25
- package/dist/housekeeping.d.ts +64 -0
- package/dist/housekeeping.js +384 -0
- package/dist/housekeepingOps.d.ts +150 -0
- package/dist/housekeepingOps.js +218 -0
- package/dist/housekeepingWorker.d.ts +1 -0
- package/dist/housekeepingWorker.js +113 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +19 -0
- package/dist/localDetectionData.d.ts +10 -0
- package/dist/localDetectionData.js +48 -0
- package/dist/localDetectionUpdates.d.ts +2 -2
- package/dist/localDetectionUpdates.js +4 -4
- package/dist/localSafetySnapshot.d.ts +48 -2
- package/dist/localSafetySnapshot.js +241 -25
- package/dist/machineIdentity.d.ts +7 -0
- package/dist/machineIdentity.js +20 -2
- package/dist/monitorFastPath.d.ts +191 -0
- package/dist/monitorFastPath.js +1198 -0
- package/dist/nativeHook.js +9 -2
- package/dist/pendingDrain.d.ts +82 -0
- package/dist/pendingDrain.js +215 -0
- package/dist/pendingDrainer.d.ts +30 -0
- package/dist/pendingDrainer.js +217 -0
- package/dist/perfSnapshot.d.ts +1 -1
- package/dist/perfSnapshot.js +28 -4
- package/dist/policyCacheFiles.d.ts +73 -0
- package/dist/policyCacheFiles.js +320 -0
- package/dist/policyGateHealth.d.ts +8 -0
- package/dist/policyGateHealth.js +17 -0
- package/dist/policyRefresh.d.ts +28 -0
- package/dist/policyRefresh.js +69 -0
- package/dist/protectionMaintenance.d.ts +15 -0
- package/dist/protectionMaintenance.js +43 -1
- package/dist/runtimeConfig.d.ts +77 -20
- package/dist/runtimeConfig.js +278 -57
- package/dist/selfTest.d.ts +6 -1
- package/dist/selfTest.js +11 -4
- package/dist/telemetry.d.ts +136 -4
- package/dist/telemetry.js +493 -96
- package/dist/verdictIpc.d.ts +60 -0
- package/dist/verdictIpc.js +504 -19
- package/dist/verdictIpcClient.d.ts +175 -18
- package/dist/verdictIpcClient.js +277 -59
- package/dist/version.json +1 -1
- package/package.json +2 -2
package/dist/askDialog.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
|
183
|
+
({ outcome, latencyMs } = await dialog(reason, dialogTimeoutMs, { allowAlways, alwaysScopeLabel: scopeLabel, alwaysLimitsLabel: limitsLabel, alwaysCovers, alwaysNotOffered, subject }));
|
|
167
184
|
}
|
|
168
185
|
catch {
|
|
169
186
|
outcome = 'unavailable';
|
package/dist/callerEnv.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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;
|