@fml-inc/fml 0.8.7 → 0.8.9
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/dist/{chunk-6BTC3S7C.js → chunk-2DGHAVVR.js} +1 -1
- package/dist/{chunk-LPSZCTTJ.js → chunk-2ZCXGYI5.js} +1 -1
- package/dist/{chunk-LOJPIJZW.js → chunk-GDOXY4W2.js} +2 -2
- package/dist/{chunk-4YLSKFDU.js → chunk-H5LWJ5PH.js} +1 -1
- package/dist/{chunk-B3H2YWPJ.js → chunk-HDAFEHUY.js} +62 -14
- package/dist/{chunk-5JJQWV4G.js → chunk-JPHN4VKM.js} +1 -1
- package/dist/{chunk-XFALHTDL.js → chunk-TWJ62CZX.js} +2 -2
- package/dist/{chunk-PJWMSHHZ.js → chunk-WE7MAWAW.js} +1 -1
- package/dist/cli.js +12 -9
- package/dist/hook-handler.js +4 -4
- package/dist/mcp/server.js +2 -2
- package/dist/observer-server.js +7 -7
- package/dist/pi/extension.js +1427 -1198
- package/dist/sync-cycle.js +3 -3
- package/dist/telemetry-config-HRSNDMTG.js +148 -0
- package/dist/{token-store-ORR5MIJL.js → token-store-GYU3AXBK.js} +1 -1
- package/package.json +2 -2
package/dist/pi/extension.js
CHANGED
|
@@ -1,1227 +1,699 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
6
10
|
|
|
7
|
-
//
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
var LEGACY_PI_EXTENSION_MARKER = "__panopticonPiExtensionTest";
|
|
13
|
-
var LEGACY_PI_EXTENSION_SIGNATURES = Object.freeze([
|
|
14
|
-
"PANOPTICON_PORT",
|
|
15
|
-
'"/hooks"',
|
|
16
|
-
"UserPromptSubmit",
|
|
17
|
-
"PreToolUse",
|
|
18
|
-
"PostToolUse",
|
|
19
|
-
"SessionEnd"
|
|
20
|
-
]);
|
|
21
|
-
function pathApiFor(platform) {
|
|
22
|
-
return platform === "win32" ? path.win32 : path.posix;
|
|
11
|
+
// src/dirs.ts
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import path3 from "node:path";
|
|
14
|
+
function isPresent(value) {
|
|
15
|
+
return value !== void 0 && value.trim().length > 0;
|
|
23
16
|
}
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return pathApi.join(context.homeDir, ".pi", "agent");
|
|
17
|
+
function normalizeAbsolutePath(value, variableName, pathApi) {
|
|
18
|
+
if (!pathApi.isAbsolute(value)) {
|
|
19
|
+
throw new Error(`${variableName} must be an absolute path: ${value}`);
|
|
28
20
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
21
|
+
return pathApi.normalize(value);
|
|
22
|
+
}
|
|
23
|
+
function explicitDirectory(value, variableName, pathApi) {
|
|
24
|
+
if (!isPresent(value)) return void 0;
|
|
25
|
+
return normalizeAbsolutePath(value, variableName, pathApi);
|
|
26
|
+
}
|
|
27
|
+
function platformBaseDirectory(value, pathApi) {
|
|
28
|
+
if (!isPresent(value) || !pathApi.isAbsolute(value)) return void 0;
|
|
29
|
+
return pathApi.normalize(value);
|
|
30
|
+
}
|
|
31
|
+
function resolveFmlDirectories(options = {}) {
|
|
32
|
+
const platform = options.platform ?? process.platform;
|
|
33
|
+
const env = options.env ?? process.env;
|
|
34
|
+
const pathApi = platform === "win32" ? path3.win32 : path3.posix;
|
|
35
|
+
const homeDir = normalizeAbsolutePath(
|
|
36
|
+
options.homeDir ?? os.homedir(),
|
|
37
|
+
"home directory",
|
|
38
|
+
pathApi
|
|
39
|
+
);
|
|
40
|
+
const explicitDataDir = explicitDirectory(
|
|
41
|
+
env.FML_DATA_DIR,
|
|
42
|
+
"FML_DATA_DIR",
|
|
43
|
+
pathApi
|
|
44
|
+
);
|
|
45
|
+
const explicitStateDir = explicitDirectory(
|
|
46
|
+
env.FML_STATE_DIR,
|
|
47
|
+
"FML_STATE_DIR",
|
|
48
|
+
pathApi
|
|
49
|
+
);
|
|
50
|
+
const explicitLogDir = explicitDirectory(
|
|
51
|
+
env.FML_LOG_DIR,
|
|
52
|
+
"FML_LOG_DIR",
|
|
53
|
+
pathApi
|
|
54
|
+
);
|
|
55
|
+
let defaultDataDir;
|
|
56
|
+
let defaultStateDir;
|
|
57
|
+
let nativeLogDir;
|
|
58
|
+
switch (platform) {
|
|
59
|
+
case "darwin": {
|
|
60
|
+
defaultDataDir = pathApi.join(
|
|
61
|
+
homeDir,
|
|
62
|
+
"Library",
|
|
63
|
+
"Application Support",
|
|
64
|
+
"fml"
|
|
65
|
+
);
|
|
66
|
+
defaultStateDir = defaultDataDir;
|
|
67
|
+
nativeLogDir = pathApi.join(homeDir, "Library", "Logs", "fml");
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case "win32": {
|
|
71
|
+
const roamingAppData = platformBaseDirectory(env.APPDATA, pathApi) ?? pathApi.join(homeDir, "AppData", "Roaming");
|
|
72
|
+
const localAppData = platformBaseDirectory(env.LOCALAPPDATA, pathApi) ?? pathApi.join(homeDir, "AppData", "Local");
|
|
73
|
+
defaultDataDir = pathApi.join(roamingAppData, "fml");
|
|
74
|
+
defaultStateDir = pathApi.join(localAppData, "fml");
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
default: {
|
|
78
|
+
const xdgDataHome = platformBaseDirectory(env.XDG_DATA_HOME, pathApi) ?? pathApi.join(homeDir, ".local", "share");
|
|
79
|
+
const xdgStateHome = platformBaseDirectory(env.XDG_STATE_HOME, pathApi) ?? pathApi.join(homeDir, ".local", "state");
|
|
80
|
+
defaultDataDir = pathApi.join(xdgDataHome, "fml");
|
|
81
|
+
defaultStateDir = pathApi.join(xdgStateHome, "fml");
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
32
84
|
}
|
|
33
|
-
|
|
85
|
+
const dataDir = explicitDataDir ?? defaultDataDir;
|
|
86
|
+
const stateDir = explicitStateDir ?? explicitDataDir ?? defaultStateDir;
|
|
87
|
+
const defaultLogDir = nativeLogDir ?? (platform === "win32" ? pathApi.join(defaultDataDir, "logs") : pathApi.join(defaultStateDir, "log"));
|
|
88
|
+
const explicitObserverDir = explicitDirectory(
|
|
89
|
+
env.FML_OBSERVER_DIR,
|
|
90
|
+
"FML_OBSERVER_DIR",
|
|
91
|
+
pathApi
|
|
92
|
+
);
|
|
93
|
+
const legacyPanopticonHome = explicitDirectory(
|
|
94
|
+
env.PANOPTICON_HOME,
|
|
95
|
+
"PANOPTICON_HOME",
|
|
96
|
+
pathApi
|
|
97
|
+
);
|
|
98
|
+
const fmlHome = explicitDirectory(env.FML_HOME, "FML_HOME", pathApi);
|
|
99
|
+
return {
|
|
100
|
+
dataDir,
|
|
101
|
+
stateDir,
|
|
102
|
+
logDir: explicitLogDir ?? defaultLogDir,
|
|
103
|
+
observerDir: explicitObserverDir ?? legacyPanopticonHome ?? (fmlHome ? pathApi.join(fmlHome, "panopticon") : pathApi.join(homeDir, ".fml", "panopticon"))
|
|
104
|
+
};
|
|
34
105
|
}
|
|
35
|
-
function
|
|
106
|
+
function observerPort() {
|
|
107
|
+
const configured = process.env.FML_OBSERVER_PORT ?? process.env.PANOPTICON_PORT;
|
|
108
|
+
if (configured) {
|
|
109
|
+
const parsed = Number.parseInt(configured, 10);
|
|
110
|
+
if (Number.isInteger(parsed) && parsed > 0 && parsed <= 65535) {
|
|
111
|
+
return parsed;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return 4318 + (process.getuid?.() ?? 0) % 100;
|
|
115
|
+
}
|
|
116
|
+
var DIRECTORIES, FML_DATA_DIR, FML_STATE_DIR, FML_LOG_DIR, FML_OBSERVER_DIR, FML_OBSERVER_PORT, FML_OBSERVER_STATE_FILE, FML_OBSERVER_LOCK_FILE, FML_OBSERVER_AUTH_TOKEN_FILE, FML_OBSERVER_ENDPOINT_FILE, FML_OBSERVER_LOG_FILE, FML_UPDATE_LOG_FILE, FML_INSTALLATION_ID_FILE, FML_PANOPTICON_DB_FILE, FML_PANOPTICON_DB_OBSERVER_STATE_FILE, FML_PANOPTICON_DB_OBSERVER_LOCK_FILE, FML_LEGACY_OBSERVER_DB_FILE, FML_HOOK_INBOX_DIR, FML_HOOK_CONTEXT_STATE_DIR, FML_HOOK_CAPTURE_DISABLED_FILE;
|
|
117
|
+
var init_dirs = __esm({
|
|
118
|
+
"src/dirs.ts"() {
|
|
119
|
+
"use strict";
|
|
120
|
+
DIRECTORIES = resolveFmlDirectories();
|
|
121
|
+
FML_DATA_DIR = DIRECTORIES.dataDir;
|
|
122
|
+
FML_STATE_DIR = DIRECTORIES.stateDir;
|
|
123
|
+
FML_LOG_DIR = DIRECTORIES.logDir;
|
|
124
|
+
FML_OBSERVER_DIR = DIRECTORIES.observerDir;
|
|
125
|
+
FML_OBSERVER_PORT = observerPort();
|
|
126
|
+
FML_OBSERVER_STATE_FILE = path3.join(
|
|
127
|
+
FML_OBSERVER_DIR,
|
|
128
|
+
"observer.pid.json"
|
|
129
|
+
);
|
|
130
|
+
FML_OBSERVER_LOCK_FILE = path3.join(
|
|
131
|
+
FML_OBSERVER_DIR,
|
|
132
|
+
"observer.lock"
|
|
133
|
+
);
|
|
134
|
+
FML_OBSERVER_AUTH_TOKEN_FILE = path3.join(
|
|
135
|
+
FML_OBSERVER_DIR,
|
|
136
|
+
"auth-token"
|
|
137
|
+
);
|
|
138
|
+
FML_OBSERVER_ENDPOINT_FILE = path3.join(
|
|
139
|
+
FML_OBSERVER_DIR,
|
|
140
|
+
"endpoint.json"
|
|
141
|
+
);
|
|
142
|
+
FML_OBSERVER_LOG_FILE = path3.join(FML_LOG_DIR, "observer.log");
|
|
143
|
+
FML_UPDATE_LOG_FILE = path3.join(FML_LOG_DIR, "update.log");
|
|
144
|
+
FML_INSTALLATION_ID_FILE = path3.join(
|
|
145
|
+
FML_STATE_DIR,
|
|
146
|
+
"installation_id"
|
|
147
|
+
);
|
|
148
|
+
FML_PANOPTICON_DB_FILE = path3.join(FML_STATE_DIR, "panopticon.db");
|
|
149
|
+
FML_PANOPTICON_DB_OBSERVER_STATE_FILE = `${FML_PANOPTICON_DB_FILE}.observer.pid.json`;
|
|
150
|
+
FML_PANOPTICON_DB_OBSERVER_LOCK_FILE = `${FML_PANOPTICON_DB_FILE}.observer.lock`;
|
|
151
|
+
FML_LEGACY_OBSERVER_DB_FILE = path3.join(
|
|
152
|
+
FML_OBSERVER_DIR,
|
|
153
|
+
"observer.db"
|
|
154
|
+
);
|
|
155
|
+
FML_HOOK_INBOX_DIR = path3.join(FML_OBSERVER_DIR, "hook-inbox");
|
|
156
|
+
FML_HOOK_CONTEXT_STATE_DIR = path3.join(
|
|
157
|
+
FML_OBSERVER_DIR,
|
|
158
|
+
"hook-context-state"
|
|
159
|
+
);
|
|
160
|
+
FML_HOOK_CAPTURE_DISABLED_FILE = path3.join(
|
|
161
|
+
FML_OBSERVER_DIR,
|
|
162
|
+
"hook-capture.disabled"
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// src/config.ts
|
|
168
|
+
import fs2 from "node:fs";
|
|
169
|
+
import path4 from "node:path";
|
|
170
|
+
function isHostedBackendUrl(url) {
|
|
171
|
+
let normalized;
|
|
36
172
|
try {
|
|
37
|
-
|
|
38
|
-
} catch
|
|
39
|
-
|
|
40
|
-
throw error;
|
|
173
|
+
normalized = normalizeConvexUrl(url);
|
|
174
|
+
} catch {
|
|
175
|
+
return false;
|
|
41
176
|
}
|
|
177
|
+
return normalized === DEFAULT_PROD_URL || LEGACY_HOSTED_BACKEND_URLS.includes(normalized);
|
|
42
178
|
}
|
|
43
|
-
function
|
|
44
|
-
return
|
|
45
|
-
(signature) => source.includes(signature)
|
|
46
|
-
));
|
|
179
|
+
function isValidEnvName(envName) {
|
|
180
|
+
return ENV_NAME_RE.test(envName);
|
|
47
181
|
}
|
|
48
|
-
function
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
if (!
|
|
52
|
-
|
|
182
|
+
function normalizeConvexUrl(value) {
|
|
183
|
+
const url = new URL(value);
|
|
184
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
185
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback) || url.username || url.password || url.search || url.hash || url.pathname !== "" && url.pathname !== "/") {
|
|
186
|
+
throw new Error(
|
|
187
|
+
"FML environment URL must be HTTPS (HTTP is allowed only for loopback) with no path, credentials, query, or fragment"
|
|
188
|
+
);
|
|
53
189
|
}
|
|
54
|
-
|
|
55
|
-
return { removed: true, preserved: false };
|
|
190
|
+
return url.origin;
|
|
56
191
|
}
|
|
57
|
-
function
|
|
58
|
-
|
|
59
|
-
const temporaryPath = path.join(
|
|
60
|
-
path.dirname(filePath),
|
|
61
|
-
`.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`
|
|
62
|
-
);
|
|
63
|
-
try {
|
|
64
|
-
fs.writeFileSync(temporaryPath, source, { encoding: "utf8", mode: 384 });
|
|
65
|
-
if (process.platform === "win32" && fs.existsSync(filePath)) {
|
|
66
|
-
fs.rmSync(filePath);
|
|
67
|
-
}
|
|
68
|
-
fs.renameSync(temporaryPath, filePath);
|
|
69
|
-
} finally {
|
|
70
|
-
fs.rmSync(temporaryPath, { force: true });
|
|
71
|
-
}
|
|
192
|
+
function normalizeAppUrl(value) {
|
|
193
|
+
return normalizeConvexUrl(value);
|
|
72
194
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
resolvePiAgentDir(context, pathApi),
|
|
78
|
-
"extensions"
|
|
195
|
+
function readEnvConfig() {
|
|
196
|
+
try {
|
|
197
|
+
const parsed = JSON.parse(
|
|
198
|
+
fs2.readFileSync(ENV_CONFIG_PATH, "utf8")
|
|
79
199
|
);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
context.packageRoot,
|
|
83
|
-
"dist",
|
|
84
|
-
"pi",
|
|
85
|
-
"extension.js"
|
|
86
|
-
),
|
|
87
|
-
extensionsDir,
|
|
88
|
-
destinationPath: pathApi.join(extensionsDir, "fml.js"),
|
|
89
|
-
legacyDestinationPath: pathApi.join(extensionsDir, "panopticon.js")
|
|
90
|
-
};
|
|
91
|
-
},
|
|
92
|
-
install(paths) {
|
|
93
|
-
const source = fs.readFileSync(paths.sourcePath, "utf8");
|
|
94
|
-
if (!source.includes(FML_PI_EXTENSION_MARKER)) {
|
|
95
|
-
throw new Error(
|
|
96
|
-
`Packaged Pi extension is invalid: ${paths.sourcePath} does not contain the FML ownership marker`
|
|
97
|
-
);
|
|
200
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
201
|
+
throw new Error("invalid environment config");
|
|
98
202
|
}
|
|
99
|
-
const
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
status: "preserved",
|
|
104
|
-
destinationPath: paths.destinationPath,
|
|
105
|
-
reason: "an extension not managed by FML already exists at this path",
|
|
106
|
-
legacyRemoved: legacy2.removed,
|
|
107
|
-
legacyPreserved: legacy2.preserved
|
|
108
|
-
};
|
|
203
|
+
const config = parsed;
|
|
204
|
+
if (typeof config.active !== "string" || !isValidEnvName(config.active)) {
|
|
205
|
+
throw new Error("invalid active environment");
|
|
109
206
|
}
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
|
|
207
|
+
const environments = {};
|
|
208
|
+
if (config.environments && typeof config.environments === "object" && !Array.isArray(config.environments)) {
|
|
209
|
+
for (const [name, environment] of Object.entries(
|
|
210
|
+
config.environments
|
|
211
|
+
)) {
|
|
212
|
+
if (!isValidEnvName(name) || !environment || typeof environment !== "object" || Array.isArray(environment)) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const entry = environment;
|
|
216
|
+
if (typeof entry.convexUrl !== "string") continue;
|
|
217
|
+
try {
|
|
218
|
+
const appUrl = typeof entry.appUrl === "string" ? normalizeAppUrl(entry.appUrl) : void 0;
|
|
219
|
+
environments[name] = {
|
|
220
|
+
convexUrl: normalizeConvexUrl(entry.convexUrl),
|
|
221
|
+
...appUrl ? { appUrl } : {}
|
|
222
|
+
};
|
|
223
|
+
} catch {
|
|
224
|
+
}
|
|
225
|
+
}
|
|
113
226
|
}
|
|
114
|
-
|
|
227
|
+
environments[DEFAULT_TARGET_NAME] = { convexUrl: DEFAULT_PROD_URL };
|
|
115
228
|
return {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
legacyPreserved: legacy.preserved
|
|
229
|
+
schemaVersion: 1,
|
|
230
|
+
active: config.active,
|
|
231
|
+
environments
|
|
120
232
|
};
|
|
121
|
-
}
|
|
122
|
-
uninstall(paths) {
|
|
123
|
-
const existing = readFileIfPresent(paths.destinationPath);
|
|
124
|
-
const legacy = removeLegacyExtension(paths.legacyDestinationPath);
|
|
125
|
-
if (existing === null) {
|
|
126
|
-
return {
|
|
127
|
-
status: "absent",
|
|
128
|
-
destinationPath: paths.destinationPath,
|
|
129
|
-
legacyRemoved: legacy.removed,
|
|
130
|
-
legacyPreserved: legacy.preserved
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
if (!isOwnedExtension(existing)) {
|
|
134
|
-
return {
|
|
135
|
-
status: "preserved",
|
|
136
|
-
destinationPath: paths.destinationPath,
|
|
137
|
-
reason: "the existing extension is not managed by FML",
|
|
138
|
-
legacyRemoved: legacy.removed,
|
|
139
|
-
legacyPreserved: legacy.preserved
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
fs.rmSync(paths.destinationPath);
|
|
233
|
+
} catch {
|
|
143
234
|
return {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
235
|
+
schemaVersion: 1,
|
|
236
|
+
active: DEFAULT_TARGET_NAME,
|
|
237
|
+
environments: {
|
|
238
|
+
[DEFAULT_TARGET_NAME]: { convexUrl: DEFAULT_PROD_URL }
|
|
239
|
+
}
|
|
148
240
|
};
|
|
149
241
|
}
|
|
242
|
+
}
|
|
243
|
+
function resolveEnvConvexUrl(envName) {
|
|
244
|
+
const configured = readEnvConfig().environments?.[envName]?.convexUrl;
|
|
245
|
+
if (configured) return configured;
|
|
246
|
+
if (envName === DEFAULT_TARGET_NAME) return DEFAULT_PROD_URL;
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
function getActiveEnv() {
|
|
250
|
+
const config = readEnvConfig();
|
|
251
|
+
const name = config.active;
|
|
252
|
+
const stored = config.environments?.[name];
|
|
253
|
+
const override = process.env.FML_CONVEX_URL;
|
|
254
|
+
const convexUrl = override ? normalizeConvexUrl(override) : resolveEnvConvexUrl(name);
|
|
255
|
+
const appUrl = stored?.appUrl && stored.convexUrl === convexUrl ? stored.appUrl : null;
|
|
256
|
+
return { name, convexUrl, appUrl };
|
|
257
|
+
}
|
|
258
|
+
function authStorePathFor(envName) {
|
|
259
|
+
return path4.join(FML_DATA_DIR, `auth.${envName}.json`);
|
|
260
|
+
}
|
|
261
|
+
function getSiteUrlForConvexUrl(convexUrl) {
|
|
262
|
+
const explicit = process.env.CONVEX_SITE_URL;
|
|
263
|
+
if (explicit) return explicit.replace(/\/$/, "");
|
|
264
|
+
return (convexUrl ?? DEFAULT_PROD_URL).replace(/\/$/, "");
|
|
265
|
+
}
|
|
266
|
+
var DEFAULT_PROD_URL, DEFAULT_SYNC_URL, DEFAULT_TARGET_NAME, LEGACY_HOSTED_BACKEND_URLS, ENV_CONFIG_PATH, ENV_NAME_RE, CONVEX_URL, WORKOS_API_URL;
|
|
267
|
+
var init_config = __esm({
|
|
268
|
+
"src/config.ts"() {
|
|
269
|
+
"use strict";
|
|
270
|
+
init_dirs();
|
|
271
|
+
DEFAULT_PROD_URL = "https://api.fml.inc";
|
|
272
|
+
DEFAULT_SYNC_URL = DEFAULT_PROD_URL;
|
|
273
|
+
DEFAULT_TARGET_NAME = "fml";
|
|
274
|
+
LEGACY_HOSTED_BACKEND_URLS = [
|
|
275
|
+
"https://trustworthy-chihuahua-382.convex.site"
|
|
276
|
+
];
|
|
277
|
+
ENV_CONFIG_PATH = path4.join(FML_DATA_DIR, "env.json");
|
|
278
|
+
ENV_NAME_RE = /^[A-Za-z0-9_-]+$/;
|
|
279
|
+
CONVEX_URL = (process.env.FML_CONVEX_URL ?? getActiveEnv().convexUrl ?? DEFAULT_PROD_URL).replace(/\/$/, "");
|
|
280
|
+
WORKOS_API_URL = "https://api.workos.com";
|
|
281
|
+
}
|
|
150
282
|
});
|
|
151
283
|
|
|
152
|
-
// src/
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
"SessionStart",
|
|
172
|
-
"Setup",
|
|
173
|
-
"InstructionsLoaded",
|
|
174
|
-
"UserPromptSubmit",
|
|
175
|
-
"UserPromptExpansion",
|
|
176
|
-
"PreToolUse",
|
|
177
|
-
"PermissionRequest",
|
|
178
|
-
"PostToolUse",
|
|
179
|
-
"PostToolUseFailure",
|
|
180
|
-
"PostToolBatch",
|
|
181
|
-
"PermissionDenied",
|
|
182
|
-
"Notification",
|
|
183
|
-
"SubagentStart",
|
|
184
|
-
"SubagentStop",
|
|
185
|
-
"TaskCreated",
|
|
186
|
-
"TaskCompleted",
|
|
187
|
-
"Stop",
|
|
188
|
-
"StopFailure",
|
|
189
|
-
"TeammateIdle",
|
|
190
|
-
"ConfigChange",
|
|
191
|
-
"CwdChanged",
|
|
192
|
-
"PreCompact",
|
|
193
|
-
"PostCompact",
|
|
194
|
-
"SessionEnd",
|
|
195
|
-
"Elicitation",
|
|
196
|
-
"ElicitationResult"
|
|
197
|
-
],
|
|
198
|
-
codex: [
|
|
199
|
-
"SessionStart",
|
|
200
|
-
"UserPromptSubmit",
|
|
201
|
-
"PreToolUse",
|
|
202
|
-
"PermissionRequest",
|
|
203
|
-
"PostToolUse",
|
|
204
|
-
"Stop"
|
|
205
|
-
],
|
|
206
|
-
gemini: [
|
|
207
|
-
"SessionStart",
|
|
208
|
-
"BeforeAgent",
|
|
209
|
-
"BeforeTool",
|
|
210
|
-
"AfterTool",
|
|
211
|
-
"AfterAgent"
|
|
212
|
-
],
|
|
213
|
-
grok: [
|
|
214
|
-
"SessionStart",
|
|
215
|
-
"UserPromptSubmit",
|
|
216
|
-
"PreToolUse",
|
|
217
|
-
"PostToolUse",
|
|
218
|
-
"PostToolUseFailure",
|
|
219
|
-
"PermissionDenied",
|
|
220
|
-
"Stop",
|
|
221
|
-
"StopFailure",
|
|
222
|
-
"Notification",
|
|
223
|
-
"SubagentStart",
|
|
224
|
-
"SubagentStop",
|
|
225
|
-
"PreCompact",
|
|
226
|
-
"PostCompact",
|
|
227
|
-
"SessionEnd"
|
|
228
|
-
],
|
|
229
|
-
pi: [
|
|
230
|
-
"SessionStart",
|
|
231
|
-
"UserPromptSubmit",
|
|
232
|
-
"TurnStart",
|
|
233
|
-
"PreToolUse",
|
|
234
|
-
"PostToolUse",
|
|
235
|
-
"PostToolUseFailure",
|
|
236
|
-
"Stop",
|
|
237
|
-
"Notification",
|
|
238
|
-
"ConfigChange",
|
|
239
|
-
"PreCompact",
|
|
240
|
-
"PostCompact",
|
|
241
|
-
"SessionEnd"
|
|
242
|
-
]
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
// ../../packages/agent-targets/src/catalog/hook-events.ts
|
|
246
|
-
var CLAUDE_HOOK_EVENTS = Object.freeze([
|
|
247
|
-
...hook_events_default.claude
|
|
248
|
-
]);
|
|
249
|
-
var CODEX_HOOK_EVENTS = Object.freeze([
|
|
250
|
-
...hook_events_default.codex
|
|
251
|
-
]);
|
|
252
|
-
var GEMINI_HOOK_EVENTS = Object.freeze([
|
|
253
|
-
...hook_events_default.gemini
|
|
254
|
-
]);
|
|
255
|
-
var GROK_HOOK_EVENTS = Object.freeze([
|
|
256
|
-
...hook_events_default.grok
|
|
257
|
-
]);
|
|
258
|
-
var PI_HOOK_EVENTS = Object.freeze([...hook_events_default.pi]);
|
|
284
|
+
// src/sentry.ts
|
|
285
|
+
var sentry, client, Sentry;
|
|
286
|
+
var init_sentry = __esm({
|
|
287
|
+
"src/sentry.ts"() {
|
|
288
|
+
"use strict";
|
|
289
|
+
sentry = null;
|
|
290
|
+
client = null;
|
|
291
|
+
Sentry = {
|
|
292
|
+
captureException: (err) => sentry?.captureException(err),
|
|
293
|
+
captureMessage: (msg) => sentry?.captureMessage(msg),
|
|
294
|
+
flush: async (timeoutMs) => {
|
|
295
|
+
try {
|
|
296
|
+
await client?.flush(timeoutMs);
|
|
297
|
+
} catch {
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
});
|
|
259
303
|
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
"
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
"
|
|
275
|
-
|
|
276
|
-
"
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
"
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
|
|
304
|
+
// src/telemetry-config.ts
|
|
305
|
+
var telemetry_config_exports = {};
|
|
306
|
+
__export(telemetry_config_exports, {
|
|
307
|
+
refreshCliTelemetryConfig: () => refreshCliTelemetryConfig,
|
|
308
|
+
resolveCliSentryConfig: () => resolveCliSentryConfig
|
|
309
|
+
});
|
|
310
|
+
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
311
|
+
import fs3 from "node:fs";
|
|
312
|
+
import path5 from "node:path";
|
|
313
|
+
function parseSentryConfig(value) {
|
|
314
|
+
if (value === null) return null;
|
|
315
|
+
if (!value || typeof value !== "object")
|
|
316
|
+
throw new Error("Invalid Sentry config");
|
|
317
|
+
const config = value;
|
|
318
|
+
if (typeof config.dsn !== "string") throw new Error("Invalid Sentry DSN");
|
|
319
|
+
const url = new URL(config.dsn);
|
|
320
|
+
if (url.protocol !== "https:" || !url.username || url.password || url.search || url.hash || !/^\/(?:.*\/)?\d+$/.test(url.pathname)) {
|
|
321
|
+
throw new Error("Invalid Sentry DSN");
|
|
322
|
+
}
|
|
323
|
+
if (config.environment !== void 0 && (typeof config.environment !== "string" || !config.environment.trim())) {
|
|
324
|
+
throw new Error("Invalid Sentry environment");
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
dsn: config.dsn,
|
|
328
|
+
...typeof config.environment === "string" ? { environment: config.environment } : {}
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function cachePathFor(siteUrl) {
|
|
332
|
+
const key = isHostedBackendUrl(siteUrl) ? DEFAULT_SYNC_URL : siteUrl;
|
|
333
|
+
return path5.join(
|
|
334
|
+
FML_DATA_DIR,
|
|
335
|
+
"telemetry-config",
|
|
336
|
+
`${createHash2("sha256").update(key).digest("hex")}.json`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
function readCache(siteUrl) {
|
|
340
|
+
try {
|
|
341
|
+
const value = JSON.parse(
|
|
342
|
+
fs3.readFileSync(cachePathFor(siteUrl), "utf8")
|
|
343
|
+
);
|
|
344
|
+
if (!value || typeof value !== "object") return void 0;
|
|
345
|
+
const entry = value;
|
|
346
|
+
if (typeof entry.siteUrl !== "string" || entry.siteUrl !== siteUrl && !(isHostedBackendUrl(entry.siteUrl) && isHostedBackendUrl(siteUrl)) || typeof entry.fetchedAt !== "number" || !Number.isFinite(entry.fetchedAt))
|
|
347
|
+
return void 0;
|
|
348
|
+
return {
|
|
349
|
+
siteUrl: entry.siteUrl,
|
|
350
|
+
fetchedAt: entry.fetchedAt,
|
|
351
|
+
...entry.sentry === void 0 ? {} : { sentry: parseSentryConfig(entry.sentry) }
|
|
352
|
+
};
|
|
353
|
+
} catch {
|
|
354
|
+
return void 0;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function writeCache(entry) {
|
|
358
|
+
const cachePath = cachePathFor(entry.siteUrl);
|
|
359
|
+
const temporaryPath = `${cachePath}.${randomUUID2()}.tmp`;
|
|
360
|
+
try {
|
|
361
|
+
fs3.mkdirSync(path5.dirname(cachePath), { recursive: true, mode: 448 });
|
|
362
|
+
fs3.writeFileSync(temporaryPath, JSON.stringify(entry), { mode: 384 });
|
|
363
|
+
fs3.renameSync(temporaryPath, cachePath);
|
|
364
|
+
} catch {
|
|
365
|
+
} finally {
|
|
366
|
+
try {
|
|
367
|
+
fs3.rmSync(temporaryPath, { force: true });
|
|
368
|
+
} catch {
|
|
303
369
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
async function refreshCliTelemetryConfig(opts) {
|
|
373
|
+
try {
|
|
374
|
+
if (opts.signal?.aborted) return;
|
|
375
|
+
const siteUrl = opts.siteUrl ?? getSiteUrlForConvexUrl(resolveEnvConvexUrl(opts.envName));
|
|
376
|
+
if (!opts.siteUrl && !isHostedBackendUrl(siteUrl)) return;
|
|
377
|
+
if (!opts.force) {
|
|
378
|
+
const age = Date.now() - (readCache(siteUrl)?.fetchedAt ?? Number.NaN);
|
|
379
|
+
if (age >= 0 && age < CONFIG_TTL_MS) return;
|
|
380
|
+
}
|
|
381
|
+
const key = cachePathFor(siteUrl);
|
|
382
|
+
let refresh = refreshesInFlight.get(key);
|
|
383
|
+
if (!refresh) {
|
|
384
|
+
refresh = fetchAndCache(siteUrl, opts.accessToken, opts.signal).finally(
|
|
385
|
+
() => refreshesInFlight.delete(key)
|
|
386
|
+
);
|
|
387
|
+
refreshesInFlight.set(key, refresh);
|
|
308
388
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
if (current === "" && (character === "1" || character === "2") && command[index + 1] === ">") {
|
|
314
|
-
if (command[index + 2] === ">") {
|
|
315
|
-
tokens.push(`${character}>>`);
|
|
316
|
-
index += 2;
|
|
317
|
-
} else if (command[index + 2] === "&" && (command[index + 3] === "1" || command[index + 3] === "2")) {
|
|
318
|
-
tokens.push(`${character}>&${command[index + 3]}`);
|
|
319
|
-
index += 3;
|
|
320
|
-
} else {
|
|
321
|
-
tokens.push(`${character}>`);
|
|
322
|
-
index += 1;
|
|
323
|
-
}
|
|
324
|
-
continue;
|
|
325
|
-
}
|
|
326
|
-
if (/[;&|<>]/.test(character)) {
|
|
327
|
-
if (current) {
|
|
328
|
-
tokens.push(current);
|
|
329
|
-
current = "";
|
|
330
|
-
}
|
|
331
|
-
const next = command[index + 1];
|
|
332
|
-
if (character === ">" && next === "&" && (command[index + 2] === "1" || command[index + 2] === "2")) {
|
|
333
|
-
tokens.push(`>&${command[index + 2]}`);
|
|
334
|
-
index += 2;
|
|
335
|
-
continue;
|
|
336
|
-
}
|
|
337
|
-
if (character === "&" && next === "&" || character === "|" && next === "|" || character === "<" && next === "<" || character === ">" && next === ">") {
|
|
338
|
-
tokens.push(`${character}${next}`);
|
|
339
|
-
index += 1;
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
tokens.push(character);
|
|
343
|
-
continue;
|
|
344
|
-
}
|
|
345
|
-
if (/\s/.test(character)) {
|
|
346
|
-
if (current) {
|
|
347
|
-
tokens.push(current);
|
|
348
|
-
current = "";
|
|
349
|
-
}
|
|
350
|
-
continue;
|
|
351
|
-
}
|
|
352
|
-
current += character;
|
|
389
|
+
await refresh;
|
|
390
|
+
} catch {
|
|
353
391
|
}
|
|
354
|
-
if (quote || escaping) return [];
|
|
355
|
-
if (current) tokens.push(current);
|
|
356
|
-
return tokens;
|
|
357
392
|
}
|
|
358
|
-
function
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
393
|
+
async function fetchAndCache(siteUrl, accessToken, signal) {
|
|
394
|
+
const timeout = AbortSignal.timeout(CONFIG_TIMEOUT_MS);
|
|
395
|
+
const response = await fetch(`${siteUrl}/api/telemetry/config`, {
|
|
396
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
397
|
+
signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
|
|
398
|
+
redirect: "error"
|
|
399
|
+
});
|
|
400
|
+
if (!response.ok) throw new Error("Config fetch failed");
|
|
401
|
+
const data = await response.json();
|
|
402
|
+
if (!data || typeof data !== "object" || data.ok !== true)
|
|
403
|
+
throw new Error("Invalid telemetry config");
|
|
404
|
+
const sentry2 = data.sentry;
|
|
405
|
+
writeCache({
|
|
406
|
+
siteUrl,
|
|
407
|
+
fetchedAt: Date.now(),
|
|
408
|
+
...sentry2 === void 0 ? {} : { sentry: parseSentryConfig(sentry2) }
|
|
409
|
+
});
|
|
375
410
|
}
|
|
376
|
-
function
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
411
|
+
async function resolveCliSentryConfig(defaultDsn, forceRefresh = false) {
|
|
412
|
+
if (process.env.FML_SENTRY_DSN !== void 0) {
|
|
413
|
+
return process.env.FML_SENTRY_DSN ? {
|
|
414
|
+
dsn: process.env.FML_SENTRY_DSN,
|
|
415
|
+
environment: process.env.FML_SENTRY_ENVIRONMENT
|
|
416
|
+
} : null;
|
|
417
|
+
}
|
|
418
|
+
const active = getActiveEnv();
|
|
419
|
+
const siteUrl = getSiteUrlForConvexUrl(active.convexUrl);
|
|
420
|
+
const hosted = active.convexUrl !== null && isHostedBackendUrl(active.convexUrl) && isHostedBackendUrl(siteUrl);
|
|
421
|
+
const fallback = hosted && defaultDsn ? {
|
|
422
|
+
dsn: defaultDsn,
|
|
423
|
+
environment: process.env.DEPLOYMENT_ENV ?? process.env.NODE_ENV ?? "production"
|
|
424
|
+
} : null;
|
|
425
|
+
const auth = readTokens(active.name);
|
|
426
|
+
if (!auth || auth.refreshInvalidAt || (auth.siteUrl ? auth.siteUrl !== siteUrl && !(hosted && isHostedBackendUrl(auth.siteUrl)) : !hosted))
|
|
427
|
+
return fallback;
|
|
428
|
+
if (forceRefresh && auth.expiresAt > Date.now()) {
|
|
429
|
+
await refreshCliTelemetryConfig({
|
|
430
|
+
envName: active.name,
|
|
431
|
+
accessToken: auth.accessToken,
|
|
432
|
+
siteUrl: auth.siteUrl,
|
|
433
|
+
force: true
|
|
434
|
+
});
|
|
393
435
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
function readCommandFileArgs(args, flagsWithValues, patternFlags = flagsWithValues) {
|
|
397
|
-
const stripped = stripSimpleOptions(args, flagsWithValues, patternFlags);
|
|
398
|
-
return stripped.consumedPatternFlag ? stripped.args : stripped.args.slice(1);
|
|
436
|
+
const cached = readCache(siteUrl);
|
|
437
|
+
return cached?.sentry === void 0 ? fallback : cached.sentry;
|
|
399
438
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
commandTokens.shift();
|
|
411
|
-
}
|
|
412
|
-
const commandName = commandTokens.shift()?.split("/").pop();
|
|
413
|
-
if (!commandName) return null;
|
|
414
|
-
let args;
|
|
415
|
-
switch (commandName) {
|
|
416
|
-
case "bat":
|
|
417
|
-
case "batcat":
|
|
418
|
-
case "cat":
|
|
419
|
-
case "file":
|
|
420
|
-
case "less":
|
|
421
|
-
case "more":
|
|
422
|
-
case "nl":
|
|
423
|
-
case "stat":
|
|
424
|
-
case "wc":
|
|
425
|
-
args = stripSimpleOptions(commandTokens).args;
|
|
426
|
-
break;
|
|
427
|
-
case "head":
|
|
428
|
-
case "tail":
|
|
429
|
-
args = stripSimpleOptions(commandTokens, /* @__PURE__ */ new Set(["-n", "-c"])).args;
|
|
430
|
-
break;
|
|
431
|
-
case "sed":
|
|
432
|
-
if (commandTokens.some(
|
|
433
|
-
(argument) => argument.startsWith("-i") || argument === "--in-place" || argument.startsWith("--in-place=")
|
|
434
|
-
)) {
|
|
435
|
-
return null;
|
|
436
|
-
}
|
|
437
|
-
args = readCommandFileArgs(commandTokens, /* @__PURE__ */ new Set(["-e", "-f"]));
|
|
438
|
-
break;
|
|
439
|
-
case "awk":
|
|
440
|
-
args = readCommandFileArgs(
|
|
441
|
-
commandTokens,
|
|
442
|
-
/* @__PURE__ */ new Set(["-f", "-v"]),
|
|
443
|
-
/* @__PURE__ */ new Set(["-f"])
|
|
444
|
-
);
|
|
445
|
-
break;
|
|
446
|
-
case "grep":
|
|
447
|
-
case "rg":
|
|
448
|
-
args = readCommandFileArgs(
|
|
449
|
-
commandTokens,
|
|
450
|
-
/* @__PURE__ */ new Set(["-A", "-B", "-C", "-e", "-f", "-g", "-m", "-t"]),
|
|
451
|
-
/* @__PURE__ */ new Set(["-e", "-f"])
|
|
452
|
-
);
|
|
453
|
-
break;
|
|
454
|
-
default:
|
|
455
|
-
return null;
|
|
439
|
+
var CONFIG_TTL_MS, CONFIG_TIMEOUT_MS, refreshesInFlight;
|
|
440
|
+
var init_telemetry_config = __esm({
|
|
441
|
+
"src/telemetry-config.ts"() {
|
|
442
|
+
"use strict";
|
|
443
|
+
init_dirs();
|
|
444
|
+
init_config();
|
|
445
|
+
init_token_store();
|
|
446
|
+
CONFIG_TTL_MS = 60 * 60 * 1e3;
|
|
447
|
+
CONFIG_TIMEOUT_MS = 1500;
|
|
448
|
+
refreshesInFlight = /* @__PURE__ */ new Map();
|
|
456
449
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// src/auth/token-store.ts
|
|
453
|
+
import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
|
|
454
|
+
import { execFileSync } from "node:child_process";
|
|
455
|
+
import fs4 from "node:fs";
|
|
456
|
+
import path6 from "node:path";
|
|
457
|
+
function getCurrentProcessIdentity() {
|
|
458
|
+
let cache = processGlobals[PROCESS_IDENTITY_KEY];
|
|
459
|
+
if (!cache) {
|
|
460
|
+
cache = { resolved: false };
|
|
461
|
+
processGlobals[PROCESS_IDENTITY_KEY] = cache;
|
|
466
462
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
463
|
+
if (!cache.resolved) {
|
|
464
|
+
cache.value = getProcessIdentity(process.pid) ?? void 0;
|
|
465
|
+
cache.resolved = true;
|
|
470
466
|
}
|
|
471
|
-
return
|
|
467
|
+
return cache.value;
|
|
472
468
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
var MAX_EXTRACTED_TEXT_BYTES = 64 * 1024;
|
|
476
|
-
function isRecord(value) {
|
|
477
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
469
|
+
function storePathFor(envName) {
|
|
470
|
+
return authStorePathFor(envName ?? getActiveEnv().name);
|
|
478
471
|
}
|
|
479
|
-
function
|
|
480
|
-
|
|
481
|
-
const value = record[key];
|
|
482
|
-
if (typeof value === "string" && value.length > 0) return value;
|
|
483
|
-
}
|
|
484
|
-
return void 0;
|
|
472
|
+
function resolveEnvName(envName) {
|
|
473
|
+
return envName ?? getActiveEnv().name;
|
|
485
474
|
}
|
|
486
|
-
function
|
|
487
|
-
|
|
488
|
-
|
|
475
|
+
function ensureDir(envName) {
|
|
476
|
+
const dir = path6.dirname(storePathFor(envName));
|
|
477
|
+
fs4.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
489
478
|
try {
|
|
490
|
-
|
|
479
|
+
fs4.chmodSync(dir, 448);
|
|
491
480
|
} catch {
|
|
492
|
-
return void 0;
|
|
493
481
|
}
|
|
494
482
|
}
|
|
495
|
-
function
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
483
|
+
function writeSecretFile(filePath, data) {
|
|
484
|
+
const temporaryFile = `${filePath}.${process.pid}.${randomUUID3()}.tmp`;
|
|
485
|
+
let descriptor = null;
|
|
486
|
+
try {
|
|
487
|
+
descriptor = fs4.openSync(temporaryFile, "wx", 384);
|
|
488
|
+
fs4.writeFileSync(descriptor, data, { encoding: "utf8" });
|
|
489
|
+
fs4.fsyncSync(descriptor);
|
|
490
|
+
fs4.closeSync(descriptor);
|
|
491
|
+
descriptor = null;
|
|
492
|
+
fs4.renameSync(temporaryFile, filePath);
|
|
493
|
+
try {
|
|
494
|
+
fs4.chmodSync(filePath, 384);
|
|
495
|
+
} catch {
|
|
496
|
+
}
|
|
497
|
+
} finally {
|
|
498
|
+
if (descriptor !== null) {
|
|
499
|
+
try {
|
|
500
|
+
fs4.closeSync(descriptor);
|
|
501
|
+
} catch {
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
fs4.unlinkSync(temporaryFile);
|
|
506
|
+
} catch {
|
|
507
|
+
}
|
|
499
508
|
}
|
|
500
|
-
return void 0;
|
|
501
509
|
}
|
|
502
|
-
function
|
|
503
|
-
let
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
low = middle;
|
|
509
|
-
} else {
|
|
510
|
-
high = middle - 1;
|
|
511
|
-
}
|
|
510
|
+
function readTokens(envName) {
|
|
511
|
+
let raw;
|
|
512
|
+
try {
|
|
513
|
+
raw = fs4.readFileSync(storePathFor(envName), "utf-8");
|
|
514
|
+
} catch {
|
|
515
|
+
return null;
|
|
512
516
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
517
|
+
try {
|
|
518
|
+
return parseStoredAuthShape(JSON.parse(raw));
|
|
519
|
+
} catch {
|
|
520
|
+
return null;
|
|
516
521
|
}
|
|
517
|
-
return value.slice(0, end);
|
|
518
|
-
}
|
|
519
|
-
function boundedExtractedText(value, field) {
|
|
520
|
-
if (value === void 0) return void 0;
|
|
521
|
-
const originalBytes = Buffer.byteLength(value, "utf8");
|
|
522
|
-
if (originalBytes <= MAX_EXTRACTED_TEXT_BYTES) return value;
|
|
523
|
-
const digest = createHash("sha256").update(value).digest("hex");
|
|
524
|
-
const marker = `
|
|
525
|
-
[fml:${field}:truncated originalBytes=${originalBytes} sha256=${digest}]`;
|
|
526
|
-
return `${utf8Prefix(
|
|
527
|
-
value,
|
|
528
|
-
MAX_EXTRACTED_TEXT_BYTES - Buffer.byteLength(marker, "utf8")
|
|
529
|
-
)}${marker}`;
|
|
530
522
|
}
|
|
531
|
-
function
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if (isRecord(value)) return value;
|
|
523
|
+
function parseStoredAuthShape(parsed) {
|
|
524
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
525
|
+
return null;
|
|
535
526
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
const value = payload[key];
|
|
541
|
-
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
542
|
-
const milliseconds = Math.round(
|
|
543
|
-
value < 1e10 ? value * 1e3 : value
|
|
544
|
-
);
|
|
545
|
-
if (Number.isSafeInteger(milliseconds) && milliseconds > 0) {
|
|
546
|
-
return milliseconds;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
if (typeof value === "string") {
|
|
550
|
-
const parsed = Date.parse(value);
|
|
551
|
-
if (Number.isSafeInteger(parsed) && parsed >= 0) return parsed;
|
|
552
|
-
}
|
|
527
|
+
const auth = parsed;
|
|
528
|
+
const user = auth.user;
|
|
529
|
+
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))) {
|
|
530
|
+
return null;
|
|
553
531
|
}
|
|
554
|
-
return
|
|
532
|
+
return auth;
|
|
555
533
|
}
|
|
556
|
-
function
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
case "AfterTool":
|
|
564
|
-
return "PostToolUse";
|
|
565
|
-
case "AfterAgent":
|
|
566
|
-
return "Stop";
|
|
567
|
-
default:
|
|
568
|
-
return eventType;
|
|
534
|
+
function readTokensForReconciliation(envName) {
|
|
535
|
+
let raw;
|
|
536
|
+
try {
|
|
537
|
+
raw = fs4.readFileSync(storePathFor(envName), "utf8");
|
|
538
|
+
} catch (error) {
|
|
539
|
+
if (error.code === "ENOENT") return null;
|
|
540
|
+
throw error;
|
|
569
541
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
stop: "Stop",
|
|
579
|
-
stop_failure: "StopFailure",
|
|
580
|
-
notification: "Notification",
|
|
581
|
-
subagent_start: "SubagentStart",
|
|
582
|
-
subagent_stop: "SubagentStop",
|
|
583
|
-
pre_compact: "PreCompact",
|
|
584
|
-
post_compact: "PostCompact",
|
|
585
|
-
session_end: "SessionEnd"
|
|
586
|
-
});
|
|
587
|
-
function grokEventType(eventType) {
|
|
588
|
-
return Object.hasOwn(GROK_EVENT_TYPES, eventType) ? GROK_EVENT_TYPES[eventType] ?? eventType : eventType;
|
|
589
|
-
}
|
|
590
|
-
function geminiUserPrompt(payload) {
|
|
591
|
-
const request = payload.llm_request;
|
|
592
|
-
if (!isRecord(request) || !Array.isArray(request.messages)) return void 0;
|
|
593
|
-
const userMessage = [...request.messages].reverse().find(
|
|
594
|
-
(message) => isRecord(message) && message.role === "user"
|
|
595
|
-
);
|
|
596
|
-
if (!userMessage) return void 0;
|
|
597
|
-
if (typeof userMessage.content === "string") {
|
|
598
|
-
return userMessage.content || void 0;
|
|
542
|
+
let parsed;
|
|
543
|
+
try {
|
|
544
|
+
parsed = JSON.parse(raw);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
throw new CorruptStoredCredentialsError(
|
|
547
|
+
"Stored credentials are not valid JSON",
|
|
548
|
+
{ cause: error }
|
|
549
|
+
);
|
|
599
550
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
551
|
+
const auth = parseStoredAuthShape(parsed);
|
|
552
|
+
if (!auth) {
|
|
553
|
+
throw new CorruptStoredCredentialsError(
|
|
554
|
+
"Stored credentials have an invalid shape"
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
return auth;
|
|
605
558
|
}
|
|
606
|
-
function
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
"sessionId",
|
|
610
|
-
"conversation_id",
|
|
611
|
-
"conversationId"
|
|
612
|
-
]);
|
|
613
|
-
const rawEventType = firstString(payload, [
|
|
614
|
-
"hook_event_name",
|
|
615
|
-
"hookEventName",
|
|
616
|
-
"event_name",
|
|
617
|
-
"eventName",
|
|
618
|
-
"event"
|
|
619
|
-
]);
|
|
620
|
-
if (!sessionId || !rawEventType) return null;
|
|
621
|
-
const eventType = target === "gemini" ? geminiEventType(rawEventType) : target === "grok" ? grokEventType(rawEventType) : rawEventType;
|
|
622
|
-
const toolInput = nestedRecord(payload, ["tool_input", "toolInput"]);
|
|
623
|
-
const cwd = firstString(payload, [
|
|
624
|
-
"cwd",
|
|
625
|
-
"working_directory",
|
|
626
|
-
"workingDirectory",
|
|
627
|
-
"workspaceRoot"
|
|
628
|
-
]) ?? context.cwd;
|
|
629
|
-
const command = boundedExtractedText(
|
|
630
|
-
firstText(payload, ["command"]) ?? (toolInput ? firstText(toolInput, ["command", "cmd"]) : void 0),
|
|
631
|
-
"command"
|
|
632
|
-
);
|
|
633
|
-
const toolName = firstString(payload, ["tool_name", "toolName", "name"]);
|
|
634
|
-
const filePath = firstString(payload, ["file_path", "filePath", "path"]) ?? (toolInput ? firstString(toolInput, ["file_path", "filePath", "path"]) : void 0) ?? (command && toolName && SHELL_TOOL_NAMES.has(toolName.toLowerCase()) ? extractReadFilePathFromCommand(command) ?? void 0 : void 0);
|
|
635
|
-
const userPrompt = boundedExtractedText(
|
|
636
|
-
firstText(payload, ["prompt", "user_prompt", "userPrompt"]) ?? (target === "gemini" ? geminiUserPrompt(payload) : void 0),
|
|
637
|
-
"user_prompt"
|
|
638
|
-
);
|
|
639
|
-
const plan = boundedExtractedText(firstText(payload, ["plan"]), "plan");
|
|
640
|
-
const allowedPrompts = boundedExtractedText(
|
|
641
|
-
firstText(payload, ["allowed_prompts", "allowedPrompts"]),
|
|
642
|
-
"allowed_prompts"
|
|
643
|
-
);
|
|
644
|
-
const toolResult = boundedExtractedText(
|
|
645
|
-
firstText(payload, [
|
|
646
|
-
"tool_result",
|
|
647
|
-
"toolResult",
|
|
648
|
-
"tool_response",
|
|
649
|
-
"toolResponse"
|
|
650
|
-
]),
|
|
651
|
-
"tool_result"
|
|
652
|
-
);
|
|
653
|
-
const nativeEventId = firstString(payload, [
|
|
654
|
-
"hook_id",
|
|
655
|
-
"hookId",
|
|
656
|
-
"event_id",
|
|
657
|
-
"eventId",
|
|
658
|
-
"tool_use_id",
|
|
659
|
-
"toolUseId",
|
|
660
|
-
"tool_call_id",
|
|
661
|
-
"toolCallId",
|
|
662
|
-
"prompt_id",
|
|
663
|
-
"promptId"
|
|
664
|
-
]) ?? (eventType === "UserPromptSubmit" || eventType === "Stop" ? firstString(payload, ["turn_id", "turnId"]) : void 0);
|
|
665
|
-
return {
|
|
666
|
-
sessionId,
|
|
667
|
-
eventType,
|
|
668
|
-
timestampMs: timestampMs(payload, context.nowMs),
|
|
669
|
-
...nativeEventId ? { nativeEventId } : {},
|
|
670
|
-
...cwd ? { cwd } : {},
|
|
671
|
-
...toolName ? { toolName } : {},
|
|
672
|
-
...userPrompt ? { userPrompt } : {},
|
|
673
|
-
...filePath ? { filePath } : {},
|
|
674
|
-
...command ? { command } : {},
|
|
675
|
-
...plan ? { plan } : {},
|
|
676
|
-
...allowedPrompts ? { allowedPrompts } : {},
|
|
677
|
-
...toolResult ? { toolResult } : {}
|
|
678
|
-
};
|
|
559
|
+
function writeTokensUnlocked(auth, envName) {
|
|
560
|
+
ensureDir(envName);
|
|
561
|
+
writeSecretFile(storePathFor(envName), JSON.stringify(auth, null, 2));
|
|
679
562
|
}
|
|
680
|
-
function
|
|
681
|
-
return
|
|
682
|
-
targetId,
|
|
683
|
-
nativeEvents,
|
|
684
|
-
normalize: (payload, context) => normalizedEvent(targetId, payload, context)
|
|
685
|
-
});
|
|
563
|
+
function credentialHash(value) {
|
|
564
|
+
return createHash3("sha256").update(value).digest("hex");
|
|
686
565
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
capability("codex", CODEX_HOOK_EVENTS),
|
|
690
|
-
capability("gemini", GEMINI_HOOK_EVENTS),
|
|
691
|
-
capability("grok", GROK_HOOK_EVENTS),
|
|
692
|
-
capability("pi", PI_HOOK_EVENTS)
|
|
693
|
-
]);
|
|
694
|
-
|
|
695
|
-
// src/auth/token-store.ts
|
|
696
|
-
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
697
|
-
import { execFileSync } from "node:child_process";
|
|
698
|
-
import fs3 from "node:fs";
|
|
699
|
-
import path5 from "node:path";
|
|
700
|
-
|
|
701
|
-
// src/config.ts
|
|
702
|
-
import fs2 from "node:fs";
|
|
703
|
-
import path4 from "node:path";
|
|
704
|
-
|
|
705
|
-
// src/dirs.ts
|
|
706
|
-
import os from "node:os";
|
|
707
|
-
import path3 from "node:path";
|
|
708
|
-
function isPresent(value) {
|
|
709
|
-
return value !== void 0 && value.trim().length > 0;
|
|
566
|
+
function describeError(error) {
|
|
567
|
+
return error instanceof Error ? error.message : String(error);
|
|
710
568
|
}
|
|
711
|
-
function
|
|
712
|
-
|
|
713
|
-
|
|
569
|
+
function getSyncDestinationAuthBinding(envName) {
|
|
570
|
+
const resolvedEnvName = resolveEnvName(envName);
|
|
571
|
+
const envToken = process.env.FML_TOKEN;
|
|
572
|
+
if (envToken?.startsWith("fml_srt_")) {
|
|
573
|
+
return {
|
|
574
|
+
destinationIdentity: `service-refresh:${credentialHash(envToken)}`
|
|
575
|
+
};
|
|
714
576
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
577
|
+
if (envToken?.startsWith("fml_st_")) {
|
|
578
|
+
return {
|
|
579
|
+
destinationIdentity: `service-access:${credentialHash(envToken)}`
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
const stored = readTokens(resolvedEnvName);
|
|
583
|
+
if (!stored) return null;
|
|
584
|
+
if (stored.tokenType === "service") {
|
|
585
|
+
return {
|
|
586
|
+
destinationIdentity: `service-refresh:${credentialHash(stored.refreshToken)}`
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
const destinationIdentity = `oauth-user:${stored.user.id}`;
|
|
590
|
+
if (stored.orgId && stored.orgSlug) {
|
|
591
|
+
return {
|
|
592
|
+
destinationIdentity,
|
|
593
|
+
organizationId: stored.orgId,
|
|
594
|
+
organizationSlug: stored.orgSlug
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
return { destinationIdentity, organizationSelectionRequired: true };
|
|
720
598
|
}
|
|
721
|
-
function
|
|
722
|
-
|
|
723
|
-
return pathApi.normalize(value);
|
|
599
|
+
function getSyncDestinationIdentity(envName) {
|
|
600
|
+
return getSyncDestinationAuthBinding(envName)?.destinationIdentity ?? null;
|
|
724
601
|
}
|
|
725
|
-
function
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
"FML_LOG_DIR",
|
|
747
|
-
pathApi
|
|
748
|
-
);
|
|
749
|
-
let defaultDataDir;
|
|
750
|
-
let defaultStateDir;
|
|
751
|
-
let nativeLogDir;
|
|
752
|
-
switch (platform) {
|
|
753
|
-
case "darwin": {
|
|
754
|
-
defaultDataDir = pathApi.join(
|
|
755
|
-
homeDir,
|
|
756
|
-
"Library",
|
|
757
|
-
"Application Support",
|
|
758
|
-
"fml"
|
|
759
|
-
);
|
|
760
|
-
defaultStateDir = defaultDataDir;
|
|
761
|
-
nativeLogDir = pathApi.join(homeDir, "Library", "Logs", "fml");
|
|
762
|
-
break;
|
|
763
|
-
}
|
|
764
|
-
case "win32": {
|
|
765
|
-
const roamingAppData = platformBaseDirectory(env.APPDATA, pathApi) ?? pathApi.join(homeDir, "AppData", "Roaming");
|
|
766
|
-
const localAppData = platformBaseDirectory(env.LOCALAPPDATA, pathApi) ?? pathApi.join(homeDir, "AppData", "Local");
|
|
767
|
-
defaultDataDir = pathApi.join(roamingAppData, "fml");
|
|
768
|
-
defaultStateDir = pathApi.join(localAppData, "fml");
|
|
769
|
-
break;
|
|
770
|
-
}
|
|
771
|
-
default: {
|
|
772
|
-
const xdgDataHome = platformBaseDirectory(env.XDG_DATA_HOME, pathApi) ?? pathApi.join(homeDir, ".local", "share");
|
|
773
|
-
const xdgStateHome = platformBaseDirectory(env.XDG_STATE_HOME, pathApi) ?? pathApi.join(homeDir, ".local", "state");
|
|
774
|
-
defaultDataDir = pathApi.join(xdgDataHome, "fml");
|
|
775
|
-
defaultStateDir = pathApi.join(xdgStateHome, "fml");
|
|
776
|
-
break;
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
const dataDir = explicitDataDir ?? defaultDataDir;
|
|
780
|
-
const stateDir = explicitStateDir ?? explicitDataDir ?? defaultStateDir;
|
|
781
|
-
const defaultLogDir = nativeLogDir ?? (platform === "win32" ? pathApi.join(defaultDataDir, "logs") : pathApi.join(defaultStateDir, "log"));
|
|
782
|
-
const explicitObserverDir = explicitDirectory(
|
|
783
|
-
env.FML_OBSERVER_DIR,
|
|
784
|
-
"FML_OBSERVER_DIR",
|
|
785
|
-
pathApi
|
|
786
|
-
);
|
|
787
|
-
const legacyPanopticonHome = explicitDirectory(
|
|
788
|
-
env.PANOPTICON_HOME,
|
|
789
|
-
"PANOPTICON_HOME",
|
|
790
|
-
pathApi
|
|
791
|
-
);
|
|
792
|
-
const fmlHome = explicitDirectory(env.FML_HOME, "FML_HOME", pathApi);
|
|
793
|
-
return {
|
|
794
|
-
dataDir,
|
|
795
|
-
stateDir,
|
|
796
|
-
logDir: explicitLogDir ?? defaultLogDir,
|
|
797
|
-
observerDir: explicitObserverDir ?? legacyPanopticonHome ?? (fmlHome ? pathApi.join(fmlHome, "panopticon") : pathApi.join(homeDir, ".fml", "panopticon"))
|
|
798
|
-
};
|
|
602
|
+
function waitForTokenRefresh(promise, signal) {
|
|
603
|
+
if (!signal) return promise;
|
|
604
|
+
if (signal.aborted) return Promise.resolve(null);
|
|
605
|
+
return new Promise((resolve, reject) => {
|
|
606
|
+
const onAbort = () => {
|
|
607
|
+
cleanup();
|
|
608
|
+
resolve(null);
|
|
609
|
+
};
|
|
610
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
611
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
612
|
+
void promise.then(
|
|
613
|
+
(value) => {
|
|
614
|
+
cleanup();
|
|
615
|
+
resolve(value);
|
|
616
|
+
},
|
|
617
|
+
(error) => {
|
|
618
|
+
cleanup();
|
|
619
|
+
reject(error);
|
|
620
|
+
}
|
|
621
|
+
);
|
|
622
|
+
});
|
|
799
623
|
}
|
|
800
|
-
function
|
|
801
|
-
const
|
|
802
|
-
if (
|
|
803
|
-
|
|
804
|
-
if (Number.isInteger(parsed) && parsed > 0 && parsed <= 65535) {
|
|
805
|
-
return parsed;
|
|
806
|
-
}
|
|
624
|
+
function getOrStartTokenRefresh(flights, key, forceRefresh, refresh, signal) {
|
|
625
|
+
const existing = flights.get(key);
|
|
626
|
+
if (existing && (!forceRefresh || existing.forceRefresh)) {
|
|
627
|
+
return waitForTokenRefresh(existing.promise, signal);
|
|
807
628
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
var FML_OBSERVER_STATE_FILE = path3.join(
|
|
817
|
-
FML_OBSERVER_DIR,
|
|
818
|
-
"observer.pid.json"
|
|
819
|
-
);
|
|
820
|
-
var FML_OBSERVER_LOCK_FILE = path3.join(
|
|
821
|
-
FML_OBSERVER_DIR,
|
|
822
|
-
"observer.lock"
|
|
823
|
-
);
|
|
824
|
-
var FML_OBSERVER_AUTH_TOKEN_FILE = path3.join(
|
|
825
|
-
FML_OBSERVER_DIR,
|
|
826
|
-
"auth-token"
|
|
827
|
-
);
|
|
828
|
-
var FML_OBSERVER_ENDPOINT_FILE = path3.join(
|
|
829
|
-
FML_OBSERVER_DIR,
|
|
830
|
-
"endpoint.json"
|
|
831
|
-
);
|
|
832
|
-
var FML_OBSERVER_LOG_FILE = path3.join(FML_LOG_DIR, "observer.log");
|
|
833
|
-
var FML_UPDATE_LOG_FILE = path3.join(FML_LOG_DIR, "update.log");
|
|
834
|
-
var FML_INSTALLATION_ID_FILE = path3.join(
|
|
835
|
-
FML_STATE_DIR,
|
|
836
|
-
"installation_id"
|
|
837
|
-
);
|
|
838
|
-
var FML_PANOPTICON_DB_FILE = path3.join(FML_STATE_DIR, "panopticon.db");
|
|
839
|
-
var FML_PANOPTICON_DB_OBSERVER_STATE_FILE = `${FML_PANOPTICON_DB_FILE}.observer.pid.json`;
|
|
840
|
-
var FML_PANOPTICON_DB_OBSERVER_LOCK_FILE = `${FML_PANOPTICON_DB_FILE}.observer.lock`;
|
|
841
|
-
var FML_LEGACY_OBSERVER_DB_FILE = path3.join(
|
|
842
|
-
FML_OBSERVER_DIR,
|
|
843
|
-
"observer.db"
|
|
844
|
-
);
|
|
845
|
-
var FML_HOOK_INBOX_DIR = path3.join(FML_OBSERVER_DIR, "hook-inbox");
|
|
846
|
-
var FML_HOOK_CONTEXT_STATE_DIR = path3.join(
|
|
847
|
-
FML_OBSERVER_DIR,
|
|
848
|
-
"hook-context-state"
|
|
849
|
-
);
|
|
850
|
-
var FML_HOOK_CAPTURE_DISABLED_FILE = path3.join(
|
|
851
|
-
FML_OBSERVER_DIR,
|
|
852
|
-
"hook-capture.disabled"
|
|
853
|
-
);
|
|
854
|
-
|
|
855
|
-
// src/config.ts
|
|
856
|
-
var DEFAULT_PROD_URL = "https://api.fml.inc";
|
|
857
|
-
var DEFAULT_TARGET_NAME = "fml";
|
|
858
|
-
var ENV_CONFIG_PATH = path4.join(FML_DATA_DIR, "env.json");
|
|
859
|
-
var ENV_NAME_RE = /^[A-Za-z0-9_-]+$/;
|
|
860
|
-
function isValidEnvName(envName) {
|
|
861
|
-
return ENV_NAME_RE.test(envName);
|
|
629
|
+
const operation = existing ? existing.promise.then(refresh, refresh) : refresh();
|
|
630
|
+
let flight;
|
|
631
|
+
const promise = operation.finally(() => {
|
|
632
|
+
if (flights.get(key) === flight) flights.delete(key);
|
|
633
|
+
});
|
|
634
|
+
flight = { forceRefresh, promise };
|
|
635
|
+
flights.set(key, flight);
|
|
636
|
+
return waitForTokenRefresh(promise, signal);
|
|
862
637
|
}
|
|
863
|
-
function
|
|
864
|
-
|
|
865
|
-
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
866
|
-
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback) || url.username || url.password || url.search || url.hash || url.pathname !== "" && url.pathname !== "/") {
|
|
867
|
-
throw new Error(
|
|
868
|
-
"FML environment URL must be HTTPS (HTTP is allowed only for loopback) with no path, credentials, query, or fragment"
|
|
869
|
-
);
|
|
870
|
-
}
|
|
871
|
-
return url.origin;
|
|
638
|
+
function refreshLockPath(envName) {
|
|
639
|
+
return `${storePathFor(envName)}.refresh.lock`;
|
|
872
640
|
}
|
|
873
|
-
function
|
|
874
|
-
return
|
|
641
|
+
function pendingOAuthRefreshPrefix(envName) {
|
|
642
|
+
return `${path6.basename(storePathFor(envName))}.refresh-result.`;
|
|
875
643
|
}
|
|
876
|
-
function
|
|
644
|
+
function parsePendingOAuthRefresh(raw) {
|
|
877
645
|
try {
|
|
878
|
-
const parsed = JSON.parse(
|
|
879
|
-
|
|
880
|
-
)
|
|
881
|
-
|
|
882
|
-
throw new Error("invalid environment config");
|
|
883
|
-
}
|
|
884
|
-
const config = parsed;
|
|
885
|
-
if (typeof config.active !== "string" || !isValidEnvName(config.active)) {
|
|
886
|
-
throw new Error("invalid active environment");
|
|
887
|
-
}
|
|
888
|
-
const environments = {};
|
|
889
|
-
if (config.environments && typeof config.environments === "object" && !Array.isArray(config.environments)) {
|
|
890
|
-
for (const [name, environment] of Object.entries(
|
|
891
|
-
config.environments
|
|
892
|
-
)) {
|
|
893
|
-
if (!isValidEnvName(name) || !environment || typeof environment !== "object" || Array.isArray(environment)) {
|
|
894
|
-
continue;
|
|
895
|
-
}
|
|
896
|
-
const entry = environment;
|
|
897
|
-
if (typeof entry.convexUrl !== "string") continue;
|
|
898
|
-
try {
|
|
899
|
-
const appUrl = typeof entry.appUrl === "string" ? normalizeAppUrl(entry.appUrl) : void 0;
|
|
900
|
-
environments[name] = {
|
|
901
|
-
convexUrl: normalizeConvexUrl(entry.convexUrl),
|
|
902
|
-
...appUrl ? { appUrl } : {}
|
|
903
|
-
};
|
|
904
|
-
} catch {
|
|
905
|
-
}
|
|
906
|
-
}
|
|
646
|
+
const parsed = JSON.parse(raw);
|
|
647
|
+
const refreshed = parsed.refreshed;
|
|
648
|
+
if (parsed.version !== 1 || typeof parsed.previousRefreshTokenHash !== "string" || !/^[a-f0-9]{64}$/.test(parsed.previousRefreshTokenHash) || typeof parsed.createdAtMs !== "number" || !Number.isFinite(parsed.createdAtMs) || !refreshed || typeof refreshed.accessToken !== "string" || typeof refreshed.refreshToken !== "string" || typeof refreshed.expiresAt !== "number" || !Number.isFinite(refreshed.expiresAt) || !refreshed.user || typeof refreshed.user.id !== "string" || typeof refreshed.user.email !== "string" || typeof refreshed.user.name !== "string") {
|
|
649
|
+
return null;
|
|
907
650
|
}
|
|
908
|
-
|
|
909
|
-
return {
|
|
910
|
-
schemaVersion: 1,
|
|
911
|
-
active: config.active,
|
|
912
|
-
environments
|
|
913
|
-
};
|
|
651
|
+
return parsed;
|
|
914
652
|
} catch {
|
|
915
|
-
return
|
|
916
|
-
schemaVersion: 1,
|
|
917
|
-
active: DEFAULT_TARGET_NAME,
|
|
918
|
-
environments: {
|
|
919
|
-
[DEFAULT_TARGET_NAME]: { convexUrl: DEFAULT_PROD_URL }
|
|
920
|
-
}
|
|
921
|
-
};
|
|
653
|
+
return null;
|
|
922
654
|
}
|
|
923
655
|
}
|
|
924
|
-
function
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
const appUrl = stored?.appUrl && stored.convexUrl === convexUrl ? stored.appUrl : null;
|
|
937
|
-
return { name, convexUrl, appUrl };
|
|
938
|
-
}
|
|
939
|
-
function authStorePathFor(envName) {
|
|
940
|
-
return path4.join(FML_DATA_DIR, `auth.${envName}.json`);
|
|
941
|
-
}
|
|
942
|
-
var CONVEX_URL = (process.env.FML_CONVEX_URL ?? getActiveEnv().convexUrl ?? DEFAULT_PROD_URL).replace(/\/$/, "");
|
|
943
|
-
var WORKOS_API_URL = "https://api.workos.com";
|
|
944
|
-
function getSiteUrlForConvexUrl(convexUrl) {
|
|
945
|
-
const explicit = process.env.CONVEX_SITE_URL;
|
|
946
|
-
if (explicit) return explicit.replace(/\/$/, "");
|
|
947
|
-
return (convexUrl ?? DEFAULT_PROD_URL).replace(/\/$/, "");
|
|
656
|
+
function stagePendingOAuthRefresh(previousRefreshToken, refreshed, envName) {
|
|
657
|
+
ensureDir(envName);
|
|
658
|
+
const resultPath = `${storePathFor(envName)}.refresh-result.${Date.now()}.${randomUUID3()}.json`;
|
|
659
|
+
const pending = {
|
|
660
|
+
version: 1,
|
|
661
|
+
previousRefreshTokenHash: credentialHash(previousRefreshToken),
|
|
662
|
+
refreshed,
|
|
663
|
+
createdAtMs: Date.now()
|
|
664
|
+
};
|
|
665
|
+
writeSecretFile(resultPath, `${JSON.stringify(pending)}
|
|
666
|
+
`);
|
|
667
|
+
return resultPath;
|
|
948
668
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
try {
|
|
958
|
-
await client?.flush(timeoutMs);
|
|
959
|
-
} catch {
|
|
669
|
+
function removePendingOAuthRefresh(resultPath) {
|
|
670
|
+
try {
|
|
671
|
+
fs4.unlinkSync(resultPath);
|
|
672
|
+
} catch (error) {
|
|
673
|
+
if (error.code !== "ENOENT") {
|
|
674
|
+
console.error(
|
|
675
|
+
`[fml] Auth: unable to remove a committed refresh result: ${error instanceof Error ? error.message : String(error)}`
|
|
676
|
+
);
|
|
960
677
|
}
|
|
961
678
|
}
|
|
962
|
-
};
|
|
963
|
-
|
|
964
|
-
// src/auth/token-store.ts
|
|
965
|
-
var TOKEN_REFRESH_WINDOW_MS = 6e4;
|
|
966
|
-
var OAUTH_REFRESH_LOCK_POLL_MS = 50;
|
|
967
|
-
var OAUTH_REFRESH_LOCK_STALE_MS = 45e3;
|
|
968
|
-
var OAUTH_REFRESH_RECOVERY_STALE_MS = 45e3;
|
|
969
|
-
var OAUTH_REFRESH_LOCK_WAIT_MS = 6e4;
|
|
970
|
-
var OAUTH_REFRESH_REQUEST_TIMEOUT_MS = 3e4;
|
|
971
|
-
var PROCESS_IDENTITY_STALE_MS = 45e3;
|
|
972
|
-
var PROCESS_IDENTITY_RECHECK_MS = 5e3;
|
|
973
|
-
var PROCESS_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("@fml-inc/fml:auth-process-instance");
|
|
974
|
-
var PROCESS_IDENTITY_KEY = /* @__PURE__ */ Symbol.for("@fml-inc/fml:auth-process-identity");
|
|
975
|
-
var processGlobals = globalThis;
|
|
976
|
-
var existingProcessInstanceId = processGlobals[PROCESS_INSTANCE_KEY];
|
|
977
|
-
var CURRENT_PROCESS_INSTANCE_ID = typeof existingProcessInstanceId === "string" ? existingProcessInstanceId : randomUUID2();
|
|
978
|
-
processGlobals[PROCESS_INSTANCE_KEY] = CURRENT_PROCESS_INSTANCE_ID;
|
|
979
|
-
function getCurrentProcessIdentity() {
|
|
980
|
-
let cache = processGlobals[PROCESS_IDENTITY_KEY];
|
|
981
|
-
if (!cache) {
|
|
982
|
-
cache = { resolved: false };
|
|
983
|
-
processGlobals[PROCESS_IDENTITY_KEY] = cache;
|
|
984
|
-
}
|
|
985
|
-
if (!cache.resolved) {
|
|
986
|
-
cache.value = getProcessIdentity(process.pid) ?? void 0;
|
|
987
|
-
cache.resolved = true;
|
|
988
|
-
}
|
|
989
|
-
return cache.value;
|
|
990
|
-
}
|
|
991
|
-
function storePathFor(envName) {
|
|
992
|
-
return authStorePathFor(envName ?? getActiveEnv().name);
|
|
993
|
-
}
|
|
994
|
-
function resolveEnvName(envName) {
|
|
995
|
-
return envName ?? getActiveEnv().name;
|
|
996
679
|
}
|
|
997
|
-
function
|
|
998
|
-
const
|
|
999
|
-
|
|
680
|
+
function reconcilePendingOAuthRefreshes(envName, unreadableStorePolicy = "preserve-staged") {
|
|
681
|
+
const storePath = storePathFor(envName);
|
|
682
|
+
const directory = path6.dirname(storePath);
|
|
683
|
+
const prefix = pendingOAuthRefreshPrefix(envName);
|
|
684
|
+
let resultPaths;
|
|
1000
685
|
try {
|
|
1001
|
-
|
|
1002
|
-
} catch {
|
|
686
|
+
resultPaths = fs4.readdirSync(directory).filter((entry) => entry.startsWith(prefix) && entry.endsWith(".json")).map((entry) => path6.join(directory, entry));
|
|
687
|
+
} catch (error) {
|
|
688
|
+
if (error.code === "ENOENT") return;
|
|
689
|
+
throw error;
|
|
1003
690
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
const temporaryFile = `${filePath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
1007
|
-
let descriptor = null;
|
|
1008
|
-
try {
|
|
1009
|
-
descriptor = fs3.openSync(temporaryFile, "wx", 384);
|
|
1010
|
-
fs3.writeFileSync(descriptor, data, { encoding: "utf8" });
|
|
1011
|
-
fs3.fsyncSync(descriptor);
|
|
1012
|
-
fs3.closeSync(descriptor);
|
|
1013
|
-
descriptor = null;
|
|
1014
|
-
fs3.renameSync(temporaryFile, filePath);
|
|
691
|
+
const pendingResults = resultPaths.map((resultPath) => {
|
|
692
|
+
let pending = null;
|
|
1015
693
|
try {
|
|
1016
|
-
|
|
1017
|
-
} catch {
|
|
1018
|
-
|
|
1019
|
-
} finally {
|
|
1020
|
-
if (descriptor !== null) {
|
|
1021
|
-
try {
|
|
1022
|
-
fs3.closeSync(descriptor);
|
|
1023
|
-
} catch {
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
try {
|
|
1027
|
-
fs3.unlinkSync(temporaryFile);
|
|
1028
|
-
} catch {
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
function readTokens(envName) {
|
|
1033
|
-
let raw;
|
|
1034
|
-
try {
|
|
1035
|
-
raw = fs3.readFileSync(storePathFor(envName), "utf-8");
|
|
1036
|
-
} catch {
|
|
1037
|
-
return null;
|
|
1038
|
-
}
|
|
1039
|
-
try {
|
|
1040
|
-
return parseStoredAuthShape(JSON.parse(raw));
|
|
1041
|
-
} catch {
|
|
1042
|
-
return null;
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
var CorruptStoredCredentialsError = class extends Error {
|
|
1046
|
-
};
|
|
1047
|
-
function parseStoredAuthShape(parsed) {
|
|
1048
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1049
|
-
return null;
|
|
1050
|
-
}
|
|
1051
|
-
const auth = parsed;
|
|
1052
|
-
const user = auth.user;
|
|
1053
|
-
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))) {
|
|
1054
|
-
return null;
|
|
1055
|
-
}
|
|
1056
|
-
return auth;
|
|
1057
|
-
}
|
|
1058
|
-
function readTokensForReconciliation(envName) {
|
|
1059
|
-
let raw;
|
|
1060
|
-
try {
|
|
1061
|
-
raw = fs3.readFileSync(storePathFor(envName), "utf8");
|
|
1062
|
-
} catch (error) {
|
|
1063
|
-
if (error.code === "ENOENT") return null;
|
|
1064
|
-
throw error;
|
|
1065
|
-
}
|
|
1066
|
-
let parsed;
|
|
1067
|
-
try {
|
|
1068
|
-
parsed = JSON.parse(raw);
|
|
1069
|
-
} catch (error) {
|
|
1070
|
-
throw new CorruptStoredCredentialsError(
|
|
1071
|
-
"Stored credentials are not valid JSON",
|
|
1072
|
-
{ cause: error }
|
|
1073
|
-
);
|
|
1074
|
-
}
|
|
1075
|
-
const auth = parseStoredAuthShape(parsed);
|
|
1076
|
-
if (!auth) {
|
|
1077
|
-
throw new CorruptStoredCredentialsError(
|
|
1078
|
-
"Stored credentials have an invalid shape"
|
|
1079
|
-
);
|
|
1080
|
-
}
|
|
1081
|
-
return auth;
|
|
1082
|
-
}
|
|
1083
|
-
function writeTokensUnlocked(auth, envName) {
|
|
1084
|
-
ensureDir(envName);
|
|
1085
|
-
writeSecretFile(storePathFor(envName), JSON.stringify(auth, null, 2));
|
|
1086
|
-
}
|
|
1087
|
-
function credentialHash(value) {
|
|
1088
|
-
return createHash2("sha256").update(value).digest("hex");
|
|
1089
|
-
}
|
|
1090
|
-
function describeError(error) {
|
|
1091
|
-
return error instanceof Error ? error.message : String(error);
|
|
1092
|
-
}
|
|
1093
|
-
function getSyncDestinationAuthBinding(envName) {
|
|
1094
|
-
const resolvedEnvName = resolveEnvName(envName);
|
|
1095
|
-
const envToken = process.env.FML_TOKEN;
|
|
1096
|
-
if (envToken?.startsWith("fml_srt_")) {
|
|
1097
|
-
return {
|
|
1098
|
-
destinationIdentity: `service-refresh:${credentialHash(envToken)}`
|
|
1099
|
-
};
|
|
1100
|
-
}
|
|
1101
|
-
if (envToken?.startsWith("fml_st_")) {
|
|
1102
|
-
return {
|
|
1103
|
-
destinationIdentity: `service-access:${credentialHash(envToken)}`
|
|
1104
|
-
};
|
|
1105
|
-
}
|
|
1106
|
-
const stored = readTokens(resolvedEnvName);
|
|
1107
|
-
if (!stored) return null;
|
|
1108
|
-
if (stored.tokenType === "service") {
|
|
1109
|
-
return {
|
|
1110
|
-
destinationIdentity: `service-refresh:${credentialHash(stored.refreshToken)}`
|
|
1111
|
-
};
|
|
1112
|
-
}
|
|
1113
|
-
const destinationIdentity = `oauth-user:${stored.user.id}`;
|
|
1114
|
-
if (stored.orgId && stored.orgSlug) {
|
|
1115
|
-
return {
|
|
1116
|
-
destinationIdentity,
|
|
1117
|
-
organizationId: stored.orgId,
|
|
1118
|
-
organizationSlug: stored.orgSlug
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
return { destinationIdentity, organizationSelectionRequired: true };
|
|
1122
|
-
}
|
|
1123
|
-
function getSyncDestinationIdentity(envName) {
|
|
1124
|
-
return getSyncDestinationAuthBinding(envName)?.destinationIdentity ?? null;
|
|
1125
|
-
}
|
|
1126
|
-
var serviceTokenCaches = /* @__PURE__ */ new Map();
|
|
1127
|
-
var serviceRefreshPromises = /* @__PURE__ */ new Map();
|
|
1128
|
-
var oauthRefreshPromises = /* @__PURE__ */ new Map();
|
|
1129
|
-
var cacheKey = (envName) => envName;
|
|
1130
|
-
function waitForTokenRefresh(promise, signal) {
|
|
1131
|
-
if (!signal) return promise;
|
|
1132
|
-
if (signal.aborted) return Promise.resolve(null);
|
|
1133
|
-
return new Promise((resolve, reject) => {
|
|
1134
|
-
const onAbort = () => {
|
|
1135
|
-
cleanup();
|
|
1136
|
-
resolve(null);
|
|
1137
|
-
};
|
|
1138
|
-
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
1139
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1140
|
-
void promise.then(
|
|
1141
|
-
(value) => {
|
|
1142
|
-
cleanup();
|
|
1143
|
-
resolve(value);
|
|
1144
|
-
},
|
|
1145
|
-
(error) => {
|
|
1146
|
-
cleanup();
|
|
1147
|
-
reject(error);
|
|
1148
|
-
}
|
|
1149
|
-
);
|
|
1150
|
-
});
|
|
1151
|
-
}
|
|
1152
|
-
function getOrStartTokenRefresh(flights, key, forceRefresh, refresh, signal) {
|
|
1153
|
-
const existing = flights.get(key);
|
|
1154
|
-
if (existing && (!forceRefresh || existing.forceRefresh)) {
|
|
1155
|
-
return waitForTokenRefresh(existing.promise, signal);
|
|
1156
|
-
}
|
|
1157
|
-
const operation = existing ? existing.promise.then(refresh, refresh) : refresh();
|
|
1158
|
-
let flight;
|
|
1159
|
-
const promise = operation.finally(() => {
|
|
1160
|
-
if (flights.get(key) === flight) flights.delete(key);
|
|
1161
|
-
});
|
|
1162
|
-
flight = { forceRefresh, promise };
|
|
1163
|
-
flights.set(key, flight);
|
|
1164
|
-
return waitForTokenRefresh(promise, signal);
|
|
1165
|
-
}
|
|
1166
|
-
function refreshLockPath(envName) {
|
|
1167
|
-
return `${storePathFor(envName)}.refresh.lock`;
|
|
1168
|
-
}
|
|
1169
|
-
function pendingOAuthRefreshPrefix(envName) {
|
|
1170
|
-
return `${path5.basename(storePathFor(envName))}.refresh-result.`;
|
|
1171
|
-
}
|
|
1172
|
-
function parsePendingOAuthRefresh(raw) {
|
|
1173
|
-
try {
|
|
1174
|
-
const parsed = JSON.parse(raw);
|
|
1175
|
-
const refreshed = parsed.refreshed;
|
|
1176
|
-
if (parsed.version !== 1 || typeof parsed.previousRefreshTokenHash !== "string" || !/^[a-f0-9]{64}$/.test(parsed.previousRefreshTokenHash) || typeof parsed.createdAtMs !== "number" || !Number.isFinite(parsed.createdAtMs) || !refreshed || typeof refreshed.accessToken !== "string" || typeof refreshed.refreshToken !== "string" || typeof refreshed.expiresAt !== "number" || !Number.isFinite(refreshed.expiresAt) || !refreshed.user || typeof refreshed.user.id !== "string" || typeof refreshed.user.email !== "string" || typeof refreshed.user.name !== "string") {
|
|
1177
|
-
return null;
|
|
1178
|
-
}
|
|
1179
|
-
return parsed;
|
|
1180
|
-
} catch {
|
|
1181
|
-
return null;
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
function stagePendingOAuthRefresh(previousRefreshToken, refreshed, envName) {
|
|
1185
|
-
ensureDir(envName);
|
|
1186
|
-
const resultPath = `${storePathFor(envName)}.refresh-result.${Date.now()}.${randomUUID2()}.json`;
|
|
1187
|
-
const pending = {
|
|
1188
|
-
version: 1,
|
|
1189
|
-
previousRefreshTokenHash: credentialHash(previousRefreshToken),
|
|
1190
|
-
refreshed,
|
|
1191
|
-
createdAtMs: Date.now()
|
|
1192
|
-
};
|
|
1193
|
-
writeSecretFile(resultPath, `${JSON.stringify(pending)}
|
|
1194
|
-
`);
|
|
1195
|
-
return resultPath;
|
|
1196
|
-
}
|
|
1197
|
-
function removePendingOAuthRefresh(resultPath) {
|
|
1198
|
-
try {
|
|
1199
|
-
fs3.unlinkSync(resultPath);
|
|
1200
|
-
} catch (error) {
|
|
1201
|
-
if (error.code !== "ENOENT") {
|
|
1202
|
-
console.error(
|
|
1203
|
-
`[fml] Auth: unable to remove a committed refresh result: ${error instanceof Error ? error.message : String(error)}`
|
|
1204
|
-
);
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
function reconcilePendingOAuthRefreshes(envName, unreadableStorePolicy = "preserve-staged") {
|
|
1209
|
-
const storePath = storePathFor(envName);
|
|
1210
|
-
const directory = path5.dirname(storePath);
|
|
1211
|
-
const prefix = pendingOAuthRefreshPrefix(envName);
|
|
1212
|
-
let resultPaths;
|
|
1213
|
-
try {
|
|
1214
|
-
resultPaths = fs3.readdirSync(directory).filter((entry) => entry.startsWith(prefix) && entry.endsWith(".json")).map((entry) => path5.join(directory, entry));
|
|
1215
|
-
} catch (error) {
|
|
1216
|
-
if (error.code === "ENOENT") return;
|
|
1217
|
-
throw error;
|
|
1218
|
-
}
|
|
1219
|
-
const pendingResults = resultPaths.map((resultPath) => {
|
|
1220
|
-
let pending = null;
|
|
1221
|
-
try {
|
|
1222
|
-
pending = parsePendingOAuthRefresh(fs3.readFileSync(resultPath, "utf8"));
|
|
1223
|
-
} catch (error) {
|
|
1224
|
-
if (error.code !== "ENOENT") throw error;
|
|
694
|
+
pending = parsePendingOAuthRefresh(fs4.readFileSync(resultPath, "utf8"));
|
|
695
|
+
} catch (error) {
|
|
696
|
+
if (error.code !== "ENOENT") throw error;
|
|
1225
697
|
}
|
|
1226
698
|
return { resultPath, pending };
|
|
1227
699
|
}).sort(
|
|
@@ -1274,7 +746,7 @@ function parseRefreshLockOwner(raw) {
|
|
|
1274
746
|
function getProcessIdentity(pid) {
|
|
1275
747
|
try {
|
|
1276
748
|
if (process.platform === "linux") {
|
|
1277
|
-
const stat =
|
|
749
|
+
const stat = fs4.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
1278
750
|
const fieldsAfterCommand = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
|
|
1279
751
|
const startTimeTicks = fieldsAfterCommand[19];
|
|
1280
752
|
return startTimeTicks ? `linux:${startTimeTicks}` : null;
|
|
@@ -1326,7 +798,7 @@ function parseRecoveryMarkerOwner(raw) {
|
|
|
1326
798
|
function snapshotLeaseFile(filePath) {
|
|
1327
799
|
let stat;
|
|
1328
800
|
try {
|
|
1329
|
-
stat =
|
|
801
|
+
stat = fs4.lstatSync(filePath);
|
|
1330
802
|
} catch (error) {
|
|
1331
803
|
if (error.code === "ENOENT") return null;
|
|
1332
804
|
throw error;
|
|
@@ -1335,7 +807,7 @@ function snapshotLeaseFile(filePath) {
|
|
|
1335
807
|
try {
|
|
1336
808
|
return {
|
|
1337
809
|
stat,
|
|
1338
|
-
raw:
|
|
810
|
+
raw: fs4.readFileSync(path6.join(filePath, "owner.json"), "utf8")
|
|
1339
811
|
};
|
|
1340
812
|
} catch (error) {
|
|
1341
813
|
if (error.code === "ENOENT") {
|
|
@@ -1345,7 +817,7 @@ function snapshotLeaseFile(filePath) {
|
|
|
1345
817
|
}
|
|
1346
818
|
}
|
|
1347
819
|
try {
|
|
1348
|
-
return { stat, raw:
|
|
820
|
+
return { stat, raw: fs4.readFileSync(filePath, "utf8") };
|
|
1349
821
|
} catch (error) {
|
|
1350
822
|
if (error.code === "ENOENT") return null;
|
|
1351
823
|
throw error;
|
|
@@ -1357,21 +829,21 @@ function snapshotsMatch(observed, current) {
|
|
|
1357
829
|
function restoreMovedLease(movedPath, originalPath) {
|
|
1358
830
|
let movedStat;
|
|
1359
831
|
try {
|
|
1360
|
-
movedStat =
|
|
832
|
+
movedStat = fs4.lstatSync(movedPath);
|
|
1361
833
|
} catch (error) {
|
|
1362
834
|
if (error.code === "ENOENT") return;
|
|
1363
835
|
throw error;
|
|
1364
836
|
}
|
|
1365
837
|
if (!movedStat.isFile()) return;
|
|
1366
838
|
try {
|
|
1367
|
-
|
|
839
|
+
fs4.linkSync(movedPath, originalPath);
|
|
1368
840
|
} catch (error) {
|
|
1369
841
|
const code = error.code;
|
|
1370
842
|
if (code === "EEXIST" || code === "ENOENT") return;
|
|
1371
843
|
throw error;
|
|
1372
844
|
}
|
|
1373
845
|
try {
|
|
1374
|
-
|
|
846
|
+
fs4.unlinkSync(movedPath);
|
|
1375
847
|
} catch (error) {
|
|
1376
848
|
if (error.code !== "ENOENT") throw error;
|
|
1377
849
|
}
|
|
@@ -1385,27 +857,27 @@ function pathMatchesClaim(filePath, claimedStat, ownerId, parseOwner) {
|
|
|
1385
857
|
function markClaimReleased(filePath, claimedStat, owner, parseOwner) {
|
|
1386
858
|
let descriptor;
|
|
1387
859
|
try {
|
|
1388
|
-
descriptor =
|
|
860
|
+
descriptor = fs4.openSync(filePath, "r+");
|
|
1389
861
|
} catch (error) {
|
|
1390
862
|
if (error.code === "ENOENT") return false;
|
|
1391
863
|
throw error;
|
|
1392
864
|
}
|
|
1393
865
|
try {
|
|
1394
|
-
const currentStat =
|
|
866
|
+
const currentStat = fs4.fstatSync(descriptor);
|
|
1395
867
|
if (currentStat.dev !== claimedStat.dev || currentStat.ino !== claimedStat.ino) {
|
|
1396
868
|
return false;
|
|
1397
869
|
}
|
|
1398
|
-
const currentOwner = parseOwner(
|
|
870
|
+
const currentOwner = parseOwner(fs4.readFileSync(descriptor, "utf8"));
|
|
1399
871
|
if (currentOwner?.ownerId !== owner.ownerId) return false;
|
|
1400
872
|
const serialized = Buffer.from(
|
|
1401
873
|
`${JSON.stringify({ ...owner, releasedAtMs: Date.now() })}
|
|
1402
874
|
`,
|
|
1403
875
|
"utf8"
|
|
1404
876
|
);
|
|
1405
|
-
|
|
877
|
+
fs4.ftruncateSync(descriptor, 0);
|
|
1406
878
|
let offset = 0;
|
|
1407
879
|
while (offset < serialized.length) {
|
|
1408
|
-
offset +=
|
|
880
|
+
offset += fs4.writeSync(
|
|
1409
881
|
descriptor,
|
|
1410
882
|
serialized,
|
|
1411
883
|
offset,
|
|
@@ -1413,19 +885,19 @@ function markClaimReleased(filePath, claimedStat, owner, parseOwner) {
|
|
|
1413
885
|
offset
|
|
1414
886
|
);
|
|
1415
887
|
}
|
|
1416
|
-
|
|
888
|
+
fs4.fsyncSync(descriptor);
|
|
1417
889
|
return true;
|
|
1418
890
|
} finally {
|
|
1419
|
-
|
|
891
|
+
fs4.closeSync(descriptor);
|
|
1420
892
|
}
|
|
1421
893
|
}
|
|
1422
894
|
function releaseOwnedLease(filePath, claimedStat, owner, parseOwner, description) {
|
|
1423
|
-
const releasedPath = `${filePath}.released.${
|
|
895
|
+
const releasedPath = `${filePath}.released.${randomUUID3()}`;
|
|
1424
896
|
let moved = false;
|
|
1425
897
|
try {
|
|
1426
898
|
if (!markClaimReleased(filePath, claimedStat, owner, parseOwner)) return;
|
|
1427
899
|
try {
|
|
1428
|
-
|
|
900
|
+
fs4.renameSync(filePath, releasedPath);
|
|
1429
901
|
moved = true;
|
|
1430
902
|
} catch (error) {
|
|
1431
903
|
if (error.code === "ENOENT") return;
|
|
@@ -1438,7 +910,7 @@ function releaseOwnedLease(filePath, claimedStat, owner, parseOwner, description
|
|
|
1438
910
|
moved = false;
|
|
1439
911
|
return;
|
|
1440
912
|
}
|
|
1441
|
-
|
|
913
|
+
fs4.rmSync(releasedPath, {
|
|
1442
914
|
recursive: released.stat.isDirectory(),
|
|
1443
915
|
force: true
|
|
1444
916
|
});
|
|
@@ -1490,7 +962,6 @@ function isProcessAlive(pid) {
|
|
|
1490
962
|
return error.code === "EPERM";
|
|
1491
963
|
}
|
|
1492
964
|
}
|
|
1493
|
-
var processIdentityChecks = /* @__PURE__ */ new Map();
|
|
1494
965
|
function ownerPidWasReused(owner, lockMtimeMs) {
|
|
1495
966
|
const now = Date.now();
|
|
1496
967
|
if (now - Math.max(owner.createdAtMs, lockMtimeMs) < PROCESS_IDENTITY_STALE_MS)
|
|
@@ -1513,9 +984,9 @@ function ownerPidWasReused(owner, lockMtimeMs) {
|
|
|
1513
984
|
return currentIdentity !== null && currentIdentity !== owner.processIdentity;
|
|
1514
985
|
}
|
|
1515
986
|
function writeClaimToDescriptor(descriptor, serializedOwner) {
|
|
1516
|
-
const claimedStat =
|
|
1517
|
-
|
|
1518
|
-
|
|
987
|
+
const claimedStat = fs4.fstatSync(descriptor);
|
|
988
|
+
fs4.writeFileSync(descriptor, serializedOwner, { encoding: "utf8" });
|
|
989
|
+
fs4.fsyncSync(descriptor);
|
|
1519
990
|
return claimedStat;
|
|
1520
991
|
}
|
|
1521
992
|
function recoverAbandonedRecoveryMarker(recoveryPath) {
|
|
@@ -1525,15 +996,15 @@ function recoverAbandonedRecoveryMarker(recoveryPath) {
|
|
|
1525
996
|
const ownerAlive = owner ? isProcessAlive(owner.pid) : false;
|
|
1526
997
|
const reclaimable = owner ? owner.releasedAtMs !== void 0 || !ownerAlive || ownerAlive && ownerPidWasReused(owner, observed.stat.mtimeMs) : Date.now() - observed.stat.mtimeMs > OAUTH_REFRESH_RECOVERY_STALE_MS;
|
|
1527
998
|
if (!reclaimable) return;
|
|
1528
|
-
const abandonedPath = `${recoveryPath}.stale.${
|
|
999
|
+
const abandonedPath = `${recoveryPath}.stale.${randomUUID3()}`;
|
|
1529
1000
|
try {
|
|
1530
|
-
|
|
1001
|
+
fs4.renameSync(recoveryPath, abandonedPath);
|
|
1531
1002
|
const moved = snapshotLeaseFile(abandonedPath);
|
|
1532
1003
|
if (!moved || !snapshotsMatch(observed, moved)) {
|
|
1533
1004
|
restoreMovedLease(abandonedPath, recoveryPath);
|
|
1534
1005
|
return;
|
|
1535
1006
|
}
|
|
1536
|
-
|
|
1007
|
+
fs4.rmSync(abandonedPath, {
|
|
1537
1008
|
recursive: moved.stat.isDirectory(),
|
|
1538
1009
|
force: true
|
|
1539
1010
|
});
|
|
@@ -1542,7 +1013,7 @@ function recoverAbandonedRecoveryMarker(recoveryPath) {
|
|
|
1542
1013
|
}
|
|
1543
1014
|
}
|
|
1544
1015
|
function acquireRecoveryMarker(recoveryPath) {
|
|
1545
|
-
const ownerId =
|
|
1016
|
+
const ownerId = randomUUID3();
|
|
1546
1017
|
const owner = {
|
|
1547
1018
|
version: 1,
|
|
1548
1019
|
ownerId,
|
|
@@ -1553,7 +1024,7 @@ function acquireRecoveryMarker(recoveryPath) {
|
|
|
1553
1024
|
};
|
|
1554
1025
|
let descriptor;
|
|
1555
1026
|
try {
|
|
1556
|
-
descriptor =
|
|
1027
|
+
descriptor = fs4.openSync(recoveryPath, "wx", 384);
|
|
1557
1028
|
} catch (error) {
|
|
1558
1029
|
if (error.code === "EEXIST") return null;
|
|
1559
1030
|
throw error;
|
|
@@ -1566,7 +1037,7 @@ function acquireRecoveryMarker(recoveryPath) {
|
|
|
1566
1037
|
`
|
|
1567
1038
|
);
|
|
1568
1039
|
} finally {
|
|
1569
|
-
|
|
1040
|
+
fs4.closeSync(descriptor);
|
|
1570
1041
|
}
|
|
1571
1042
|
const isOwned = () => pathMatchesClaim(
|
|
1572
1043
|
recoveryPath,
|
|
@@ -1610,15 +1081,15 @@ function recoverStaleRefreshLock(lockPath) {
|
|
|
1610
1081
|
}
|
|
1611
1082
|
if (!reclaimable) return;
|
|
1612
1083
|
if (!recovery.isOwned()) return;
|
|
1613
|
-
const abandonedPath = `${lockPath}.stale.${
|
|
1084
|
+
const abandonedPath = `${lockPath}.stale.${randomUUID3()}`;
|
|
1614
1085
|
try {
|
|
1615
|
-
|
|
1086
|
+
fs4.renameSync(lockPath, abandonedPath);
|
|
1616
1087
|
const moved = snapshotLeaseFile(abandonedPath);
|
|
1617
1088
|
if (!moved || !snapshotsMatch(observed, moved)) {
|
|
1618
1089
|
restoreMovedLease(abandonedPath, lockPath);
|
|
1619
1090
|
return;
|
|
1620
1091
|
}
|
|
1621
|
-
|
|
1092
|
+
fs4.rmSync(abandonedPath, {
|
|
1622
1093
|
recursive: moved.stat.isDirectory(),
|
|
1623
1094
|
force: true
|
|
1624
1095
|
});
|
|
@@ -1645,7 +1116,7 @@ async function acquireRefreshLock(envName, signal) {
|
|
|
1645
1116
|
if (signal?.aborted) {
|
|
1646
1117
|
throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
1647
1118
|
}
|
|
1648
|
-
if (
|
|
1119
|
+
if (fs4.existsSync(recoveryPath)) {
|
|
1649
1120
|
try {
|
|
1650
1121
|
recoverAbandonedRecoveryMarker(recoveryPath);
|
|
1651
1122
|
} catch (recoveryError) {
|
|
@@ -1659,7 +1130,7 @@ async function acquireRefreshLock(envName, signal) {
|
|
|
1659
1130
|
}
|
|
1660
1131
|
let descriptor;
|
|
1661
1132
|
try {
|
|
1662
|
-
descriptor =
|
|
1133
|
+
descriptor = fs4.openSync(lockPath, "wx", 384);
|
|
1663
1134
|
} catch (error) {
|
|
1664
1135
|
if (error.code !== "EEXIST") {
|
|
1665
1136
|
console.error(
|
|
@@ -1678,7 +1149,7 @@ async function acquireRefreshLock(envName, signal) {
|
|
|
1678
1149
|
await sleep(OAUTH_REFRESH_LOCK_POLL_MS, signal);
|
|
1679
1150
|
continue;
|
|
1680
1151
|
}
|
|
1681
|
-
const ownerId =
|
|
1152
|
+
const ownerId = randomUUID3();
|
|
1682
1153
|
const owner = {
|
|
1683
1154
|
version: 1,
|
|
1684
1155
|
ownerId,
|
|
@@ -1695,13 +1166,13 @@ async function acquireRefreshLock(envName, signal) {
|
|
|
1695
1166
|
`
|
|
1696
1167
|
);
|
|
1697
1168
|
} catch (error) {
|
|
1698
|
-
|
|
1169
|
+
fs4.closeSync(descriptor);
|
|
1699
1170
|
console.error(
|
|
1700
1171
|
`[fml] Auth: unable to initialize refresh lock: ${error instanceof Error ? error.message : String(error)}`
|
|
1701
1172
|
);
|
|
1702
1173
|
return null;
|
|
1703
1174
|
}
|
|
1704
|
-
|
|
1175
|
+
fs4.closeSync(descriptor);
|
|
1705
1176
|
const isOwned = () => pathMatchesClaim(lockPath, claimedStat, ownerId, parseRefreshLockOwner);
|
|
1706
1177
|
const releaseClaim = () => releaseOwnedLease(
|
|
1707
1178
|
lockPath,
|
|
@@ -1716,7 +1187,7 @@ async function acquireRefreshLock(envName, signal) {
|
|
|
1716
1187
|
}
|
|
1717
1188
|
let entryBlocked;
|
|
1718
1189
|
try {
|
|
1719
|
-
entryBlocked =
|
|
1190
|
+
entryBlocked = fs4.existsSync(recoveryPath) || !isOwned();
|
|
1720
1191
|
} catch (error) {
|
|
1721
1192
|
console.error(
|
|
1722
1193
|
`[fml] Auth: unable to verify refresh lock ownership: ${describeError(error)}`
|
|
@@ -1882,7 +1353,7 @@ async function getValidToken(opts) {
|
|
|
1882
1353
|
return stored.accessToken;
|
|
1883
1354
|
}
|
|
1884
1355
|
if (stored.tokenType === "service") {
|
|
1885
|
-
|
|
1356
|
+
const accessToken2 = await getOrStartTokenRefresh(
|
|
1886
1357
|
serviceRefreshPromises,
|
|
1887
1358
|
key,
|
|
1888
1359
|
forceRefresh,
|
|
@@ -1928,6 +1399,12 @@ async function getValidToken(opts) {
|
|
|
1928
1399
|
}),
|
|
1929
1400
|
opts?.signal
|
|
1930
1401
|
);
|
|
1402
|
+
await refreshTelemetryConfigAfterTokenRefresh(
|
|
1403
|
+
envName,
|
|
1404
|
+
accessToken2,
|
|
1405
|
+
opts?.signal
|
|
1406
|
+
);
|
|
1407
|
+
return accessToken2;
|
|
1931
1408
|
}
|
|
1932
1409
|
if (stored.refreshInvalidAt) {
|
|
1933
1410
|
console.error(
|
|
@@ -1935,13 +1412,34 @@ async function getValidToken(opts) {
|
|
|
1935
1412
|
);
|
|
1936
1413
|
return null;
|
|
1937
1414
|
}
|
|
1938
|
-
|
|
1415
|
+
const accessToken = await getOrStartTokenRefresh(
|
|
1939
1416
|
oauthRefreshPromises,
|
|
1940
1417
|
key,
|
|
1941
1418
|
forceRefresh,
|
|
1942
1419
|
() => refreshTokenWithLock(envName, forceRefresh, opts?.signal),
|
|
1943
1420
|
opts?.signal
|
|
1944
1421
|
);
|
|
1422
|
+
await refreshTelemetryConfigAfterTokenRefresh(
|
|
1423
|
+
envName,
|
|
1424
|
+
accessToken,
|
|
1425
|
+
opts?.signal
|
|
1426
|
+
);
|
|
1427
|
+
return accessToken;
|
|
1428
|
+
}
|
|
1429
|
+
async function refreshTelemetryConfigAfterTokenRefresh(envName, accessToken, signal) {
|
|
1430
|
+
if (!accessToken || signal?.aborted) return;
|
|
1431
|
+
try {
|
|
1432
|
+
const current = readTokens(envName);
|
|
1433
|
+
if (current?.accessToken !== accessToken) return;
|
|
1434
|
+
const { refreshCliTelemetryConfig: refreshCliTelemetryConfig2 } = await Promise.resolve().then(() => (init_telemetry_config(), telemetry_config_exports));
|
|
1435
|
+
await refreshCliTelemetryConfig2({
|
|
1436
|
+
envName,
|
|
1437
|
+
accessToken,
|
|
1438
|
+
siteUrl: current.siteUrl,
|
|
1439
|
+
signal
|
|
1440
|
+
});
|
|
1441
|
+
} catch {
|
|
1442
|
+
}
|
|
1945
1443
|
}
|
|
1946
1444
|
async function refreshTokenWithLock(envName, forceRefresh = false, signal) {
|
|
1947
1445
|
const lock = await acquireRefreshLock(envName, signal);
|
|
@@ -2047,7 +1545,9 @@ async function refreshToken(stored, lock, envName, signal) {
|
|
|
2047
1545
|
},
|
|
2048
1546
|
orgId: stored.orgId,
|
|
2049
1547
|
orgSlug: stored.orgSlug,
|
|
2050
|
-
workosClientId: stored.workosClientId
|
|
1548
|
+
workosClientId: stored.workosClientId,
|
|
1549
|
+
// The backend this login belongs to does not change on rotation.
|
|
1550
|
+
...stored.siteUrl ? { siteUrl: stored.siteUrl } : {}
|
|
2051
1551
|
};
|
|
2052
1552
|
const pendingPath = stagePendingOAuthRefresh(
|
|
2053
1553
|
stored.refreshToken,
|
|
@@ -2082,6 +1582,733 @@ async function refreshToken(stored, lock, envName, signal) {
|
|
|
2082
1582
|
return null;
|
|
2083
1583
|
}
|
|
2084
1584
|
}
|
|
1585
|
+
var TOKEN_REFRESH_WINDOW_MS, OAUTH_REFRESH_LOCK_POLL_MS, OAUTH_REFRESH_LOCK_STALE_MS, OAUTH_REFRESH_RECOVERY_STALE_MS, OAUTH_REFRESH_LOCK_WAIT_MS, OAUTH_REFRESH_REQUEST_TIMEOUT_MS, PROCESS_IDENTITY_STALE_MS, PROCESS_IDENTITY_RECHECK_MS, PROCESS_INSTANCE_KEY, PROCESS_IDENTITY_KEY, processGlobals, existingProcessInstanceId, CURRENT_PROCESS_INSTANCE_ID, CorruptStoredCredentialsError, serviceTokenCaches, serviceRefreshPromises, oauthRefreshPromises, cacheKey, processIdentityChecks;
|
|
1586
|
+
var init_token_store = __esm({
|
|
1587
|
+
"src/auth/token-store.ts"() {
|
|
1588
|
+
"use strict";
|
|
1589
|
+
init_config();
|
|
1590
|
+
init_sentry();
|
|
1591
|
+
TOKEN_REFRESH_WINDOW_MS = 6e4;
|
|
1592
|
+
OAUTH_REFRESH_LOCK_POLL_MS = 50;
|
|
1593
|
+
OAUTH_REFRESH_LOCK_STALE_MS = 45e3;
|
|
1594
|
+
OAUTH_REFRESH_RECOVERY_STALE_MS = 45e3;
|
|
1595
|
+
OAUTH_REFRESH_LOCK_WAIT_MS = 6e4;
|
|
1596
|
+
OAUTH_REFRESH_REQUEST_TIMEOUT_MS = 3e4;
|
|
1597
|
+
PROCESS_IDENTITY_STALE_MS = 45e3;
|
|
1598
|
+
PROCESS_IDENTITY_RECHECK_MS = 5e3;
|
|
1599
|
+
PROCESS_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("@fml-inc/fml:auth-process-instance");
|
|
1600
|
+
PROCESS_IDENTITY_KEY = /* @__PURE__ */ Symbol.for("@fml-inc/fml:auth-process-identity");
|
|
1601
|
+
processGlobals = globalThis;
|
|
1602
|
+
existingProcessInstanceId = processGlobals[PROCESS_INSTANCE_KEY];
|
|
1603
|
+
CURRENT_PROCESS_INSTANCE_ID = typeof existingProcessInstanceId === "string" ? existingProcessInstanceId : randomUUID3();
|
|
1604
|
+
processGlobals[PROCESS_INSTANCE_KEY] = CURRENT_PROCESS_INSTANCE_ID;
|
|
1605
|
+
CorruptStoredCredentialsError = class extends Error {
|
|
1606
|
+
};
|
|
1607
|
+
serviceTokenCaches = /* @__PURE__ */ new Map();
|
|
1608
|
+
serviceRefreshPromises = /* @__PURE__ */ new Map();
|
|
1609
|
+
oauthRefreshPromises = /* @__PURE__ */ new Map();
|
|
1610
|
+
cacheKey = (envName) => envName;
|
|
1611
|
+
processIdentityChecks = /* @__PURE__ */ new Map();
|
|
1612
|
+
}
|
|
1613
|
+
});
|
|
1614
|
+
|
|
1615
|
+
// src/pi/extension.ts
|
|
1616
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
1617
|
+
import fs5 from "node:fs";
|
|
1618
|
+
import os2 from "node:os";
|
|
1619
|
+
import path8 from "node:path";
|
|
1620
|
+
|
|
1621
|
+
// ../../packages/agent-targets/src/install/pi/index.ts
|
|
1622
|
+
import fs from "node:fs";
|
|
1623
|
+
import path from "node:path";
|
|
1624
|
+
import { randomUUID } from "node:crypto";
|
|
1625
|
+
var FML_PI_EXTENSION_MARKER = "__FML_PI_EXTENSION_V1__";
|
|
1626
|
+
var LEGACY_PI_EXTENSION_MARKER = "__panopticonPiExtensionTest";
|
|
1627
|
+
var LEGACY_PI_EXTENSION_SIGNATURES = Object.freeze([
|
|
1628
|
+
"PANOPTICON_PORT",
|
|
1629
|
+
'"/hooks"',
|
|
1630
|
+
"UserPromptSubmit",
|
|
1631
|
+
"PreToolUse",
|
|
1632
|
+
"PostToolUse",
|
|
1633
|
+
"SessionEnd"
|
|
1634
|
+
]);
|
|
1635
|
+
function pathApiFor(platform) {
|
|
1636
|
+
return platform === "win32" ? path.win32 : path.posix;
|
|
1637
|
+
}
|
|
1638
|
+
function resolvePiAgentDir(context, pathApi) {
|
|
1639
|
+
const configured = context.env.PI_CODING_AGENT_DIR?.trim();
|
|
1640
|
+
if (!configured) {
|
|
1641
|
+
return pathApi.join(context.homeDir, ".pi", "agent");
|
|
1642
|
+
}
|
|
1643
|
+
if (configured === "~") return context.homeDir;
|
|
1644
|
+
if (configured.startsWith("~/") || configured.startsWith("~\\")) {
|
|
1645
|
+
return pathApi.join(context.homeDir, configured.slice(2));
|
|
1646
|
+
}
|
|
1647
|
+
return pathApi.normalize(configured);
|
|
1648
|
+
}
|
|
1649
|
+
function readFileIfPresent(filePath) {
|
|
1650
|
+
try {
|
|
1651
|
+
return fs.readFileSync(filePath, "utf8");
|
|
1652
|
+
} catch (error) {
|
|
1653
|
+
if (error.code === "ENOENT") return null;
|
|
1654
|
+
throw error;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
function isOwnedExtension(source, legacy = false) {
|
|
1658
|
+
return source.includes(FML_PI_EXTENSION_MARKER) || legacy && (source.includes(LEGACY_PI_EXTENSION_MARKER) || LEGACY_PI_EXTENSION_SIGNATURES.every(
|
|
1659
|
+
(signature) => source.includes(signature)
|
|
1660
|
+
));
|
|
1661
|
+
}
|
|
1662
|
+
function removeLegacyExtension(filePath) {
|
|
1663
|
+
const source = readFileIfPresent(filePath);
|
|
1664
|
+
if (source === null) return { removed: false, preserved: false };
|
|
1665
|
+
if (!isOwnedExtension(source, true)) {
|
|
1666
|
+
return { removed: false, preserved: true };
|
|
1667
|
+
}
|
|
1668
|
+
fs.rmSync(filePath);
|
|
1669
|
+
return { removed: true, preserved: false };
|
|
1670
|
+
}
|
|
1671
|
+
function writeExtensionAtomically(filePath, source) {
|
|
1672
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 448 });
|
|
1673
|
+
const temporaryPath = path.join(
|
|
1674
|
+
path.dirname(filePath),
|
|
1675
|
+
`.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`
|
|
1676
|
+
);
|
|
1677
|
+
try {
|
|
1678
|
+
fs.writeFileSync(temporaryPath, source, { encoding: "utf8", mode: 384 });
|
|
1679
|
+
if (process.platform === "win32" && fs.existsSync(filePath)) {
|
|
1680
|
+
fs.rmSync(filePath);
|
|
1681
|
+
}
|
|
1682
|
+
fs.renameSync(temporaryPath, filePath);
|
|
1683
|
+
} finally {
|
|
1684
|
+
fs.rmSync(temporaryPath, { force: true });
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
var PI_EXTENSION_INSTALLER = Object.freeze({
|
|
1688
|
+
resolvePaths(context) {
|
|
1689
|
+
const pathApi = pathApiFor(context.platform);
|
|
1690
|
+
const extensionsDir = pathApi.join(
|
|
1691
|
+
resolvePiAgentDir(context, pathApi),
|
|
1692
|
+
"extensions"
|
|
1693
|
+
);
|
|
1694
|
+
return {
|
|
1695
|
+
sourcePath: pathApi.join(
|
|
1696
|
+
context.packageRoot,
|
|
1697
|
+
"dist",
|
|
1698
|
+
"pi",
|
|
1699
|
+
"extension.js"
|
|
1700
|
+
),
|
|
1701
|
+
extensionsDir,
|
|
1702
|
+
destinationPath: pathApi.join(extensionsDir, "fml.js"),
|
|
1703
|
+
legacyDestinationPath: pathApi.join(extensionsDir, "panopticon.js")
|
|
1704
|
+
};
|
|
1705
|
+
},
|
|
1706
|
+
install(paths) {
|
|
1707
|
+
const source = fs.readFileSync(paths.sourcePath, "utf8");
|
|
1708
|
+
if (!source.includes(FML_PI_EXTENSION_MARKER)) {
|
|
1709
|
+
throw new Error(
|
|
1710
|
+
`Packaged Pi extension is invalid: ${paths.sourcePath} does not contain the FML ownership marker`
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
const existing = readFileIfPresent(paths.destinationPath);
|
|
1714
|
+
if (existing !== null && !isOwnedExtension(existing)) {
|
|
1715
|
+
const legacy2 = removeLegacyExtension(paths.legacyDestinationPath);
|
|
1716
|
+
return {
|
|
1717
|
+
status: "preserved",
|
|
1718
|
+
destinationPath: paths.destinationPath,
|
|
1719
|
+
reason: "an extension not managed by FML already exists at this path",
|
|
1720
|
+
legacyRemoved: legacy2.removed,
|
|
1721
|
+
legacyPreserved: legacy2.preserved
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
const status = existing === null ? "installed" : existing === source ? "unchanged" : "updated";
|
|
1725
|
+
if (status !== "unchanged") {
|
|
1726
|
+
writeExtensionAtomically(paths.destinationPath, source);
|
|
1727
|
+
}
|
|
1728
|
+
const legacy = removeLegacyExtension(paths.legacyDestinationPath);
|
|
1729
|
+
return {
|
|
1730
|
+
status,
|
|
1731
|
+
destinationPath: paths.destinationPath,
|
|
1732
|
+
legacyRemoved: legacy.removed,
|
|
1733
|
+
legacyPreserved: legacy.preserved
|
|
1734
|
+
};
|
|
1735
|
+
},
|
|
1736
|
+
uninstall(paths) {
|
|
1737
|
+
const existing = readFileIfPresent(paths.destinationPath);
|
|
1738
|
+
const legacy = removeLegacyExtension(paths.legacyDestinationPath);
|
|
1739
|
+
if (existing === null) {
|
|
1740
|
+
return {
|
|
1741
|
+
status: "absent",
|
|
1742
|
+
destinationPath: paths.destinationPath,
|
|
1743
|
+
legacyRemoved: legacy.removed,
|
|
1744
|
+
legacyPreserved: legacy.preserved
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
if (!isOwnedExtension(existing)) {
|
|
1748
|
+
return {
|
|
1749
|
+
status: "preserved",
|
|
1750
|
+
destinationPath: paths.destinationPath,
|
|
1751
|
+
reason: "the existing extension is not managed by FML",
|
|
1752
|
+
legacyRemoved: legacy.removed,
|
|
1753
|
+
legacyPreserved: legacy.preserved
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
fs.rmSync(paths.destinationPath);
|
|
1757
|
+
return {
|
|
1758
|
+
status: "removed",
|
|
1759
|
+
destinationPath: paths.destinationPath,
|
|
1760
|
+
legacyRemoved: legacy.removed,
|
|
1761
|
+
legacyPreserved: legacy.preserved
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
});
|
|
1765
|
+
|
|
1766
|
+
// src/hooks/context.ts
|
|
1767
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
1768
|
+
import path7 from "node:path";
|
|
1769
|
+
|
|
1770
|
+
// ../../packages/agent-targets/src/hooks/normalize.ts
|
|
1771
|
+
import { createHash } from "node:crypto";
|
|
1772
|
+
|
|
1773
|
+
// ../../packages/agent-targets/src/hooks/types.ts
|
|
1774
|
+
var HOOK_TARGETS = Object.freeze([
|
|
1775
|
+
"claude",
|
|
1776
|
+
"codex",
|
|
1777
|
+
"gemini",
|
|
1778
|
+
"grok",
|
|
1779
|
+
"pi"
|
|
1780
|
+
]);
|
|
1781
|
+
|
|
1782
|
+
// ../../packages/agent-targets/src/catalog/hook-events.json
|
|
1783
|
+
var hook_events_default = {
|
|
1784
|
+
claude: [
|
|
1785
|
+
"SessionStart",
|
|
1786
|
+
"Setup",
|
|
1787
|
+
"InstructionsLoaded",
|
|
1788
|
+
"UserPromptSubmit",
|
|
1789
|
+
"UserPromptExpansion",
|
|
1790
|
+
"PreToolUse",
|
|
1791
|
+
"PermissionRequest",
|
|
1792
|
+
"PostToolUse",
|
|
1793
|
+
"PostToolUseFailure",
|
|
1794
|
+
"PostToolBatch",
|
|
1795
|
+
"PermissionDenied",
|
|
1796
|
+
"Notification",
|
|
1797
|
+
"SubagentStart",
|
|
1798
|
+
"SubagentStop",
|
|
1799
|
+
"TaskCreated",
|
|
1800
|
+
"TaskCompleted",
|
|
1801
|
+
"Stop",
|
|
1802
|
+
"StopFailure",
|
|
1803
|
+
"TeammateIdle",
|
|
1804
|
+
"ConfigChange",
|
|
1805
|
+
"CwdChanged",
|
|
1806
|
+
"PreCompact",
|
|
1807
|
+
"PostCompact",
|
|
1808
|
+
"SessionEnd",
|
|
1809
|
+
"Elicitation",
|
|
1810
|
+
"ElicitationResult"
|
|
1811
|
+
],
|
|
1812
|
+
codex: [
|
|
1813
|
+
"SessionStart",
|
|
1814
|
+
"UserPromptSubmit",
|
|
1815
|
+
"PreToolUse",
|
|
1816
|
+
"PermissionRequest",
|
|
1817
|
+
"PostToolUse",
|
|
1818
|
+
"Stop"
|
|
1819
|
+
],
|
|
1820
|
+
gemini: [
|
|
1821
|
+
"SessionStart",
|
|
1822
|
+
"BeforeAgent",
|
|
1823
|
+
"BeforeTool",
|
|
1824
|
+
"AfterTool",
|
|
1825
|
+
"AfterAgent"
|
|
1826
|
+
],
|
|
1827
|
+
grok: [
|
|
1828
|
+
"SessionStart",
|
|
1829
|
+
"UserPromptSubmit",
|
|
1830
|
+
"PreToolUse",
|
|
1831
|
+
"PostToolUse",
|
|
1832
|
+
"PostToolUseFailure",
|
|
1833
|
+
"PermissionDenied",
|
|
1834
|
+
"Stop",
|
|
1835
|
+
"StopFailure",
|
|
1836
|
+
"Notification",
|
|
1837
|
+
"SubagentStart",
|
|
1838
|
+
"SubagentStop",
|
|
1839
|
+
"PreCompact",
|
|
1840
|
+
"PostCompact",
|
|
1841
|
+
"SessionEnd"
|
|
1842
|
+
],
|
|
1843
|
+
pi: [
|
|
1844
|
+
"SessionStart",
|
|
1845
|
+
"UserPromptSubmit",
|
|
1846
|
+
"TurnStart",
|
|
1847
|
+
"PreToolUse",
|
|
1848
|
+
"PostToolUse",
|
|
1849
|
+
"PostToolUseFailure",
|
|
1850
|
+
"Stop",
|
|
1851
|
+
"Notification",
|
|
1852
|
+
"ConfigChange",
|
|
1853
|
+
"PreCompact",
|
|
1854
|
+
"PostCompact",
|
|
1855
|
+
"SessionEnd"
|
|
1856
|
+
]
|
|
1857
|
+
};
|
|
1858
|
+
|
|
1859
|
+
// ../../packages/agent-targets/src/catalog/hook-events.ts
|
|
1860
|
+
var CLAUDE_HOOK_EVENTS = Object.freeze([
|
|
1861
|
+
...hook_events_default.claude
|
|
1862
|
+
]);
|
|
1863
|
+
var CODEX_HOOK_EVENTS = Object.freeze([
|
|
1864
|
+
...hook_events_default.codex
|
|
1865
|
+
]);
|
|
1866
|
+
var GEMINI_HOOK_EVENTS = Object.freeze([
|
|
1867
|
+
...hook_events_default.gemini
|
|
1868
|
+
]);
|
|
1869
|
+
var GROK_HOOK_EVENTS = Object.freeze([
|
|
1870
|
+
...hook_events_default.grok
|
|
1871
|
+
]);
|
|
1872
|
+
var PI_HOOK_EVENTS = Object.freeze([...hook_events_default.pi]);
|
|
1873
|
+
|
|
1874
|
+
// ../../packages/agent-targets/src/hooks/read-command.ts
|
|
1875
|
+
import path2 from "node:path";
|
|
1876
|
+
var SHELL_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
1877
|
+
"bash",
|
|
1878
|
+
"exec_command",
|
|
1879
|
+
"shell"
|
|
1880
|
+
]);
|
|
1881
|
+
var COMMAND_SEPARATORS = /* @__PURE__ */ new Set([
|
|
1882
|
+
";",
|
|
1883
|
+
"&",
|
|
1884
|
+
"&&",
|
|
1885
|
+
"|",
|
|
1886
|
+
"||",
|
|
1887
|
+
"<",
|
|
1888
|
+
"<<",
|
|
1889
|
+
">",
|
|
1890
|
+
">>",
|
|
1891
|
+
">&1",
|
|
1892
|
+
">&2",
|
|
1893
|
+
"1>",
|
|
1894
|
+
"1>>",
|
|
1895
|
+
"1>&1",
|
|
1896
|
+
"1>&2",
|
|
1897
|
+
"2>",
|
|
1898
|
+
"2>>",
|
|
1899
|
+
"2>&1",
|
|
1900
|
+
"2>&2"
|
|
1901
|
+
]);
|
|
1902
|
+
function tokenizeShellLikeCommand(command) {
|
|
1903
|
+
const tokens = [];
|
|
1904
|
+
let current = "";
|
|
1905
|
+
let quote = null;
|
|
1906
|
+
let escaping = false;
|
|
1907
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
1908
|
+
const character = command[index];
|
|
1909
|
+
if (escaping) {
|
|
1910
|
+
current += character;
|
|
1911
|
+
escaping = false;
|
|
1912
|
+
continue;
|
|
1913
|
+
}
|
|
1914
|
+
if (character === "\\") {
|
|
1915
|
+
escaping = true;
|
|
1916
|
+
continue;
|
|
1917
|
+
}
|
|
1918
|
+
if (quote) {
|
|
1919
|
+
if (character === quote) quote = null;
|
|
1920
|
+
else current += character;
|
|
1921
|
+
continue;
|
|
1922
|
+
}
|
|
1923
|
+
if (character === "'" || character === '"') {
|
|
1924
|
+
quote = character;
|
|
1925
|
+
continue;
|
|
1926
|
+
}
|
|
1927
|
+
if (current === "" && (character === "1" || character === "2") && command[index + 1] === ">") {
|
|
1928
|
+
if (command[index + 2] === ">") {
|
|
1929
|
+
tokens.push(`${character}>>`);
|
|
1930
|
+
index += 2;
|
|
1931
|
+
} else if (command[index + 2] === "&" && (command[index + 3] === "1" || command[index + 3] === "2")) {
|
|
1932
|
+
tokens.push(`${character}>&${command[index + 3]}`);
|
|
1933
|
+
index += 3;
|
|
1934
|
+
} else {
|
|
1935
|
+
tokens.push(`${character}>`);
|
|
1936
|
+
index += 1;
|
|
1937
|
+
}
|
|
1938
|
+
continue;
|
|
1939
|
+
}
|
|
1940
|
+
if (/[;&|<>]/.test(character)) {
|
|
1941
|
+
if (current) {
|
|
1942
|
+
tokens.push(current);
|
|
1943
|
+
current = "";
|
|
1944
|
+
}
|
|
1945
|
+
const next = command[index + 1];
|
|
1946
|
+
if (character === ">" && next === "&" && (command[index + 2] === "1" || command[index + 2] === "2")) {
|
|
1947
|
+
tokens.push(`>&${command[index + 2]}`);
|
|
1948
|
+
index += 2;
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
if (character === "&" && next === "&" || character === "|" && next === "|" || character === "<" && next === "<" || character === ">" && next === ">") {
|
|
1952
|
+
tokens.push(`${character}${next}`);
|
|
1953
|
+
index += 1;
|
|
1954
|
+
continue;
|
|
1955
|
+
}
|
|
1956
|
+
tokens.push(character);
|
|
1957
|
+
continue;
|
|
1958
|
+
}
|
|
1959
|
+
if (/\s/.test(character)) {
|
|
1960
|
+
if (current) {
|
|
1961
|
+
tokens.push(current);
|
|
1962
|
+
current = "";
|
|
1963
|
+
}
|
|
1964
|
+
continue;
|
|
1965
|
+
}
|
|
1966
|
+
current += character;
|
|
1967
|
+
}
|
|
1968
|
+
if (quote || escaping) return [];
|
|
1969
|
+
if (current) tokens.push(current);
|
|
1970
|
+
return tokens;
|
|
1971
|
+
}
|
|
1972
|
+
function splitCommandSegments(tokens) {
|
|
1973
|
+
const segments = [];
|
|
1974
|
+
let current = [];
|
|
1975
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
1976
|
+
const token = tokens[index];
|
|
1977
|
+
if (COMMAND_SEPARATORS.has(token)) {
|
|
1978
|
+
if (current.length > 0) {
|
|
1979
|
+
segments.push(current);
|
|
1980
|
+
current = [];
|
|
1981
|
+
}
|
|
1982
|
+
if (token === "<" || token === "<<") index += 1;
|
|
1983
|
+
continue;
|
|
1984
|
+
}
|
|
1985
|
+
current.push(token);
|
|
1986
|
+
}
|
|
1987
|
+
if (current.length > 0) segments.push(current);
|
|
1988
|
+
return segments;
|
|
1989
|
+
}
|
|
1990
|
+
function stripSimpleOptions(args, flagsWithValues = /* @__PURE__ */ new Set(), patternFlags = /* @__PURE__ */ new Set()) {
|
|
1991
|
+
const remaining = [];
|
|
1992
|
+
let consumedPatternFlag = false;
|
|
1993
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1994
|
+
const argument = args[index];
|
|
1995
|
+
if (argument === "--") {
|
|
1996
|
+
remaining.push(...args.slice(index + 1));
|
|
1997
|
+
break;
|
|
1998
|
+
}
|
|
1999
|
+
if (argument.startsWith("-") && argument.length > 1) {
|
|
2000
|
+
if (flagsWithValues.has(argument)) {
|
|
2001
|
+
if (patternFlags.has(argument)) consumedPatternFlag = true;
|
|
2002
|
+
if (!argument.includes("=")) index += 1;
|
|
2003
|
+
}
|
|
2004
|
+
continue;
|
|
2005
|
+
}
|
|
2006
|
+
remaining.push(argument);
|
|
2007
|
+
}
|
|
2008
|
+
return { args: remaining, consumedPatternFlag };
|
|
2009
|
+
}
|
|
2010
|
+
function readCommandFileArgs(args, flagsWithValues, patternFlags = flagsWithValues) {
|
|
2011
|
+
const stripped = stripSimpleOptions(args, flagsWithValues, patternFlags);
|
|
2012
|
+
return stripped.consumedPatternFlag ? stripped.args : stripped.args.slice(1);
|
|
2013
|
+
}
|
|
2014
|
+
function isPlausibleReadPath(argument) {
|
|
2015
|
+
if (!argument || argument === "-" || argument.startsWith("-")) return false;
|
|
2016
|
+
if (COMMAND_SEPARATORS.has(argument)) return false;
|
|
2017
|
+
if (/[\0*?[{}`$]/.test(argument)) return false;
|
|
2018
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(argument)) return false;
|
|
2019
|
+
return path2.posix.isAbsolute(argument) || path2.win32.isAbsolute(argument) || argument.startsWith("~/") || argument.startsWith("./") || argument.startsWith("../") || argument.includes("/") || /\.[A-Za-z0-9][A-Za-z0-9_-]*$/.test(argument) || /^[A-Z][A-Za-z0-9_.-]*$/.test(argument);
|
|
2020
|
+
}
|
|
2021
|
+
function filePathFromSegment(tokens) {
|
|
2022
|
+
const commandTokens = [...tokens];
|
|
2023
|
+
while (commandTokens[0]?.includes("=") && /^[A-Za-z_][A-Za-z0-9_]*=/.test(commandTokens[0])) {
|
|
2024
|
+
commandTokens.shift();
|
|
2025
|
+
}
|
|
2026
|
+
const commandName = commandTokens.shift()?.split("/").pop();
|
|
2027
|
+
if (!commandName) return null;
|
|
2028
|
+
let args;
|
|
2029
|
+
switch (commandName) {
|
|
2030
|
+
case "bat":
|
|
2031
|
+
case "batcat":
|
|
2032
|
+
case "cat":
|
|
2033
|
+
case "file":
|
|
2034
|
+
case "less":
|
|
2035
|
+
case "more":
|
|
2036
|
+
case "nl":
|
|
2037
|
+
case "stat":
|
|
2038
|
+
case "wc":
|
|
2039
|
+
args = stripSimpleOptions(commandTokens).args;
|
|
2040
|
+
break;
|
|
2041
|
+
case "head":
|
|
2042
|
+
case "tail":
|
|
2043
|
+
args = stripSimpleOptions(commandTokens, /* @__PURE__ */ new Set(["-n", "-c"])).args;
|
|
2044
|
+
break;
|
|
2045
|
+
case "sed":
|
|
2046
|
+
if (commandTokens.some(
|
|
2047
|
+
(argument) => argument.startsWith("-i") || argument === "--in-place" || argument.startsWith("--in-place=")
|
|
2048
|
+
)) {
|
|
2049
|
+
return null;
|
|
2050
|
+
}
|
|
2051
|
+
args = readCommandFileArgs(commandTokens, /* @__PURE__ */ new Set(["-e", "-f"]));
|
|
2052
|
+
break;
|
|
2053
|
+
case "awk":
|
|
2054
|
+
args = readCommandFileArgs(
|
|
2055
|
+
commandTokens,
|
|
2056
|
+
/* @__PURE__ */ new Set(["-f", "-v"]),
|
|
2057
|
+
/* @__PURE__ */ new Set(["-f"])
|
|
2058
|
+
);
|
|
2059
|
+
break;
|
|
2060
|
+
case "grep":
|
|
2061
|
+
case "rg":
|
|
2062
|
+
args = readCommandFileArgs(
|
|
2063
|
+
commandTokens,
|
|
2064
|
+
/* @__PURE__ */ new Set(["-A", "-B", "-C", "-e", "-f", "-g", "-m", "-t"]),
|
|
2065
|
+
/* @__PURE__ */ new Set(["-e", "-f"])
|
|
2066
|
+
);
|
|
2067
|
+
break;
|
|
2068
|
+
default:
|
|
2069
|
+
return null;
|
|
2070
|
+
}
|
|
2071
|
+
return args.find(isPlausibleReadPath) ?? null;
|
|
2072
|
+
}
|
|
2073
|
+
function extractReadFilePathFromCommand(command) {
|
|
2074
|
+
const tokens = tokenizeShellLikeCommand(command);
|
|
2075
|
+
if (tokens.length === 0) return null;
|
|
2076
|
+
for (let index = 0; index < tokens.length - 1; index += 1) {
|
|
2077
|
+
if (tokens[index] === "<" && isPlausibleReadPath(tokens[index + 1])) {
|
|
2078
|
+
return tokens[index + 1];
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
for (const segment of splitCommandSegments(tokens)) {
|
|
2082
|
+
const filePath = filePathFromSegment(segment);
|
|
2083
|
+
if (filePath) return filePath;
|
|
2084
|
+
}
|
|
2085
|
+
return null;
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
// ../../packages/agent-targets/src/hooks/normalize.ts
|
|
2089
|
+
var MAX_EXTRACTED_TEXT_BYTES = 64 * 1024;
|
|
2090
|
+
function isRecord(value) {
|
|
2091
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2092
|
+
}
|
|
2093
|
+
function firstString(record, keys) {
|
|
2094
|
+
for (const key of keys) {
|
|
2095
|
+
const value = record[key];
|
|
2096
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
2097
|
+
}
|
|
2098
|
+
return void 0;
|
|
2099
|
+
}
|
|
2100
|
+
function extractedText(value) {
|
|
2101
|
+
if (typeof value === "string") return value;
|
|
2102
|
+
if (value === void 0 || value === null) return void 0;
|
|
2103
|
+
try {
|
|
2104
|
+
return JSON.stringify(value);
|
|
2105
|
+
} catch {
|
|
2106
|
+
return void 0;
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
function firstText(record, keys) {
|
|
2110
|
+
for (const key of keys) {
|
|
2111
|
+
const value = extractedText(record[key]);
|
|
2112
|
+
if (value !== void 0 && value.length > 0) return value;
|
|
2113
|
+
}
|
|
2114
|
+
return void 0;
|
|
2115
|
+
}
|
|
2116
|
+
function utf8Prefix(value, maxBytes) {
|
|
2117
|
+
let low = 0;
|
|
2118
|
+
let high = value.length;
|
|
2119
|
+
while (low < high) {
|
|
2120
|
+
const middle = Math.ceil((low + high) / 2);
|
|
2121
|
+
if (Buffer.byteLength(value.slice(0, middle), "utf8") <= maxBytes) {
|
|
2122
|
+
low = middle;
|
|
2123
|
+
} else {
|
|
2124
|
+
high = middle - 1;
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
let end = low;
|
|
2128
|
+
if (end > 0 && end < value.length && value.charCodeAt(end - 1) >= 55296 && value.charCodeAt(end - 1) <= 56319 && value.charCodeAt(end) >= 56320 && value.charCodeAt(end) <= 57343) {
|
|
2129
|
+
end -= 1;
|
|
2130
|
+
}
|
|
2131
|
+
return value.slice(0, end);
|
|
2132
|
+
}
|
|
2133
|
+
function boundedExtractedText(value, field) {
|
|
2134
|
+
if (value === void 0) return void 0;
|
|
2135
|
+
const originalBytes = Buffer.byteLength(value, "utf8");
|
|
2136
|
+
if (originalBytes <= MAX_EXTRACTED_TEXT_BYTES) return value;
|
|
2137
|
+
const digest = createHash("sha256").update(value).digest("hex");
|
|
2138
|
+
const marker = `
|
|
2139
|
+
[fml:${field}:truncated originalBytes=${originalBytes} sha256=${digest}]`;
|
|
2140
|
+
return `${utf8Prefix(
|
|
2141
|
+
value,
|
|
2142
|
+
MAX_EXTRACTED_TEXT_BYTES - Buffer.byteLength(marker, "utf8")
|
|
2143
|
+
)}${marker}`;
|
|
2144
|
+
}
|
|
2145
|
+
function nestedRecord(record, keys) {
|
|
2146
|
+
for (const key of keys) {
|
|
2147
|
+
const value = record[key];
|
|
2148
|
+
if (isRecord(value)) return value;
|
|
2149
|
+
}
|
|
2150
|
+
return void 0;
|
|
2151
|
+
}
|
|
2152
|
+
function timestampMs(payload, fallback) {
|
|
2153
|
+
for (const key of ["timestamp_ms", "timestampMs", "timestamp"]) {
|
|
2154
|
+
const value = payload[key];
|
|
2155
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
2156
|
+
const milliseconds = Math.round(
|
|
2157
|
+
value < 1e10 ? value * 1e3 : value
|
|
2158
|
+
);
|
|
2159
|
+
if (Number.isSafeInteger(milliseconds) && milliseconds > 0) {
|
|
2160
|
+
return milliseconds;
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
if (typeof value === "string") {
|
|
2164
|
+
const parsed = Date.parse(value);
|
|
2165
|
+
if (Number.isSafeInteger(parsed) && parsed >= 0) return parsed;
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
return fallback;
|
|
2169
|
+
}
|
|
2170
|
+
function geminiEventType(eventType) {
|
|
2171
|
+
switch (eventType) {
|
|
2172
|
+
case "BeforeAgent":
|
|
2173
|
+
case "BeforeModel":
|
|
2174
|
+
return "UserPromptSubmit";
|
|
2175
|
+
case "BeforeTool":
|
|
2176
|
+
return "PreToolUse";
|
|
2177
|
+
case "AfterTool":
|
|
2178
|
+
return "PostToolUse";
|
|
2179
|
+
case "AfterAgent":
|
|
2180
|
+
return "Stop";
|
|
2181
|
+
default:
|
|
2182
|
+
return eventType;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
var GROK_EVENT_TYPES = Object.freeze({
|
|
2186
|
+
session_start: "SessionStart",
|
|
2187
|
+
user_prompt_submit: "UserPromptSubmit",
|
|
2188
|
+
pre_tool_use: "PreToolUse",
|
|
2189
|
+
post_tool_use: "PostToolUse",
|
|
2190
|
+
post_tool_use_failure: "PostToolUseFailure",
|
|
2191
|
+
permission_denied: "PermissionDenied",
|
|
2192
|
+
stop: "Stop",
|
|
2193
|
+
stop_failure: "StopFailure",
|
|
2194
|
+
notification: "Notification",
|
|
2195
|
+
subagent_start: "SubagentStart",
|
|
2196
|
+
subagent_stop: "SubagentStop",
|
|
2197
|
+
pre_compact: "PreCompact",
|
|
2198
|
+
post_compact: "PostCompact",
|
|
2199
|
+
session_end: "SessionEnd"
|
|
2200
|
+
});
|
|
2201
|
+
function grokEventType(eventType) {
|
|
2202
|
+
return Object.hasOwn(GROK_EVENT_TYPES, eventType) ? GROK_EVENT_TYPES[eventType] ?? eventType : eventType;
|
|
2203
|
+
}
|
|
2204
|
+
function geminiUserPrompt(payload) {
|
|
2205
|
+
const request = payload.llm_request;
|
|
2206
|
+
if (!isRecord(request) || !Array.isArray(request.messages)) return void 0;
|
|
2207
|
+
const userMessage = [...request.messages].reverse().find(
|
|
2208
|
+
(message) => isRecord(message) && message.role === "user"
|
|
2209
|
+
);
|
|
2210
|
+
if (!userMessage) return void 0;
|
|
2211
|
+
if (typeof userMessage.content === "string") {
|
|
2212
|
+
return userMessage.content || void 0;
|
|
2213
|
+
}
|
|
2214
|
+
if (!Array.isArray(userMessage.content)) return void 0;
|
|
2215
|
+
const text = userMessage.content.filter(
|
|
2216
|
+
(part) => isRecord(part) && part.type === "text" && typeof part.text === "string"
|
|
2217
|
+
).map((part) => part.text).join("\n");
|
|
2218
|
+
return text || void 0;
|
|
2219
|
+
}
|
|
2220
|
+
function normalizedEvent(target, payload, context) {
|
|
2221
|
+
const sessionId = firstString(payload, [
|
|
2222
|
+
"session_id",
|
|
2223
|
+
"sessionId",
|
|
2224
|
+
"conversation_id",
|
|
2225
|
+
"conversationId"
|
|
2226
|
+
]);
|
|
2227
|
+
const rawEventType = firstString(payload, [
|
|
2228
|
+
"hook_event_name",
|
|
2229
|
+
"hookEventName",
|
|
2230
|
+
"event_name",
|
|
2231
|
+
"eventName",
|
|
2232
|
+
"event"
|
|
2233
|
+
]);
|
|
2234
|
+
if (!sessionId || !rawEventType) return null;
|
|
2235
|
+
const eventType = target === "gemini" ? geminiEventType(rawEventType) : target === "grok" ? grokEventType(rawEventType) : rawEventType;
|
|
2236
|
+
const toolInput = nestedRecord(payload, ["tool_input", "toolInput"]);
|
|
2237
|
+
const cwd = firstString(payload, [
|
|
2238
|
+
"cwd",
|
|
2239
|
+
"working_directory",
|
|
2240
|
+
"workingDirectory",
|
|
2241
|
+
"workspaceRoot"
|
|
2242
|
+
]) ?? context.cwd;
|
|
2243
|
+
const command = boundedExtractedText(
|
|
2244
|
+
firstText(payload, ["command"]) ?? (toolInput ? firstText(toolInput, ["command", "cmd"]) : void 0),
|
|
2245
|
+
"command"
|
|
2246
|
+
);
|
|
2247
|
+
const toolName = firstString(payload, ["tool_name", "toolName", "name"]);
|
|
2248
|
+
const filePath = firstString(payload, ["file_path", "filePath", "path"]) ?? (toolInput ? firstString(toolInput, ["file_path", "filePath", "path"]) : void 0) ?? (command && toolName && SHELL_TOOL_NAMES.has(toolName.toLowerCase()) ? extractReadFilePathFromCommand(command) ?? void 0 : void 0);
|
|
2249
|
+
const userPrompt = boundedExtractedText(
|
|
2250
|
+
firstText(payload, ["prompt", "user_prompt", "userPrompt"]) ?? (target === "gemini" ? geminiUserPrompt(payload) : void 0),
|
|
2251
|
+
"user_prompt"
|
|
2252
|
+
);
|
|
2253
|
+
const plan = boundedExtractedText(firstText(payload, ["plan"]), "plan");
|
|
2254
|
+
const allowedPrompts = boundedExtractedText(
|
|
2255
|
+
firstText(payload, ["allowed_prompts", "allowedPrompts"]),
|
|
2256
|
+
"allowed_prompts"
|
|
2257
|
+
);
|
|
2258
|
+
const toolResult = boundedExtractedText(
|
|
2259
|
+
firstText(payload, [
|
|
2260
|
+
"tool_result",
|
|
2261
|
+
"toolResult",
|
|
2262
|
+
"tool_response",
|
|
2263
|
+
"toolResponse"
|
|
2264
|
+
]),
|
|
2265
|
+
"tool_result"
|
|
2266
|
+
);
|
|
2267
|
+
const nativeEventId = firstString(payload, [
|
|
2268
|
+
"hook_id",
|
|
2269
|
+
"hookId",
|
|
2270
|
+
"event_id",
|
|
2271
|
+
"eventId",
|
|
2272
|
+
"tool_use_id",
|
|
2273
|
+
"toolUseId",
|
|
2274
|
+
"tool_call_id",
|
|
2275
|
+
"toolCallId",
|
|
2276
|
+
"prompt_id",
|
|
2277
|
+
"promptId"
|
|
2278
|
+
]) ?? (eventType === "UserPromptSubmit" || eventType === "Stop" ? firstString(payload, ["turn_id", "turnId"]) : void 0);
|
|
2279
|
+
return {
|
|
2280
|
+
sessionId,
|
|
2281
|
+
eventType,
|
|
2282
|
+
timestampMs: timestampMs(payload, context.nowMs),
|
|
2283
|
+
...nativeEventId ? { nativeEventId } : {},
|
|
2284
|
+
...cwd ? { cwd } : {},
|
|
2285
|
+
...toolName ? { toolName } : {},
|
|
2286
|
+
...userPrompt ? { userPrompt } : {},
|
|
2287
|
+
...filePath ? { filePath } : {},
|
|
2288
|
+
...command ? { command } : {},
|
|
2289
|
+
...plan ? { plan } : {},
|
|
2290
|
+
...allowedPrompts ? { allowedPrompts } : {},
|
|
2291
|
+
...toolResult ? { toolResult } : {}
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
function capability(targetId, nativeEvents) {
|
|
2295
|
+
return Object.freeze({
|
|
2296
|
+
targetId,
|
|
2297
|
+
nativeEvents,
|
|
2298
|
+
normalize: (payload, context) => normalizedEvent(targetId, payload, context)
|
|
2299
|
+
});
|
|
2300
|
+
}
|
|
2301
|
+
var HOOK_NORMALIZATION_CAPABILITIES = Object.freeze([
|
|
2302
|
+
capability("claude", CLAUDE_HOOK_EVENTS),
|
|
2303
|
+
capability("codex", CODEX_HOOK_EVENTS),
|
|
2304
|
+
capability("gemini", GEMINI_HOOK_EVENTS),
|
|
2305
|
+
capability("grok", GROK_HOOK_EVENTS),
|
|
2306
|
+
capability("pi", PI_HOOK_EVENTS)
|
|
2307
|
+
]);
|
|
2308
|
+
|
|
2309
|
+
// src/hooks/context.ts
|
|
2310
|
+
init_token_store();
|
|
2311
|
+
init_dirs();
|
|
2085
2312
|
|
|
2086
2313
|
// src/repo.ts
|
|
2087
2314
|
import { execFile, execFileSync as execFileSync2 } from "node:child_process";
|
|
@@ -2132,6 +2359,8 @@ function resolveRepositoryRootFromCwd(cwd, timeoutMs = GIT_TIMEOUT_MS) {
|
|
|
2132
2359
|
}
|
|
2133
2360
|
|
|
2134
2361
|
// src/hooks/sync-loop.ts
|
|
2362
|
+
init_token_store();
|
|
2363
|
+
init_config();
|
|
2135
2364
|
var MAX_RETRY_INTERVAL_MS = 5 * 6e4;
|
|
2136
2365
|
var MAX_RETRY_AFTER_MS = 24 * 60 * 6e4;
|
|
2137
2366
|
var MAX_HOOK_EVENT_ROW_JSON_BYTES = 2 * 1024 * 1024;
|
|
@@ -2211,14 +2440,14 @@ function sameDestination(left, right) {
|
|
|
2211
2440
|
return left.environment === right.environment && left.targetUrl === right.targetUrl && left.destinationIdentity === right.destinationIdentity;
|
|
2212
2441
|
}
|
|
2213
2442
|
function hookContextInjectionId(target, syncId) {
|
|
2214
|
-
const digest =
|
|
2443
|
+
const digest = createHash4("sha256").update(`${INJECTION_ID_VERSION}\0${target}\0${syncId}`).digest("base64url");
|
|
2215
2444
|
return `${INJECTION_ID_VERSION}_${digest}`;
|
|
2216
2445
|
}
|
|
2217
2446
|
function canonicalHookContextFilePath(filePath, cwd, repositoryRoot) {
|
|
2218
2447
|
const windows = [filePath, cwd, repositoryRoot].some(
|
|
2219
2448
|
(value) => value !== void 0 && value !== null && /^[A-Za-z]:[\\/]/.test(value)
|
|
2220
2449
|
);
|
|
2221
|
-
const pathApi = windows ?
|
|
2450
|
+
const pathApi = windows ? path7.win32 : path7.posix;
|
|
2222
2451
|
const normalized = pathApi.normalize(filePath);
|
|
2223
2452
|
const resolved = pathApi.isAbsolute(normalized) ? normalized : cwd && pathApi.isAbsolute(cwd) ? pathApi.resolve(cwd, normalized) : normalized;
|
|
2224
2453
|
if (repositoryRoot && pathApi.isAbsolute(resolved)) {
|
|
@@ -2540,17 +2769,17 @@ function absoluteEnvPath(value) {
|
|
|
2540
2769
|
const trimmed = value.trim();
|
|
2541
2770
|
if (trimmed === "~") return os2.homedir();
|
|
2542
2771
|
if (trimmed.startsWith("~/") || trimmed.startsWith("~\\")) {
|
|
2543
|
-
return
|
|
2772
|
+
return path8.join(os2.homedir(), trimmed.slice(2));
|
|
2544
2773
|
}
|
|
2545
|
-
return
|
|
2774
|
+
return path8.isAbsolute(trimmed) ? path8.normalize(trimmed) : void 0;
|
|
2546
2775
|
}
|
|
2547
2776
|
function observerDirectory() {
|
|
2548
2777
|
const fmlHome = absoluteEnvPath(process.env.FML_HOME);
|
|
2549
|
-
return absoluteEnvPath(process.env.FML_OBSERVER_DIR) ?? absoluteEnvPath(process.env.PANOPTICON_HOME) ?? (fmlHome ?
|
|
2778
|
+
return absoluteEnvPath(process.env.FML_OBSERVER_DIR) ?? absoluteEnvPath(process.env.PANOPTICON_HOME) ?? (fmlHome ? path8.join(fmlHome, "panopticon") : path8.join(os2.homedir(), ".fml", "panopticon"));
|
|
2550
2779
|
}
|
|
2551
2780
|
function readObserverEndpoint(filePath) {
|
|
2552
2781
|
try {
|
|
2553
|
-
const parsed = JSON.parse(
|
|
2782
|
+
const parsed = JSON.parse(fs5.readFileSync(filePath, "utf8"));
|
|
2554
2783
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2555
2784
|
return null;
|
|
2556
2785
|
}
|
|
@@ -2565,15 +2794,15 @@ function readObserverEndpoint(filePath) {
|
|
|
2565
2794
|
}
|
|
2566
2795
|
function readObserverAuthToken(filePath) {
|
|
2567
2796
|
try {
|
|
2568
|
-
return
|
|
2797
|
+
return fs5.readFileSync(filePath, "utf8").trim() || null;
|
|
2569
2798
|
} catch {
|
|
2570
2799
|
return null;
|
|
2571
2800
|
}
|
|
2572
2801
|
}
|
|
2573
2802
|
async function deliver(event) {
|
|
2574
2803
|
const directory = observerDirectory();
|
|
2575
|
-
const endpoint = readObserverEndpoint(
|
|
2576
|
-
const authToken = readObserverAuthToken(
|
|
2804
|
+
const endpoint = readObserverEndpoint(path8.join(directory, "endpoint.json"));
|
|
2805
|
+
const authToken = readObserverAuthToken(path8.join(directory, "auth-token"));
|
|
2577
2806
|
if (!endpoint || !authToken) return;
|
|
2578
2807
|
const host = endpoint.host === "::1" ? "[::1]" : endpoint.host;
|
|
2579
2808
|
try {
|
|
@@ -2715,7 +2944,7 @@ var PiReceiptComponent = class {
|
|
|
2715
2944
|
}
|
|
2716
2945
|
};
|
|
2717
2946
|
function fmlPiExtension(pi) {
|
|
2718
|
-
let sessionId =
|
|
2947
|
+
let sessionId = randomUUID4();
|
|
2719
2948
|
let initializedSessionId = null;
|
|
2720
2949
|
let cwd;
|
|
2721
2950
|
let repository = null;
|