@fml-inc/fml 0.8.2 → 0.8.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fml",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "FML agent tools for Claude Code",
5
5
  "mcpServers": {
6
6
  "fml": {
package/dist/cli.js CHANGED
@@ -124,7 +124,7 @@ var POLL_INTERVAL_MS = 50;
124
124
  var LOG_ROTATE_BYTES = 10 * 1024 * 1024;
125
125
  var LOG_ROTATE_FILES = 3;
126
126
  function expectedObserverVersion() {
127
- return true ? "0.8.2+e2bf128f" : "dev";
127
+ return true ? "0.8.3+499a546c" : "dev";
128
128
  }
129
129
  function delay(ms) {
130
130
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -7898,7 +7898,7 @@ var PACKAGE_NAME = "@fml-inc/fml";
7898
7898
  var UPDATE_TIMEOUT_MS = 12e4;
7899
7899
  var SETUP_TIMEOUT_MS = 3e5;
7900
7900
  function getCurrentVersion() {
7901
- return true ? "0.8.2+e2bf128f" : "unknown";
7901
+ return true ? "0.8.3+499a546c" : "unknown";
7902
7902
  }
7903
7903
  function parseVersion(version) {
7904
7904
  const match = SEMVER_PATTERN.exec(version);
@@ -9037,7 +9037,7 @@ async function handleConfigDetail(opts) {
9037
9037
  // src/commands/tools.ts
9038
9038
  import { readFileSync } from "fs";
9039
9039
  import path29 from "path";
9040
- var pluginVersion = true ? "0.8.2+e2bf128f" : void 0;
9040
+ var pluginVersion = true ? "0.8.3+499a546c" : void 0;
9041
9041
  async function getApi() {
9042
9042
  const api = await getAuthenticatedClient();
9043
9043
  if (!api) {
@@ -9194,7 +9194,7 @@ if (process.argv.length <= 2 || process.argv.includes("--help") || process.argv.
9194
9194
  printBanner();
9195
9195
  }
9196
9196
  var program = new Command().name("fml").description("FML CLI and agent tools").version(
9197
- true ? "0.8.2+e2bf128f" : "dev"
9197
+ true ? "0.8.3+499a546c" : "dev"
9198
9198
  );
9199
9199
  function formatCommandInventory(cmd, prefix = "") {
9200
9200
  const name = prefix ? `${prefix} ${cmd.name()}` : cmd.name();
@@ -56,6 +56,14 @@ var EDIT_TOOL_NAMES = /* @__PURE__ */ new Set([
56
56
  "write_file"
57
57
  ]);
58
58
  var READ_TOOL_NAMES = /* @__PURE__ */ new Set(["read", "read_file"]);
59
+ function hookContextFileOperation(toolName) {
60
+ const normalizedToolName = toolName.toLowerCase();
61
+ if (EDIT_TOOL_NAMES.has(normalizedToolName)) return "edit";
62
+ if (READ_TOOL_NAMES.has(normalizedToolName) || SHELL_TOOL_NAMES.has(normalizedToolName)) {
63
+ return "read";
64
+ }
65
+ return null;
66
+ }
59
67
  function sameDestination(left, right) {
60
68
  return left.environment === right.environment && left.targetUrl === right.targetUrl && left.destinationIdentity === right.destinationIdentity;
61
69
  }
@@ -130,7 +138,7 @@ function pruneOldMarkers(directory, nowMs) {
130
138
  }
131
139
  }
132
140
  }
133
- function canonicalFilePath(filePath, cwd, repositoryRoot) {
141
+ function canonicalHookContextFilePath(filePath, cwd, repositoryRoot) {
134
142
  const windows = [filePath, cwd, repositoryRoot].some(
135
143
  (value) => value !== void 0 && value !== null && /^[A-Za-z]:[\\/]/.test(value)
136
144
  );
@@ -159,7 +167,9 @@ function canonicalFilePath(filePath, cwd, repositoryRoot) {
159
167
  return resolved.replace(/\\/g, "/");
160
168
  }
161
169
  function buildServerHookContextRequest(target2, event, stateDir = FML_HOOK_CONTEXT_STATE_DIR, repositoryRoot = null) {
162
- if (target2 !== "claude" && target2 !== "codex") return null;
170
+ if (target2 !== "claude" && target2 !== "codex" && target2 !== "pi") {
171
+ return null;
172
+ }
163
173
  if (!event.repository) return null;
164
174
  const markerSessionId = `${target2}:${event.sessionId}`;
165
175
  const base = {
@@ -185,11 +195,14 @@ function buildServerHookContextRequest(target2, event, stateDir = FML_HOOK_CONTE
185
195
  if (event.eventType !== "PreToolUse" || !event.toolName) {
186
196
  return null;
187
197
  }
188
- const toolName = event.toolName.toLowerCase();
189
- const fileOperation = EDIT_TOOL_NAMES.has(toolName) ? "edit" : READ_TOOL_NAMES.has(toolName) || SHELL_TOOL_NAMES.has(toolName) ? "read" : null;
198
+ const fileOperation = hookContextFileOperation(event.toolName);
190
199
  if (!fileOperation) return null;
191
200
  if (!event.filePath) return null;
192
- const filePath = canonicalFilePath(event.filePath, event.cwd, repositoryRoot);
201
+ const filePath = canonicalHookContextFilePath(
202
+ event.filePath,
203
+ event.cwd,
204
+ repositoryRoot
205
+ );
193
206
  if (pathContextWasEmitted(
194
207
  markerSessionId,
195
208
  `${event.repository}:${filePath}`,
@@ -432,39 +445,19 @@ function renderHookContextReceipt(receiptItems, nowMs) {
432
445
  function appendModelContextGuidance(additionalContext) {
433
446
  return `${additionalContext}${CONTEXT_GUIDANCE_SEPARATOR}${MODEL_CONTEXT_GUIDANCE}`;
434
447
  }
435
- function hookOutput(request, additionalContext, receiptItems) {
436
- const receipt = renderHookContextReceipt(receiptItems, request.nowMs);
437
- return {
438
- ...receipt ? { systemMessage: receipt } : {},
439
- hookSpecificOutput: {
440
- hookEventName: request.eventType,
441
- additionalContext: appendModelContextGuidance(additionalContext)
442
- }
443
- };
444
- }
445
- async function requestServerHookContext(target2, event, destination, opts = {}) {
448
+ async function requestServerHookContextInjection(request, destination, opts = {}) {
446
449
  const deadlineSignal = AbortSignal.timeout(
447
450
  opts.timeoutMs ?? HOOK_CONTEXT_TIMEOUT_MS
448
451
  );
449
- const stateDir = opts.stateDir ?? FML_HOOK_CONTEXT_STATE_DIR;
450
- pruneOldMarkers(stateDir, Date.now());
451
- const repositoryRoot = event.eventType === "PreToolUse" && event.cwd ? (opts.resolveRepositoryRoot ?? ((cwd) => resolveRepositoryRootFromCwd(cwd, 250)))(event.cwd) : null;
452
- const request = buildServerHookContextRequest(
453
- target2,
454
- event,
455
- stateDir,
456
- repositoryRoot
457
- );
458
- if (!request) return {};
459
452
  try {
460
453
  const token = await getValidToken({
461
454
  env: destination.environment,
462
455
  signal: deadlineSignal
463
456
  });
464
- if (!token) return {};
457
+ if (!token) return null;
465
458
  const currentDestination = getActiveHookDestinationBinding();
466
459
  if (!currentDestination || !sameDestination(destination, currentDestination)) {
467
- return {};
460
+ return null;
468
461
  }
469
462
  const response = await (opts.fetch ?? fetch)(
470
463
  `${destination.targetUrl.replace(/\/+$/, "")}${HOOK_CONTEXT_ENDPOINT}`,
@@ -478,34 +471,72 @@ async function requestServerHookContext(target2, event, destination, opts = {})
478
471
  signal: deadlineSignal
479
472
  }
480
473
  );
481
- if (!response.ok) return {};
474
+ if (!response.ok) return null;
482
475
  const body = await response.json();
483
- if (!body || typeof body !== "object" || Array.isArray(body)) return {};
476
+ if (!body || typeof body !== "object" || Array.isArray(body)) return null;
484
477
  const responseBody = body;
485
478
  const additionalContext = responseBody.additionalContext;
486
- if (request.eventType === "PreToolUse" && request.filePath && additionalContext === null) {
479
+ if (additionalContext === null) {
480
+ return { additionalContext: null, receipt: null };
481
+ }
482
+ if (typeof additionalContext !== "string" || additionalContext.length === 0 || additionalContext.length > MAX_ADDITIONAL_CONTEXT_CHARS) {
483
+ return null;
484
+ }
485
+ const receiptItems = receiptItemsFromResponse(responseBody.receiptItems);
486
+ return {
487
+ additionalContext: appendModelContextGuidance(additionalContext),
488
+ // A malformed receipt is presentation-only and must not suppress model
489
+ // context. The renderer also fails open independently.
490
+ receipt: receiptItems === null ? null : renderHookContextReceipt(receiptItems, request.nowMs)
491
+ };
492
+ } catch {
493
+ return null;
494
+ }
495
+ }
496
+ function hookOutput(request, injection) {
497
+ return {
498
+ ...injection.receipt ? { systemMessage: injection.receipt } : {},
499
+ hookSpecificOutput: {
500
+ hookEventName: request.eventType,
501
+ additionalContext: injection.additionalContext
502
+ }
503
+ };
504
+ }
505
+ async function requestServerHookContext(target2, event, destination, opts = {}) {
506
+ const stateDir = opts.stateDir ?? FML_HOOK_CONTEXT_STATE_DIR;
507
+ pruneOldMarkers(stateDir, Date.now());
508
+ const repositoryRoot = event.eventType === "PreToolUse" && event.cwd ? (opts.resolveRepositoryRoot ?? ((cwd) => resolveRepositoryRootFromCwd(cwd, 250)))(event.cwd) : null;
509
+ const request = buildServerHookContextRequest(
510
+ target2,
511
+ event,
512
+ stateDir,
513
+ repositoryRoot
514
+ );
515
+ if (!request) return {};
516
+ const injection = await requestServerHookContextInjection(
517
+ request,
518
+ destination,
519
+ opts
520
+ );
521
+ if (injection === null) return {};
522
+ if (injection.additionalContext === null) {
523
+ if (request.eventType === "PreToolUse" && request.filePath) {
487
524
  claimPathContext(
488
525
  `${target2}:${request.sessionId}`,
489
526
  `${request.repository}:${request.filePath}`,
490
527
  stateDir
491
528
  );
492
- return {};
493
- }
494
- if (typeof additionalContext !== "string" || additionalContext.length === 0 || additionalContext.length > MAX_ADDITIONAL_CONTEXT_CHARS) {
495
- return {};
496
529
  }
497
- const receiptItems = receiptItemsFromResponse(responseBody.receiptItems);
498
- if (request.eventType === "PreToolUse" && request.filePath && !claimPathContext(
499
- `${target2}:${request.sessionId}`,
500
- `${request.repository}:${request.filePath}`,
501
- stateDir
502
- )) {
503
- return {};
504
- }
505
- return hookOutput(request, additionalContext, receiptItems ?? []);
506
- } catch {
507
530
  return {};
508
531
  }
532
+ if (request.eventType === "PreToolUse" && request.filePath && !claimPathContext(
533
+ `${target2}:${request.sessionId}`,
534
+ `${request.repository}:${request.filePath}`,
535
+ stateDir
536
+ )) {
537
+ return {};
538
+ }
539
+ return hookOutput(request, injection);
509
540
  }
510
541
 
511
542
  // src/hooks/handler.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.2+e2bf128f" : "dev";
1242
+ var packageVersion = true ? "0.8.3+499a546c" : "dev";
1243
1243
  var health = () => ({
1244
1244
  status: "ok",
1245
1245
  service: OBSERVER_SERVICE_NAME,