@pugi/cli 0.1.0-beta.4 → 0.1.0-beta.40
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/THIRD_PARTY_NOTICES.md +40 -0
- package/assets/pugi-mascot.ansi +15 -25
- package/bin/run.js +33 -1
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +15 -0
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash-classifier.js +108 -1
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/diff-capture.js +73 -0
- package/dist/core/context/index.js +7 -0
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +86 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/dispatch.js +218 -2
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +322 -0
- package/dist/core/engine/anvil-client.js +115 -5
- package/dist/core/engine/budgets.js +98 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +860 -211
- package/dist/core/engine/prompts.js +88 -2
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +992 -36
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/hooks/events.js +44 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +213 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +776 -0
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/mcp/client.js +75 -6
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-tools.js +662 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +24 -2
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory-sync/queue.js +158 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/history.js +11 -1
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/session.js +1899 -38
- package/dist/core/repl/slash-commands.js +406 -21
- package/dist/core/repl/store/session-store.js +31 -2
- package/dist/core/repl/workspace-context.js +22 -0
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/session.js +92 -0
- package/dist/core/settings.js +80 -0
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +457 -0
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +113 -24
- package/dist/core/subagents/index.js +18 -5
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/index.js +28 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +3073 -321
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +242 -11
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +390 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +184 -0
- package/dist/runtime/commands/lsp.js +368 -0
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +508 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +128 -0
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +17 -2
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +32 -0
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +177 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +543 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +531 -0
- package/dist/runtime/version.js +65 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +556 -0
- package/dist/tools/ask-user-question.js +213 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/file-tools.js +85 -14
- package/dist/tools/lsp-tools.js +189 -0
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/registry.js +46 -0
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/web-fetch.js +147 -2
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +10 -0
- package/dist/tui/ask-modal.js +2 -2
- package/dist/tui/ask-user-question-prompt.js +192 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +82 -8
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +69 -2
- package/dist/tui/markdown-render.js +4 -4
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +35 -0
- package/dist/tui/repl-render.js +303 -13
- package/dist/tui/repl-splash.js +2 -2
- package/dist/tui/repl.js +72 -14
- package/dist/tui/splash.js +1 -1
- package/dist/tui/status-bar.js +94 -16
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/tool-stream-pane.js +52 -3
- package/dist/tui/update-banner.js +20 -2
- package/dist/tui/vim-input.js +267 -0
- package/docs/examples/codegraph.mcp.json +10 -0
- package/package.json +12 -6
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +11 -0
- package/test/scenarios/identity.scenario.txt +11 -0
- package/test/scenarios/persona-handoff.scenario.txt +11 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pugi hooks v2 — executor (Wave 7 Phase 1).
|
|
3
|
+
*
|
|
4
|
+
* Spawns the hook command with the JSON payload on stdin, captures
|
|
5
|
+
* stdout / stderr, applies the timeout watchdog, and parses the
|
|
6
|
+
* decision protocol:
|
|
7
|
+
*
|
|
8
|
+
* - Exit 0 + empty stdout -> { decision: 'allow' }
|
|
9
|
+
* - Exit 0 + JSON `{"decision":"deny"}` -> blocked
|
|
10
|
+
* - Exit 0 + JSON `{"decision":"ask"}` -> ask operator (TUI) /
|
|
11
|
+
* refuse in headless
|
|
12
|
+
* - Exit 0 + JSON `{"additionalContext"}` -> inject into next prompt
|
|
13
|
+
* - Exit 1 -> log warning, continue
|
|
14
|
+
* - Exit 2 -> block; stderr = reason
|
|
15
|
+
* (Claude Code compat)
|
|
16
|
+
*
|
|
17
|
+
* Safety properties:
|
|
18
|
+
* - 1 MiB output cap per stream (configurable). Misbehaving hooks
|
|
19
|
+
* that emit unbounded output are killed.
|
|
20
|
+
* - SIGTERM then SIGKILL with a 2 s grace window.
|
|
21
|
+
* - Spawn failures never crash the parent.
|
|
22
|
+
*
|
|
23
|
+
* Brand voice: ASCII only.
|
|
24
|
+
*/
|
|
25
|
+
import { spawn } from 'node:child_process';
|
|
26
|
+
import { HOOK_OUTPUT_CAP_BYTES_V2, } from './types.js';
|
|
27
|
+
const SIGKILL_GRACE_MS = 2_000;
|
|
28
|
+
/**
|
|
29
|
+
* Execute a single hook + return a typed result. Never throws — every
|
|
30
|
+
* failure mode (spawn error, timeout, stream cap) lands in a
|
|
31
|
+
* structured result so the caller can log + continue.
|
|
32
|
+
*/
|
|
33
|
+
export async function executeHook(opts) {
|
|
34
|
+
const { hook, payload, env, outputCapBytes = HOOK_OUTPUT_CAP_BYTES_V2 } = opts;
|
|
35
|
+
if (hook.type !== 'command') {
|
|
36
|
+
// Phase 1 only supports command-type hooks; other types resolve to
|
|
37
|
+
// an allow-decision so the rest of the pipeline keeps moving.
|
|
38
|
+
return makeUnsupportedTypeResult(hook);
|
|
39
|
+
}
|
|
40
|
+
const command = hook.command ?? '';
|
|
41
|
+
if (command.trim() === '') {
|
|
42
|
+
return {
|
|
43
|
+
hookCommand: '',
|
|
44
|
+
event: hook.event,
|
|
45
|
+
exitCode: -1,
|
|
46
|
+
stdout: '',
|
|
47
|
+
stderr: 'pugi hooks v2: command hook has empty command',
|
|
48
|
+
decision: { decision: 'allow' },
|
|
49
|
+
elapsedMs: 0,
|
|
50
|
+
timedOut: false,
|
|
51
|
+
blocked: false,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const startedAt = Date.now();
|
|
55
|
+
const timeoutMs = hook.timeoutMs ?? 5_000;
|
|
56
|
+
const stdinJson = JSON.stringify(payload);
|
|
57
|
+
const childEnv = {
|
|
58
|
+
...(env ?? process.env),
|
|
59
|
+
PUGI_HOOK_PAYLOAD: stdinJson,
|
|
60
|
+
PUGI_HOOK_EVENT: payload.hook_event_name,
|
|
61
|
+
PUGI_HOOK_SESSION_ID: payload.session_id,
|
|
62
|
+
PUGI_HOOK_CWD: payload.cwd,
|
|
63
|
+
PUGI_HOOK_TRANSCRIPT_PATH: payload.transcript_path,
|
|
64
|
+
PUGI_HOOK_PERMISSION_MODE: payload.permission_mode,
|
|
65
|
+
PUGI_HOOK_TOOL_NAME: payload.tool_name ?? '',
|
|
66
|
+
PUGI_HOOK_AGENT_ID: payload.agent_id ?? '',
|
|
67
|
+
PUGI_HOOK_AGENT_TYPE: payload.agent_type ?? '',
|
|
68
|
+
};
|
|
69
|
+
return new Promise((resolvePromise) => {
|
|
70
|
+
const child = spawn('/bin/sh', ['-c', command], {
|
|
71
|
+
env: childEnv,
|
|
72
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
73
|
+
});
|
|
74
|
+
let stdout = '';
|
|
75
|
+
let stderr = '';
|
|
76
|
+
let timedOut = false;
|
|
77
|
+
let killedForCap = false;
|
|
78
|
+
let sigKillTimer;
|
|
79
|
+
const escalateKill = () => {
|
|
80
|
+
if (sigKillTimer)
|
|
81
|
+
return;
|
|
82
|
+
sigKillTimer = setTimeout(() => {
|
|
83
|
+
if (!child.killed)
|
|
84
|
+
child.kill('SIGKILL');
|
|
85
|
+
}, SIGKILL_GRACE_MS);
|
|
86
|
+
if (sigKillTimer.unref)
|
|
87
|
+
sigKillTimer.unref();
|
|
88
|
+
};
|
|
89
|
+
const enforceCap = () => {
|
|
90
|
+
if (killedForCap)
|
|
91
|
+
return;
|
|
92
|
+
if (stdout.length + stderr.length <= outputCapBytes)
|
|
93
|
+
return;
|
|
94
|
+
killedForCap = true;
|
|
95
|
+
child.kill('SIGTERM');
|
|
96
|
+
escalateKill();
|
|
97
|
+
};
|
|
98
|
+
child.stdout?.on('data', (chunk) => {
|
|
99
|
+
if (killedForCap)
|
|
100
|
+
return;
|
|
101
|
+
stdout += chunk.toString('utf8');
|
|
102
|
+
enforceCap();
|
|
103
|
+
});
|
|
104
|
+
child.stderr?.on('data', (chunk) => {
|
|
105
|
+
if (killedForCap)
|
|
106
|
+
return;
|
|
107
|
+
stderr += chunk.toString('utf8');
|
|
108
|
+
enforceCap();
|
|
109
|
+
});
|
|
110
|
+
if (child.stdin) {
|
|
111
|
+
child.stdin.on('error', () => {
|
|
112
|
+
// EPIPE on a hook that ignores stdin is benign — payload also
|
|
113
|
+
// arrives via env var PUGI_HOOK_PAYLOAD.
|
|
114
|
+
});
|
|
115
|
+
child.stdin.end(stdinJson);
|
|
116
|
+
}
|
|
117
|
+
const timer = setTimeout(() => {
|
|
118
|
+
timedOut = true;
|
|
119
|
+
child.kill('SIGTERM');
|
|
120
|
+
escalateKill();
|
|
121
|
+
}, timeoutMs);
|
|
122
|
+
if (timer.unref)
|
|
123
|
+
timer.unref();
|
|
124
|
+
child.on('error', (error) => {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
if (sigKillTimer)
|
|
127
|
+
clearTimeout(sigKillTimer);
|
|
128
|
+
resolvePromise({
|
|
129
|
+
hookCommand: truncate(command, 200),
|
|
130
|
+
event: hook.event,
|
|
131
|
+
exitCode: -1,
|
|
132
|
+
stdout,
|
|
133
|
+
stderr: stderr || error.message,
|
|
134
|
+
decision: { decision: 'allow' },
|
|
135
|
+
elapsedMs: Date.now() - startedAt,
|
|
136
|
+
timedOut: false,
|
|
137
|
+
blocked: false,
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
child.on('close', (code, signal) => {
|
|
141
|
+
clearTimeout(timer);
|
|
142
|
+
if (sigKillTimer)
|
|
143
|
+
clearTimeout(sigKillTimer);
|
|
144
|
+
const exitCode = resolveExitCode(code, signal);
|
|
145
|
+
const decision = parseDecision(exitCode, stdout, stderr);
|
|
146
|
+
const blocked = isBlocked(exitCode, decision);
|
|
147
|
+
const blockReason = blocked ? blockReasonOf(exitCode, decision, stderr) : undefined;
|
|
148
|
+
const additionalContext = readAdditionalContext(decision);
|
|
149
|
+
resolvePromise({
|
|
150
|
+
hookCommand: truncate(command, 200),
|
|
151
|
+
event: hook.event,
|
|
152
|
+
exitCode,
|
|
153
|
+
stdout,
|
|
154
|
+
stderr,
|
|
155
|
+
decision,
|
|
156
|
+
elapsedMs: Date.now() - startedAt,
|
|
157
|
+
timedOut,
|
|
158
|
+
blocked,
|
|
159
|
+
...(blockReason !== undefined ? { blockReason } : {}),
|
|
160
|
+
...(additionalContext !== undefined ? { additionalContext } : {}),
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function makeUnsupportedTypeResult(hook) {
|
|
166
|
+
return {
|
|
167
|
+
hookCommand: `<${hook.type}>`,
|
|
168
|
+
event: hook.event,
|
|
169
|
+
exitCode: 0,
|
|
170
|
+
stdout: '',
|
|
171
|
+
stderr: `pugi hooks v2: type '${hook.type}' is not supported in Phase 1`,
|
|
172
|
+
decision: { decision: 'allow' },
|
|
173
|
+
elapsedMs: 0,
|
|
174
|
+
timedOut: false,
|
|
175
|
+
blocked: false,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function resolveExitCode(code, signal) {
|
|
179
|
+
if (code !== null)
|
|
180
|
+
return code;
|
|
181
|
+
if (signal === 'SIGTERM')
|
|
182
|
+
return -15;
|
|
183
|
+
if (signal === 'SIGKILL')
|
|
184
|
+
return -9;
|
|
185
|
+
return -1;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Parse the JSON decision protocol. Falls back to `{ decision: 'allow' }`
|
|
189
|
+
* for any shape we do not recognize so a sloppy hook script does not
|
|
190
|
+
* silently start blocking tool dispatch.
|
|
191
|
+
*
|
|
192
|
+
* Exit-2 is the CC-compat shortcut: stderr is the block reason.
|
|
193
|
+
*/
|
|
194
|
+
export function parseDecision(exitCode, stdout, stderr) {
|
|
195
|
+
if (exitCode === 2) {
|
|
196
|
+
const reason = stderr.trim() || 'hook exited 2';
|
|
197
|
+
return { decision: 'deny', reason };
|
|
198
|
+
}
|
|
199
|
+
const trimmed = stdout.trim();
|
|
200
|
+
if (trimmed === '') {
|
|
201
|
+
return { decision: 'allow' };
|
|
202
|
+
}
|
|
203
|
+
// Try JSON first, but only when the stdout looks like a JSON object —
|
|
204
|
+
// a hook that prints `hello world` to stdout is not a decision.
|
|
205
|
+
if (!trimmed.startsWith('{')) {
|
|
206
|
+
return { decision: 'allow' };
|
|
207
|
+
}
|
|
208
|
+
let parsed;
|
|
209
|
+
try {
|
|
210
|
+
parsed = JSON.parse(trimmed);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return { decision: 'allow' };
|
|
214
|
+
}
|
|
215
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
216
|
+
return { decision: 'allow' };
|
|
217
|
+
}
|
|
218
|
+
const raw = parsed;
|
|
219
|
+
const decision = typeof raw.decision === 'string' ? raw.decision : undefined;
|
|
220
|
+
if (decision === 'deny') {
|
|
221
|
+
const reason = typeof raw.reason === 'string' && raw.reason.length > 0
|
|
222
|
+
? raw.reason
|
|
223
|
+
: 'hook returned decision=deny';
|
|
224
|
+
return { decision: 'deny', reason };
|
|
225
|
+
}
|
|
226
|
+
if (decision === 'ask') {
|
|
227
|
+
const prompt = typeof raw.prompt === 'string' && raw.prompt.length > 0
|
|
228
|
+
? raw.prompt
|
|
229
|
+
: 'hook requested operator approval';
|
|
230
|
+
return { decision: 'ask', prompt };
|
|
231
|
+
}
|
|
232
|
+
if (decision === 'allow' && typeof raw.additionalContext === 'string') {
|
|
233
|
+
return {
|
|
234
|
+
decision: 'allow',
|
|
235
|
+
additionalContext: raw.additionalContext,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (decision === 'allow') {
|
|
239
|
+
return { decision: 'allow' };
|
|
240
|
+
}
|
|
241
|
+
if (typeof raw.additionalContext === 'string') {
|
|
242
|
+
return { additionalContext: raw.additionalContext };
|
|
243
|
+
}
|
|
244
|
+
return { decision: 'allow' };
|
|
245
|
+
}
|
|
246
|
+
function isBlocked(exitCode, decision) {
|
|
247
|
+
if (exitCode === 2)
|
|
248
|
+
return true;
|
|
249
|
+
if ('decision' in decision && decision.decision === 'deny')
|
|
250
|
+
return true;
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
function blockReasonOf(exitCode, decision, stderr) {
|
|
254
|
+
if (exitCode === 2) {
|
|
255
|
+
return stderr.trim() || 'hook exited 2';
|
|
256
|
+
}
|
|
257
|
+
if ('decision' in decision && decision.decision === 'deny') {
|
|
258
|
+
return decision.reason;
|
|
259
|
+
}
|
|
260
|
+
return 'hook blocked';
|
|
261
|
+
}
|
|
262
|
+
function readAdditionalContext(decision) {
|
|
263
|
+
if (!('decision' in decision)) {
|
|
264
|
+
return decision.additionalContext;
|
|
265
|
+
}
|
|
266
|
+
if (decision.decision === 'allow' && 'additionalContext' in decision) {
|
|
267
|
+
return decision.additionalContext;
|
|
268
|
+
}
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
function truncate(value, max) {
|
|
272
|
+
if (value.length <= max)
|
|
273
|
+
return value;
|
|
274
|
+
return `${value.slice(0, max - 3)}...`;
|
|
275
|
+
}
|
|
276
|
+
/** Convenience predicate exported for the event-emitter. */
|
|
277
|
+
export function isToolEventV2(event) {
|
|
278
|
+
return (event === 'PreToolUse' ||
|
|
279
|
+
event === 'PostToolUse' ||
|
|
280
|
+
event === 'PostToolUseFailure');
|
|
281
|
+
}
|
|
282
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pugi hooks v2 — public surface (Wave 7 Phase 1).
|
|
3
|
+
*
|
|
4
|
+
* The v2 surface is a Claude Code parity layer that lives alongside
|
|
5
|
+
* the existing `core/hooks/` MVP module. See `types.ts` for the
|
|
6
|
+
* design note explaining why both surfaces co-exist.
|
|
7
|
+
*
|
|
8
|
+
* Stable imports:
|
|
9
|
+
*
|
|
10
|
+
* import {
|
|
11
|
+
* loadHooksConfigV2,
|
|
12
|
+
* fireHookEventV2,
|
|
13
|
+
* type HookEventV2,
|
|
14
|
+
* } from '../core/hooks/v2/index.js';
|
|
15
|
+
*
|
|
16
|
+
* Brand voice: ASCII only.
|
|
17
|
+
*/
|
|
18
|
+
export { ALL_HOOK_EVENTS_V2, DEFAULT_HOOK_TIMEOUT_MS_V2, HOOK_OUTPUT_CAP_BYTES_V2, MAX_HOOK_TIMEOUT_MS_V2, PHASE_1_HOOK_EVENTS, } from './types.js';
|
|
19
|
+
export { HooksConfigV2, SUPPORTED_HOOK_TYPES_V2, defaultGlobalHooksPath, defaultProjectHooksPath, loadHooksConfigV2, } from './loader.js';
|
|
20
|
+
export { compileMatcher, matches } from './matcher.js';
|
|
21
|
+
export { executeHook, isToolEventV2, parseDecision } from './executor.js';
|
|
22
|
+
export { ensureHookTrust, getHookTrust, listHookTrust, setHookTrust, trustKey, trustLedgerPath, } from './trust.js';
|
|
23
|
+
export { fireHookEventV2 } from './event-emitter.js';
|
|
24
|
+
export { firePostCompact, firePostToolUse, firePostToolUseFailure, firePreCompact, firePreToolUse, fireSessionEnd, fireSessionStart, fireStop, fireUserPromptSubmit, } from './lifecycle.js';
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pugi hooks v2 - lifecycle integration helpers (Wave 7 Phase 1).
|
|
3
|
+
*
|
|
4
|
+
* High-level fire-and-forget wrappers that hide the loader + emitter
|
|
5
|
+
* plumbing from the callers. Each function:
|
|
6
|
+
*
|
|
7
|
+
* 1. Loads the merged hooks config for the workspace.
|
|
8
|
+
* 2. Returns early when the config is empty (zero-cost when no hooks).
|
|
9
|
+
* 3. Resolves the trust state (skipping denied hooks).
|
|
10
|
+
* 4. Fires every matching hook + returns the aggregated outcome.
|
|
11
|
+
*
|
|
12
|
+
* The lifecycle helpers are called from:
|
|
13
|
+
* - `core/repl/session.ts` (SessionStart, SessionEnd, UserPromptSubmit,
|
|
14
|
+
* Stop)
|
|
15
|
+
* - `core/engine/tool-bridge.ts` (PreToolUse, PostToolUse,
|
|
16
|
+
* PostToolUseFailure)
|
|
17
|
+
* - `runtime/commands/compact.ts` (PreCompact, PostCompact)
|
|
18
|
+
*
|
|
19
|
+
* Best-effort: all helpers swallow exceptions so a misconfigured hook
|
|
20
|
+
* NEVER crashes the session boot path. Errors surface via
|
|
21
|
+
* `pugi hooks doctor` (Phase 2) and the per-session events log.
|
|
22
|
+
*
|
|
23
|
+
* Brand voice: ASCII only.
|
|
24
|
+
*/
|
|
25
|
+
import { fireHookEventV2 } from './event-emitter.js';
|
|
26
|
+
import { loadHooksConfigV2 } from './loader.js';
|
|
27
|
+
/** Empty no-op outcome. */
|
|
28
|
+
function noopOutcome(event) {
|
|
29
|
+
return { event, results: [], anyBlocked: false };
|
|
30
|
+
}
|
|
31
|
+
/** Internal helper that loads + fires + swallows errors. */
|
|
32
|
+
async function fireSafe(ctx, event, extra = {}) {
|
|
33
|
+
try {
|
|
34
|
+
const config = loadHooksConfigV2({
|
|
35
|
+
workspaceRoot: ctx.workspaceRoot,
|
|
36
|
+
});
|
|
37
|
+
if (config.isEmpty()) {
|
|
38
|
+
return noopOutcome(event);
|
|
39
|
+
}
|
|
40
|
+
return await fireHookEventV2({
|
|
41
|
+
config,
|
|
42
|
+
event,
|
|
43
|
+
sessionId: ctx.sessionId,
|
|
44
|
+
workspaceRoot: ctx.workspaceRoot,
|
|
45
|
+
transcriptPath: ctx.transcriptPath,
|
|
46
|
+
permissionMode: ctx.permissionMode,
|
|
47
|
+
...(ctx.promptFn !== undefined ? { promptFn: ctx.promptFn } : {}),
|
|
48
|
+
...extra,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Lifecycle helpers MUST NOT crash the session. A malformed config
|
|
53
|
+
// surfaces via `pugi hooks doctor`.
|
|
54
|
+
return noopOutcome(event);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Fire `SessionStart` hooks. Never throws. */
|
|
58
|
+
export function fireSessionStart(ctx) {
|
|
59
|
+
return fireSafe(ctx, 'SessionStart');
|
|
60
|
+
}
|
|
61
|
+
/** Fire `SessionEnd` hooks. Never throws. */
|
|
62
|
+
export function fireSessionEnd(ctx) {
|
|
63
|
+
return fireSafe(ctx, 'SessionEnd');
|
|
64
|
+
}
|
|
65
|
+
/** Fire `Stop` hooks. Never throws. */
|
|
66
|
+
export function fireStop(ctx) {
|
|
67
|
+
return fireSafe(ctx, 'Stop');
|
|
68
|
+
}
|
|
69
|
+
/** Fire `UserPromptSubmit` hooks. Never throws. */
|
|
70
|
+
export function fireUserPromptSubmit(ctx, prompt) {
|
|
71
|
+
return fireSafe(ctx, 'UserPromptSubmit', { userPrompt: prompt });
|
|
72
|
+
}
|
|
73
|
+
/** Fire `PreToolUse` hooks. Never throws. Caller checks `anyBlocked`. */
|
|
74
|
+
export function firePreToolUse(ctx) {
|
|
75
|
+
return fireSafe(ctx, 'PreToolUse', {
|
|
76
|
+
toolName: ctx.toolName,
|
|
77
|
+
toolInput: ctx.toolInput,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/** Fire `PostToolUse` hooks. Never throws. */
|
|
81
|
+
export function firePostToolUse(ctx, toolResult) {
|
|
82
|
+
return fireSafe(ctx, 'PostToolUse', {
|
|
83
|
+
toolName: ctx.toolName,
|
|
84
|
+
toolInput: ctx.toolInput,
|
|
85
|
+
toolResult,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/** Fire `PostToolUseFailure` hooks. Never throws. */
|
|
89
|
+
export function firePostToolUseFailure(ctx, toolError) {
|
|
90
|
+
return fireSafe(ctx, 'PostToolUseFailure', {
|
|
91
|
+
toolName: ctx.toolName,
|
|
92
|
+
toolInput: ctx.toolInput,
|
|
93
|
+
toolError,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** Fire `PreCompact` hooks. Never throws. */
|
|
97
|
+
export function firePreCompact(ctx) {
|
|
98
|
+
return fireSafe(ctx, 'PreCompact');
|
|
99
|
+
}
|
|
100
|
+
/** Fire `PostCompact` hooks. Never throws. */
|
|
101
|
+
export function firePostCompact(ctx) {
|
|
102
|
+
return fireSafe(ctx, 'PostCompact');
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pugi hooks v2 — config loader (Wave 7 Phase 1).
|
|
3
|
+
*
|
|
4
|
+
* Reads two sources and merges them with project-overrides-user
|
|
5
|
+
* precedence:
|
|
6
|
+
*
|
|
7
|
+
* 1. Global user config — `~/.pugi/hooks.json` (or `$PUGI_HOME/hooks.json`)
|
|
8
|
+
* 2. Project config — `<workspaceRoot>/.pugi/hooks.json`
|
|
9
|
+
*
|
|
10
|
+
* Merge rule: the project file's hooks are CONCATENATED after the
|
|
11
|
+
* global file's hooks. When the runner walks the merged list, project
|
|
12
|
+
* hooks therefore run AFTER global hooks for the same event. The
|
|
13
|
+
* `precedence` term in the docs refers to overrides via matcher /
|
|
14
|
+
* event-type collision, NOT order-of-evaluation: a project hook on
|
|
15
|
+
* `PreToolUse|bash` shadows a global hook on the same event+matcher
|
|
16
|
+
* because the matcher engine deduplicates within the merged config.
|
|
17
|
+
*
|
|
18
|
+
* Failure modes:
|
|
19
|
+
* - Missing files -> empty entries from that side. Both missing -> the
|
|
20
|
+
* loader returns an empty config (`isEmpty()` true).
|
|
21
|
+
* - Malformed JSON -> the loader throws with the file path in the
|
|
22
|
+
* error so the operator can find + fix it.
|
|
23
|
+
* - Schema violation -> the loader throws with the Zod issue list.
|
|
24
|
+
*
|
|
25
|
+
* Brand voice: ASCII only.
|
|
26
|
+
*/
|
|
27
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
28
|
+
import { homedir } from 'node:os';
|
|
29
|
+
import { resolve } from 'node:path';
|
|
30
|
+
import { z } from 'zod';
|
|
31
|
+
import { DEFAULT_HOOK_TIMEOUT_MS_V2, MAX_HOOK_TIMEOUT_MS_V2, } from './types.js';
|
|
32
|
+
const hookEventEnum = z.enum([
|
|
33
|
+
'SessionStart',
|
|
34
|
+
'SessionEnd',
|
|
35
|
+
'UserPromptSubmit',
|
|
36
|
+
'PreToolUse',
|
|
37
|
+
'PostToolUse',
|
|
38
|
+
'PostToolUseFailure',
|
|
39
|
+
'Stop',
|
|
40
|
+
'PreCompact',
|
|
41
|
+
'PostCompact',
|
|
42
|
+
'Notification',
|
|
43
|
+
'SubagentStart',
|
|
44
|
+
'SubagentStop',
|
|
45
|
+
'SubagentToolUse',
|
|
46
|
+
'PermissionRequest',
|
|
47
|
+
'PermissionGranted',
|
|
48
|
+
'PermissionDenied',
|
|
49
|
+
'PreEdit',
|
|
50
|
+
'PostEdit',
|
|
51
|
+
'PreWrite',
|
|
52
|
+
'PostWrite',
|
|
53
|
+
'PreRead',
|
|
54
|
+
'PostRead',
|
|
55
|
+
'PreBash',
|
|
56
|
+
'PostBash',
|
|
57
|
+
'McpServerConnect',
|
|
58
|
+
'McpServerDisconnect',
|
|
59
|
+
'McpToolCall',
|
|
60
|
+
'McpToolResult',
|
|
61
|
+
'AgentSpawn',
|
|
62
|
+
'AgentComplete',
|
|
63
|
+
'TaskCompleted',
|
|
64
|
+
'PromptInjection',
|
|
65
|
+
]);
|
|
66
|
+
const hookTypeEnum = z.enum(['command', 'http', 'mcp_tool', 'prompt', 'agent']);
|
|
67
|
+
const hookConfigSchema = z
|
|
68
|
+
.object({
|
|
69
|
+
event: hookEventEnum,
|
|
70
|
+
matcher: z.string().min(1),
|
|
71
|
+
type: hookTypeEnum,
|
|
72
|
+
command: z.string().min(1).optional(),
|
|
73
|
+
timeoutMs: z
|
|
74
|
+
.number()
|
|
75
|
+
.int()
|
|
76
|
+
.positive()
|
|
77
|
+
.max(MAX_HOOK_TIMEOUT_MS_V2)
|
|
78
|
+
.optional(),
|
|
79
|
+
description: z.string().min(1).max(200).optional(),
|
|
80
|
+
})
|
|
81
|
+
.strict()
|
|
82
|
+
.superRefine((entry, ctx) => {
|
|
83
|
+
if (entry.type === 'command' && !entry.command) {
|
|
84
|
+
ctx.addIssue({
|
|
85
|
+
code: z.ZodIssueCode.custom,
|
|
86
|
+
path: ['command'],
|
|
87
|
+
message: '`command` is required when `type === "command"`',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
const hooksFileSchema = z
|
|
92
|
+
.object({
|
|
93
|
+
version: z.literal(1),
|
|
94
|
+
hooks: z.array(hookConfigSchema).default([]),
|
|
95
|
+
})
|
|
96
|
+
.strict();
|
|
97
|
+
/** Default global config location: `~/.pugi/hooks.json` (PUGI_HOME-aware). */
|
|
98
|
+
export function defaultGlobalHooksPath(home) {
|
|
99
|
+
const root = home ?? process.env.PUGI_HOME ?? resolve(homedir(), '.pugi');
|
|
100
|
+
return resolve(root, 'hooks.json');
|
|
101
|
+
}
|
|
102
|
+
/** Default project config location: `<workspaceRoot>/.pugi/hooks.json`. */
|
|
103
|
+
export function defaultProjectHooksPath(workspaceRoot) {
|
|
104
|
+
return resolve(workspaceRoot, '.pugi', 'hooks.json');
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Immutable snapshot of the merged global + project config. Holds the
|
|
108
|
+
* source paths so audit + doctor surfaces can show "where did this come
|
|
109
|
+
* from".
|
|
110
|
+
*/
|
|
111
|
+
export class HooksConfigV2 {
|
|
112
|
+
globalPath;
|
|
113
|
+
projectPath;
|
|
114
|
+
globalHooks;
|
|
115
|
+
projectHooks;
|
|
116
|
+
constructor(globalPath, projectPath, globalHooks, projectHooks) {
|
|
117
|
+
this.globalPath = globalPath;
|
|
118
|
+
this.projectPath = projectPath;
|
|
119
|
+
this.globalHooks = globalHooks;
|
|
120
|
+
this.projectHooks = projectHooks;
|
|
121
|
+
}
|
|
122
|
+
/** Path of the global config file (may not exist on disk). */
|
|
123
|
+
globalConfigPath() {
|
|
124
|
+
return this.globalPath;
|
|
125
|
+
}
|
|
126
|
+
/** Path of the project config file (may not exist on disk). */
|
|
127
|
+
projectConfigPath() {
|
|
128
|
+
return this.projectPath;
|
|
129
|
+
}
|
|
130
|
+
/** All declared hooks (global first, project second). */
|
|
131
|
+
all() {
|
|
132
|
+
return [...this.globalHooks, ...this.projectHooks];
|
|
133
|
+
}
|
|
134
|
+
/** Hooks declared in the project config only. */
|
|
135
|
+
projectOnly() {
|
|
136
|
+
return [...this.projectHooks];
|
|
137
|
+
}
|
|
138
|
+
/** Hooks declared in the global config only. */
|
|
139
|
+
globalOnly() {
|
|
140
|
+
return [...this.globalHooks];
|
|
141
|
+
}
|
|
142
|
+
/** All hooks declared for a given event, project AFTER global. */
|
|
143
|
+
forEvent(event) {
|
|
144
|
+
return this.all().filter((h) => h.event === event);
|
|
145
|
+
}
|
|
146
|
+
/** True iff no hooks are configured. */
|
|
147
|
+
isEmpty() {
|
|
148
|
+
return this.globalHooks.length === 0 && this.projectHooks.length === 0;
|
|
149
|
+
}
|
|
150
|
+
/** Empty snapshot for when both files are absent. */
|
|
151
|
+
static empty(globalPath, projectPath) {
|
|
152
|
+
return new HooksConfigV2(globalPath, projectPath, [], []);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Load + validate the merged hooks config. Returns an empty config
|
|
157
|
+
* when neither file exists. Throws on JSON / schema errors with the
|
|
158
|
+
* source path in the message.
|
|
159
|
+
*
|
|
160
|
+
* Non-null invariant: returns a `HooksConfigV2` instance in all cases.
|
|
161
|
+
* Callers may chain `.isEmpty()` without a guard.
|
|
162
|
+
*/
|
|
163
|
+
export function loadHooksConfigV2(opts) {
|
|
164
|
+
const globalPath = opts.globalPathOverride ?? defaultGlobalHooksPath();
|
|
165
|
+
const projectPath = opts.projectPathOverride ?? defaultProjectHooksPath(opts.workspaceRoot);
|
|
166
|
+
const globalHooks = readOne(globalPath);
|
|
167
|
+
const projectHooks = readOne(projectPath);
|
|
168
|
+
return new HooksConfigV2(globalPath, projectPath, globalHooks, projectHooks);
|
|
169
|
+
}
|
|
170
|
+
function readOne(path) {
|
|
171
|
+
if (!existsSync(path)) {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
let raw;
|
|
175
|
+
try {
|
|
176
|
+
raw = readFileSync(path, 'utf8');
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
throw new Error(`pugi hooks v2: cannot read ${path}: ${error.message}`);
|
|
180
|
+
}
|
|
181
|
+
if (raw.trim() === '') {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
let parsed;
|
|
185
|
+
try {
|
|
186
|
+
parsed = JSON.parse(raw);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
throw new Error(`pugi hooks v2: ${path} is not valid JSON: ${error.message}`);
|
|
190
|
+
}
|
|
191
|
+
const result = hooksFileSchema.safeParse(parsed);
|
|
192
|
+
if (!result.success) {
|
|
193
|
+
const issues = result.error.issues
|
|
194
|
+
.map((issue) => `${issue.path.join('.') || '<root>'} ${issue.message}`)
|
|
195
|
+
.join('; ');
|
|
196
|
+
throw new Error(`pugi hooks v2: ${path} failed schema validation: ${issues}`);
|
|
197
|
+
}
|
|
198
|
+
return materialize(result.data);
|
|
199
|
+
}
|
|
200
|
+
function materialize(data) {
|
|
201
|
+
// Apply the default timeout to every entry so downstream code can
|
|
202
|
+
// assume `timeoutMs` is always set.
|
|
203
|
+
return data.hooks.map((entry) => ({
|
|
204
|
+
...entry,
|
|
205
|
+
timeoutMs: entry.timeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS_V2,
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
/** Re-exports for callers that want the literal enum values. */
|
|
209
|
+
export const SUPPORTED_HOOK_TYPES_V2 = [
|
|
210
|
+
'command',
|
|
211
|
+
'http',
|
|
212
|
+
'mcp_tool',
|
|
213
|
+
'prompt',
|
|
214
|
+
'agent',
|
|
215
|
+
];
|
|
216
|
+
//# sourceMappingURL=loader.js.map
|