@fml-inc/fml 0.7.0 → 0.8.0-rc.2
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/.claude-plugin/plugin.json +2 -4
- package/README.md +323 -79
- package/agent-assets/fml/SKILL.md +180 -0
- package/bin/fml +4 -18
- package/bin/hook-handler +7 -0
- package/bin/mcp-server +4 -18
- package/bin/observer +6 -0
- package/dist/chunk-3KISRT57.js +140 -0
- package/dist/chunk-56TPYMNK.js +4657 -0
- package/dist/chunk-7V7G7NVF.js +142 -0
- package/dist/chunk-A6HAEVJX.js +1606 -0
- package/dist/chunk-DZKYW327.js +1037 -0
- package/dist/chunk-ENW2GJNO.js +109 -0
- package/dist/{chunk-PZ5AY32C.js → chunk-MLKGABMK.js} +0 -1
- package/dist/{chunk-PTUZMKSN.js → chunk-R45CWWZO.js} +34 -23
- package/dist/chunk-WZLLO563.js +953 -0
- package/dist/chunk-YSU5CS2V.js +3331 -0
- package/dist/cli.js +7742 -1072
- package/dist/{esm-2EUDFZWP.js → esm-4YKLKFRX.js} +546 -741
- package/dist/hook-handler.js +440 -0
- package/dist/mcp/server.js +112 -98
- package/dist/observer-server.js +1566 -0
- package/dist/sync-cycle.js +31 -0
- package/dist/{token-store-3IQSLOTQ.js → token-store-BT5THIAJ.js} +10 -3
- package/hooks/hooks.json +290 -0
- package/package.json +24 -27
- package/dist/chunk-PTUZMKSN.js.map +0 -1
- package/dist/chunk-PZ5AY32C.js.map +0 -1
- package/dist/chunk-S4TJNLEN.js +0 -61
- package/dist/chunk-S4TJNLEN.js.map +0 -1
- package/dist/chunk-VV4JHH4H.js +0 -437
- package/dist/chunk-VV4JHH4H.js.map +0 -1
- package/dist/chunk-YH5ROR7G.js +0 -69
- package/dist/chunk-YH5ROR7G.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/daemon-MSLDHLMH.js +0 -21
- package/dist/daemon-MSLDHLMH.js.map +0 -1
- package/dist/daemon-utils-OODBIYB7.js +0 -10
- package/dist/daemon-utils-OODBIYB7.js.map +0 -1
- package/dist/esm-2EUDFZWP.js.map +0 -1
- package/dist/mcp/server.js.map +0 -1
- package/dist/postinstall.js +0 -16
- package/dist/postinstall.js.map +0 -1
- package/dist/token-store-3IQSLOTQ.js.map +0 -1
- package/scripts/postinstall.cjs +0 -25
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
hookCaptureIsDisabled
|
|
4
|
+
} from "./chunk-ENW2GJNO.js";
|
|
5
|
+
import {
|
|
6
|
+
getActiveHookDestinationBinding
|
|
7
|
+
} from "./chunk-DZKYW327.js";
|
|
8
|
+
import {
|
|
9
|
+
HOOK_TARGETS,
|
|
10
|
+
SHELL_TOOL_NAMES,
|
|
11
|
+
createHookInboxEntry,
|
|
12
|
+
hookInboxDeliveryPayload,
|
|
13
|
+
normalizeObserverHookCapture,
|
|
14
|
+
persistHookInboxEntry,
|
|
15
|
+
readObserverAuthToken
|
|
16
|
+
} from "./chunk-56TPYMNK.js";
|
|
17
|
+
import {
|
|
18
|
+
resolveRepositoryRootFromCwd,
|
|
19
|
+
resolveRepositorySlugFromCwd
|
|
20
|
+
} from "./chunk-7V7G7NVF.js";
|
|
21
|
+
import {
|
|
22
|
+
FML_HOOK_CONTEXT_STATE_DIR,
|
|
23
|
+
FML_HOOK_INBOX_DIR,
|
|
24
|
+
FML_OBSERVER_AUTH_TOKEN_FILE,
|
|
25
|
+
FML_OBSERVER_ENDPOINT_FILE,
|
|
26
|
+
getValidToken
|
|
27
|
+
} from "./chunk-A6HAEVJX.js";
|
|
28
|
+
import "./chunk-MLKGABMK.js";
|
|
29
|
+
|
|
30
|
+
// src/hooks/handler.ts
|
|
31
|
+
import fs2 from "fs";
|
|
32
|
+
|
|
33
|
+
// src/hooks/context.ts
|
|
34
|
+
import { createHash } from "crypto";
|
|
35
|
+
import fs from "fs";
|
|
36
|
+
import path from "path";
|
|
37
|
+
var HOOK_CONTEXT_ENDPOINT = "/v1/panopticon/hook-context";
|
|
38
|
+
var HOOK_CONTEXT_TIMEOUT_MS = 2e3;
|
|
39
|
+
var MAX_ADDITIONAL_CONTEXT_CHARS = 4e3;
|
|
40
|
+
var MAX_PROMPT_CHARS = 2e4;
|
|
41
|
+
var STATE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
42
|
+
var EDIT_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
43
|
+
"apply_patch",
|
|
44
|
+
"edit",
|
|
45
|
+
"multiedit",
|
|
46
|
+
"notebookedit",
|
|
47
|
+
"write",
|
|
48
|
+
"write_file"
|
|
49
|
+
]);
|
|
50
|
+
var READ_TOOL_NAMES = /* @__PURE__ */ new Set(["read", "read_file"]);
|
|
51
|
+
function sameDestination(left, right) {
|
|
52
|
+
return left.environment === right.environment && left.targetUrl === right.targetUrl && left.destinationIdentity === right.destinationIdentity;
|
|
53
|
+
}
|
|
54
|
+
function markerPath(stateDir, kind, value) {
|
|
55
|
+
const digest = createHash("sha256").update(value).digest("hex");
|
|
56
|
+
return path.join(stateDir, kind, digest);
|
|
57
|
+
}
|
|
58
|
+
function ensureMarkerDirectory(filePath) {
|
|
59
|
+
try {
|
|
60
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 448 });
|
|
61
|
+
return true;
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function isSubsequentUserPrompt(sessionId, eventSyncId, stateDir = FML_HOOK_CONTEXT_STATE_DIR) {
|
|
67
|
+
const filePath = markerPath(stateDir, "prompt", sessionId);
|
|
68
|
+
if (!ensureMarkerDirectory(filePath)) return false;
|
|
69
|
+
try {
|
|
70
|
+
fs.writeFileSync(filePath, eventSyncId, { flag: "wx", mode: 384 });
|
|
71
|
+
return false;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error.code !== "EEXIST") return false;
|
|
74
|
+
try {
|
|
75
|
+
return fs.readFileSync(filePath, "utf8") !== eventSyncId;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function pathContextWasEmitted(sessionId, filePath, stateDir) {
|
|
82
|
+
try {
|
|
83
|
+
return fs.existsSync(
|
|
84
|
+
markerPath(stateDir, "path", `${sessionId}:${filePath}`)
|
|
85
|
+
);
|
|
86
|
+
} catch {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function claimPathContext(sessionId, filePath, stateDir) {
|
|
91
|
+
const claim = markerPath(stateDir, "path", `${sessionId}:${filePath}`);
|
|
92
|
+
if (!ensureMarkerDirectory(claim)) return false;
|
|
93
|
+
try {
|
|
94
|
+
fs.writeFileSync(claim, "", { flag: "wx", mode: 384 });
|
|
95
|
+
return true;
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function pruneOldMarkers(directory, nowMs) {
|
|
101
|
+
if ((nowMs + process.pid) % 256 !== 0) return;
|
|
102
|
+
for (const kind of ["prompt", "path"]) {
|
|
103
|
+
const markerDirectory = path.join(directory, kind);
|
|
104
|
+
let names;
|
|
105
|
+
try {
|
|
106
|
+
names = fs.readdirSync(markerDirectory).slice(0, 1e3);
|
|
107
|
+
} catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
for (const name of names) {
|
|
111
|
+
const filePath = path.join(markerDirectory, name);
|
|
112
|
+
try {
|
|
113
|
+
if (nowMs - fs.statSync(filePath).mtimeMs > STATE_MAX_AGE_MS) {
|
|
114
|
+
fs.rmSync(filePath, { force: true });
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function canonicalFilePath(filePath, cwd, repositoryRoot) {
|
|
122
|
+
const windows = [filePath, cwd, repositoryRoot].some(
|
|
123
|
+
(value) => value !== void 0 && value !== null && /^[A-Za-z]:[\\/]/.test(value)
|
|
124
|
+
);
|
|
125
|
+
const pathApi = windows ? path.win32 : path.posix;
|
|
126
|
+
const normalized = pathApi.normalize(filePath);
|
|
127
|
+
const resolved = pathApi.isAbsolute(normalized) ? normalized : cwd && pathApi.isAbsolute(cwd) ? pathApi.resolve(cwd, normalized) : normalized;
|
|
128
|
+
if (repositoryRoot && pathApi.isAbsolute(resolved)) {
|
|
129
|
+
const relative2 = pathApi.relative(
|
|
130
|
+
pathApi.normalize(repositoryRoot),
|
|
131
|
+
resolved
|
|
132
|
+
);
|
|
133
|
+
if (relative2 && relative2 !== ".." && !relative2.startsWith(`..${pathApi.sep}`) && !pathApi.isAbsolute(relative2)) {
|
|
134
|
+
return relative2.replace(/\\/g, "/");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (!pathApi.isAbsolute(resolved)) {
|
|
138
|
+
return resolved.replace(/^[.][\\/]/, "").replace(/\\/g, "/");
|
|
139
|
+
}
|
|
140
|
+
if (!cwd || pathApi.isAbsolute(cwd) !== pathApi.isAbsolute(resolved)) {
|
|
141
|
+
return resolved.replace(/\\/g, "/");
|
|
142
|
+
}
|
|
143
|
+
const relative = pathApi.relative(pathApi.normalize(cwd), resolved);
|
|
144
|
+
if (relative && relative !== ".." && !relative.startsWith(`..${pathApi.sep}`) && !pathApi.isAbsolute(relative)) {
|
|
145
|
+
return relative.replace(/\\/g, "/");
|
|
146
|
+
}
|
|
147
|
+
return resolved.replace(/\\/g, "/");
|
|
148
|
+
}
|
|
149
|
+
function buildServerHookContextRequest(target2, event, stateDir = FML_HOOK_CONTEXT_STATE_DIR, repositoryRoot = null) {
|
|
150
|
+
if (target2 !== "claude" && target2 !== "codex") return null;
|
|
151
|
+
if (!event.repository) return null;
|
|
152
|
+
const markerSessionId = `${target2}:${event.sessionId}`;
|
|
153
|
+
const base = {
|
|
154
|
+
repository: event.repository,
|
|
155
|
+
sessionId: event.sessionId,
|
|
156
|
+
nowMs: event.timestampMs
|
|
157
|
+
};
|
|
158
|
+
if (event.eventType === "SessionStart") {
|
|
159
|
+
return { ...base, eventType: "SessionStart" };
|
|
160
|
+
}
|
|
161
|
+
if (event.eventType === "UserPromptSubmit" && event.userPrompt) {
|
|
162
|
+
if (!isSubsequentUserPrompt(markerSessionId, event.syncId, stateDir)) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
...base,
|
|
167
|
+
eventType: "UserPromptSubmit",
|
|
168
|
+
prompt: event.userPrompt.slice(0, MAX_PROMPT_CHARS)
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (event.eventType !== "PreToolUse" || !event.toolName) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const toolName = event.toolName.toLowerCase();
|
|
175
|
+
const fileOperation = EDIT_TOOL_NAMES.has(toolName) ? "edit" : READ_TOOL_NAMES.has(toolName) || SHELL_TOOL_NAMES.has(toolName) ? "read" : null;
|
|
176
|
+
if (!fileOperation) return null;
|
|
177
|
+
if (!event.filePath) return null;
|
|
178
|
+
const filePath = canonicalFilePath(event.filePath, event.cwd, repositoryRoot);
|
|
179
|
+
if (pathContextWasEmitted(
|
|
180
|
+
markerSessionId,
|
|
181
|
+
`${event.repository}:${filePath}`,
|
|
182
|
+
stateDir
|
|
183
|
+
)) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
return { ...base, eventType: "PreToolUse", filePath, fileOperation };
|
|
187
|
+
}
|
|
188
|
+
function hookOutput(request, additionalContext) {
|
|
189
|
+
return {
|
|
190
|
+
hookSpecificOutput: {
|
|
191
|
+
hookEventName: request.eventType,
|
|
192
|
+
additionalContext
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
async function requestServerHookContext(target2, event, destination, opts = {}) {
|
|
197
|
+
const deadlineSignal = AbortSignal.timeout(
|
|
198
|
+
opts.timeoutMs ?? HOOK_CONTEXT_TIMEOUT_MS
|
|
199
|
+
);
|
|
200
|
+
const stateDir = opts.stateDir ?? FML_HOOK_CONTEXT_STATE_DIR;
|
|
201
|
+
pruneOldMarkers(stateDir, Date.now());
|
|
202
|
+
const repositoryRoot = event.eventType === "PreToolUse" && event.cwd ? (opts.resolveRepositoryRoot ?? ((cwd) => resolveRepositoryRootFromCwd(cwd, 250)))(event.cwd) : null;
|
|
203
|
+
const request = buildServerHookContextRequest(
|
|
204
|
+
target2,
|
|
205
|
+
event,
|
|
206
|
+
stateDir,
|
|
207
|
+
repositoryRoot
|
|
208
|
+
);
|
|
209
|
+
if (!request) return {};
|
|
210
|
+
try {
|
|
211
|
+
const token = await getValidToken({
|
|
212
|
+
env: destination.environment,
|
|
213
|
+
signal: deadlineSignal
|
|
214
|
+
});
|
|
215
|
+
if (!token) return {};
|
|
216
|
+
const currentDestination = getActiveHookDestinationBinding();
|
|
217
|
+
if (!currentDestination || !sameDestination(destination, currentDestination)) {
|
|
218
|
+
return {};
|
|
219
|
+
}
|
|
220
|
+
const response = await (opts.fetch ?? fetch)(
|
|
221
|
+
`${destination.targetUrl.replace(/\/+$/, "")}${HOOK_CONTEXT_ENDPOINT}`,
|
|
222
|
+
{
|
|
223
|
+
method: "POST",
|
|
224
|
+
headers: {
|
|
225
|
+
Authorization: token.startsWith("Bearer ") ? token : `Bearer ${token}`,
|
|
226
|
+
"Content-Type": "application/json"
|
|
227
|
+
},
|
|
228
|
+
body: JSON.stringify(request),
|
|
229
|
+
signal: deadlineSignal
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
if (!response.ok) return {};
|
|
233
|
+
const body = await response.json();
|
|
234
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return {};
|
|
235
|
+
const additionalContext = body.additionalContext;
|
|
236
|
+
if (request.eventType === "PreToolUse" && request.filePath && additionalContext === null) {
|
|
237
|
+
claimPathContext(
|
|
238
|
+
`${target2}:${request.sessionId}`,
|
|
239
|
+
`${request.repository}:${request.filePath}`,
|
|
240
|
+
stateDir
|
|
241
|
+
);
|
|
242
|
+
return {};
|
|
243
|
+
}
|
|
244
|
+
if (typeof additionalContext !== "string" || additionalContext.length === 0 || additionalContext.length > MAX_ADDITIONAL_CONTEXT_CHARS) {
|
|
245
|
+
return {};
|
|
246
|
+
}
|
|
247
|
+
if (request.eventType === "PreToolUse" && request.filePath && !claimPathContext(
|
|
248
|
+
`${target2}:${request.sessionId}`,
|
|
249
|
+
`${request.repository}:${request.filePath}`,
|
|
250
|
+
stateDir
|
|
251
|
+
)) {
|
|
252
|
+
return {};
|
|
253
|
+
}
|
|
254
|
+
return hookOutput(request, additionalContext);
|
|
255
|
+
} catch {
|
|
256
|
+
return {};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/hooks/handler.ts
|
|
261
|
+
var MAX_HOOK_STDIN_BYTES = 4 * 1024 * 1024;
|
|
262
|
+
var HOOK_REQUEST_TIMEOUT_MS = 500;
|
|
263
|
+
var HOOK_STDIN_TIMEOUT_MS = 5e3;
|
|
264
|
+
var HOOK_REPO_RESOLUTION_TIMEOUT_MS = 250;
|
|
265
|
+
function parseHookTarget(value) {
|
|
266
|
+
return HOOK_TARGETS.includes(value) ? value : null;
|
|
267
|
+
}
|
|
268
|
+
function observerEndpoint(endpointFile) {
|
|
269
|
+
try {
|
|
270
|
+
const parsed = JSON.parse(fs2.readFileSync(endpointFile, "utf8"));
|
|
271
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
const record = parsed;
|
|
275
|
+
if (record.host !== "127.0.0.1" && record.host !== "::1" && record.host !== "localhost" || typeof record.port !== "number" || !Number.isInteger(record.port) || record.port <= 0 || record.port > 65535) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
return { host: record.host, port: record.port };
|
|
279
|
+
} catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async function readBoundedInput(input, timeoutMs) {
|
|
284
|
+
return await new Promise((resolve) => {
|
|
285
|
+
const chunks = [];
|
|
286
|
+
let totalBytes = 0;
|
|
287
|
+
let oversized = false;
|
|
288
|
+
let settled = false;
|
|
289
|
+
const swallowLateError = () => {
|
|
290
|
+
};
|
|
291
|
+
const cleanup = () => {
|
|
292
|
+
clearTimeout(timer);
|
|
293
|
+
input.off("data", onData);
|
|
294
|
+
input.off("end", onEnd);
|
|
295
|
+
input.off("close", onClose);
|
|
296
|
+
input.off("error", onError);
|
|
297
|
+
};
|
|
298
|
+
const finish = (value, drain = false) => {
|
|
299
|
+
if (settled) return;
|
|
300
|
+
settled = true;
|
|
301
|
+
cleanup();
|
|
302
|
+
if (drain) {
|
|
303
|
+
input.on("error", swallowLateError);
|
|
304
|
+
input.resume();
|
|
305
|
+
const unref = input.unref;
|
|
306
|
+
unref?.call(input);
|
|
307
|
+
}
|
|
308
|
+
resolve(value);
|
|
309
|
+
};
|
|
310
|
+
const onData = (chunk) => {
|
|
311
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
312
|
+
totalBytes += bytes.byteLength;
|
|
313
|
+
if (totalBytes > MAX_HOOK_STDIN_BYTES) {
|
|
314
|
+
oversized = true;
|
|
315
|
+
chunks.length = 0;
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (!oversized) chunks.push(bytes);
|
|
319
|
+
};
|
|
320
|
+
const onEnd = () => {
|
|
321
|
+
finish(oversized ? null : Buffer.concat(chunks, totalBytes));
|
|
322
|
+
};
|
|
323
|
+
const onClose = () => finish(null);
|
|
324
|
+
const onError = () => finish(null);
|
|
325
|
+
const timer = setTimeout(() => finish(null, true), timeoutMs);
|
|
326
|
+
input.on("data", onData);
|
|
327
|
+
input.once("end", onEnd);
|
|
328
|
+
input.once("close", onClose);
|
|
329
|
+
input.once("error", onError);
|
|
330
|
+
input.resume();
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
async function processHookHandler(target2, opts, includeContext) {
|
|
334
|
+
const input = opts.input ?? process.stdin;
|
|
335
|
+
const bytes = await readBoundedInput(
|
|
336
|
+
input,
|
|
337
|
+
opts.inputTimeoutMs ?? HOOK_STDIN_TIMEOUT_MS
|
|
338
|
+
);
|
|
339
|
+
if (!bytes || bytes.byteLength === 0) return { captured: false, output: {} };
|
|
340
|
+
let payload;
|
|
341
|
+
try {
|
|
342
|
+
payload = JSON.parse(bytes.toString("utf8"));
|
|
343
|
+
} catch {
|
|
344
|
+
return { captured: false, output: {} };
|
|
345
|
+
}
|
|
346
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
347
|
+
return { captured: false, output: {} };
|
|
348
|
+
}
|
|
349
|
+
if (hookCaptureIsDisabled(opts.captureDisabledFile)) {
|
|
350
|
+
return { captured: false, output: {} };
|
|
351
|
+
}
|
|
352
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
353
|
+
const hookRequest = {
|
|
354
|
+
target: target2,
|
|
355
|
+
payload,
|
|
356
|
+
cwd
|
|
357
|
+
};
|
|
358
|
+
let event = normalizeObserverHookCapture(hookRequest);
|
|
359
|
+
if (event?.cwd) {
|
|
360
|
+
try {
|
|
361
|
+
const repository = (opts.resolveRepository ?? ((directory) => resolveRepositorySlugFromCwd(
|
|
362
|
+
directory,
|
|
363
|
+
HOOK_REPO_RESOLUTION_TIMEOUT_MS
|
|
364
|
+
)))(event.cwd);
|
|
365
|
+
if (repository) event = { ...event, repository };
|
|
366
|
+
} catch {
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const inboxDir = opts.inboxDir ?? FML_HOOK_INBOX_DIR;
|
|
370
|
+
let inboxEntry;
|
|
371
|
+
let persisted = false;
|
|
372
|
+
let destination = null;
|
|
373
|
+
if (event) {
|
|
374
|
+
try {
|
|
375
|
+
destination = getActiveHookDestinationBinding();
|
|
376
|
+
} catch {
|
|
377
|
+
}
|
|
378
|
+
inboxEntry = createHookInboxEntry(
|
|
379
|
+
hookRequest,
|
|
380
|
+
event,
|
|
381
|
+
destination ?? void 0
|
|
382
|
+
);
|
|
383
|
+
try {
|
|
384
|
+
persistHookInboxEntry(inboxEntry, inboxDir, opts.inboxLimits);
|
|
385
|
+
persisted = true;
|
|
386
|
+
} catch {
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const contextPromise = includeContext && event && destination ? opts.requestContext ? opts.requestContext(target2, event, destination) : requestServerHookContext(target2, event, destination, {
|
|
390
|
+
...opts.contextFetch ? { fetch: opts.contextFetch } : {},
|
|
391
|
+
...opts.contextTimeoutMs === void 0 ? {} : { timeoutMs: opts.contextTimeoutMs },
|
|
392
|
+
...opts.contextStateDir ? { stateDir: opts.contextStateDir } : {}
|
|
393
|
+
}) : Promise.resolve({});
|
|
394
|
+
const endpoint = observerEndpoint(
|
|
395
|
+
opts.endpointFile ?? FML_OBSERVER_ENDPOINT_FILE
|
|
396
|
+
);
|
|
397
|
+
const authToken = readObserverAuthToken(
|
|
398
|
+
opts.authTokenFile ?? FML_OBSERVER_AUTH_TOKEN_FILE
|
|
399
|
+
);
|
|
400
|
+
const deliveryPromise = (async () => {
|
|
401
|
+
if (!endpoint || !authToken) return persisted;
|
|
402
|
+
const host = endpoint.host === "::1" ? "[::1]" : endpoint.host;
|
|
403
|
+
const fetchRequest = opts.fetch ?? fetch;
|
|
404
|
+
try {
|
|
405
|
+
const response = await fetchRequest(
|
|
406
|
+
`http://${host}:${endpoint.port}/hooks`,
|
|
407
|
+
{
|
|
408
|
+
method: "POST",
|
|
409
|
+
headers: {
|
|
410
|
+
Authorization: `Bearer ${authToken}`,
|
|
411
|
+
"Content-Type": "application/json"
|
|
412
|
+
},
|
|
413
|
+
body: JSON.stringify(
|
|
414
|
+
inboxEntry ? hookInboxDeliveryPayload(inboxEntry) : hookRequest
|
|
415
|
+
),
|
|
416
|
+
signal: AbortSignal.timeout(
|
|
417
|
+
opts.timeoutMs ?? HOOK_REQUEST_TIMEOUT_MS
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
);
|
|
421
|
+
await response.arrayBuffer();
|
|
422
|
+
return response.ok || persisted;
|
|
423
|
+
} catch {
|
|
424
|
+
return persisted;
|
|
425
|
+
}
|
|
426
|
+
})();
|
|
427
|
+
const [captured, output2] = await Promise.all([
|
|
428
|
+
deliveryPromise,
|
|
429
|
+
contextPromise.catch(() => ({}))
|
|
430
|
+
]);
|
|
431
|
+
return { captured, output: output2 };
|
|
432
|
+
}
|
|
433
|
+
async function runHookHandlerForOutput(target2, opts = {}) {
|
|
434
|
+
return (await processHookHandler(target2, opts, true)).output;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/hook-handler.ts
|
|
438
|
+
var target = parseHookTarget(process.argv[2]);
|
|
439
|
+
var output = target ? await runHookHandlerForOutput(target).catch(() => ({})) : {};
|
|
440
|
+
process.stdout.write(JSON.stringify(output));
|