@fml-inc/fml 0.8.5 → 0.8.6-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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fml",
3
- "version": "0.8.5",
3
+ "version": "0.8.6-rc.2",
4
4
  "description": "FML agent tools for Claude Code",
5
5
  "mcpServers": {
6
6
  "fml": {
package/README.md CHANGED
@@ -25,6 +25,20 @@ npm install --global @fml-inc/fml
25
25
  fml setup
26
26
  ```
27
27
 
28
+ For a customer-hosted deployment, pass its public backend (Convex HTTP
29
+ actions) origin directly to login. The CLI validates and saves the URL as the
30
+ active `custom` environment before authenticating, so later CLI and observer
31
+ traffic uses the same deployment:
32
+
33
+ ```bash
34
+ fml login https://actions.fml.example.com
35
+ # Headless alternative:
36
+ fml login https://actions.fml.example.com --device
37
+ ```
38
+
39
+ The URL must be an HTTPS origin with no path, query, credentials, or fragment.
40
+ Plain HTTP is accepted only for loopback development servers.
41
+
28
42
  Run `fml update` to check for and install a newer global package with npm. It
29
43
  reports the version installed and refreshes FML setup automatically. Stable
30
44
  releases stay on npm's `latest` channel; prerelease versions stay on `next`.
@@ -243,10 +257,11 @@ modes. An existing caller-supplied state directory retains its directory mode.
243
257
  ## Automatic updates
244
258
 
245
259
  While the observer is running it checks npm for a newer FML release about once
246
- an hour (with jitter) and, when one exists, applies it by running `fml update`
247
- in the background: `npm install --global` followed by `fml setup`, which
248
- restarts the observer onto the new version. Output from that background update
249
- is written to `update.log` in the FML log directory.
260
+ a day (with jitter). A session-start hook also requests a check, limited to one
261
+ session-start check per hour. When a newer release exists, the observer applies
262
+ it by running `fml update` in the background: `npm install --global` followed by
263
+ `fml setup`, which restarts the observer onto the new version. Output from that
264
+ background update is written to `update.log` in the FML log directory.
250
265
 
251
266
  Auto-update is on by default. To disable it, set `FML_AUTO_UPDATE=off` and
252
267
  restart the observer so it picks up the change:
@@ -302,7 +317,7 @@ fml start Start the local observer and continuous raw sync
302
317
  fml restart Stop and start the local observer
303
318
  fml stop Stop the local observer and disable raw sync
304
319
 
305
- fml login Sign in to FML
320
+ fml login [backend-url] Sign in to hosted FML or a customer-hosted backend
306
321
  fml logout Clear stored credentials
307
322
  fml org [slug] Show or select an organization
308
323
  fml env [name-or-url] Show or select a backend environment
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  FML_HOOK_INBOX_DIR
3
- } from "./chunk-ZBSC57CW.js";
3
+ } from "./chunk-KWCFT54L.js";
4
4
 
5
5
  // ../../packages/panopticon/src/observer/auth.ts
6
6
  import crypto from "crypto";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  FML_HOOK_CAPTURE_DISABLED_FILE
3
- } from "./chunk-ZBSC57CW.js";
3
+ } from "./chunk-KWCFT54L.js";
4
4
 
5
5
  // src/json-file.ts
6
6
  import { randomUUID } from "crypto";
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  OBSERVER_SERVICE_NAME
3
- } from "./chunk-732O7ISN.js";
3
+ } from "./chunk-3T5UCGBR.js";
4
4
  import {
5
5
  FML_OBSERVER_LOCK_FILE,
6
6
  FML_OBSERVER_PORT,
7
7
  FML_OBSERVER_STATE_FILE,
8
8
  Sentry
9
- } from "./chunk-ZBSC57CW.js";
9
+ } from "./chunk-KWCFT54L.js";
10
10
 
11
11
  // src/observer/state.ts
12
12
  import { randomUUID } from "crypto";
@@ -990,7 +990,7 @@ var PACKAGE_NAME = "@fml-inc/fml";
990
990
  var UPDATE_TIMEOUT_MS = 12e4;
991
991
  var SETUP_TIMEOUT_MS = 3e5;
992
992
  function getCurrentVersion() {
993
- return true ? "0.8.5+446d0559" : "unknown";
993
+ return true ? "0.8.6-rc.2+bda7cc0a" : "unknown";
994
994
  }
995
995
  function parseVersion(version) {
996
996
  const match = SEMVER_PATTERN.exec(version);
@@ -6,7 +6,7 @@ import {
6
6
  getSiteUrlForConvexUrl,
7
7
  getSyncDestinationIdentity,
8
8
  getValidToken
9
- } from "./chunk-ZBSC57CW.js";
9
+ } from "./chunk-KWCFT54L.js";
10
10
 
11
11
  // src/hooks/sync-loop.ts
12
12
  import { createHash } from "crypto";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  resolvePackageRoot,
3
3
  runRawSyncOnce
4
- } from "./chunk-T3ZFT3QP.js";
4
+ } from "./chunk-Z2LWDZ6Z.js";
5
5
 
6
6
  // src/observer/sync-cycle.ts
7
7
  import { fork } from "child_process";
@@ -454,7 +454,7 @@ function parseStoredAuthShape(parsed) {
454
454
  }
455
455
  const auth = parsed;
456
456
  const user = auth.user;
457
- if (typeof auth.accessToken !== "string" || typeof auth.refreshToken !== "string" || typeof auth.expiresAt !== "number" || !Number.isFinite(auth.expiresAt) || !user || typeof user.id !== "string" || typeof user.email !== "string" || typeof user.name !== "string" || auth.orgId !== void 0 && typeof auth.orgId !== "string" || auth.orgSlug !== void 0 && typeof auth.orgSlug !== "string" || auth.workosClientId !== void 0 && typeof auth.workosClientId !== "string" || auth.tokenType !== void 0 && auth.tokenType !== "oauth" && auth.tokenType !== "service" || auth.refreshInvalidAt !== void 0 && (typeof auth.refreshInvalidAt !== "number" || !Number.isFinite(auth.refreshInvalidAt))) {
457
+ if (typeof auth.accessToken !== "string" || typeof auth.refreshToken !== "string" || typeof auth.expiresAt !== "number" || !Number.isFinite(auth.expiresAt) || !user || typeof user.id !== "string" || typeof user.email !== "string" || typeof user.name !== "string" || auth.orgId !== void 0 && typeof auth.orgId !== "string" || auth.orgSlug !== void 0 && typeof auth.orgSlug !== "string" || auth.workosClientId !== void 0 && typeof auth.workosClientId !== "string" || auth.siteUrl !== void 0 && typeof auth.siteUrl !== "string" || auth.tokenType !== void 0 && auth.tokenType !== "oauth" && auth.tokenType !== "service" || auth.refreshInvalidAt !== void 0 && (typeof auth.refreshInvalidAt !== "number" || !Number.isFinite(auth.refreshInvalidAt))) {
458
458
  return null;
459
459
  }
460
460
  return auth;
@@ -1314,7 +1314,8 @@ async function storeServiceRefreshToken(refreshToken2, opts) {
1314
1314
  email: "service-token",
1315
1315
  name: "FML service token"
1316
1316
  },
1317
- tokenType: "service"
1317
+ tokenType: "service",
1318
+ ...opts?.siteUrl ? { siteUrl: opts.siteUrl } : {}
1318
1319
  },
1319
1320
  envName
1320
1321
  );
@@ -7,7 +7,7 @@ import {
7
7
  getSelectedOrg,
8
8
  getSiteUrlForConvexUrl,
9
9
  getValidToken
10
- } from "./chunk-ZBSC57CW.js";
10
+ } from "./chunk-KWCFT54L.js";
11
11
 
12
12
  // src/fml-client.ts
13
13
  var LOGIN_EXPIRED_MESSAGE = "Authentication expired. Run `fml login` to sign in again, then restart Claude Code.";
@@ -79,7 +79,7 @@ function createFmlClient(token, options = {}) {
79
79
  const repo = resolveRepoFromCwd(process.cwd());
80
80
  if (repo) body.repo = repo.repo;
81
81
  if (isServiceToken) {
82
- const { readTokens, SERVICE_TOKEN_LOGIN_USER_ID } = await import("./token-store-QYXUL4NG.js");
82
+ const { readTokens, SERVICE_TOKEN_LOGIN_USER_ID } = await import("./token-store-TEPDSK2A.js");
83
83
  const stored = readTokens(options.envName);
84
84
  const storedUserExternalId = stored?.tokenType === "service" && stored.user.id !== SERVICE_TOKEN_LOGIN_USER_ID ? stored.user.id : void 0;
85
85
  const userExternalId = process.env.FML_USER_EXTERNAL_ID ?? storedUserExternalId;
@@ -6,7 +6,7 @@ import {
6
6
  getSiteUrlForConvexUrl,
7
7
  getSyncDestinationIdentity,
8
8
  getValidToken
9
- } from "./chunk-ZBSC57CW.js";
9
+ } from "./chunk-KWCFT54L.js";
10
10
 
11
11
  // ../../packages/panopticon/src/artifact.ts
12
12
  import {
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  createFmlClient,
4
4
  getAuthenticatedClient
5
- } from "./chunk-P4HOUZXC.js";
5
+ } from "./chunk-RZ5LWWRO.js";
6
6
  import {
7
7
  OBSERVER_HEARTBEAT_STALE_MS,
8
8
  execBinSync,
@@ -13,13 +13,13 @@ import {
13
13
  readObserverStateStatus,
14
14
  removeObserverStateIfOwned,
15
15
  resolveBin
16
- } from "./chunk-VOSPLAYR.js";
16
+ } from "./chunk-GQ2WVOGY.js";
17
17
  import {
18
18
  readJsonFile,
19
19
  setHookCaptureDisabled,
20
20
  writeJsonFile,
21
21
  writeTextFileAtomic
22
- } from "./chunk-IIGTUGO5.js";
22
+ } from "./chunk-FGR7SHV7.js";
23
23
  import {
24
24
  CODEX_HOOK_EVENTS,
25
25
  GEMINI_HOOK_EVENTS,
@@ -27,7 +27,7 @@ import {
27
27
  isObserverHealth,
28
28
  readHookInboxStatus,
29
29
  readObserverAuthToken
30
- } from "./chunk-732O7ISN.js";
30
+ } from "./chunk-3T5UCGBR.js";
31
31
  import "./chunk-7V7G7NVF.js";
32
32
  import {
33
33
  claudeConfigDirectory,
@@ -37,7 +37,7 @@ import {
37
37
  initializeSyncSettings,
38
38
  resolvePackageRoot,
39
39
  runRawSyncOnce
40
- } from "./chunk-T3ZFT3QP.js";
40
+ } from "./chunk-Z2LWDZ6Z.js";
41
41
  import {
42
42
  DEFAULT_PROD_URL,
43
43
  DEFAULT_TARGET_NAME,
@@ -79,7 +79,7 @@ import {
79
79
  storeServiceRefreshToken,
80
80
  writeEnvConfig,
81
81
  writeTokens
82
- } from "./chunk-ZBSC57CW.js";
82
+ } from "./chunk-KWCFT54L.js";
83
83
  import "./chunk-MLKGABMK.js";
84
84
 
85
85
  // src/cli.ts
@@ -127,7 +127,7 @@ var POLL_INTERVAL_MS = 50;
127
127
  var LOG_ROTATE_BYTES = 10 * 1024 * 1024;
128
128
  var LOG_ROTATE_FILES = 3;
129
129
  function expectedObserverVersion() {
130
- return true ? "0.8.5+446d0559" : "dev";
130
+ return true ? "0.8.6-rc.2+bda7cc0a" : "dev";
131
131
  }
132
132
  function delay(ms) {
133
133
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -6827,13 +6827,17 @@ async function handleFmlRestart() {
6827
6827
  // src/auth/oauth.ts
6828
6828
  import crypto from "crypto";
6829
6829
  import http from "http";
6830
- async function fetchWorkosClientId(siteUrl) {
6830
+ async function fetchWorkosLoginConfig(siteUrl) {
6831
6831
  const response = await fetch(`${siteUrl}/api/auth/config`);
6832
6832
  const data = await response.json();
6833
6833
  if (!response.ok || data.ok === false || !data.workosClientId) {
6834
6834
  throw new Error(data.error ?? `HTTP ${response.status}`);
6835
6835
  }
6836
- return data.workosClientId;
6836
+ const authUrl = new URL(data.workosAuthUrl ?? WORKOS_AUTH_URL);
6837
+ if (authUrl.protocol !== "https:" || authUrl.username || authUrl.password || authUrl.pathname !== "/" || authUrl.search || authUrl.hash) {
6838
+ throw new Error("FML backend returned an invalid WorkOS authorization URL");
6839
+ }
6840
+ return { clientId: data.workosClientId, authUrl: authUrl.origin };
6837
6841
  }
6838
6842
  async function canOpenBrowser() {
6839
6843
  if (process.env.FML_DEVICE_FLOW === "1") return false;
@@ -6852,13 +6856,15 @@ async function canOpenBrowser() {
6852
6856
  return true;
6853
6857
  }
6854
6858
  async function login(options) {
6855
- const clientId = await fetchWorkosClientId(options.siteUrl);
6859
+ const { clientId, authUrl: workosAuthUrl } = await fetchWorkosLoginConfig(
6860
+ options.siteUrl
6861
+ );
6856
6862
  const codeVerifier = crypto.randomBytes(32).toString("base64url");
6857
6863
  const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");
6858
6864
  const state = crypto.randomBytes(16).toString("hex");
6859
6865
  const { port, waitForCode, close } = await startCallbackServer(state);
6860
6866
  const redirectUri = `http://127.0.0.1:${port}/callback`;
6861
- const authUrl = new URL(`${WORKOS_AUTH_URL}/user_management/authorize`);
6867
+ const authUrl = new URL(`${workosAuthUrl}/user_management/authorize`);
6862
6868
  authUrl.searchParams.set("client_id", clientId);
6863
6869
  authUrl.searchParams.set("redirect_uri", redirectUri);
6864
6870
  authUrl.searchParams.set("response_type", "code");
@@ -6907,7 +6913,8 @@ async function login(options) {
6907
6913
  email: data.user.email,
6908
6914
  name: userName2
6909
6915
  },
6910
- workosClientId: clientId
6916
+ workosClientId: clientId,
6917
+ siteUrl: options.siteUrl
6911
6918
  },
6912
6919
  options.envName
6913
6920
  );
