@fml-inc/fml 0.8.0-rc.1 → 0.8.0-rc.3
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 +1 -1
- package/README.md +29 -16
- package/dist/{chunk-CK5MEL5J.js → chunk-3KISRT57.js} +1 -1
- package/dist/{chunk-HUR2HCUG.js → chunk-7DWFJENY.js} +2 -2
- package/dist/{chunk-T5YVLSUC.js → chunk-7V7G7NVF.js} +4 -0
- package/dist/{chunk-7DR5X7XI.js → chunk-A6HAEVJX.js} +136 -52
- package/dist/{chunk-F66IBM3Z.js → chunk-DZKYW327.js} +2 -2
- package/dist/{chunk-I4GZPTBM.js → chunk-ENW2GJNO.js} +1 -1
- package/dist/{chunk-DEMVOVVY.js → chunk-KBT5RMAU.js} +263 -32
- package/dist/{chunk-P3QLARXE.js → chunk-R45CWWZO.js} +3 -3
- package/dist/{chunk-JXSSPQ5B.js → chunk-YSU5CS2V.js} +1 -1
- package/dist/cli.js +1708 -1351
- package/dist/hook-handler.js +299 -42
- package/dist/mcp/server.js +7 -7
- package/dist/observer-server.js +8 -8
- package/dist/pi/extension.js +350 -0
- package/dist/sync-cycle.js +3 -3
- package/dist/{token-store-R7WXEPLI.js → token-store-BT5THIAJ.js} +1 -1
- package/package.json +6 -4
package/dist/hook-handler.js
CHANGED
|
@@ -1,40 +1,280 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
hookCaptureIsDisabled
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ENW2GJNO.js";
|
|
5
5
|
import {
|
|
6
6
|
getActiveHookDestinationBinding
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-DZKYW327.js";
|
|
8
8
|
import {
|
|
9
|
-
|
|
9
|
+
SHELL_TOOL_NAMES,
|
|
10
10
|
createHookInboxEntry,
|
|
11
11
|
hookInboxDeliveryPayload,
|
|
12
12
|
normalizeObserverHookCapture,
|
|
13
13
|
persistHookInboxEntry,
|
|
14
14
|
readObserverAuthToken
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-KBT5RMAU.js";
|
|
16
16
|
import {
|
|
17
|
+
resolveRepositoryRootFromCwd,
|
|
17
18
|
resolveRepositorySlugFromCwd
|
|
18
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-7V7G7NVF.js";
|
|
19
20
|
import {
|
|
21
|
+
FML_HOOK_CONTEXT_STATE_DIR,
|
|
20
22
|
FML_HOOK_INBOX_DIR,
|
|
21
23
|
FML_OBSERVER_AUTH_TOKEN_FILE,
|
|
22
|
-
FML_OBSERVER_ENDPOINT_FILE
|
|
23
|
-
|
|
24
|
+
FML_OBSERVER_ENDPOINT_FILE,
|
|
25
|
+
getValidToken
|
|
26
|
+
} from "./chunk-A6HAEVJX.js";
|
|
24
27
|
import "./chunk-MLKGABMK.js";
|
|
25
28
|
|
|
26
29
|
// src/hooks/handler.ts
|
|
30
|
+
import fs2 from "fs";
|
|
31
|
+
|
|
32
|
+
// src/hooks/context.ts
|
|
33
|
+
import { createHash } from "crypto";
|
|
27
34
|
import fs from "fs";
|
|
35
|
+
import path from "path";
|
|
36
|
+
var HOOK_CONTEXT_ENDPOINT = "/v1/panopticon/hook-context";
|
|
37
|
+
var HOOK_CONTEXT_TIMEOUT_MS = 2e3;
|
|
38
|
+
var MAX_ADDITIONAL_CONTEXT_CHARS = 4e3;
|
|
39
|
+
var MAX_PROMPT_CHARS = 2e4;
|
|
40
|
+
var STATE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
41
|
+
var EDIT_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
42
|
+
"apply_patch",
|
|
43
|
+
"edit",
|
|
44
|
+
"multiedit",
|
|
45
|
+
"notebookedit",
|
|
46
|
+
"write",
|
|
47
|
+
"write_file"
|
|
48
|
+
]);
|
|
49
|
+
var READ_TOOL_NAMES = /* @__PURE__ */ new Set(["read", "read_file"]);
|
|
50
|
+
function sameDestination(left, right) {
|
|
51
|
+
return left.environment === right.environment && left.targetUrl === right.targetUrl && left.destinationIdentity === right.destinationIdentity;
|
|
52
|
+
}
|
|
53
|
+
function markerPath(stateDir, kind, value) {
|
|
54
|
+
const digest = createHash("sha256").update(value).digest("hex");
|
|
55
|
+
return path.join(stateDir, kind, digest);
|
|
56
|
+
}
|
|
57
|
+
function ensureMarkerDirectory(filePath) {
|
|
58
|
+
try {
|
|
59
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 448 });
|
|
60
|
+
return true;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function isSubsequentUserPrompt(sessionId, eventSyncId, stateDir = FML_HOOK_CONTEXT_STATE_DIR) {
|
|
66
|
+
const filePath = markerPath(stateDir, "prompt", sessionId);
|
|
67
|
+
if (!ensureMarkerDirectory(filePath)) return false;
|
|
68
|
+
try {
|
|
69
|
+
fs.writeFileSync(filePath, eventSyncId, { flag: "wx", mode: 384 });
|
|
70
|
+
return false;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (error.code !== "EEXIST") return false;
|
|
73
|
+
try {
|
|
74
|
+
return fs.readFileSync(filePath, "utf8") !== eventSyncId;
|
|
75
|
+
} catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function pathContextWasEmitted(sessionId, filePath, stateDir) {
|
|
81
|
+
try {
|
|
82
|
+
return fs.existsSync(
|
|
83
|
+
markerPath(stateDir, "path", `${sessionId}:${filePath}`)
|
|
84
|
+
);
|
|
85
|
+
} catch {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function claimPathContext(sessionId, filePath, stateDir) {
|
|
90
|
+
const claim = markerPath(stateDir, "path", `${sessionId}:${filePath}`);
|
|
91
|
+
if (!ensureMarkerDirectory(claim)) return false;
|
|
92
|
+
try {
|
|
93
|
+
fs.writeFileSync(claim, "", { flag: "wx", mode: 384 });
|
|
94
|
+
return true;
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function pruneOldMarkers(directory, nowMs) {
|
|
100
|
+
if ((nowMs + process.pid) % 256 !== 0) return;
|
|
101
|
+
for (const kind of ["prompt", "path"]) {
|
|
102
|
+
const markerDirectory = path.join(directory, kind);
|
|
103
|
+
let names;
|
|
104
|
+
try {
|
|
105
|
+
names = fs.readdirSync(markerDirectory).slice(0, 1e3);
|
|
106
|
+
} catch {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
for (const name of names) {
|
|
110
|
+
const filePath = path.join(markerDirectory, name);
|
|
111
|
+
try {
|
|
112
|
+
if (nowMs - fs.statSync(filePath).mtimeMs > STATE_MAX_AGE_MS) {
|
|
113
|
+
fs.rmSync(filePath, { force: true });
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function canonicalFilePath(filePath, cwd, repositoryRoot) {
|
|
121
|
+
const windows = [filePath, cwd, repositoryRoot].some(
|
|
122
|
+
(value) => value !== void 0 && value !== null && /^[A-Za-z]:[\\/]/.test(value)
|
|
123
|
+
);
|
|
124
|
+
const pathApi = windows ? path.win32 : path.posix;
|
|
125
|
+
const normalized = pathApi.normalize(filePath);
|
|
126
|
+
const resolved = pathApi.isAbsolute(normalized) ? normalized : cwd && pathApi.isAbsolute(cwd) ? pathApi.resolve(cwd, normalized) : normalized;
|
|
127
|
+
if (repositoryRoot && pathApi.isAbsolute(resolved)) {
|
|
128
|
+
const relative2 = pathApi.relative(
|
|
129
|
+
pathApi.normalize(repositoryRoot),
|
|
130
|
+
resolved
|
|
131
|
+
);
|
|
132
|
+
if (relative2 && relative2 !== ".." && !relative2.startsWith(`..${pathApi.sep}`) && !pathApi.isAbsolute(relative2)) {
|
|
133
|
+
return relative2.replace(/\\/g, "/");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (!pathApi.isAbsolute(resolved)) {
|
|
137
|
+
return resolved.replace(/^[.][\\/]/, "").replace(/\\/g, "/");
|
|
138
|
+
}
|
|
139
|
+
if (!cwd || pathApi.isAbsolute(cwd) !== pathApi.isAbsolute(resolved)) {
|
|
140
|
+
return resolved.replace(/\\/g, "/");
|
|
141
|
+
}
|
|
142
|
+
const relative = pathApi.relative(pathApi.normalize(cwd), resolved);
|
|
143
|
+
if (relative && relative !== ".." && !relative.startsWith(`..${pathApi.sep}`) && !pathApi.isAbsolute(relative)) {
|
|
144
|
+
return relative.replace(/\\/g, "/");
|
|
145
|
+
}
|
|
146
|
+
return resolved.replace(/\\/g, "/");
|
|
147
|
+
}
|
|
148
|
+
function buildServerHookContextRequest(target2, event, stateDir = FML_HOOK_CONTEXT_STATE_DIR, repositoryRoot = null) {
|
|
149
|
+
if (target2 !== "claude" && target2 !== "codex") return null;
|
|
150
|
+
if (!event.repository) return null;
|
|
151
|
+
const markerSessionId = `${target2}:${event.sessionId}`;
|
|
152
|
+
const base = {
|
|
153
|
+
repository: event.repository,
|
|
154
|
+
sessionId: event.sessionId,
|
|
155
|
+
nowMs: event.timestampMs
|
|
156
|
+
};
|
|
157
|
+
if (event.eventType === "SessionStart") {
|
|
158
|
+
return { ...base, eventType: "SessionStart" };
|
|
159
|
+
}
|
|
160
|
+
if (event.eventType === "UserPromptSubmit" && event.userPrompt) {
|
|
161
|
+
if (!isSubsequentUserPrompt(markerSessionId, event.syncId, stateDir)) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
...base,
|
|
166
|
+
eventType: "UserPromptSubmit",
|
|
167
|
+
prompt: event.userPrompt.slice(0, MAX_PROMPT_CHARS)
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (event.eventType !== "PreToolUse" || !event.toolName) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const toolName = event.toolName.toLowerCase();
|
|
174
|
+
const fileOperation = EDIT_TOOL_NAMES.has(toolName) ? "edit" : READ_TOOL_NAMES.has(toolName) || SHELL_TOOL_NAMES.has(toolName) ? "read" : null;
|
|
175
|
+
if (!fileOperation) return null;
|
|
176
|
+
if (!event.filePath) return null;
|
|
177
|
+
const filePath = canonicalFilePath(event.filePath, event.cwd, repositoryRoot);
|
|
178
|
+
if (pathContextWasEmitted(
|
|
179
|
+
markerSessionId,
|
|
180
|
+
`${event.repository}:${filePath}`,
|
|
181
|
+
stateDir
|
|
182
|
+
)) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
return { ...base, eventType: "PreToolUse", filePath, fileOperation };
|
|
186
|
+
}
|
|
187
|
+
function hookOutput(request, additionalContext) {
|
|
188
|
+
return {
|
|
189
|
+
hookSpecificOutput: {
|
|
190
|
+
hookEventName: request.eventType,
|
|
191
|
+
additionalContext
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
async function requestServerHookContext(target2, event, destination, opts = {}) {
|
|
196
|
+
const deadlineSignal = AbortSignal.timeout(
|
|
197
|
+
opts.timeoutMs ?? HOOK_CONTEXT_TIMEOUT_MS
|
|
198
|
+
);
|
|
199
|
+
const stateDir = opts.stateDir ?? FML_HOOK_CONTEXT_STATE_DIR;
|
|
200
|
+
pruneOldMarkers(stateDir, Date.now());
|
|
201
|
+
const repositoryRoot = event.eventType === "PreToolUse" && event.cwd ? (opts.resolveRepositoryRoot ?? ((cwd) => resolveRepositoryRootFromCwd(cwd, 250)))(event.cwd) : null;
|
|
202
|
+
const request = buildServerHookContextRequest(
|
|
203
|
+
target2,
|
|
204
|
+
event,
|
|
205
|
+
stateDir,
|
|
206
|
+
repositoryRoot
|
|
207
|
+
);
|
|
208
|
+
if (!request) return {};
|
|
209
|
+
try {
|
|
210
|
+
const token = await getValidToken({
|
|
211
|
+
env: destination.environment,
|
|
212
|
+
signal: deadlineSignal
|
|
213
|
+
});
|
|
214
|
+
if (!token) return {};
|
|
215
|
+
const currentDestination = getActiveHookDestinationBinding();
|
|
216
|
+
if (!currentDestination || !sameDestination(destination, currentDestination)) {
|
|
217
|
+
return {};
|
|
218
|
+
}
|
|
219
|
+
const response = await (opts.fetch ?? fetch)(
|
|
220
|
+
`${destination.targetUrl.replace(/\/+$/, "")}${HOOK_CONTEXT_ENDPOINT}`,
|
|
221
|
+
{
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: {
|
|
224
|
+
Authorization: token.startsWith("Bearer ") ? token : `Bearer ${token}`,
|
|
225
|
+
"Content-Type": "application/json"
|
|
226
|
+
},
|
|
227
|
+
body: JSON.stringify(request),
|
|
228
|
+
signal: deadlineSignal
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
if (!response.ok) return {};
|
|
232
|
+
const body = await response.json();
|
|
233
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return {};
|
|
234
|
+
const additionalContext = body.additionalContext;
|
|
235
|
+
if (request.eventType === "PreToolUse" && request.filePath && additionalContext === null) {
|
|
236
|
+
claimPathContext(
|
|
237
|
+
`${target2}:${request.sessionId}`,
|
|
238
|
+
`${request.repository}:${request.filePath}`,
|
|
239
|
+
stateDir
|
|
240
|
+
);
|
|
241
|
+
return {};
|
|
242
|
+
}
|
|
243
|
+
if (typeof additionalContext !== "string" || additionalContext.length === 0 || additionalContext.length > MAX_ADDITIONAL_CONTEXT_CHARS) {
|
|
244
|
+
return {};
|
|
245
|
+
}
|
|
246
|
+
if (request.eventType === "PreToolUse" && request.filePath && !claimPathContext(
|
|
247
|
+
`${target2}:${request.sessionId}`,
|
|
248
|
+
`${request.repository}:${request.filePath}`,
|
|
249
|
+
stateDir
|
|
250
|
+
)) {
|
|
251
|
+
return {};
|
|
252
|
+
}
|
|
253
|
+
return hookOutput(request, additionalContext);
|
|
254
|
+
} catch {
|
|
255
|
+
return {};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/hooks/handler.ts
|
|
28
260
|
var MAX_HOOK_STDIN_BYTES = 4 * 1024 * 1024;
|
|
29
261
|
var HOOK_REQUEST_TIMEOUT_MS = 500;
|
|
30
262
|
var HOOK_STDIN_TIMEOUT_MS = 5e3;
|
|
31
263
|
var HOOK_REPO_RESOLUTION_TIMEOUT_MS = 250;
|
|
264
|
+
var INSTALLED_HOOK_TARGETS = Object.freeze([
|
|
265
|
+
"claude",
|
|
266
|
+
"codex",
|
|
267
|
+
"gemini",
|
|
268
|
+
"grok"
|
|
269
|
+
]);
|
|
32
270
|
function parseHookTarget(value) {
|
|
33
|
-
return
|
|
271
|
+
return INSTALLED_HOOK_TARGETS.includes(
|
|
272
|
+
value
|
|
273
|
+
) ? value : null;
|
|
34
274
|
}
|
|
35
275
|
function observerEndpoint(endpointFile) {
|
|
36
276
|
try {
|
|
37
|
-
const parsed = JSON.parse(
|
|
277
|
+
const parsed = JSON.parse(fs2.readFileSync(endpointFile, "utf8"));
|
|
38
278
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
39
279
|
return null;
|
|
40
280
|
}
|
|
@@ -97,23 +337,25 @@ async function readBoundedInput(input, timeoutMs) {
|
|
|
97
337
|
input.resume();
|
|
98
338
|
});
|
|
99
339
|
}
|
|
100
|
-
async function
|
|
340
|
+
async function processHookHandler(target2, opts, includeContext) {
|
|
101
341
|
const input = opts.input ?? process.stdin;
|
|
102
342
|
const bytes = await readBoundedInput(
|
|
103
343
|
input,
|
|
104
344
|
opts.inputTimeoutMs ?? HOOK_STDIN_TIMEOUT_MS
|
|
105
345
|
);
|
|
106
|
-
if (!bytes || bytes.byteLength === 0) return false;
|
|
346
|
+
if (!bytes || bytes.byteLength === 0) return { captured: false, output: {} };
|
|
107
347
|
let payload;
|
|
108
348
|
try {
|
|
109
349
|
payload = JSON.parse(bytes.toString("utf8"));
|
|
110
350
|
} catch {
|
|
111
|
-
return false;
|
|
351
|
+
return { captured: false, output: {} };
|
|
112
352
|
}
|
|
113
353
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
114
|
-
return false;
|
|
354
|
+
return { captured: false, output: {} };
|
|
355
|
+
}
|
|
356
|
+
if (hookCaptureIsDisabled(opts.captureDisabledFile)) {
|
|
357
|
+
return { captured: false, output: {} };
|
|
115
358
|
}
|
|
116
|
-
if (hookCaptureIsDisabled(opts.captureDisabledFile)) return false;
|
|
117
359
|
const cwd = opts.cwd ?? process.cwd();
|
|
118
360
|
const hookRequest = {
|
|
119
361
|
target: target2,
|
|
@@ -134,8 +376,8 @@ async function runHookHandler(target2, opts = {}) {
|
|
|
134
376
|
const inboxDir = opts.inboxDir ?? FML_HOOK_INBOX_DIR;
|
|
135
377
|
let inboxEntry;
|
|
136
378
|
let persisted = false;
|
|
379
|
+
let destination = null;
|
|
137
380
|
if (event) {
|
|
138
|
-
let destination = null;
|
|
139
381
|
try {
|
|
140
382
|
destination = getActiveHookDestinationBinding();
|
|
141
383
|
} catch {
|
|
@@ -151,40 +393,55 @@ async function runHookHandler(target2, opts = {}) {
|
|
|
151
393
|
} catch {
|
|
152
394
|
}
|
|
153
395
|
}
|
|
396
|
+
const contextPromise = includeContext && event && destination ? opts.requestContext ? opts.requestContext(target2, event, destination) : requestServerHookContext(target2, event, destination, {
|
|
397
|
+
...opts.contextFetch ? { fetch: opts.contextFetch } : {},
|
|
398
|
+
...opts.contextTimeoutMs === void 0 ? {} : { timeoutMs: opts.contextTimeoutMs },
|
|
399
|
+
...opts.contextStateDir ? { stateDir: opts.contextStateDir } : {}
|
|
400
|
+
}) : Promise.resolve({});
|
|
154
401
|
const endpoint = observerEndpoint(
|
|
155
402
|
opts.endpointFile ?? FML_OBSERVER_ENDPOINT_FILE
|
|
156
403
|
);
|
|
157
404
|
const authToken = readObserverAuthToken(
|
|
158
405
|
opts.authTokenFile ?? FML_OBSERVER_AUTH_TOKEN_FILE
|
|
159
406
|
);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
407
|
+
const deliveryPromise = (async () => {
|
|
408
|
+
if (!endpoint || !authToken) return persisted;
|
|
409
|
+
const host = endpoint.host === "::1" ? "[::1]" : endpoint.host;
|
|
410
|
+
const fetchRequest = opts.fetch ?? fetch;
|
|
411
|
+
try {
|
|
412
|
+
const response = await fetchRequest(
|
|
413
|
+
`http://${host}:${endpoint.port}/hooks`,
|
|
414
|
+
{
|
|
415
|
+
method: "POST",
|
|
416
|
+
headers: {
|
|
417
|
+
Authorization: `Bearer ${authToken}`,
|
|
418
|
+
"Content-Type": "application/json"
|
|
419
|
+
},
|
|
420
|
+
body: JSON.stringify(
|
|
421
|
+
inboxEntry ? hookInboxDeliveryPayload(inboxEntry) : hookRequest
|
|
422
|
+
),
|
|
423
|
+
signal: AbortSignal.timeout(
|
|
424
|
+
opts.timeoutMs ?? HOOK_REQUEST_TIMEOUT_MS
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
await response.arrayBuffer();
|
|
429
|
+
return response.ok || persisted;
|
|
430
|
+
} catch {
|
|
431
|
+
return persisted;
|
|
432
|
+
}
|
|
433
|
+
})();
|
|
434
|
+
const [captured, output2] = await Promise.all([
|
|
435
|
+
deliveryPromise,
|
|
436
|
+
contextPromise.catch(() => ({}))
|
|
437
|
+
]);
|
|
438
|
+
return { captured, output: output2 };
|
|
439
|
+
}
|
|
440
|
+
async function runHookHandlerForOutput(target2, opts = {}) {
|
|
441
|
+
return (await processHookHandler(target2, opts, true)).output;
|
|
183
442
|
}
|
|
184
443
|
|
|
185
444
|
// src/hook-handler.ts
|
|
186
445
|
var target = parseHookTarget(process.argv[2]);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
process.stdout.write("{}");
|
|
446
|
+
var output = target ? await runHookHandlerForOutput(target).catch(() => ({})) : {};
|
|
447
|
+
process.stdout.write(JSON.stringify(output));
|
package/dist/mcp/server.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createFmlClient,
|
|
4
4
|
getAuthenticatedClient
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-R45CWWZO.js";
|
|
6
6
|
import {
|
|
7
7
|
resolveRepoFromCwd
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-7V7G7NVF.js";
|
|
9
9
|
import {
|
|
10
10
|
FML_DATA_DIR,
|
|
11
11
|
FML_LOG_DIR,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
getValidToken,
|
|
18
18
|
initSentry,
|
|
19
19
|
readTokens
|
|
20
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-A6HAEVJX.js";
|
|
21
21
|
import "../chunk-MLKGABMK.js";
|
|
22
22
|
|
|
23
23
|
// src/mcp/server.ts
|
|
@@ -75,15 +75,15 @@ function registerTools(server2, environment = getActiveEnv()) {
|
|
|
75
75
|
const registerSessionTimelineTool = () => {
|
|
76
76
|
server2.tool(
|
|
77
77
|
"get_session_timeline",
|
|
78
|
-
'Get chronological timeline for a coding agent session. Returns messages and
|
|
78
|
+
'Get chronological timeline for a coding agent session. Returns messages, hook events, and transcript-derived scanner events interleaved by timestamp. Use source="events" for the scanner event stream (thinking, tool_call, permission_mode, stop_hook_summary, ...); source="hooks" only returns live hook-capture events and is empty for scanner-only sessions. REQUIRES: sessionId from list_engineering_sessions.',
|
|
79
79
|
{
|
|
80
80
|
sessionId: z.string().describe("The session ID to get details for."),
|
|
81
81
|
includeSystemMessages: z.boolean().optional().describe("Include system messages (default false)."),
|
|
82
|
-
source: z.enum(["messages", "hooks", "all"]).optional().describe(
|
|
83
|
-
'Filter by source: "messages" (scanner messages only), "hooks" (hook events only), "all" (
|
|
82
|
+
source: z.enum(["messages", "hooks", "events", "all"]).optional().describe(
|
|
83
|
+
'Filter by source: "messages" (scanner messages only), "hooks" (live hook-capture events only), "events" (transcript-derived scanner events), "all" (everything interleaved). Default "all".'
|
|
84
84
|
),
|
|
85
85
|
eventType: z.string().optional().describe(
|
|
86
|
-
'Filter hook events by type (
|
|
86
|
+
'Filter hook/scanner events by type (hook: "PermissionRequest", "PreToolUse", "Stop"; scanner: "tool_call", "permission_mode", "stop_hook_summary"). Only applies when source includes hooks or events.'
|
|
87
87
|
),
|
|
88
88
|
limit: z.number().optional().describe("Max entries to return (default 50, max 200)."),
|
|
89
89
|
offset: z.number().optional().describe("Skip first N entries for pagination.")
|
package/dist/observer-server.js
CHANGED
|
@@ -6,11 +6,11 @@ import {
|
|
|
6
6
|
observerLeaseIsOwned,
|
|
7
7
|
removeObserverStateIfOwned,
|
|
8
8
|
updateObserverState
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-7DWFJENY.js";
|
|
10
10
|
import {
|
|
11
11
|
createHookSyncLoop,
|
|
12
12
|
getActiveHookDestinationBinding
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-DZKYW327.js";
|
|
14
14
|
import {
|
|
15
15
|
OBSERVER_PROTOCOL_VERSION,
|
|
16
16
|
OBSERVER_SERVICE_NAME,
|
|
@@ -25,15 +25,15 @@ import {
|
|
|
25
25
|
parseHookInboxMetadata,
|
|
26
26
|
readHookInboxStatusAsync,
|
|
27
27
|
retireHookInboxEntry
|
|
28
|
-
} from "./chunk-
|
|
29
|
-
import "./chunk-
|
|
28
|
+
} from "./chunk-KBT5RMAU.js";
|
|
29
|
+
import "./chunk-7V7G7NVF.js";
|
|
30
30
|
import {
|
|
31
31
|
ObserverSyncCycleAbortedError,
|
|
32
32
|
runObserverSyncCycleSubprocess
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-3KISRT57.js";
|
|
34
34
|
import {
|
|
35
35
|
createCliRawSync
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-YSU5CS2V.js";
|
|
37
37
|
import {
|
|
38
38
|
FML_OBSERVER_AUTH_TOKEN_FILE,
|
|
39
39
|
FML_OBSERVER_ENDPOINT_FILE,
|
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
getSyncDestinationAuthBinding,
|
|
51
51
|
getValidToken,
|
|
52
52
|
observerOnlyOverrideWarning
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-A6HAEVJX.js";
|
|
54
54
|
import "./chunk-MLKGABMK.js";
|
|
55
55
|
|
|
56
56
|
// src/observer-server.ts
|
|
@@ -1239,7 +1239,7 @@ var hookRejectionStatus = () => ({
|
|
|
1239
1239
|
...lastRejectedReason !== void 0 ? { lastRejectedReason } : {},
|
|
1240
1240
|
...lastRejectedTarget !== void 0 ? { lastRejectedTarget } : {}
|
|
1241
1241
|
});
|
|
1242
|
-
var packageVersion = true ? "0.8.0-rc.
|
|
1242
|
+
var packageVersion = true ? "0.8.0-rc.3+246af775" : "dev";
|
|
1243
1243
|
var health = () => ({
|
|
1244
1244
|
status: "ok",
|
|
1245
1245
|
service: OBSERVER_SERVICE_NAME,
|