@harnessforce/claude-code-plugin 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,1510 @@
1
+ let node_os = require("node:os");
2
+ let node_perf_hooks = require("node:perf_hooks");
3
+ let node_path = require("node:path");
4
+ let node_crypto = require("node:crypto");
5
+ let node_fs_promises = require("node:fs/promises");
6
+ let node_child_process = require("node:child_process");
7
+ let node_fs = require("node:fs");
8
+ //#region ../../packages/agent-core/src/hook-warning.ts
9
+ const RESTART_FAILED_WARNING = "Harnessforceのhookを起動し直せませんでした";
10
+ const INVALID_ENDPOINT_WARNING = "Harnessforceの送信先が不正です。`harnessforce init`を実行し直してください";
11
+ const REGISTRATION_FAILED_WARNING = "Harnessforceへのsession登録に失敗しました。通信を確かめてください";
12
+ const notConfiguredWarning = (agent) => agent === "claude_code" ? "Harnessforceの送信が設定されていません。`/harnessforce:setup`を実行してください" : "Harnessforceの送信が設定されていません。`harnessforce init`を実行してください";
13
+ const warningOnlyOutput = (message) => `${JSON.stringify({ systemMessage: message })}\n`;
14
+ //#endregion
15
+ //#region ../../packages/agent-core/src/config/canonical.ts
16
+ const byCodeUnit$1 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
17
+ function canonicalJson(value) {
18
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
19
+ if (typeof value === "object" && value !== null) return `{${Object.keys(value).sort(byCodeUnit$1).map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
20
+ return JSON.stringify(value);
21
+ }
22
+ const sha256 = (data) => (0, node_crypto.createHash)("sha256").update(data).digest("hex");
23
+ const hashValue = (value) => sha256(Buffer.from(canonicalJson(value), "utf8"));
24
+ function hashFileContent(content) {
25
+ const normalized = content.includes("\r\n") ? Buffer.from(content.toString("latin1").replace(/\r\n/g, "\n"), "latin1") : content;
26
+ return sha256(normalized);
27
+ }
28
+ function sortComponents(components) {
29
+ return [...components].sort((a, b) => byCodeUnit$1(a.kind, b.kind) || byCodeUnit$1(a.source, b.source) || byCodeUnit$1(a.id, b.id));
30
+ }
31
+ //#endregion
32
+ //#region ../../packages/agent-core/src/object.ts
33
+ const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
34
+ //#endregion
35
+ //#region ../../packages/agent-core/src/config/files.ts
36
+ const MAX_DEPTH = 8;
37
+ const MAX_FILE_BYTES = 1048576;
38
+ var CollectionExpired = class extends Error {};
39
+ async function readFileIfExists(path, guard) {
40
+ guard();
41
+ const info = await (0, node_fs_promises.stat)(path).catch(() => void 0);
42
+ if (!info?.isFile() || info.size > MAX_FILE_BYTES) return void 0;
43
+ return (0, node_fs_promises.readFile)(path).catch(() => void 0);
44
+ }
45
+ async function exists(path, guard) {
46
+ guard();
47
+ return (0, node_fs_promises.stat)(path).then(() => true, () => false);
48
+ }
49
+ async function readJsonObjectFile(path, guard) {
50
+ const content = await readFileIfExists(path, guard);
51
+ if (!content) return void 0;
52
+ try {
53
+ const value = JSON.parse(content.toString("utf8"));
54
+ return isObject(value) ? value : void 0;
55
+ } catch {
56
+ return;
57
+ }
58
+ }
59
+ async function entries(dir, guard) {
60
+ guard();
61
+ return (await (0, node_fs_promises.readdir)(dir, { withFileTypes: true }).catch(() => [])).filter((entry) => !entry.name.startsWith("."));
62
+ }
63
+ async function kindOf(dir, entry, guard) {
64
+ if (entry.isFile()) return "file";
65
+ if (entry.isDirectory()) return "directory";
66
+ if (!entry.isSymbolicLink()) return void 0;
67
+ guard();
68
+ const target = await (0, node_fs_promises.stat)((0, node_path.join)(dir, entry.name)).catch(() => void 0);
69
+ if (target?.isFile()) return "file";
70
+ return target?.isDirectory() ? "directory" : void 0;
71
+ }
72
+ async function listFiles(dir, ext, recursive, guard) {
73
+ const found = [];
74
+ const visited = /* @__PURE__ */ new Set();
75
+ const walk = async (relative, depth) => {
76
+ const current = relative ? (0, node_path.join)(dir, relative) : dir;
77
+ guard();
78
+ const real = await (0, node_fs_promises.realpath)(current).catch(() => void 0);
79
+ if (real === void 0 || visited.has(real)) return;
80
+ visited.add(real);
81
+ for (const entry of await entries(current, guard)) {
82
+ const path = relative ? `${relative}/${entry.name}` : entry.name;
83
+ const kind = await kindOf(current, entry, guard);
84
+ if (kind === "file" && entry.name.endsWith(ext)) found.push(path);
85
+ if (kind === "directory" && recursive && depth < MAX_DEPTH) await walk(path, depth + 1);
86
+ }
87
+ };
88
+ await walk("", 0);
89
+ return found;
90
+ }
91
+ async function listDirectories(dir, guard) {
92
+ const names = [];
93
+ for (const entry of await entries(dir, guard)) if (await kindOf(dir, entry, guard) === "directory") names.push(entry.name);
94
+ return names;
95
+ }
96
+ //#endregion
97
+ //#region ../../packages/agent-core/src/managed.ts
98
+ const MANAGED_DIRS = {
99
+ darwin: "/Library/Application Support/ClaudeCode",
100
+ win32: "C:\\Program Files\\ClaudeCode"
101
+ };
102
+ const managedDirFor = (platform) => MANAGED_DIRS[platform] ?? "/etc/claude-code";
103
+ const noGuard = () => {};
104
+ async function readManagedEnv(managedDir, names) {
105
+ const dropInDir = (0, node_path.join)(managedDir, "managed-settings.d");
106
+ const dropIns = (await listFiles(dropInDir, ".json", false, noGuard)).sort(byCodeUnit$1);
107
+ const files = [(0, node_path.join)(managedDir, "managed-settings.json"), ...dropIns.map((name) => (0, node_path.join)(dropInDir, name))];
108
+ const values = Object.fromEntries(names.map((name) => [name, void 0]));
109
+ for (const file of files) {
110
+ const env = (await readJsonObjectFile(file, noGuard))?.env;
111
+ if (!isObject(env)) continue;
112
+ for (const name of names) if (typeof env[name] === "string") values[name] = env[name];
113
+ }
114
+ return values;
115
+ }
116
+ //#endregion
117
+ //#region ../../packages/agent-core/src/process/lookup.ts
118
+ const DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD";
119
+ const NPM_SHIM_MAX_BYTES = 65536;
120
+ const SHEBANG_MAX_BYTES = 256;
121
+ const CMD_UNSAFE = /["%!\r\n]/;
122
+ const NPM_SHIM_PROG_LINE = "SET \"_prog=%dp0%\\node.exe\"";
123
+ const NPM_SHIM_LAUNCH_LINE = /"%_prog%"[ \t]+"%dp0%\\([^"]+)"[ \t]+%\*$/;
124
+ const pathFor = (platform) => platform === "win32" ? node_path.win32 : node_path.posix;
125
+ const isBatch = (file) => /\.(cmd|bat)$/i.test(file);
126
+ function envValue(env, name, platform) {
127
+ if (platform !== "win32") return env[name];
128
+ const upper = name.toUpperCase();
129
+ const key = Object.keys(env).find((k) => k.toUpperCase() === upper);
130
+ return key === void 0 ? void 0 : env[key];
131
+ }
132
+ async function readBytes(path, length) {
133
+ try {
134
+ const handle = await (0, node_fs_promises.open)(path, "r");
135
+ try {
136
+ const buffer = Buffer.alloc(length);
137
+ const { bytesRead } = await handle.read(buffer, 0, length, 0);
138
+ return buffer.subarray(0, bytesRead);
139
+ } finally {
140
+ await handle.close();
141
+ }
142
+ } catch {
143
+ return;
144
+ }
145
+ }
146
+ const defaultFs = {
147
+ isFile: async (path) => {
148
+ try {
149
+ return (await (0, node_fs_promises.stat)(path)).isFile();
150
+ } catch {
151
+ return false;
152
+ }
153
+ },
154
+ isExecutable: async (path) => {
155
+ try {
156
+ await (0, node_fs_promises.access)(path, node_fs.constants.X_OK);
157
+ return true;
158
+ } catch {
159
+ return false;
160
+ }
161
+ },
162
+ exists: async (path) => {
163
+ try {
164
+ await (0, node_fs_promises.stat)(path);
165
+ return true;
166
+ } catch {
167
+ return false;
168
+ }
169
+ },
170
+ readSmallText: async (path, maxBytes) => {
171
+ const bytes = await readBytes(path, maxBytes + 1);
172
+ return bytes === void 0 || bytes.length > maxBytes ? void 0 : bytes.toString("utf8");
173
+ },
174
+ readHead: async (path, maxBytes) => (await readBytes(path, maxBytes))?.toString("utf8")
175
+ };
176
+ async function excludedDirectories(context) {
177
+ const path = pathFor(context.platform);
178
+ const fs = context.fs ?? defaultFs;
179
+ const key = (dir) => {
180
+ const resolved = path.resolve(dir);
181
+ return context.platform === "win32" ? resolved.toLowerCase() : resolved;
182
+ };
183
+ const exact = new Set(context.bases.map(key));
184
+ const roots = [];
185
+ for (const base of context.bases) {
186
+ let dir = path.resolve(base);
187
+ for (;;) {
188
+ if (await fs.exists(path.join(dir, ".git"))) {
189
+ roots.push(key(dir));
190
+ break;
191
+ }
192
+ const parent = path.dirname(dir);
193
+ if (parent === dir) break;
194
+ dir = parent;
195
+ }
196
+ }
197
+ return (dir) => {
198
+ const candidate = key(dir);
199
+ if (exact.has(candidate)) return true;
200
+ return roots.some((root) => {
201
+ const prefix = root.endsWith(path.sep) ? root : `${root}${path.sep}`;
202
+ return candidate === root || candidate.startsWith(prefix);
203
+ });
204
+ };
205
+ }
206
+ function candidateNames(name, context) {
207
+ if (context.platform !== "win32") return [name];
208
+ const extensions = (envValue(context.env, "PATHEXT", "win32") ?? DEFAULT_PATHEXT).split(";").filter(Boolean);
209
+ const extension = node_path.win32.extname(name).toLowerCase();
210
+ if (extension && extensions.some((e) => e.toLowerCase() === extension)) return [name];
211
+ return extensions.map((e) => `${name}${e}`);
212
+ }
213
+ async function isRunnable(file, context) {
214
+ const fs = context.fs ?? defaultFs;
215
+ if (!await fs.isFile(file)) return false;
216
+ return context.platform === "win32" || fs.isExecutable(file);
217
+ }
218
+ async function findIn(name, context, excluded) {
219
+ const path = pathFor(context.platform);
220
+ const dirs = (envValue(context.env, "PATH", context.platform) ?? "").split(path.delimiter).filter((dir) => dir !== "" && path.isAbsolute(dir) && !excluded(dir));
221
+ for (const dir of dirs) for (const candidate of candidateNames(name, context)) {
222
+ const file = path.join(dir, candidate);
223
+ if (await isRunnable(file, context)) return file;
224
+ }
225
+ }
226
+ async function findCommand(name, context) {
227
+ return findIn(name, context, await excludedDirectories(context));
228
+ }
229
+ async function findProgram(name, context) {
230
+ const file = await findCommand(name, context);
231
+ return file === void 0 || context.platform === "win32" && isBatch(file) ? void 0 : file;
232
+ }
233
+ async function npmShimScript(file, fs) {
234
+ if (!/\.cmd$/i.test(file)) return void 0;
235
+ const text = await fs.readSmallText(file, NPM_SHIM_MAX_BYTES);
236
+ if (text === void 0) return void 0;
237
+ const lines = text.split(/\r?\n/);
238
+ if (!lines.some((line) => line.trim() === NPM_SHIM_PROG_LINE)) return void 0;
239
+ const launches = lines.flatMap((line) => {
240
+ const match = NPM_SHIM_LAUNCH_LINE.exec(line);
241
+ return match?.[1] === void 0 ? [] : [match[1]];
242
+ });
243
+ const [script] = launches;
244
+ if (launches.length !== 1 || script === void 0 || CMD_UNSAFE.test(script)) return void 0;
245
+ return node_path.win32.join(node_path.win32.dirname(file), script);
246
+ }
247
+ async function isEnvNodeScript(file, fs) {
248
+ const head = await fs.readHead(file, SHEBANG_MAX_BYTES);
249
+ const end = head?.indexOf("\n") ?? -1;
250
+ if (head === void 0 || end < 0) return false;
251
+ const line = head.slice(0, end).replace(/\r$/, "");
252
+ if (!line.startsWith("#!")) return false;
253
+ const words = line.slice(2).split(/[ \t]+/).filter(Boolean);
254
+ const [interpreter, program] = words;
255
+ return words.length === 2 && interpreter !== void 0 && node_path.posix.isAbsolute(interpreter) && node_path.posix.basename(interpreter) === "env" && program === "node";
256
+ }
257
+ async function nodeScriptFor(file, context) {
258
+ const fs = context.fs ?? defaultFs;
259
+ if (context.platform === "win32") return npmShimScript(file, fs);
260
+ return await isEnvNodeScript(file, fs) ? file : void 0;
261
+ }
262
+ function cmdExe(context, excluded) {
263
+ const usable = (file) => node_path.win32.isAbsolute(file) && !excluded(node_path.win32.dirname(file));
264
+ const comSpec = envValue(context.env, "ComSpec", "win32");
265
+ if (comSpec !== void 0 && usable(comSpec)) return comSpec;
266
+ const systemRoot = envValue(context.env, "SystemRoot", "win32");
267
+ if (systemRoot === void 0 || !node_path.win32.isAbsolute(systemRoot)) return void 0;
268
+ if (excluded(systemRoot)) return void 0;
269
+ const file = node_path.win32.join(systemRoot, "System32", "cmd.exe");
270
+ return usable(file) ? file : void 0;
271
+ }
272
+ async function commandLineFor(file, args, context, { quoteArgs = true } = {}) {
273
+ const script = await nodeScriptFor(file, context);
274
+ if (script !== void 0) return {
275
+ file: context.execPath ?? process.execPath,
276
+ args: [script, ...args],
277
+ verbatim: false
278
+ };
279
+ if (context.platform !== "win32" || !isBatch(file)) return {
280
+ file,
281
+ args: [...args],
282
+ verbatim: false
283
+ };
284
+ if ([file, ...args].some((word) => CMD_UNSAFE.test(word))) return void 0;
285
+ const shell = cmdExe(context, await excludedDirectories(context));
286
+ if (shell === void 0) return void 0;
287
+ return {
288
+ file: shell,
289
+ args: [
290
+ "/d",
291
+ "/s",
292
+ "/c",
293
+ `"${quoteArgs ? [file, ...args].map((word) => `"${word}"`).join(" ") : [`"${file}"`, ...args].join(" ")}"`
294
+ ],
295
+ verbatim: true
296
+ };
297
+ }
298
+ //#endregion
299
+ //#region ../../packages/agent-core/src/process/git.ts
300
+ const GIT_TIMEOUT_MS = 1e3;
301
+ const execGit = (file, args, env, timeoutMs) => new Promise((resolve) => {
302
+ (0, node_child_process.execFile)(file, args, {
303
+ encoding: "utf8",
304
+ env,
305
+ timeout: timeoutMs,
306
+ windowsHide: true
307
+ }, (error, stdout) => resolve(error ? void 0 : stdout.trim() || void 0));
308
+ });
309
+ function createGitRunner({ platform, env, processCwd, excludeTarget, timeoutMs = GIT_TIMEOUT_MS, fs, exec = execGit }) {
310
+ const isGitVariable = (name) => (platform === "win32" ? name.toUpperCase() : name).startsWith("GIT_");
311
+ const gitEnv = Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== void 0 && !isGitVariable(entry[0])));
312
+ const resolved = /* @__PURE__ */ new Map();
313
+ const gitFor = (cwd) => {
314
+ const bases = excludeTarget ? [processCwd, cwd] : [processCwd];
315
+ const key = bases.join("\0");
316
+ let file = resolved.get(key);
317
+ if (!file) {
318
+ file = findProgram("git", {
319
+ platform,
320
+ env,
321
+ bases,
322
+ ...fs ? { fs } : {}
323
+ });
324
+ resolved.set(key, file);
325
+ }
326
+ return file;
327
+ };
328
+ return async (cwd, args) => {
329
+ const file = await gitFor(cwd);
330
+ return file === void 0 ? void 0 : exec(file, [
331
+ "-C",
332
+ cwd,
333
+ ...args
334
+ ], gitEnv, timeoutMs);
335
+ };
336
+ }
337
+ //#endregion
338
+ //#region ../../packages/agent-core/src/process/runtime-env.ts
339
+ const RUNTIME_VARIABLES = [
340
+ "NODE_TLS_REJECT_UNAUTHORIZED",
341
+ "NODE_EXTRA_CA_CERTS",
342
+ "NODE_OPTIONS",
343
+ "HTTP_PROXY",
344
+ "HTTPS_PROXY",
345
+ "NO_PROXY",
346
+ "http_proxy",
347
+ "https_proxy",
348
+ "no_proxy",
349
+ "OPENSSL_CONF",
350
+ "SSL_CERT_FILE",
351
+ "SSL_CERT_DIR"
352
+ ];
353
+ const STASH = "HARNESSFORCE_RUNTIME_ENV";
354
+ const IGNORED_WHILE_WAITING = ["SIGINT", "SIGQUIT"];
355
+ const FORWARDED = ["SIGTERM", "SIGHUP"];
356
+ const isRuntimeVariable = (name, platform) => platform === "win32" ? RUNTIME_VARIABLES.some((v) => v.toUpperCase() === name.toUpperCase()) : RUNTIME_VARIABLES.includes(name);
357
+ function splitRuntimeEnv(env, platform) {
358
+ const clean = {};
359
+ const removed = {};
360
+ for (const [name, value] of Object.entries(env)) {
361
+ if (value === void 0) continue;
362
+ if (isRuntimeVariable(name, platform)) removed[name] = value;
363
+ else clean[name] = value;
364
+ }
365
+ return {
366
+ clean,
367
+ removed
368
+ };
369
+ }
370
+ const spawnSelf = (env) => (0, node_child_process.spawn)(process.execPath, [...process.execArgv, ...process.argv.slice(1)], {
371
+ env,
372
+ stdio: "inherit"
373
+ });
374
+ function relaunchWithoutRuntimeVariables({ platform, env, stash, spawnSelf: start }) {
375
+ const { clean, removed } = splitRuntimeEnv(env, platform);
376
+ if (Object.keys(removed).length === 0) return Promise.resolve({ kind: "not-needed" });
377
+ const childEnv = Object.fromEntries(Object.entries(clean).filter(([name]) => name !== STASH));
378
+ if (stash) childEnv[STASH] = JSON.stringify(removed);
379
+ return new Promise((resolve) => {
380
+ let child;
381
+ try {
382
+ child = start(childEnv);
383
+ } catch {
384
+ return resolve({ kind: "failed" });
385
+ }
386
+ const ignore = () => {};
387
+ const forward = (signal) => child.kill(signal);
388
+ for (const signal of IGNORED_WHILE_WAITING) process.on(signal, ignore);
389
+ for (const signal of FORWARDED) process.on(signal, forward);
390
+ const finish = (outcome) => {
391
+ for (const signal of IGNORED_WHILE_WAITING) process.off(signal, ignore);
392
+ for (const signal of FORWARDED) process.off(signal, forward);
393
+ resolve(outcome);
394
+ };
395
+ child.once("error", () => finish({ kind: "failed" }));
396
+ child.once("exit", (code, signal) => finish({
397
+ kind: "exited",
398
+ code: code ?? 128 + (signal ? node_os.constants.signals[signal] : 0)
399
+ }));
400
+ });
401
+ }
402
+ const NO_WHITE_SPACE = new RegExp(`^[^\\t-\\r \\u0085\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000]+$`);
403
+ const isToken = (value, maxLength = 256) => {
404
+ const length = [...value].length;
405
+ return length >= 1 && length <= maxLength && NO_WHITE_SPACE.test(value);
406
+ };
407
+ const INSTANT_PATTERN = `^(?:1(?:6(?:7[7-9]|[89]\\d)|[7-9]\\d\\d)|2(?:[01]\\d\\d|2(?:[0-5]\\d|6[0-2])))-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d)$`;
408
+ const INSTANT_PARTS = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(?:Z|([+-])(\d{2}):(\d{2}))$/;
409
+ const INSTANT = new RegExp(INSTANT_PATTERN);
410
+ const MIN_INSTANT_MS = Date.UTC(1677, 8, 21, 0, 12, 44);
411
+ const MAX_INSTANT_MS = Date.UTC(2262, 3, 11, 23, 47, 16);
412
+ function isInstant(value) {
413
+ const parts = INSTANT.test(value) ? INSTANT_PARTS.exec(value) : null;
414
+ if (!parts) return false;
415
+ const [, ...fields] = parts;
416
+ const [year, month, day, hour, minute, second] = fields.slice(0, 6).map(Number);
417
+ const [fraction = "", sign, offsetHours = "0", offsetMinutes = "0"] = fields.slice(6);
418
+ const localMs = Date.UTC(year, month - 1, day, hour, minute, second);
419
+ const local = new Date(localMs);
420
+ if (!(local.getUTCFullYear() === year && local.getUTCMonth() === month - 1 && local.getUTCDate() === day && local.getUTCHours() === hour && local.getUTCMinutes() === minute && local.getUTCSeconds() === second)) return false;
421
+ const secondMs = localMs - (sign === "-" ? -1 : 1) * (Number(offsetHours) * 60 + Number(offsetMinutes)) * 6e4;
422
+ const nanos = Number(fraction.slice(0, 9) || "0");
423
+ return secondMs >= MIN_INSTANT_MS && !(secondMs > MAX_INSTANT_MS || secondMs === MAX_INSTANT_MS && nanos > 0);
424
+ }
425
+ //#endregion
426
+ //#region ../../packages/semconv/dist/repository.js
427
+ const SCHEME_URL = /^[a-z][a-z0-9+.-]*:\/\//i;
428
+ const SCP_LIKE = /^(?:[^@/\s]+@)?([^:/\s]+):(?!\/)(.+)$/;
429
+ const HOST = /^[a-z0-9.-]+$/;
430
+ const SEGMENT = /^[A-Za-z0-9_.-]+$/;
431
+ function splitRemote(remote) {
432
+ if (SCHEME_URL.test(remote)) {
433
+ let url;
434
+ try {
435
+ url = new URL(remote);
436
+ } catch {
437
+ return;
438
+ }
439
+ return url.protocol === "file:" ? void 0 : {
440
+ host: url.hostname,
441
+ path: url.pathname
442
+ };
443
+ }
444
+ const scp = SCP_LIKE.exec(remote);
445
+ return scp?.[1] && scp[2] ? {
446
+ host: scp[1],
447
+ path: scp[2]
448
+ } : void 0;
449
+ }
450
+ function normalizeRepository(remoteUrl) {
451
+ const location = splitRemote(remoteUrl.trim());
452
+ if (!location) return void 0;
453
+ const lowerHost = location.host.toLowerCase();
454
+ const host = lowerHost === "ssh.github.com" ? "github.com" : lowerHost;
455
+ const segments = location.path.replace(/\/+$/, "").replace(/\.git$/, "").replace(/^\/+/, "").split("/");
456
+ if (segments.length !== 2 || !HOST.test(host) || !segments.every((segment) => SEGMENT.test(segment))) return void 0;
457
+ const [owner, name] = host === "github.com" ? segments.map((segment) => segment.toLowerCase()) : segments;
458
+ return `${host}/${owner}/${name}`;
459
+ }
460
+ //#endregion
461
+ //#region ../../packages/agent-core/src/config/scope.ts
462
+ const MAX_COMPONENTS = 1e3;
463
+ var TooManyComponents = class extends Error {};
464
+ var Sink = class {
465
+ guard;
466
+ components = [];
467
+ constructor(guard) {
468
+ this.guard = guard;
469
+ }
470
+ add(component) {
471
+ if (!isToken(component.id)) return;
472
+ if (this.components.length >= MAX_COMPONENTS) throw new TooManyComponents();
473
+ this.components.push(component);
474
+ }
475
+ hasDuplicates() {
476
+ return new Set(this.components.map((c) => `${c.kind}\0${c.source}\0${c.id}`)).size !== this.components.length;
477
+ }
478
+ scope = (source, idPrefix = "", version) => {
479
+ const add = (kind, id, hash) => this.add({
480
+ kind,
481
+ source,
482
+ id: `${idPrefix}${id}`,
483
+ ...version === void 0 ? {} : { version },
484
+ hash
485
+ });
486
+ return {
487
+ guard: this.guard,
488
+ addValue: (kind, id, value) => add(kind, id, hashValue(value)),
489
+ addFile: async (kind, id, path) => {
490
+ const content = await readFileIfExists(path, this.guard);
491
+ if (content) add(kind, id, hashFileContent(content));
492
+ }
493
+ };
494
+ };
495
+ };
496
+ const absoluteEnv = (value) => value && (0, node_path.isAbsolute)(value) ? value : void 0;
497
+ const withoutExt = (path, ext) => path.slice(0, -ext.length);
498
+ const colonName = (path) => withoutExt(path, ".md").replace(/\//g, ":");
499
+ async function collectLayout(s, layout) {
500
+ if (layout.skills) for (const name of await listDirectories(layout.skills, s.guard)) await s.addFile("skill", name, (0, node_path.join)(layout.skills, name, "SKILL.md"));
501
+ if (layout.agents) for (const path of await listFiles(layout.agents, ".md", true, s.guard)) await s.addFile("agent", colonName(path), (0, node_path.join)(layout.agents, path));
502
+ if (layout.commands) for (const path of await listFiles(layout.commands, ".md", true, s.guard)) await s.addFile("command", colonName(path), (0, node_path.join)(layout.commands, path));
503
+ if (layout.workflows) for (const path of await listFiles(layout.workflows, ".js", false, s.guard)) await s.addFile("workflow", withoutExt(path, ".js"), (0, node_path.join)(layout.workflows, path));
504
+ }
505
+ async function collectRules(s, base, names, rulesDir) {
506
+ for (const name of names) await s.addFile("rule", name, (0, node_path.join)(base, name));
507
+ if (rulesDir) for (const path of await listFiles(rulesDir, ".md", true, s.guard)) await s.addFile("rule", `rules/${path}`, (0, node_path.join)(rulesDir, path));
508
+ }
509
+ function collectHooks(s, hooks) {
510
+ if (!isObject(hooks)) return;
511
+ for (const [event, value] of Object.entries(hooks)) s.addValue("hook", event, value);
512
+ }
513
+ function collectMcpServers(s, servers) {
514
+ if (!isObject(servers)) return;
515
+ for (const [name, value] of Object.entries(servers)) if (isObject(value)) s.addValue("mcp_server", name, value);
516
+ }
517
+ //#endregion
518
+ //#region ../../packages/agent-core/src/config/plugins.ts
519
+ const NON_MARKETPLACE_ORIGINS = /* @__PURE__ */ new Set([
520
+ "inline",
521
+ "skills-dir",
522
+ "synced"
523
+ ]);
524
+ const PLUGIN_ID = /^([A-Za-z0-9._-]+)@([A-Za-z0-9._-]+)$/;
525
+ const isPathSegment = (name) => name !== "." && name !== "..";
526
+ const ORPHANED_MARK = ".orphaned_at";
527
+ function enabledPluginIds(sources) {
528
+ const merged = /* @__PURE__ */ new Map();
529
+ for (const settings of sources) {
530
+ const enabled = settings?.enabledPlugins;
531
+ if (isObject(enabled)) for (const [id, value] of Object.entries(enabled)) merged.set(id, value);
532
+ }
533
+ return [...merged].filter(([, value]) => value === true).map(([id]) => id);
534
+ }
535
+ async function liveVersion(pluginDir, guard) {
536
+ const live = [];
537
+ for (const version of await listDirectories(pluginDir, guard)) if (!await exists((0, node_path.join)(pluginDir, version, ORPHANED_MARK), guard)) live.push(version);
538
+ return live.length === 1 ? live[0] : void 0;
539
+ }
540
+ async function collectPlugins(pluginsRoot, settingsByPrecedence, scope, guard) {
541
+ for (const id of enabledPluginIds(settingsByPrecedence)) {
542
+ const [, name, marketplace] = PLUGIN_ID.exec(id) ?? [];
543
+ if (!name || !marketplace || !isPathSegment(name) || !isPathSegment(marketplace) || NON_MARKETPLACE_ORIGINS.has(marketplace)) continue;
544
+ const pluginDir = (0, node_path.join)(pluginsRoot, "cache", marketplace, name);
545
+ const version = await liveVersion(pluginDir, guard);
546
+ if (version === void 0) continue;
547
+ const root = (0, node_path.join)(pluginDir, version);
548
+ const s = scope("plugin", `${id}:`, isToken(version, 64) ? version : void 0);
549
+ await collectLayout(s, {
550
+ skills: (0, node_path.join)(root, "skills"),
551
+ agents: (0, node_path.join)(root, "agents"),
552
+ commands: (0, node_path.join)(root, "commands"),
553
+ workflows: (0, node_path.join)(root, "workflows")
554
+ });
555
+ collectHooks(s, (await readJsonObjectFile((0, node_path.join)(root, "hooks/hooks.json"), guard))?.hooks);
556
+ collectMcpServers(s, (await readJsonObjectFile((0, node_path.join)(root, ".mcp.json"), guard))?.mcpServers);
557
+ }
558
+ }
559
+ //#endregion
560
+ //#region ../../packages/agent-core/src/config/collect.ts
561
+ const COLLECT_BUDGET_MS = 1e3;
562
+ async function readManagedSettings(managedDir, guard) {
563
+ let merged = await readJsonObjectFile((0, node_path.join)(managedDir, "managed-settings.json"), guard);
564
+ const dropInDir = (0, node_path.join)(managedDir, "managed-settings.d");
565
+ const dropIns = (await listFiles(dropInDir, ".json", false, guard)).sort(byCodeUnit$1);
566
+ for (const name of dropIns) {
567
+ const dropIn = await readJsonObjectFile((0, node_path.join)(dropInDir, name), guard);
568
+ if (dropIn) merged = {
569
+ ...merged,
570
+ ...dropIn
571
+ };
572
+ }
573
+ return merged;
574
+ }
575
+ function collectSettings(s, settings) {
576
+ if (!settings) return;
577
+ const { hooks, permissions, model } = settings;
578
+ collectHooks(s, hooks);
579
+ if (isObject(permissions)) s.addValue("permissions", "permissions", permissions);
580
+ if (typeof model === "string") s.addValue("model", model, model);
581
+ }
582
+ const objectAt = (value, key) => {
583
+ const found = value?.[key];
584
+ return isObject(found) ? found : {};
585
+ };
586
+ function locationsOf(options) {
587
+ const { projectRoot, managedDir, homeDir } = options;
588
+ const user = (0, node_path.join)(homeDir, ".claude");
589
+ const configDir = absoluteEnv(options.env.CLAUDE_CONFIG_DIR);
590
+ return {
591
+ projectRoot,
592
+ managedDir,
593
+ homeDir,
594
+ user,
595
+ configDir,
596
+ config: configDir ?? user,
597
+ project: (0, node_path.join)(projectRoot, ".claude")
598
+ };
599
+ }
600
+ async function collectFiles(at, s) {
601
+ const { projectRoot, managedDir, user, project } = at;
602
+ await collectRules(s.managed, managedDir, ["CLAUDE.md"]);
603
+ await collectLayout(s.managed, { skills: (0, node_path.join)(managedDir, ".claude/skills") });
604
+ if (!at.configDir) {
605
+ await collectRules(s.user, user, ["CLAUDE.md"], (0, node_path.join)(user, "rules"));
606
+ await collectLayout(s.user, {
607
+ skills: (0, node_path.join)(user, "skills"),
608
+ agents: (0, node_path.join)(user, "agents"),
609
+ commands: (0, node_path.join)(user, "commands")
610
+ });
611
+ }
612
+ await collectLayout(s.user, { workflows: (0, node_path.join)(at.config, "workflows") });
613
+ await collectRules(s.repository, projectRoot, ["CLAUDE.md", ".claude/CLAUDE.md"], (0, node_path.join)(project, "rules"));
614
+ await collectLayout(s.repository, {
615
+ skills: (0, node_path.join)(project, "skills"),
616
+ agents: (0, node_path.join)(project, "agents"),
617
+ commands: (0, node_path.join)(project, "commands"),
618
+ workflows: (0, node_path.join)(project, "workflows")
619
+ });
620
+ await collectRules(s.local, projectRoot, ["CLAUDE.local.md"]);
621
+ }
622
+ async function readAllSettings(at, guard) {
623
+ return {
624
+ managed: await readManagedSettings(at.managedDir, guard),
625
+ user: await readJsonObjectFile((0, node_path.join)(at.config, "settings.json"), guard),
626
+ repository: await readJsonObjectFile((0, node_path.join)(at.project, "settings.json"), guard),
627
+ local: await readJsonObjectFile((0, node_path.join)(at.project, "settings.local.json"), guard)
628
+ };
629
+ }
630
+ async function collectAllMcpServers(at, s, settings, guard) {
631
+ const managedMcp = await readJsonObjectFile((0, node_path.join)(at.managedDir, "managed-mcp.json"), guard);
632
+ collectMcpServers(s.managed, {
633
+ ...objectAt(settings.managed, "managedMcpServers"),
634
+ ...objectAt(managedMcp, "mcpServers")
635
+ });
636
+ const projectMcp = await readJsonObjectFile((0, node_path.join)(at.projectRoot, ".mcp.json"), guard);
637
+ collectMcpServers(s.repository, projectMcp?.mcpServers);
638
+ if (!at.configDir) {
639
+ const global = await readJsonObjectFile((0, node_path.join)(at.homeDir, ".claude.json"), guard);
640
+ collectMcpServers(s.user, global?.mcpServers);
641
+ collectMcpServers(s.local, objectAt(objectAt(global, "projects"), at.projectRoot).mcpServers);
642
+ }
643
+ }
644
+ async function collectAll(options, scope, guard) {
645
+ const at = locationsOf(options);
646
+ const scopes = {
647
+ managed: scope("managed"),
648
+ user: scope("user"),
649
+ repository: scope("repository"),
650
+ local: scope("local")
651
+ };
652
+ await collectFiles(at, scopes);
653
+ const settings = await readAllSettings(at, guard);
654
+ collectSettings(scopes.managed, settings.managed);
655
+ collectSettings(scopes.user, settings.user);
656
+ collectSettings(scopes.repository, settings.repository);
657
+ collectSettings(scopes.local, settings.local);
658
+ await collectAllMcpServers(at, scopes, settings, guard);
659
+ await collectPlugins(absoluteEnv(options.env.CLAUDE_CODE_PLUGIN_CACHE_DIR) ?? (0, node_path.join)(at.config, "plugins"), [
660
+ settings.user,
661
+ settings.repository,
662
+ settings.local,
663
+ settings.managed
664
+ ], scope, guard);
665
+ }
666
+ async function collectConfig(options) {
667
+ const guard = () => {
668
+ if (options.isExpired()) throw new CollectionExpired();
669
+ };
670
+ const sink = new Sink(guard);
671
+ try {
672
+ await collectAll(options, sink.scope, guard);
673
+ guard();
674
+ } catch (error) {
675
+ if (error instanceof CollectionExpired) return {
676
+ kind: "skipped",
677
+ reason: "timeout"
678
+ };
679
+ if (error instanceof TooManyComponents) return {
680
+ kind: "skipped",
681
+ reason: "too many components"
682
+ };
683
+ throw error;
684
+ }
685
+ if (sink.hasDuplicates()) return {
686
+ kind: "skipped",
687
+ reason: "duplicate identifier"
688
+ };
689
+ return {
690
+ kind: "collected",
691
+ components: sortComponents(sink.components)
692
+ };
693
+ }
694
+ //#endregion
695
+ //#region ../../packages/agent-core/src/url.ts
696
+ const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
697
+ "localhost",
698
+ "127.0.0.1",
699
+ "[::1]"
700
+ ]);
701
+ function parseAllowedUrl(value) {
702
+ if (!value) return void 0;
703
+ let url;
704
+ try {
705
+ url = new URL(value);
706
+ } catch {
707
+ return;
708
+ }
709
+ return url.protocol === "https:" || url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname) ? url : void 0;
710
+ }
711
+ function underBase(base, path) {
712
+ const url = new URL(base.origin);
713
+ url.pathname = `${base.pathname.replace(/\/+$/, "")}/${path}`;
714
+ return url;
715
+ }
716
+ //#endregion
717
+ //#region ../../packages/agent-core/src/ingest.ts
718
+ const SEND_TIMEOUT_MS = 2e3;
719
+ const INGEST_KEY_WORKSPACE = /^hf_ik_([A-Za-z0-9-]{1,128})_/;
720
+ const workspaceIdOf = (key) => INGEST_KEY_WORKSPACE.exec(key)?.[1];
721
+ function ingestBaseFrom(endpoint) {
722
+ const base = parseAllowedUrl(endpoint);
723
+ if (!base) return void 0;
724
+ const ingestBase = new URL(base.origin);
725
+ ingestBase.pathname = base.pathname.replace(/\/+$/, "");
726
+ return ingestBase;
727
+ }
728
+ async function postItems(destination, path, items, fetchImpl, timeoutMs = SEND_TIMEOUT_MS) {
729
+ try {
730
+ const response = await fetchImpl(underBase(destination.ingestBase, path), {
731
+ method: "POST",
732
+ headers: {
733
+ authorization: `Bearer ${destination.key}`,
734
+ "content-type": "application/json"
735
+ },
736
+ body: JSON.stringify(items),
737
+ redirect: "error",
738
+ signal: AbortSignal.timeout(timeoutMs)
739
+ });
740
+ await response.body?.cancel();
741
+ if (response.status === 401) return { kind: "unauthorized" };
742
+ return response.ok ? { kind: "accepted" } : {
743
+ kind: "failed",
744
+ reason: `HTTP ${response.status}`
745
+ };
746
+ } catch (error) {
747
+ return {
748
+ kind: "failed",
749
+ reason: error instanceof Error ? error.name : "unknown"
750
+ };
751
+ }
752
+ }
753
+ //#endregion
754
+ //#region ../../packages/agent-core/src/issue.ts
755
+ const isIssueIdentifier = (value) => isToken(value, 300);
756
+ //#endregion
757
+ //#region ../../packages/agent-core/src/using-harnessforce.ts
758
+ const usingHarnessforceBody = "---\nname: using-harnessforce\ndescription: Harnessforceのhookが入った環境でsessionを始めたとき、HarnessforceのIssue(ENG-42のような識別子)に取り組む作業を始めるとき、Harnessforceが何を記録し何を収集しないかを尋ねられたときに使う。sessionの開始時にhookがこの本文をcontextへ注入する。\n---\n\n# Harnessforceを使う\n\nこのsessionにはHarnessforceのhookが入っている。Harnessforceは、人とAIの作業をIssueに結び付けて記録する。次の規則に従う。\n\n## 何を記録するか\n\n- session registration: agent、session ID、repository、branch、commit、開始時刻、最初のprompt ID。\n- config snapshot: 利用中の設定の構成(Claude Codeだけ)。\n- OTelのテレメトリ: 利用者の設定で送るもの。\n- Issueに取り組む作業で`record-run`が記録する、Plan、Decision、Definition of Doneの充足。人が承認するまで確定しない。\n\n## 何を収集しないか\n\n- prompt、応答、toolの入出力、code diffの本文は、利用者が`--send-content`で選び、Workspaceがopt-inした場合だけ届く。既定では収集しない。\n- MCPのtoolの入力にも記録にも、これらの本文、secret、環境変数の値を含めない。\n\n## どのhookが何を送るか\n\n- SessionStart: session registrationとconfig snapshot(Codexはsession registrationだけ)。\n- UserPromptSubmit: 最初のpromptでの再送(Claude Codeだけ)。\n- hookは失敗してもsessionを止めない。送れない状態のときは、hookが警告を1行表示する。警告が出たら利用者に伝える。\n\n## どのskillとtoolをいつ使うか\n\n- IssueのIDを指定された作業、または環境変数`HARNESSFORCE_ISSUE`がある作業は、`record-run`に従い、`start_run`、`get_issue`、`record_plan`、`record_decision`、`complete_run`を使う。skillを読み込めないagentは、同じ順にtoolを呼ぶ。\n- `start_run`には、contextの`harnessforce session_id:`の行の値を渡す。複数あれば最後の値を使う。行が無ければ、session IDを推測せず、`start_run`と`complete_run`を呼ばない。\n- `/harnessforce:tune`の分析は`propose-improvements`を使う。\n- Issueが指定されていない作業では、Runの記録を始めない。\n\n## Red Flags\n\n次の考えが浮かんだら、立ち止まる。\n\n| 考え | 現実 |\n| --- | --- |\n| 小さな作業なので記録は要らない | Issueが指定された作業は、大きさによらず記録する |\n| session IDの行が無いが、推測して`start_run`を呼ぶ | 推測しない。Runがsessionと結び付かなくなる |\n| 本文を要約して渡せば記録の役に立つ | 本文はtoolに渡さない。収集しない範囲を超える |\n| 警告が出たが、気にしなくてよい | 送れていない。利用者に伝える |\n\n## 優先順位\n\n1. 利用者の指示(CLAUDE.md、AGENTS.md、直接の依頼)。利用者が記録を止めるよう指示したら、Runの記録を止める。\n2. この導入文。\n3. 既定の動作。\n\n## subagent\n\n親から特定の作業だけを委譲されたsubagentは、この導入文に従ってRunを登録しない。Runの登録は親が行う。\n".replace(/\r\n/g, "\n").replace(/^---\n[\s\S]*?\n---\n+/, "").trimEnd();
759
+ //#endregion
760
+ //#region ../../packages/agent-core/src/session-context.ts
761
+ const sessionContextLine = (sessionId) => `harnessforce session_id: ${sessionId}`;
762
+ const sessionContext = (sessionId) => ({ hookSpecificOutput: {
763
+ hookEventName: "SessionStart",
764
+ additionalContext: `${sessionContextLine(sessionId)}\n\n${usingHarnessforceBody}`
765
+ } });
766
+ //#endregion
767
+ //#region ../../packages/agent-core/src/vcs.ts
768
+ async function selectRemote(cwd, git) {
769
+ if (await git(cwd, ["rev-parse", "--is-inside-work-tree"]) !== "true") return void 0;
770
+ const remotes = (await git(cwd, ["remote"]))?.split("\n") ?? [];
771
+ return remotes.includes("origin") ? "origin" : remotes[0];
772
+ }
773
+ async function resolveVcs(cwd, git) {
774
+ const remote = await selectRemote(cwd, git);
775
+ if (!remote) return void 0;
776
+ const [url, branch, commit] = await Promise.all([
777
+ git(cwd, [
778
+ "remote",
779
+ "get-url",
780
+ remote
781
+ ]),
782
+ git(cwd, [
783
+ "symbolic-ref",
784
+ "--quiet",
785
+ "--short",
786
+ "HEAD"
787
+ ]),
788
+ git(cwd, [
789
+ "rev-parse",
790
+ "--verify",
791
+ "--quiet",
792
+ "HEAD"
793
+ ])
794
+ ]);
795
+ const repository = url === void 0 ? void 0 : normalizeRepository(url);
796
+ return {
797
+ ...repository === void 0 ? {} : { repository },
798
+ ...branch === void 0 ? {} : { branch },
799
+ ...commit === void 0 ? {} : { commit }
800
+ };
801
+ }
802
+ async function resolveProjectRoot(cwd, git) {
803
+ const topLevel = await git(cwd, ["rev-parse", "--show-toplevel"]);
804
+ return topLevel && (0, node_path.isAbsolute)(topLevel) ? topLevel : cwd;
805
+ }
806
+ //#endregion
807
+ //#region src/input.ts
808
+ const FILE_SAFE_SESSION_ID = /^[A-Za-z0-9_-]+$/;
809
+ const isFileSafeSessionId = (sessionId) => FILE_SAFE_SESSION_ID.test(sessionId);
810
+ const token = (value) => typeof value === "string" && isToken(value) ? value : void 0;
811
+ const absolutePath = (value) => typeof value === "string" && (0, node_path.isAbsolute)(value) ? value : void 0;
812
+ function parseJsonObject(raw) {
813
+ try {
814
+ const value = JSON.parse(raw);
815
+ return isObject(value) ? value : void 0;
816
+ } catch {
817
+ return;
818
+ }
819
+ }
820
+ function parseHookInput(raw) {
821
+ const fields = parseJsonObject(raw);
822
+ const sessionId = token(fields?.session_id);
823
+ const cwd = absolutePath(fields?.cwd);
824
+ if (!fields || !sessionId || !cwd) return void 0;
825
+ const scratchpadDir = absolutePath(fields.scratchpad_dir);
826
+ return {
827
+ sessionId,
828
+ cwd,
829
+ promptId: token(fields.prompt_id),
830
+ scratchpad: scratchpadDir && isFileSafeSessionId(sessionId) ? {
831
+ dir: scratchpadDir,
832
+ sessionId
833
+ } : void 0,
834
+ source: typeof fields.source === "string" ? fields.source : void 0,
835
+ fields
836
+ };
837
+ }
838
+ //#endregion
839
+ //#region src/scratchpad.ts
840
+ const registrationFile = (pad) => (0, node_path.join)(pad.dir, `registration-${pad.sessionId}.json`);
841
+ const unauthorizedMark = (pad) => (0, node_path.join)(pad.dir, `unauthorized-${pad.sessionId}`);
842
+ const firstPromptSentMark = (pad) => (0, node_path.join)(pad.dir, `first-prompt-sent-${pad.sessionId}`);
843
+ async function saveRegistration(pad, registration) {
844
+ await (0, node_fs_promises.writeFile)(registrationFile(pad), JSON.stringify(registration));
845
+ }
846
+ async function markUnauthorized(pad) {
847
+ await (0, node_fs_promises.writeFile)(unauthorizedMark(pad), "");
848
+ }
849
+ async function isMarkedUnauthorized(pad) {
850
+ return (0, node_fs_promises.access)(unauthorizedMark(pad)).then(() => true, () => false);
851
+ }
852
+ async function loadRegistration(pad) {
853
+ try {
854
+ const saved = JSON.parse(await (0, node_fs_promises.readFile)(registrationFile(pad), "utf8"));
855
+ return typeof saved === "object" && saved !== null && saved.session_id === pad.sessionId ? saved : void 0;
856
+ } catch {
857
+ return;
858
+ }
859
+ }
860
+ async function isFirstPromptSent(pad) {
861
+ return (0, node_fs_promises.access)(firstPromptSentMark(pad)).then(() => true, () => false);
862
+ }
863
+ async function claimFirstPrompt(pad) {
864
+ return (0, node_fs_promises.writeFile)(firstPromptSentMark(pad), "", { flag: "wx" }).then(() => true, () => false);
865
+ }
866
+ //#endregion
867
+ //#region src/usage/names.ts
868
+ const PLUGIN_COMPONENT = /^([A-Za-z0-9._-]+)@[A-Za-z0-9._-]+:(.+)$/;
869
+ function pluginPartsOf(identifier) {
870
+ const parts = identifier.plugin ? PLUGIN_COMPONENT.exec(identifier.id) : null;
871
+ return parts ? {
872
+ plugin: parts[1],
873
+ name: parts[2]
874
+ } : void 0;
875
+ }
876
+ function onlyOne(candidates) {
877
+ const ids = new Set(candidates.map((c) => c.id));
878
+ return ids.size === 1 ? [...ids][0] : void 0;
879
+ }
880
+ function identifierOfName(name, candidates) {
881
+ const colon = name.indexOf(":");
882
+ if (colon >= 0) {
883
+ const plugin = name.slice(0, colon);
884
+ const rest = name.slice(colon + 1);
885
+ return onlyOne(candidates.filter((c) => {
886
+ const parts = pluginPartsOf(c);
887
+ return parts?.plugin === plugin && parts.name === rest;
888
+ }));
889
+ }
890
+ const direct = candidates.filter((c) => !c.plugin && c.id === name);
891
+ return onlyOne(direct.length > 0 ? direct : candidates.filter((c) => pluginPartsOf(c)?.name === name));
892
+ }
893
+ const toToolName = (name) => name.replace(/[^A-Za-z0-9_-]/g, "_");
894
+ function toolPrefixOf(server) {
895
+ if (!server.plugin) return `mcp__${toToolName(server.id)}__`;
896
+ const parts = pluginPartsOf(server);
897
+ return parts && `mcp__plugin_${toToolName(parts.plugin)}_${toToolName(parts.name)}__`;
898
+ }
899
+ const mcpServerOfTool = (toolName, servers) => onlyOne(servers.filter((server) => {
900
+ const prefix = toolPrefixOf(server);
901
+ return prefix !== void 0 && toolName.startsWith(prefix);
902
+ }));
903
+ //#endregion
904
+ //#region src/usage/count.ts
905
+ const COUNTING_EVENTS = [
906
+ "post-tool-use",
907
+ "post-tool-use-failure",
908
+ "user-prompt-expansion",
909
+ "permission-request",
910
+ "post-compact"
911
+ ];
912
+ const named = (kind, id) => id === void 0 ? { kind } : {
913
+ kind,
914
+ id
915
+ };
916
+ const stringOf = (value) => typeof value === "string" ? value : void 0;
917
+ function toolCall(fields, identifiers) {
918
+ const tool = stringOf(fields.tool_name);
919
+ const input = isObject(fields.tool_input) ? fields.tool_input : {};
920
+ if (tool === "Skill") {
921
+ const skill = stringOf(input.skill)?.replace(/^\//, "");
922
+ return named("skill", skill === void 0 ? void 0 : identifierOfName(skill, identifiers.skill));
923
+ }
924
+ if (tool === "Agent" || tool === "Task") {
925
+ const type = stringOf(input.subagent_type);
926
+ return named("subagent", type === void 0 ? void 0 : identifierOfName(type, identifiers.agent));
927
+ }
928
+ if (tool?.startsWith("mcp__")) return named("mcp", mcpServerOfTool(tool, identifiers.mcp_server));
929
+ }
930
+ function commandCall(fields, identifiers) {
931
+ if (fields.expansion_type === "mcp_prompt") return named("command", void 0);
932
+ if (fields.expansion_type !== "slash_command") return void 0;
933
+ const name = stringOf(fields.command_name);
934
+ return named("command", name === void 0 ? void 0 : identifierOfName(name, [...identifiers.skill, ...identifiers.command]));
935
+ }
936
+ function countedOf(event, fields, identifiers) {
937
+ switch (event) {
938
+ case "post-tool-use": return toolCall(fields, identifiers);
939
+ case "post-tool-use-failure": {
940
+ const call = toolCall(fields, identifiers);
941
+ return call && {
942
+ ...call,
943
+ failed: true
944
+ };
945
+ }
946
+ case "user-prompt-expansion": return commandCall(fields, identifiers);
947
+ case "permission-request": return { kind: "permission" };
948
+ case "post-compact": return fields.trigger === "auto" || fields.trigger === "manual" ? {
949
+ kind: "compaction",
950
+ trigger: fields.trigger
951
+ } : void 0;
952
+ }
953
+ }
954
+ //#endregion
955
+ //#region src/usage/store.ts
956
+ function usageStoreOf(sessionId, env) {
957
+ const data = env.CLAUDE_PLUGIN_DATA;
958
+ if (!data || !(0, node_path.isAbsolute)(data) || !isFileSafeSessionId(sessionId)) return void 0;
959
+ return {
960
+ dir: (0, node_path.join)(data, "usage"),
961
+ sessionId
962
+ };
963
+ }
964
+ const recordFile = (store, sessionId) => (0, node_path.join)(store.dir, `${sessionId}.jsonl`);
965
+ const stateFile = (store, sessionId) => (0, node_path.join)(store.dir, `${sessionId}.json`);
966
+ const IDENTIFIER_KINDS = [
967
+ "skill",
968
+ "command",
969
+ "agent",
970
+ "mcp_server"
971
+ ];
972
+ function identifiersOf(components) {
973
+ const of = (kind) => {
974
+ const unique = /* @__PURE__ */ new Map();
975
+ for (const c of components) if (c.kind === kind) {
976
+ const plugin = c.source === "plugin";
977
+ unique.set(`${plugin}:${c.id}`, {
978
+ id: c.id,
979
+ plugin
980
+ });
981
+ }
982
+ return [...unique.values()];
983
+ };
984
+ return {
985
+ skill: of("skill"),
986
+ command: of("command"),
987
+ agent: of("agent"),
988
+ mcp_server: of("mcp_server")
989
+ };
990
+ }
991
+ async function createState(store, state) {
992
+ await (0, node_fs_promises.mkdir)(store.dir, { recursive: true });
993
+ await (0, node_fs_promises.writeFile)(stateFile(store, store.sessionId), JSON.stringify(state), { flag: "wx" }).catch((error) => {
994
+ if (error.code !== "EEXIST") throw error;
995
+ });
996
+ }
997
+ const isIdentifier = (value) => isObject(value) && typeof value.id === "string" && isToken(value.id) && typeof value.plugin === "boolean";
998
+ function parseState(value) {
999
+ if (!isObject(value) || !isObject(value.identifiers)) return void 0;
1000
+ const { workspaceId, identifiers, sentLength } = value;
1001
+ return typeof workspaceId === "string" && typeof sentLength === "number" && Number.isSafeInteger(sentLength) && sentLength >= 0 && IDENTIFIER_KINDS.every((kind) => {
1002
+ const list = identifiers[kind];
1003
+ return Array.isArray(list) && list.every(isIdentifier);
1004
+ }) ? value : void 0;
1005
+ }
1006
+ async function readState(store, sessionId = store.sessionId) {
1007
+ try {
1008
+ return parseState(JSON.parse(await (0, node_fs_promises.readFile)(stateFile(store, sessionId), "utf8")));
1009
+ } catch {
1010
+ return;
1011
+ }
1012
+ }
1013
+ async function writeSentLength(store, sessionId, state, sentLength) {
1014
+ const file = stateFile(store, sessionId);
1015
+ const partial = `${file}.${process.pid}.tmp`;
1016
+ await (0, node_fs_promises.writeFile)(partial, JSON.stringify({
1017
+ ...state,
1018
+ sentLength
1019
+ }));
1020
+ await (0, node_fs_promises.rename)(partial, file);
1021
+ }
1022
+ const RECORD_KINDS = [
1023
+ "start",
1024
+ "prompt",
1025
+ "skill",
1026
+ "command",
1027
+ "subagent",
1028
+ "mcp",
1029
+ "permission",
1030
+ "compaction"
1031
+ ];
1032
+ const appendRecord = (store, line) => (0, node_fs_promises.appendFile)(recordFile(store, store.sessionId), `${JSON.stringify(line)}\n`);
1033
+ const isOptional = (value, check) => value === void 0 || check(value);
1034
+ const isTokenValue = (value) => typeof value === "string" && isToken(value);
1035
+ function parseLine(text) {
1036
+ let value;
1037
+ try {
1038
+ value = JSON.parse(text);
1039
+ } catch {
1040
+ return [];
1041
+ }
1042
+ if (!isObject(value)) return [];
1043
+ const { at, kind, promptId, id, failed, trigger } = value;
1044
+ return typeof at === "string" && isInstant(at) && RECORD_KINDS.some((k) => k === kind) && isOptional(promptId, isTokenValue) && isOptional(id, isTokenValue) && isOptional(failed, (v) => v === true) && isOptional(trigger, (v) => v === "auto" || v === "manual") ? [value] : [];
1045
+ }
1046
+ async function readRecord(store, sessionId = store.sessionId) {
1047
+ const bytes = await (0, node_fs_promises.readFile)(recordFile(store, sessionId)).catch(() => void 0);
1048
+ if (!bytes) return void 0;
1049
+ const length = bytes.lastIndexOf(10) + 1;
1050
+ return {
1051
+ lines: bytes.subarray(0, length).toString("utf8").split("\n").flatMap(parseLine),
1052
+ length
1053
+ };
1054
+ }
1055
+ async function listSessions(store) {
1056
+ return (await (0, node_fs_promises.readdir)(store.dir).catch(() => [])).filter((name) => name.endsWith(".json")).map((name) => name.slice(0, -5)).filter(isFileSafeSessionId);
1057
+ }
1058
+ const recordChangeOf = (store, sessionId) => (0, node_fs_promises.stat)(recordFile(store, sessionId)).then(({ size, mtimeMs }) => ({
1059
+ size,
1060
+ mtimeMs
1061
+ }), () => void 0);
1062
+ const RETENTION_MS = 2592e6;
1063
+ const REMOVALS_PER_START = 100;
1064
+ async function removeExpiredFiles(store, now) {
1065
+ let removed = 0;
1066
+ for (const name of await (0, node_fs_promises.readdir)(store.dir).catch(() => [])) {
1067
+ if (removed === REMOVALS_PER_START) return;
1068
+ const file = (0, node_path.join)(store.dir, name);
1069
+ const info = await (0, node_fs_promises.lstat)(file).catch(() => void 0);
1070
+ if (!info?.isFile() || now.getTime() - info.mtimeMs <= RETENTION_MS) continue;
1071
+ if (await (0, node_fs_promises.unlink)(file).then(() => true, () => false)) removed += 1;
1072
+ }
1073
+ }
1074
+ //#endregion
1075
+ //#region src/usage/summary.ts
1076
+ const COLLECTOR_VERSION = "0.1.0";
1077
+ const MAX_ITEMS = 200;
1078
+ const byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
1079
+ function callListOf(lines, kind) {
1080
+ const byName = /* @__PURE__ */ new Map();
1081
+ const other = {
1082
+ calls: 0,
1083
+ failures: 0
1084
+ };
1085
+ for (const line of lines) {
1086
+ if (line.kind !== kind) continue;
1087
+ let calls = other;
1088
+ if (line.id !== void 0) {
1089
+ calls = byName.get(line.id) ?? {
1090
+ calls: 0,
1091
+ failures: 0
1092
+ };
1093
+ byName.set(line.id, calls);
1094
+ }
1095
+ calls.calls += 1;
1096
+ if (line.failed) calls.failures += 1;
1097
+ }
1098
+ const ranked = [...byName].map(([name, calls]) => ({
1099
+ name,
1100
+ ...calls
1101
+ })).sort((a, b) => b.calls - a.calls || byCodeUnit(a.name, b.name));
1102
+ for (const rest of ranked.slice(MAX_ITEMS)) {
1103
+ other.calls += rest.calls;
1104
+ other.failures += rest.failures;
1105
+ }
1106
+ return {
1107
+ items: ranked.slice(0, MAX_ITEMS),
1108
+ other
1109
+ };
1110
+ }
1111
+ function commandListOf(lines) {
1112
+ const { items, other } = callListOf(lines, "command");
1113
+ return {
1114
+ items: items.map(({ name, calls }) => ({
1115
+ name,
1116
+ calls
1117
+ })),
1118
+ other: { calls: other.calls }
1119
+ };
1120
+ }
1121
+ const countOf = (lines, kind) => lines.filter((line) => line.kind === kind).length;
1122
+ const compactionsOf = (lines, trigger) => lines.filter((line) => line.kind === "compaction" && line.trigger === trigger).length;
1123
+ const byTime = (lines) => lines.map((line, order) => ({
1124
+ line,
1125
+ ms: Date.parse(line.at),
1126
+ order
1127
+ })).sort((a, b) => a.ms - b.ms || a.order - b.order).map(({ line }) => line);
1128
+ function buildSummary(sessionId, lines) {
1129
+ const ordered = byTime(lines);
1130
+ const first = ordered[0];
1131
+ const last = ordered.at(-1);
1132
+ if (!first || !last) return void 0;
1133
+ const firstPromptId = ordered.find((line) => line.promptId)?.promptId;
1134
+ return {
1135
+ agent: "claude_code",
1136
+ session_id: sessionId,
1137
+ ...firstPromptId === void 0 ? {} : { first_prompt_id: firstPromptId },
1138
+ started_at: first.at,
1139
+ last_event_at: last.at,
1140
+ collector_version: COLLECTOR_VERSION,
1141
+ skills: callListOf(lines, "skill"),
1142
+ commands: commandListOf(lines),
1143
+ subagents: callListOf(lines, "subagent"),
1144
+ mcp_servers: callListOf(lines, "mcp"),
1145
+ permission_requests: countOf(lines, "permission"),
1146
+ compactions: {
1147
+ auto: compactionsOf(lines, "auto"),
1148
+ manual: compactionsOf(lines, "manual")
1149
+ }
1150
+ };
1151
+ }
1152
+ //#endregion
1153
+ //#region src/usage/send.ts
1154
+ async function sendSessionUsage(store, destination, deadlineMs, deps) {
1155
+ const state = await readState(store);
1156
+ if (!state || state.workspaceId !== workspaceIdOf(destination.key)) return void 0;
1157
+ const record = await readRecord(store);
1158
+ const summary = record ? buildSummary(store.sessionId, record.lines) : void 0;
1159
+ if (!record || !summary) return void 0;
1160
+ const remainingMs = deadlineMs - deps.now().getTime();
1161
+ if (remainingMs <= 0) return {
1162
+ kind: "failed",
1163
+ reason: "TimeoutError"
1164
+ };
1165
+ const outcome = await postItems(destination, "v1/session-usage", [summary], deps.fetch, Math.ceil(remainingMs));
1166
+ if (outcome.kind === "accepted") await writeSentLength(store, store.sessionId, state, record.length);
1167
+ return outcome;
1168
+ }
1169
+ const PENDING_IDLE_MS = 6e5;
1170
+ const PENDING_PER_REQUEST = 20;
1171
+ async function pendingSessions(store, workspaceId, nowMs) {
1172
+ const pending = [];
1173
+ for (const sessionId of await listSessions(store)) {
1174
+ if (sessionId === store.sessionId) continue;
1175
+ const change = await recordChangeOf(store, sessionId);
1176
+ if (!change || nowMs - change.mtimeMs < PENDING_IDLE_MS) continue;
1177
+ const state = await readState(store, sessionId);
1178
+ if (state?.workspaceId === workspaceId && change.size > state.sentLength) pending.push({
1179
+ sessionId,
1180
+ state,
1181
+ mtimeMs: change.mtimeMs
1182
+ });
1183
+ }
1184
+ return pending.sort((a, b) => a.mtimeMs - b.mtimeMs);
1185
+ }
1186
+ async function sendUnsentUsage(store, destination, deps) {
1187
+ const workspaceId = workspaceIdOf(destination.key);
1188
+ if (workspaceId === void 0) return void 0;
1189
+ const batch = [];
1190
+ for (const pending of await pendingSessions(store, workspaceId, deps.now().getTime())) {
1191
+ if (batch.length === PENDING_PER_REQUEST) break;
1192
+ const record = await readRecord(store, pending.sessionId);
1193
+ if (!record || record.length <= pending.state.sentLength) continue;
1194
+ const summary = buildSummary(pending.sessionId, record.lines);
1195
+ if (summary) batch.push({
1196
+ ...pending,
1197
+ record,
1198
+ summary
1199
+ });
1200
+ }
1201
+ if (batch.length === 0) return void 0;
1202
+ const outcome = await postItems(destination, "v1/session-usage", batch.map((sent) => sent.summary), deps.fetch);
1203
+ if (outcome.kind === "accepted") await Promise.all(batch.map((sent) => writeSentLength(store, sent.sessionId, sent.state, sent.record.length)));
1204
+ return outcome;
1205
+ }
1206
+ //#endregion
1207
+ //#region src/hook.ts
1208
+ const REVOKED_KEY_MESSAGES = {
1209
+ user: "送信キーが失効しています。`harnessforce init`を実行してください",
1210
+ workspace: "組織の送信キーが失効しています。Workspaceの管理者に連絡してください"
1211
+ };
1212
+ const REGISTERING_SOURCES = /* @__PURE__ */ new Set([
1213
+ "startup",
1214
+ "clear",
1215
+ "fork"
1216
+ ]);
1217
+ const logError = (deps) => (error) => deps.stderr(`harnessforce: ${error instanceof Error ? error.message : String(error)}\n`);
1218
+ const report = (deps, subject, detail) => deps.stderr(`harnessforce: ${subject} ${detail}\n`);
1219
+ async function selectUserKey(deps, cwd) {
1220
+ if (!deps.env.HARNESSFORCE_WORKSPACE_ID) return void 0;
1221
+ const read = await deps.readUserKey(cwd);
1222
+ if (read.kind === "failed") deps.stderr("harnessforce: no user key in keychain or read failed\n");
1223
+ return read.kind === "found" ? read.key : void 0;
1224
+ }
1225
+ async function resolveDestination(deps, cwd, withUserKey = true) {
1226
+ const managed = await readManagedEnv(deps.managedDir, ["HARNESSFORCE_INGEST_KEY", "HARNESSFORCE_ENDPOINT"]);
1227
+ const workspaceKey = managed.HARNESSFORCE_INGEST_KEY;
1228
+ if (!workspaceKey && !withUserKey) return {};
1229
+ const endpoint = workspaceKey ? managed.HARNESSFORCE_ENDPOINT : deps.env.HARNESSFORCE_ENDPOINT;
1230
+ const notConfigured = !endpoint || !(workspaceKey || deps.env.HARNESSFORCE_WORKSPACE_ID);
1231
+ const ingestBase = ingestBaseFrom(endpoint);
1232
+ if (!ingestBase) {
1233
+ report(deps, "session registration", "skipped (invalid endpoint)");
1234
+ return { warning: notConfigured ? notConfiguredWarning("claude_code") : INVALID_ENDPOINT_WARNING };
1235
+ }
1236
+ if (workspaceKey) return { destination: {
1237
+ ingestBase,
1238
+ key: workspaceKey,
1239
+ keyKind: "workspace"
1240
+ } };
1241
+ const userKey = await selectUserKey(deps, cwd);
1242
+ if (!userKey) {
1243
+ report(deps, "session registration", "skipped (no ingest key)");
1244
+ return notConfigured ? { warning: notConfiguredWarning("claude_code") } : {};
1245
+ }
1246
+ return { destination: {
1247
+ ingestBase,
1248
+ key: userKey,
1249
+ keyKind: "user"
1250
+ } };
1251
+ }
1252
+ function registrationSource(destination, env) {
1253
+ const issue = env.HARNESSFORCE_ISSUE;
1254
+ return destination.keyKind === "user" && issue && isIssueIdentifier(issue) ? {
1255
+ source: "cli",
1256
+ issue_identifier: issue
1257
+ } : { source: "hook" };
1258
+ }
1259
+ async function send(destination, path, item, subject, deps) {
1260
+ const outcome = await postItems(destination, path, [item], deps.fetch);
1261
+ if (outcome.kind === "failed") report(deps, subject, `failed (${outcome.reason})`);
1262
+ return outcome;
1263
+ }
1264
+ async function reportRevokedKey(destination, pad, deps) {
1265
+ const message = REVOKED_KEY_MESSAGES[destination.keyKind];
1266
+ deps.stderr(`${message}\n`);
1267
+ if (pad) await markUnauthorized(pad).catch(logError(deps));
1268
+ return message;
1269
+ }
1270
+ const writeJson = (deps, output) => deps.stdout(`${JSON.stringify(output)}\n`);
1271
+ async function registerSession(input, destination, deps) {
1272
+ const vcs = await resolveVcs(input.cwd, deps.git);
1273
+ if (!vcs) return void 0;
1274
+ const registration = {
1275
+ agent: "claude_code",
1276
+ session_id: input.sessionId,
1277
+ ...vcs,
1278
+ ...registrationSource(destination, deps.env),
1279
+ started_at: deps.now().toISOString()
1280
+ };
1281
+ if (input.scratchpad) await saveRegistration(input.scratchpad, registration).catch(logError(deps));
1282
+ return send(destination, "v1/sessions", registration, "session registration", deps);
1283
+ }
1284
+ async function collectComponents(input, deps) {
1285
+ const startedMs = deps.now().getTime();
1286
+ try {
1287
+ const result = await collectConfig({
1288
+ projectRoot: await resolveProjectRoot(input.cwd, deps.git),
1289
+ homeDir: deps.homeDir,
1290
+ managedDir: deps.managedDir,
1291
+ env: deps.env,
1292
+ isExpired: () => deps.now().getTime() - startedMs > COLLECT_BUDGET_MS
1293
+ });
1294
+ if (result.kind === "collected") return result.components;
1295
+ report(deps, "config snapshot", `skipped (${result.reason})`);
1296
+ } catch (error) {
1297
+ logError(deps)(error);
1298
+ }
1299
+ }
1300
+ async function sendConfigSnapshot(input, components, destination, deps) {
1301
+ if (components.length === 0) return void 0;
1302
+ return send(destination, "v1/config-snapshots", {
1303
+ agent: "claude_code",
1304
+ session_id: input.sessionId,
1305
+ components
1306
+ }, "config snapshot", deps);
1307
+ }
1308
+ async function createUsageState(usage, destination, components) {
1309
+ const workspaceId = workspaceIdOf(destination.key);
1310
+ if (workspaceId === void 0) return;
1311
+ await createState(usage, {
1312
+ workspaceId,
1313
+ identifiers: identifiersOf(components),
1314
+ sentLength: 0
1315
+ });
1316
+ }
1317
+ async function appendIfRecording(usage, line) {
1318
+ if (await readState(usage)) await appendRecord(usage, line);
1319
+ }
1320
+ const promptIdOf = (input) => input.promptId === void 0 ? {} : { promptId: input.promptId };
1321
+ async function startUsage(usage, destination, collected, deps) {
1322
+ const [, unsent] = await Promise.all([collected.then((components) => createUsageState(usage, destination, components ?? [])).catch(logError(deps)), sendUnsentUsage(usage, destination, deps).catch(logError(deps))]);
1323
+ if (unsent?.kind === "failed") report(deps, "session usage", `failed (${unsent.reason})`);
1324
+ return unsent ?? void 0;
1325
+ }
1326
+ async function sendSessionStart(input, usage, deps) {
1327
+ const registers = input.source === void 0 || REGISTERING_SOURCES.has(input.source);
1328
+ if (!registers && !usage) return void 0;
1329
+ if (input.scratchpad && await isMarkedUnauthorized(input.scratchpad)) return void 0;
1330
+ const { destination, warning } = await resolveDestination(deps, input.cwd);
1331
+ if (!destination) return registers ? warning : void 0;
1332
+ const collected = registers || usage && !await readState(usage) ? collectComponents(input, deps) : Promise.resolve(void 0);
1333
+ const [registration, snapshot, unsent] = await Promise.all([
1334
+ registers ? registerSession(input, destination, deps).catch(logError(deps)) : void 0,
1335
+ registers ? collected.then((components) => components ? sendConfigSnapshot(input, components, destination, deps) : void 0).catch(logError(deps)) : void 0,
1336
+ usage ? startUsage(usage, destination, collected, deps) : void 0
1337
+ ]);
1338
+ if ([
1339
+ registration,
1340
+ snapshot,
1341
+ unsent
1342
+ ].some((outcome) => outcome?.kind === "unauthorized")) return reportRevokedKey(destination, input.scratchpad, deps);
1343
+ return registration?.kind === "failed" ? REGISTRATION_FAILED_WARNING : void 0;
1344
+ }
1345
+ async function onSessionStart(input, deps) {
1346
+ const usage = usageStoreOf(input.sessionId, deps.env);
1347
+ const startedAt = deps.now();
1348
+ if (usage) await removeExpiredFiles(usage, startedAt).catch(logError(deps));
1349
+ const message = await sendSessionStart(input, usage, deps).catch((error) => {
1350
+ logError(deps)(error);
1351
+ });
1352
+ if (usage) await appendIfRecording(usage, {
1353
+ at: startedAt.toISOString(),
1354
+ kind: "start",
1355
+ ...promptIdOf(input)
1356
+ }).catch(logError(deps));
1357
+ writeJson(deps, {
1358
+ ...sessionContext(input.sessionId),
1359
+ ...message === void 0 ? {} : { systemMessage: message }
1360
+ });
1361
+ }
1362
+ async function onUserPromptSubmit(input, deps) {
1363
+ const usage = usageStoreOf(input.sessionId, deps.env);
1364
+ if (usage) await appendIfRecording(usage, {
1365
+ at: deps.now().toISOString(),
1366
+ kind: "prompt",
1367
+ ...promptIdOf(input)
1368
+ }).catch(logError(deps));
1369
+ const pad = input.scratchpad;
1370
+ if (!input.promptId || !pad || await isMarkedUnauthorized(pad)) return;
1371
+ const saved = await loadRegistration(pad);
1372
+ if (!saved || await isFirstPromptSent(pad)) return;
1373
+ const { destination } = await resolveDestination(deps, input.cwd);
1374
+ if (!destination || !await claimFirstPrompt(pad)) return;
1375
+ if ((await send(destination, "v1/sessions", {
1376
+ ...saved,
1377
+ first_prompt_id: input.promptId
1378
+ }, "session registration", deps)).kind === "unauthorized") writeJson(deps, { systemMessage: await reportRevokedKey(destination, pad, deps) });
1379
+ }
1380
+ const SESSION_END_BUDGET_MS = 1e3;
1381
+ async function onSessionEnd(input, deps) {
1382
+ const usage = usageStoreOf(input.sessionId, deps.env);
1383
+ if (!usage) return;
1384
+ if (input.scratchpad && await isMarkedUnauthorized(input.scratchpad)) return;
1385
+ const { destination } = await resolveDestination(deps, input.cwd, false);
1386
+ if (!destination) return;
1387
+ const outcome = await sendSessionUsage(usage, destination, deps.processStartMs + SESSION_END_BUDGET_MS, deps);
1388
+ if (outcome && outcome.kind !== "accepted") report(deps, "session usage", `failed (${outcome.kind === "failed" ? outcome.reason : "HTTP 401"})`);
1389
+ }
1390
+ const onCountingEvent = (event) => async (input, deps) => {
1391
+ const usage = usageStoreOf(input.sessionId, deps.env);
1392
+ const state = usage && await readState(usage);
1393
+ const counted = state && countedOf(event, input.fields, state.identifiers);
1394
+ if (usage && counted) await appendRecord(usage, {
1395
+ at: deps.now().toISOString(),
1396
+ ...promptIdOf(input),
1397
+ ...counted
1398
+ });
1399
+ };
1400
+ const HANDLERS = new Map([
1401
+ ["session-start", onSessionStart],
1402
+ ["user-prompt-submit", onUserPromptSubmit],
1403
+ ["session-end", onSessionEnd],
1404
+ ...COUNTING_EVENTS.map((event) => [event, onCountingEvent(event)])
1405
+ ]);
1406
+ async function runHook(event, rawInput, deps) {
1407
+ const handler = HANDLERS.get(event);
1408
+ const input = parseHookInput(rawInput);
1409
+ if (!handler || !input) return;
1410
+ await handler(input, deps).catch(logError(deps));
1411
+ }
1412
+ //#endregion
1413
+ //#region src/user-key.ts
1414
+ const HF_TIMEOUT_MS = 1e3;
1415
+ const HF_OUTPUT_LIMIT_BYTES = 65536;
1416
+ const BEARER = /^Bearer (\S+)$/;
1417
+ function parseKey(stdout) {
1418
+ try {
1419
+ const value = JSON.parse(stdout);
1420
+ if (!isObject(value)) return void 0;
1421
+ const authorization = value.Authorization;
1422
+ return typeof authorization === "string" ? BEARER.exec(authorization)?.[1] : void 0;
1423
+ } catch {
1424
+ return;
1425
+ }
1426
+ }
1427
+ function createUserKeyReader({ platform, env, exec, processCwd, fs }) {
1428
+ return async (cwd) => {
1429
+ const context = {
1430
+ platform,
1431
+ env,
1432
+ bases: [processCwd, cwd],
1433
+ ...fs ? { fs } : {}
1434
+ };
1435
+ const file = await findCommand("harnessforce", context);
1436
+ const command = file === void 0 ? void 0 : await commandLineFor(file, ["otel-headers"], context, { quoteArgs: false });
1437
+ if (!command) return { kind: "missing" };
1438
+ const result = await exec(command.file, command.args, {
1439
+ timeoutMs: HF_TIMEOUT_MS,
1440
+ windowsVerbatimArguments: command.verbatim,
1441
+ env
1442
+ });
1443
+ const key = result.ok ? parseKey(result.stdout) : void 0;
1444
+ return key ? {
1445
+ kind: "found",
1446
+ key
1447
+ } : { kind: "failed" };
1448
+ };
1449
+ }
1450
+ const execHf = (file, args, options) => new Promise((resolve) => {
1451
+ (0, node_child_process.execFile)(file, args, {
1452
+ encoding: "utf8",
1453
+ env: options.env,
1454
+ timeout: options.timeoutMs,
1455
+ killSignal: "SIGKILL",
1456
+ maxBuffer: HF_OUTPUT_LIMIT_BYTES,
1457
+ windowsHide: true,
1458
+ windowsVerbatimArguments: options.windowsVerbatimArguments
1459
+ }, (error, stdout) => resolve({
1460
+ ok: !error,
1461
+ stdout
1462
+ }));
1463
+ });
1464
+ //#endregion
1465
+ //#region src/main.ts
1466
+ delete process.env.HARNESSFORCE_RUNTIME_ENV;
1467
+ async function readStdin() {
1468
+ const chunks = [];
1469
+ for await (const chunk of process.stdin) chunks.push(chunk);
1470
+ return Buffer.concat(chunks).toString("utf8");
1471
+ }
1472
+ for (const stream of [process.stdout, process.stderr]) stream.on("error", () => {});
1473
+ async function main() {
1474
+ const relaunch = await relaunchWithoutRuntimeVariables({
1475
+ platform: process.platform,
1476
+ env: process.env,
1477
+ stash: false,
1478
+ spawnSelf
1479
+ });
1480
+ if (relaunch.kind === "failed") {
1481
+ process.stderr.write("harnessforce: session registration skipped (restart failed)\n");
1482
+ process.stdout.write(warningOnlyOutput(RESTART_FAILED_WARNING));
1483
+ }
1484
+ if (relaunch.kind !== "not-needed") return;
1485
+ const raw = await readStdin().catch(() => "");
1486
+ await runHook(process.argv[2] ?? "", raw, {
1487
+ env: process.env,
1488
+ now: () => /* @__PURE__ */ new Date(),
1489
+ processStartMs: node_perf_hooks.performance.timeOrigin,
1490
+ git: createGitRunner({
1491
+ platform: process.platform,
1492
+ env: process.env,
1493
+ processCwd: process.cwd(),
1494
+ excludeTarget: true
1495
+ }),
1496
+ fetch: (url, init) => fetch(url, init),
1497
+ stdout: (text) => process.stdout.write(text),
1498
+ stderr: (text) => process.stderr.write(text),
1499
+ homeDir: (0, node_os.homedir)(),
1500
+ managedDir: managedDirFor(process.platform),
1501
+ readUserKey: createUserKeyReader({
1502
+ platform: process.platform,
1503
+ env: process.env,
1504
+ exec: execHf,
1505
+ processCwd: process.cwd()
1506
+ })
1507
+ });
1508
+ }
1509
+ main();
1510
+ //#endregion