@@ -6994,7 +7001,7 @@ function startCallbackServer(expectedState) {
6994
7001
  function sleep(ms) {
6995
7002
  return new Promise((resolve) => setTimeout(resolve, ms));
6996
7003
  }
6997
- async function fetchWorkosClientId2(siteUrl) {
7004
+ async function fetchWorkosClientId(siteUrl) {
6998
7005
  const response = await fetch(`${siteUrl}/api/auth/config`);
6999
7006
  const data = await response.json();
7000
7007
  if (!response.ok || data.ok === false || !data.workosClientId) {
@@ -7079,7 +7086,7 @@ async function pollForTokens(clientId, authorization) {
7079
7086
  throw new Error("Device authorization timed out. Run `fml login` again.");
7080
7087
  }
7081
7088
  async function deviceLogin(options) {
7082
- const clientId = await fetchWorkosClientId2(options.siteUrl);
7089
+ const clientId = await fetchWorkosClientId(options.siteUrl);
7083
7090
  const authorization = await requestDeviceAuthorization(clientId);
7084
7091
  console.log("");
7085
7092
  console.log(" Sign in with this code:");
@@ -7105,7 +7112,8 @@ async function deviceLogin(options) {
7105
7112
  email: data.user.email,
7106
7113
  name
7107
7114
  },
7108
- workosClientId: clientId
7115
+ workosClientId: clientId,
7116
+ siteUrl: options.siteUrl
7109
7117
  },
7110
7118
  options.envName
7111
7119
  );
@@ -7146,7 +7154,15 @@ function resolveGitHubToken() {
7146
7154
  }
7147
7155
 
7148
7156
  // src/commands/login.ts
7149
- function captureLoginEnvironment() {
7157
+ function prepareLoginEnvironment(backendUrl) {
7158
+ if (backendUrl) {
7159
+ const convexUrl = normalizeConvexUrl(backendUrl);
7160
+ setActiveEnvironment("custom", convexUrl);
7161
+ return {
7162
+ envName: "custom",
7163
+ siteUrl: getSiteUrlForConvexUrl(convexUrl)
7164
+ };
7165
+ }
7150
7166
  const environment = getActiveEnv();
7151
7167
  return {
7152
7168
  envName: environment.name,
@@ -7285,8 +7301,8 @@ async function selectOrg(environment) {
7285
7301
  } catch {
7286
7302
  }
7287
7303
  }
7288
- async function runServiceTokenLogin(refreshToken) {
7289
- const environment = captureLoginEnvironment();
7304
+ async function runServiceTokenLogin(refreshToken, backendUrl) {
7305
+ const environment = prepareLoginEnvironment(backendUrl);
7290
7306
  const envName = environment.envName;
7291
7307
  if (!isValidEnvName(envName)) {
7292
7308
  throw new Error(
@@ -7308,9 +7324,9 @@ async function runServiceTokenLogin(refreshToken) {
7308
7324
  "Queries and agent activity sync will use the stored service token."
7309
7325
  );
7310
7326
  }
7311
- async function handleServiceTokenLogin(refreshToken) {
7327
+ async function handleServiceTokenLogin(refreshToken, backendUrl) {
7312
7328
  try {
7313
- await runServiceTokenLogin(refreshToken);
7329
+ await runServiceTokenLogin(refreshToken, backendUrl);
7314
7330
  process.exit(0);
7315
7331
  } catch (err) {
7316
7332
  const msg = err instanceof Error ? err.message : String(err);
@@ -7324,12 +7340,12 @@ async function handleServiceTokenLogin(refreshToken) {
7324
7340
  }
7325
7341
  }
7326
7342
  async function runInteractiveLogin(opts = {}) {
7327
- const environment = captureLoginEnvironment();
7343
+ const environment = prepareLoginEnvironment(opts.backendUrl);
7328
7344
  const envName = environment.envName;
7329
- const existingToken = await getValidToken({ env: envName });
7345
+ const stored = readTokens(envName);
7346
+ const canReuseStoredLogin = !opts.backendUrl || stored?.siteUrl === environment.siteUrl;
7347
+ const existingToken = canReuseStoredLogin ? await getValidToken({ env: envName }) : null;
7330
7348
  if (existingToken) {
7331
- const { readTokens: readTokens2 } = await import("./token-store-QYXUL4NG.js");
7332
- const stored = readTokens2(envName);
7333
7349
  if (stored?.tokenType === "service") {
7334
7350
  const label = stored.user.id === SERVICE_TOKEN_LOGIN_USER_ID ? "a service token" : `${stored.user.name} (${stored.user.email})`;
7335
7351
  console.log(`Already logged in with ${label} on ${envName}.`);
@@ -7356,11 +7372,14 @@ async function runInteractiveLogin(opts = {}) {
7356
7372
  }
7357
7373
  async function handleLogin(opts) {
7358
7374
  if (opts?.serviceToken) {
7359
- await handleServiceTokenLogin();
7375
+ await handleServiceTokenLogin(void 0, opts.backendUrl);
7360
7376
  return;
7361
7377
  }
7362
7378
  try {
7363
- await runInteractiveLogin({ device: opts?.device });
7379
+ await runInteractiveLogin({
7380
+ device: opts?.device,
7381
+ backendUrl: opts?.backendUrl
7382
+ });
7364
7383
  process.exit(0);
7365
7384
  } catch (err) {
7366
7385
  Sentry.captureException(err);
@@ -8822,7 +8841,7 @@ async function handleConfigDetail(opts) {
8822
8841
  // src/commands/tools.ts
8823
8842
  import { readFileSync } from "fs";
8824
8843
  import path28 from "path";
8825
- var pluginVersion = true ? "0.8.5+446d0559" : void 0;
8844
+ var pluginVersion = true ? "0.8.6-rc.2+bda7cc0a" : void 0;
8826
8845
  async function getApi() {
8827
8846
  const api = await getAuthenticatedClient();
8828
8847
  if (!api) {
@@ -8979,7 +8998,7 @@ if (process.argv.length <= 2 || process.argv.includes("--help") || process.argv.
8979
8998
  printBanner();
8980
8999
  }
8981
9000
  var program = new Command().name("fml").description("FML CLI and agent tools").version(
8982
- true ? "0.8.5+446d0559" : "dev"
9001
+ true ? "0.8.6-rc.2+bda7cc0a" : "dev"
8983
9002
  );
8984
9003
  function formatCommandInventory(cmd, prefix = "") {
8985
9004
  const name = prefix ? `${prefix} ${cmd.name()}` : cmd.name();
@@ -9028,11 +9047,14 @@ program.command("update").description("Update fml when a newer release is availa
9028
9047
  ).action(
9029
9048
  (opts) => handleUpdate({ nonInteractive: Boolean(opts.nonInteractive) })
9030
9049
  );
9031
- program.command("login").description("Sign in to your FML account").option("--device", "Use device authorization flow (no browser needed)").option(
9050
+ program.command("login").description("Sign in to your FML account").argument(
9051
+ "[backend-url]",
9052
+ "Self-hosted FML backend URL (saved as the active custom environment)"
9053
+ ).option("--device", "Use device authorization flow (no browser needed)").option(
9032
9054
  "--service-token",
9033
9055
  "Sign in with an org service refresh token (prompts for fml_srt_*)"
9034
9056
  ).action(
9035
- (opts) => handleLogin(opts)
9057
+ (backendUrl, opts) => handleLogin({ ...opts, backendUrl })
9036
9058
  );
9037
9059
  program.command("logout").description("Sign out and clear stored credentials").action(() => handleLogout());
9038
9060
  program.command("org").description("Show or select organization").argument("[slug]", "Org slug to select").action((slug) => handleOrg(slug));
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  hookCaptureIsDisabled
4
- } from "./chunk-IIGTUGO5.js";
4
+ } from "./chunk-FGR7SHV7.js";
5
5
  import {
6
6
  getActiveHookDestinationBinding
7
- } from "./chunk-ZGA57JPY.js";
7
+ } from "./chunk-GST2GTOC.js";
8
8
  import {
9
9
  SHELL_TOOL_NAMES,
10
10
  createHookInboxEntry,
@@ -12,7 +12,7 @@ import {
12
12
  normalizeObserverHookCapture,
13
13
  persistHookInboxEntry,
14
14
  readObserverAuthToken
15
- } from "./chunk-732O7ISN.js";
15
+ } from "./chunk-3T5UCGBR.js";
16
16
  import {
17
17
  resolveRepositoryRootFromCwd,
18
18
  resolveRepositorySlugFromCwd
@@ -23,7 +23,7 @@ import {
23
23
  FML_OBSERVER_AUTH_TOKEN_FILE,
24
24
  FML_OBSERVER_ENDPOINT_FILE,
25
25
  getValidToken
26
- } from "./chunk-ZBSC57CW.js";
26
+ } from "./chunk-KWCFT54L.js";
27
27
  import "./chunk-MLKGABMK.js";
28
28
 
29
29
  // src/hooks/handler.ts
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  createFmlClient,
4
4
  getAuthenticatedClient
5
- } from "../chunk-P4HOUZXC.js";
5
+ } from "../chunk-RZ5LWWRO.js";
6
6
  import {
7
7
  resolveRepoFromCwd
8
8
  } from "../chunk-7V7G7NVF.js";
@@ -17,7 +17,7 @@ import {
17
17
  getValidToken,
18
18
  initSentry,
19
19
  readTokens
20
- } from "../chunk-ZBSC57CW.js";
20
+ } from "../chunk-KWCFT54L.js";
21
21
  import "../chunk-MLKGABMK.js";
22
22
 
23
23
  // src/mcp/server.ts
@@ -11,11 +11,11 @@ import {
11
11
  removeObserverStateIfOwned,
12
12
  resolveBin,
13
13
  updateObserverState
14
- } from "./chunk-VOSPLAYR.js";
14
+ } from "./chunk-GQ2WVOGY.js";
15
15
  import {
16
16
  createHookSyncLoop,
17
17
  getActiveHookDestinationBinding
18
- } from "./chunk-ZGA57JPY.js";
18
+ } from "./chunk-GST2GTOC.js";
19
19
  import {
20
20
  OBSERVER_PROTOCOL_VERSION,
21
21
  OBSERVER_SERVICE_NAME,
@@ -30,15 +30,15 @@ import {
30
30
  parseHookInboxMetadata,
31
31
  readHookInboxStatusAsync,
32
32
  retireHookInboxEntry
33
- } from "./chunk-732O7ISN.js";
33
+ } from "./chunk-3T5UCGBR.js";
34
34
  import "./chunk-7V7G7NVF.js";
35
35
  import {
36
36
  ObserverSyncCycleAbortedError,
37
37
  runObserverSyncCycleSubprocess
38
- } from "./chunk-LWOINL4K.js";
38
+ } from "./chunk-JEZPS7F5.js";
39
39
  import {
40
40
  createCliRawSync
41
- } from "./chunk-T3ZFT3QP.js";
41
+ } from "./chunk-Z2LWDZ6Z.js";
42
42
  import {
43
43
  FML_OBSERVER_AUTH_TOKEN_FILE,
44
44
  FML_OBSERVER_ENDPOINT_FILE,
@@ -56,7 +56,7 @@ import {
56
56
  getSyncDestinationAuthBinding,
57
57
  getValidToken,
58
58
  observerOnlyOverrideWarning
59
- } from "./chunk-ZBSC57CW.js";
59
+ } from "./chunk-KWCFT54L.js";
60
60
  import "./chunk-MLKGABMK.js";
61
61
 
62
62
  // src/observer-server.ts
@@ -1200,14 +1200,17 @@ function createObserverSyncLoop(opts) {
1200
1200
  }
1201
1201
 
1202
1202
  // src/observer/auto-update.ts
1203
- var AUTO_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
1203
+ var AUTO_UPDATE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1204
1204
  var AUTO_UPDATE_JITTER_MS = 10 * 60 * 1e3;
1205
+ var AUTO_UPDATE_SESSION_START_COOLDOWN_MS = 60 * 60 * 1e3;
1205
1206
  function errorMessage2(error) {
1206
1207
  return error instanceof Error ? error.message : String(error);
1207
1208
  }
1208
1209
  function createAutoUpdater(dependencies) {
1209
1210
  let timer;
1210
1211
  let stopped = true;
1212
+ let checkInFlight = false;
1213
+ let lastSessionStartCheckAtMs;
1211
1214
  let generation = 0;
1212
1215
  const nextDelay = () => {
1213
1216
  if (dependencies.jitterMs <= 0) return dependencies.intervalMs;
@@ -1220,18 +1223,35 @@ function createAutoUpdater(dependencies) {
1220
1223
  timer = setTimeout(onTick, nextDelay());
1221
1224
  timer.unref();
1222
1225
  };
1223
- const onTick = async () => {
1224
- const tickGeneration = generation;
1225
- timer = void 0;
1226
+ const runUpdateCheck = async (checkGeneration) => {
1226
1227
  try {
1227
1228
  const result = await dependencies.runCheck();
1228
- if (!stopped && result.newer) dependencies.spawnUpdate();
1229
+ if (!stopped && checkGeneration === generation && result.newer) {
1230
+ dependencies.spawnUpdate();
1231
+ }
1229
1232
  } catch (error) {
1230
- if (!stopped) {
1231
- dependencies.log(
1232
- `[fml] Auto-update check failed: ${errorMessage2(error)}`
1233
- );
1233
+ if (!stopped && checkGeneration === generation) {
1234
+ try {
1235
+ dependencies.log(
1236
+ `[fml] Auto-update check failed: ${errorMessage2(error)}`
1237
+ );
1238
+ } catch {
1239
+ }
1234
1240
  }
1241
+ } finally {
1242
+ checkInFlight = false;
1243
+ }
1244
+ };
1245
+ const startUpdateCheck = (checkGeneration) => {
1246
+ if (stopped || checkInFlight) return null;
1247
+ checkInFlight = true;
1248
+ return runUpdateCheck(checkGeneration);
1249
+ };
1250
+ const onTick = async () => {
1251
+ const tickGeneration = generation;
1252
+ timer = void 0;
1253
+ try {
1254
+ await startUpdateCheck(tickGeneration);
1235
1255
  } finally {
1236
1256
  if (tickGeneration === generation) schedule();
1237
1257
  }
@@ -1244,6 +1264,17 @@ function createAutoUpdater(dependencies) {
1244
1264
  stopped = false;
1245
1265
  schedule();
1246
1266
  },
1267
+ onSessionStart() {
1268
+ if (stopped) return;
1269
+ const now = dependencies.nowMs();
1270
+ if (lastSessionStartCheckAtMs !== void 0 && now - lastSessionStartCheckAtMs < dependencies.sessionStartCooldownMs) {
1271
+ return;
1272
+ }
1273
+ const check = startUpdateCheck(generation);
1274
+ if (!check) return;
1275
+ lastSessionStartCheckAtMs = now;
1276
+ void check;
1277
+ },
1247
1278
  stop() {
1248
1279
  stopped = true;
1249
1280
  generation += 1;
@@ -1449,7 +1480,7 @@ var hookRejectionStatus = () => ({
1449
1480
  ...lastRejectedReason !== void 0 ? { lastRejectedReason } : {},
1450
1481
  ...lastRejectedTarget !== void 0 ? { lastRejectedTarget } : {}
1451
1482
  });
1452
- var packageVersion = true ? "0.8.5+446d0559" : "dev";
1483
+ var packageVersion = true ? "0.8.6-rc.2+bda7cc0a" : "dev";
1453
1484
  var health = () => ({
1454
1485
  status: "ok",
1455
1486
  service: OBSERVER_SERVICE_NAME,
@@ -1626,8 +1657,9 @@ var captureHookRequest = (request, metadata) => {
1626
1657
  } catch {
1627
1658
  }
1628
1659
  }
1660
+ let captured;
1629
1661
  try {
1630
- database.captureHookEvent({
1662
+ captured = database.captureHookEvent({
1631
1663
  ...event,
1632
1664
  ...destination ? { destination } : {}
1633
1665
  });
@@ -1639,6 +1671,9 @@ var captureHookRequest = (request, metadata) => {
1639
1671
  );
1640
1672
  return terminal ? "terminal_rejected" : "retry";
1641
1673
  }
1674
+ if (captured && event.eventType === "SessionStart") {
1675
+ autoUpdater?.onSessionStart();
1676
+ }
1642
1677
  hookSyncLoop?.wake();
1643
1678
  return "committed";
1644
1679
  };
@@ -1732,6 +1767,7 @@ try {
1732
1767
  }),
1733
1768
  spawnUpdate: spawnObserverUpdate,
1734
1769
  intervalMs: AUTO_UPDATE_INTERVAL_MS,
1770
+ sessionStartCooldownMs: AUTO_UPDATE_SESSION_START_COOLDOWN_MS,
1735
1771
  jitterMs: AUTO_UPDATE_JITTER_MS,
1736
1772
  log: console.warn
1737
1773
  });
@@ -1045,7 +1045,7 @@ function parseStoredAuthShape(parsed) {
1045
1045
  }
1046
1046
  const auth = parsed;
1047
1047
  const user = auth.user;
1048
- if (typeof auth.accessToken !== "string" || typeof auth.refreshToken !== "string" || typeof auth.expiresAt !== "number" || !Number.isFinite(auth.expiresAt) || !user || typeof user.id !== "string" || typeof user.email !== "string" || typeof user.name !== "string" || auth.orgId !== void 0 && typeof auth.orgId !== "string" || auth.orgSlug !== void 0 && typeof auth.orgSlug !== "string" || auth.workosClientId !== void 0 && typeof auth.workosClientId !== "string" || auth.tokenType !== void 0 && auth.tokenType !== "oauth" && auth.tokenType !== "service" || auth.refreshInvalidAt !== void 0 && (typeof auth.refreshInvalidAt !== "number" || !Number.isFinite(auth.refreshInvalidAt))) {
1048
+ if (typeof auth.accessToken !== "string" || typeof auth.refreshToken !== "string" || typeof auth.expiresAt !== "number" || !Number.isFinite(auth.expiresAt) || !user || typeof user.id !== "string" || typeof user.email !== "string" || typeof user.name !== "string" || auth.orgId !== void 0 && typeof auth.orgId !== "string" || auth.orgSlug !== void 0 && typeof auth.orgSlug !== "string" || auth.workosClientId !== void 0 && typeof auth.workosClientId !== "string" || auth.siteUrl !== void 0 && typeof auth.siteUrl !== "string" || auth.tokenType !== void 0 && auth.tokenType !== "oauth" && auth.tokenType !== "service" || auth.refreshInvalidAt !== void 0 && (typeof auth.refreshInvalidAt !== "number" || !Number.isFinite(auth.refreshInvalidAt))) {
1049
1049
  return null;
1050
1050
  }
1051
1051
  return auth;
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runObserverSyncCycle
4
- } from "./chunk-LWOINL4K.js";
5
- import "./chunk-T3ZFT3QP.js";
6
- import "./chunk-ZBSC57CW.js";
4
+ } from "./chunk-JEZPS7F5.js";
5
+ import "./chunk-Z2LWDZ6Z.js";
6
+ import "./chunk-KWCFT54L.js";
7
7
  import "./chunk-MLKGABMK.js";
8
8
 
9
9
  // src/sync-cycle.ts
@@ -10,7 +10,7 @@ import {
10
10
  setSelectedOrg,
11
11
  storeServiceRefreshToken,
12
12
  writeTokens
13
- } from "./chunk-ZBSC57CW.js";
13
+ } from "./chunk-KWCFT54L.js";
14
14
  import "./chunk-MLKGABMK.js";
15
15
  export {
16
16
  SERVICE_TOKEN_LOGIN_USER_ID,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fml-inc/fml",
3
- "version": "0.8.5",
3
+ "version": "0.8.6-rc.2",
4
4
  "description": "FML CLI and agent tools",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",