@fml-inc/fml 0.8.0-rc.1 → 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 +1 -1
- package/README.md +27 -16
- package/dist/{chunk-CK5MEL5J.js → chunk-3KISRT57.js} +1 -1
- package/dist/{chunk-DEMVOVVY.js → chunk-56TPYMNK.js} +244 -29
- 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-P3QLARXE.js → chunk-R45CWWZO.js} +3 -3
- package/dist/{chunk-HUR2HCUG.js → chunk-WZLLO563.js} +2 -2
- package/dist/{chunk-JXSSPQ5B.js → chunk-YSU5CS2V.js} +1 -1
- package/dist/cli.js +1310 -1158
- package/dist/hook-handler.js +290 -40
- package/dist/mcp/server.js +7 -7
- package/dist/observer-server.js +8 -8
- package/dist/sync-cycle.js +3 -3
- package/dist/{token-store-R7WXEPLI.js → token-store-BT5THIAJ.js} +1 -1
- package/package.json +3 -3
package/dist/hook-handler.js
CHANGED
|
@@ -1,30 +1,263 @@
|
|
|
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
|
HOOK_TARGETS,
|
|
10
|
+
SHELL_TOOL_NAMES,
|
|
10
11
|
createHookInboxEntry,
|
|
11
12
|
hookInboxDeliveryPayload,
|
|
12
13
|
normalizeObserverHookCapture,
|
|
13
14
|
persistHookInboxEntry,
|
|
14
15
|
readObserverAuthToken
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-56TPYMNK.js";
|
|
16
17
|
import {
|
|
18
|
+
resolveRepositoryRootFromCwd,
|
|
17
19
|
resolveRepositorySlugFromCwd
|
|
18
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-7V7G7NVF.js";
|
|
19
21
|
import {
|
|
22
|
+
FML_HOOK_CONTEXT_STATE_DIR,
|
|
20
23
|
FML_HOOK_INBOX_DIR,
|
|
21
24
|
FML_OBSERVER_AUTH_TOKEN_FILE,
|
|
22
|
-
FML_OBSERVER_ENDPOINT_FILE
|
|
23
|
-
|
|
25
|
+
FML_OBSERVER_ENDPOINT_FILE,
|
|
26
|
+
getValidToken
|
|
27
|
+
} from "./chunk-A6HAEVJX.js";
|
|
24
28
|
import "./chunk-MLKGABMK.js";
|
|
25
29
|
|
|
26
30
|
// src/hooks/handler.ts
|
|
31
|
+
import fs2 from "fs";
|
|
32
|
+
|
|
33
|
+
// src/hooks/context.ts
|
|
34
|
+
import { createHash } from "crypto";
|
|
27
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
|
|
28
261
|
var MAX_HOOK_STDIN_BYTES = 4 * 1024 * 1024;
|
|
29
262
|
var HOOK_REQUEST_TIMEOUT_MS = 500;
|
|
30
263
|
var HOOK_STDIN_TIMEOUT_MS = 5e3;
|
|
@@ -34,7 +267,7 @@ function parseHookTarget(value) {
|
|
|
34
267
|
}
|
|
35
268
|
function observerEndpoint(endpointFile) {
|
|
36
269
|
try {
|
|
37
|
-
const parsed = JSON.parse(
|
|
270
|
+
const parsed = JSON.parse(fs2.readFileSync(endpointFile, "utf8"));
|
|
38
271
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
39
272
|
return null;
|
|
40
273
|
}
|
|
@@ -97,23 +330,25 @@ async function readBoundedInput(input, timeoutMs) {
|
|
|
97
330
|
input.resume();
|
|
98
331
|
});
|
|
99
332
|
}
|
|
100
|
-
async function
|
|
333
|
+
async function processHookHandler(target2, opts, includeContext) {
|
|
101
334
|
const input = opts.input ?? process.stdin;
|
|
102
335
|
const bytes = await readBoundedInput(
|
|
103
336
|
input,
|
|
104
337
|
opts.inputTimeoutMs ?? HOOK_STDIN_TIMEOUT_MS
|
|
105
338
|
);
|
|
106
|
-
if (!bytes || bytes.byteLength === 0) return false;
|
|
339
|
+
if (!bytes || bytes.byteLength === 0) return { captured: false, output: {} };
|
|
107
340
|
let payload;
|
|
108
341
|
try {
|
|
109
342
|
payload = JSON.parse(bytes.toString("utf8"));
|
|
110
343
|
} catch {
|
|
111
|
-
return false;
|
|
344
|
+
return { captured: false, output: {} };
|
|
112
345
|
}
|
|
113
346
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
114
|
-
return false;
|
|
347
|
+
return { captured: false, output: {} };
|
|
348
|
+
}
|
|
349
|
+
if (hookCaptureIsDisabled(opts.captureDisabledFile)) {
|
|
350
|
+
return { captured: false, output: {} };
|
|
115
351
|
}
|
|
116
|
-
if (hookCaptureIsDisabled(opts.captureDisabledFile)) return false;
|
|
117
352
|
const cwd = opts.cwd ?? process.cwd();
|
|
118
353
|
const hookRequest = {
|
|
119
354
|
target: target2,
|
|
@@ -134,8 +369,8 @@ async function runHookHandler(target2, opts = {}) {
|
|
|
134
369
|
const inboxDir = opts.inboxDir ?? FML_HOOK_INBOX_DIR;
|
|
135
370
|
let inboxEntry;
|
|
136
371
|
let persisted = false;
|
|
372
|
+
let destination = null;
|
|
137
373
|
if (event) {
|
|
138
|
-
let destination = null;
|
|
139
374
|
try {
|
|
140
375
|
destination = getActiveHookDestinationBinding();
|
|
141
376
|
} catch {
|
|
@@ -151,40 +386,55 @@ async function runHookHandler(target2, opts = {}) {
|
|
|
151
386
|
} catch {
|
|
152
387
|
}
|
|
153
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({});
|
|
154
394
|
const endpoint = observerEndpoint(
|
|
155
395
|
opts.endpointFile ?? FML_OBSERVER_ENDPOINT_FILE
|
|
156
396
|
);
|
|
157
397
|
const authToken = readObserverAuthToken(
|
|
158
398
|
opts.authTokenFile ?? FML_OBSERVER_AUTH_TOKEN_FILE
|
|
159
399
|
);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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;
|
|
183
435
|
}
|
|
184
436
|
|
|
185
437
|
// src/hook-handler.ts
|
|
186
438
|
var target = parseHookTarget(process.argv[2]);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
process.stdout.write("{}");
|
|
439
|
+
var output = target ? await runHookHandlerForOutput(target).catch(() => ({})) : {};
|
|
440
|
+
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-WZLLO563.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-56TPYMNK.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.2+bed6eaed" : "dev";
|
|
1243
1243
|
var health = () => ({
|
|
1244
1244
|
status: "ok",
|
|
1245
1245
|
service: OBSERVER_SERVICE_NAME,
|
package/dist/sync-cycle.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runObserverSyncCycle
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-3KISRT57.js";
|
|
5
|
+
import "./chunk-YSU5CS2V.js";
|
|
6
|
+
import "./chunk-A6HAEVJX.js";
|
|
7
7
|
import "./chunk-MLKGABMK.js";
|
|
8
8
|
|
|
9
9
|
// src/sync-cycle.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fml-inc/fml",
|
|
3
|
-
"version": "0.8.0-rc.
|
|
3
|
+
"version": "0.8.0-rc.2",
|
|
4
4
|
"description": "FML CLI and agent tools",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"@types/node": "^22.0.0",
|
|
42
42
|
"tsup": "^8.0.0",
|
|
43
43
|
"typescript": "^5",
|
|
44
|
-
"@fml/
|
|
45
|
-
"@fml/
|
|
44
|
+
"@fml/agent-targets": "0.0.0",
|
|
45
|
+
"@fml/panopticon": "0.0.0"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "node scripts/generate-plugin-manifest.js && tsup",
|