@askexenow/exe-os 0.9.62 → 0.9.63

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.
@@ -95,10 +95,14 @@ function parseStackManifest(raw, publicKey) {
95
95
  }
96
96
  return parsed;
97
97
  }
98
- async function loadStackManifest(ref, fetchText = defaultFetchText, publicKey) {
99
- if (/^https?:\/\//.test(ref)) return parseStackManifest(await fetchText(ref), publicKey);
98
+ async function loadStackManifest(ref, fetchText = defaultFetchText, publicKey, authToken) {
99
+ if (/^https?:\/\//.test(ref)) return parseStackManifest(await fetchTextWithAuth(ref, fetchText, authToken), publicKey);
100
100
  return parseStackManifest(readFileSync(ref, "utf8"), publicKey);
101
101
  }
102
+ async function fetchTextWithAuth(ref, fetchText, authToken) {
103
+ if (!authToken || fetchText !== defaultFetchText) return fetchText(ref);
104
+ return defaultFetchText(ref, authToken);
105
+ }
102
106
  function parseEnv(raw) {
103
107
  const env = /* @__PURE__ */ new Map();
104
108
  for (const line of raw.split(/\r?\n/)) {
@@ -164,14 +168,16 @@ async function runStackUpdate(options) {
164
168
  const exec = options.exec ?? defaultExec;
165
169
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
166
170
  if (options.rollback) return rollbackStackUpdate(options);
167
- const manifest = await loadStackManifest(options.manifestRef, options.fetchText, options.manifestPublicKey);
171
+ const manifest = await loadStackManifest(options.manifestRef, options.fetchText, options.manifestPublicKey, options.manifestAuthToken);
168
172
  const envRaw = readFileSync(options.envFile, "utf8");
169
173
  const plan = createStackUpdatePlan(manifest, envRaw, options.targetVersion);
170
174
  assertBreakingChangesAllowed(plan, options.allowedBreakingChangeIds ?? []);
171
175
  const lockFile = options.lockFile ?? path.join(path.dirname(options.envFile), ".exe-stack-lock.json");
176
+ const previousVersion = readCurrentStackVersion(lockFile);
172
177
  if (options.dryRun || plan.changes.length === 0) {
173
178
  return { status: "planned", targetVersion: plan.targetVersion, changes: plan.changes, lockFile };
174
179
  }
180
+ await postDeployAudit(options, "started", plan.targetVersion, previousVersion);
175
181
  const backupDir = path.join(path.dirname(options.envFile), ".exe-stack-backups");
176
182
  mkdirSync(backupDir, { recursive: true });
177
183
  const stamp = now().toISOString().replace(/[:.]/g, "-");
@@ -188,6 +194,7 @@ async function runStackUpdate(options) {
188
194
  exec("docker", [...composeArgs, "up", "-d"]);
189
195
  await verifyReleaseHealth(plan.release, options.healthRetries ?? 12, options.healthDelayMs ?? 5e3);
190
196
  writeFileSync(lockFile, JSON.stringify({ stackVersion: plan.targetVersion, updatedAt: now().toISOString(), backupEnvFile, services: plan.release.services }, null, 2) + "\n");
197
+ await postDeployAudit(options, "success", plan.targetVersion, previousVersion, void 0, { changes: plan.changes.length });
191
198
  return { status: "updated", targetVersion: plan.targetVersion, changes: plan.changes, backupEnvFile, lockFile };
192
199
  } catch (err) {
193
200
  writeFileSync(options.envFile, envRaw, { mode: 384 });
@@ -196,9 +203,37 @@ async function runStackUpdate(options) {
196
203
  } catch {
197
204
  }
198
205
  const reason = err instanceof Error ? err.message : String(err);
206
+ await postDeployAudit(options, "failed", plan.targetVersion, previousVersion, reason, { rollbackAttempted: true });
199
207
  throw new Error(`Stack update failed and rollback was attempted: ${reason}`);
200
208
  }
201
209
  }
210
+ function readCurrentStackVersion(lockFile) {
211
+ if (!existsSync(lockFile)) return void 0;
212
+ try {
213
+ const parsed = JSON.parse(readFileSync(lockFile, "utf8"));
214
+ return parsed.stackVersion;
215
+ } catch {
216
+ return void 0;
217
+ }
218
+ }
219
+ async function postDeployAudit(options, status, stackVersion, previousVersion, error, metadata) {
220
+ if (!options.auditUrl) return;
221
+ const postJson = options.postJson ?? defaultPostJson;
222
+ try {
223
+ await postJson(options.auditUrl, {
224
+ stackVersion,
225
+ previousVersion,
226
+ status,
227
+ error,
228
+ metadata,
229
+ deviceId: options.deviceId,
230
+ licenseKey: options.licenseKey
231
+ }, options.manifestAuthToken);
232
+ } catch (err) {
233
+ const reason = err instanceof Error ? err.message : String(err);
234
+ console.warn(`[stack-update] deploy audit failed: ${reason}`);
235
+ }
236
+ }
202
237
  async function verifyReleaseHealth(release, retries, delayMs) {
203
238
  for (const [serviceName, service] of Object.entries(release.services)) {
204
239
  if (!service.healthUrl) continue;
@@ -235,20 +270,43 @@ function httpStatus(urlString) {
235
270
  function defaultExec(cmd, args, opts) {
236
271
  execFileSync(cmd, args, { stdio: "inherit", cwd: opts?.cwd });
237
272
  }
238
- async function defaultFetchText(ref) {
239
- const res = await fetch(ref);
273
+ async function defaultFetchText(ref, authToken) {
274
+ const res = await fetch(ref, {
275
+ headers: authToken ? { authorization: `Bearer ${authToken}` } : void 0
276
+ });
240
277
  if (!res.ok) throw new Error(`Failed to fetch ${ref}: HTTP ${res.status}`);
241
278
  return res.text();
242
279
  }
280
+ async function defaultPostJson(url, body, authToken) {
281
+ const res = await fetch(url, {
282
+ method: "POST",
283
+ headers: {
284
+ "content-type": "application/json",
285
+ ...authToken ? { authorization: `Bearer ${authToken}` } : {}
286
+ },
287
+ body: JSON.stringify(body)
288
+ });
289
+ if (!res.ok) throw new Error(`Failed to POST ${url}: HTTP ${res.status}`);
290
+ }
243
291
  function defaultStackPaths() {
244
292
  const cwdCompose = path.resolve("docker-compose.yml");
245
293
  const cwdEnv = path.resolve(".env");
246
294
  return {
247
295
  composeFile: process.env.EXE_STACK_COMPOSE_FILE || (existsSync(cwdCompose) ? cwdCompose : "/opt/exe-stack/docker-compose.yml"),
248
296
  envFile: process.env.EXE_STACK_ENV_FILE || (existsSync(cwdEnv) ? cwdEnv : "/opt/exe-stack/.env"),
249
- manifestRef: process.env.EXE_STACK_MANIFEST || "https://update.askexe.com/stack-manifest.json"
297
+ manifestRef: process.env.EXE_STACK_MANIFEST || "https://update.askexe.com/stack-manifest.json",
298
+ auditUrl: process.env.EXE_STACK_AUDIT_URL || "https://update.askexe.com/v1/deploy-audits",
299
+ manifestAuthToken: process.env.EXE_STACK_UPDATE_TOKEN,
300
+ manifestPublicKey: loadDefaultPublicKey()
250
301
  };
251
302
  }
303
+ function loadDefaultPublicKey() {
304
+ if (process.env.EXE_STACK_PUBLIC_KEY) return process.env.EXE_STACK_PUBLIC_KEY;
305
+ if (process.env.EXE_STACK_PUBLIC_KEY_FILE && existsSync(process.env.EXE_STACK_PUBLIC_KEY_FILE)) {
306
+ return readFileSync(process.env.EXE_STACK_PUBLIC_KEY_FILE, "utf8");
307
+ }
308
+ return void 0;
309
+ }
252
310
 
253
311
  // src/bin/stack-update.ts
254
312
  function parseArgs(args) {
@@ -257,6 +315,11 @@ function parseArgs(args) {
257
315
  manifestRef: defaults.manifestRef,
258
316
  composeFile: defaults.composeFile,
259
317
  envFile: defaults.envFile,
318
+ auditUrl: defaults.auditUrl,
319
+ manifestAuthToken: defaults.manifestAuthToken,
320
+ manifestPublicKey: defaults.manifestPublicKey,
321
+ deviceId: process.env.EXE_DEVICE_ID,
322
+ licenseKey: process.env.EXE_LICENSE_KEY,
260
323
  dryRun: false,
261
324
  check: false,
262
325
  rollback: false,
@@ -277,6 +340,15 @@ function parseArgs(args) {
277
340
  else if (arg === "--lock-file") opts.lockFile = next();
278
341
  else if (arg === "--public-key") opts.manifestPublicKey = readFileSync2(next(), "utf8");
279
342
  else if (arg.startsWith("--public-key=")) opts.manifestPublicKey = readFileSync2(arg.split("=").slice(1).join("="), "utf8");
343
+ else if (arg === "--auth-token") opts.manifestAuthToken = next();
344
+ else if (arg.startsWith("--auth-token=")) opts.manifestAuthToken = arg.split("=").slice(1).join("=");
345
+ else if (arg === "--auth-token-env") opts.manifestAuthToken = process.env[next()] ?? "";
346
+ else if (arg === "--audit-url") opts.auditUrl = next();
347
+ else if (arg.startsWith("--audit-url=")) opts.auditUrl = arg.split("=").slice(1).join("=");
348
+ else if (arg === "--device-id") opts.deviceId = next();
349
+ else if (arg.startsWith("--device-id=")) opts.deviceId = arg.split("=").slice(1).join("=");
350
+ else if (arg === "--license-key") opts.licenseKey = next();
351
+ else if (arg.startsWith("--license-key=")) opts.licenseKey = arg.split("=").slice(1).join("=");
280
352
  else if (arg === "--rollback") opts.rollback = true;
281
353
  else if (arg === "--dry-run") opts.dryRun = true;
282
354
  else if (arg === "--check") opts.check = true;
@@ -307,6 +379,11 @@ Options:
307
379
  --check Print available changes only
308
380
  --dry-run Plan only; do not run Docker
309
381
  --public-key <path> PEM public key for signed manifest verification
382
+ --auth-token <token> Bearer token for private update manifest/audit API
383
+ --auth-token-env <name> Read bearer token from an environment variable
384
+ --audit-url <url> Deploy-audit endpoint (default: EXE_STACK_AUDIT_URL/update.askexe.com)
385
+ --device-id <id> Device ID to include in deploy audit
386
+ --license-key <key> License key to include in deploy audit
310
387
  --rollback Restore latest backed-up .env and restart stack
311
388
  --allow-breaking <ids> Confirm breaking changes, comma-separated
312
389
  -y, --yes Non-interactive confirmation
@@ -344,7 +421,7 @@ async function main() {
344
421
  console.log(`\u2705 Stack rollback attempted using backup: ${result2.backupEnvFile ?? "latest backup"}`);
345
422
  return;
346
423
  }
347
- const manifest = await loadStackManifest(opts.manifestRef, void 0, opts.manifestPublicKey);
424
+ const manifest = await loadStackManifest(opts.manifestRef, void 0, opts.manifestPublicKey, opts.manifestAuthToken);
348
425
  const envRaw = readFileSync2(opts.envFile, "utf8");
349
426
  const plan = createStackUpdatePlan(manifest, envRaw, opts.targetVersion);
350
427
  console.log(`Exe OS stack target: ${plan.targetVersion}`);
@@ -8892,54 +8892,98 @@ async function fastDbInit() {
8892
8892
  // src/adapters/claude/hooks/bug-report-worker.ts
8893
8893
  init_database();
8894
8894
  init_tasks();
8895
- async function main() {
8896
- const toolName = process.env.BUG_TOOL_NAME ?? "unknown";
8897
- const errorText = process.env.BUG_ERROR_TEXT ?? "";
8898
- const toolInput = process.env.BUG_TOOL_INPUT ?? "{}";
8899
- const fingerprint = process.env.BUG_FINGERPRINT ?? "";
8900
- const agentId = process.env.BUG_AGENT_ID ?? "unknown";
8901
- const agentRole = process.env.BUG_AGENT_ROLE ?? "employee";
8902
- const projectName = process.env.BUG_PROJECT_NAME ?? "unknown";
8903
- await fastDbInit();
8904
- const fpPrefix = fingerprint.slice(0, 8);
8905
- const client = getClient();
8906
- const { loadEmployeesSync: loadEmployeesSync2, getEmployeeByRole: getEmployeeByRole2, getCoordinatorName: getCoordinatorName2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
8907
- const employees = loadEmployeesSync2();
8908
- const coordinatorName = getCoordinatorName2(employees);
8909
- const ctoName = getEmployeeByRole2(employees, "CTO")?.name ?? coordinatorName;
8910
- const existing = await client.execute({
8911
- sql: `SELECT id FROM tasks
8912
- WHERE assigned_to = ?
8913
- AND status IN ('open', 'in_progress')
8914
- AND title LIKE '[auto-bug]%'
8915
- AND task_file LIKE ?
8916
- LIMIT 1`,
8917
- args: [ctoName, `%${fpPrefix}%`]
8895
+
8896
+ // src/lib/bug-intake.ts
8897
+ import { createHash as createHash3, randomUUID as randomUUID4 } from "crypto";
8898
+ var BUG_INTAKE_SCHEMA_VERSION = 1;
8899
+ function firstMeaningfulLine(text) {
8900
+ return text.split("\n").find((line) => line.trim().length > 0)?.trim().slice(0, 80) ?? "unknown error";
8901
+ }
8902
+ function stableFingerprint(input) {
8903
+ const basis = [
8904
+ input.source,
8905
+ input.toolName ?? "unknown",
8906
+ firstMeaningfulLine(input.errorText ?? ""),
8907
+ input.projectName ?? "unknown"
8908
+ ].join("|");
8909
+ return createHash3("sha256").update(basis).digest("hex").slice(0, 16);
8910
+ }
8911
+ function hashLicense(licenseKey) {
8912
+ if (!licenseKey) return void 0;
8913
+ return createHash3("sha256").update(licenseKey).digest("hex").slice(0, 16);
8914
+ }
8915
+ function buildBugIntake(input) {
8916
+ const toolName = input.toolName ?? "unknown";
8917
+ const errorText = input.errorText ?? "";
8918
+ const summary = firstMeaningfulLine(errorText);
8919
+ const fingerprint = input.fingerprint && input.fingerprint.trim().length > 0 ? input.fingerprint.trim() : stableFingerprint(input);
8920
+ return {
8921
+ schemaVersion: BUG_INTAKE_SCHEMA_VERSION,
8922
+ id: randomUUID4(),
8923
+ source: input.source,
8924
+ createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
8925
+ fingerprint,
8926
+ severity: input.severity ?? "p1",
8927
+ title: `[auto-bug] ${toolName}: ${summary.slice(0, 60)}`,
8928
+ summary,
8929
+ reporterAgentId: input.reporterAgentId ?? "unknown",
8930
+ reporterAgentRole: input.reporterAgentRole ?? "employee",
8931
+ projectName: input.projectName ?? "unknown",
8932
+ toolName,
8933
+ errorText,
8934
+ toolInput: input.toolInput ?? "{}",
8935
+ runtime: input.runtime,
8936
+ repo: input.repo,
8937
+ licenseKeyHash: hashLicense(input.licenseKey),
8938
+ labels: ["auto-bug", input.source, toolName].filter(Boolean)
8939
+ };
8940
+ }
8941
+ function buildBugIntakeFromEnv(env = process.env) {
8942
+ return buildBugIntake({
8943
+ source: "hook",
8944
+ toolName: env.BUG_TOOL_NAME,
8945
+ errorText: env.BUG_ERROR_TEXT,
8946
+ toolInput: env.BUG_TOOL_INPUT,
8947
+ fingerprint: env.BUG_FINGERPRINT,
8948
+ reporterAgentId: env.BUG_AGENT_ID,
8949
+ reporterAgentRole: env.BUG_AGENT_ROLE,
8950
+ projectName: env.BUG_PROJECT_NAME,
8951
+ runtime: env.EXE_RUNTIME,
8952
+ repo: env.EXE_REPO,
8953
+ licenseKey: env.EXE_LICENSE_KEY
8918
8954
  });
8919
- if (existing.rows.length > 0) {
8920
- process.stderr.write(`[bug-report-worker] Duplicate found for fingerprint ${fingerprint}, skipping
8921
- `);
8922
- return;
8923
- }
8924
- const errorSummary = errorText.split("\n").find((line) => line.trim().length > 0)?.trim().slice(0, 60) ?? "unknown error";
8925
- const context = [
8955
+ }
8956
+ function formatBugIntakeTaskContext(record) {
8957
+ return [
8926
8958
  "## Auto-detected system bug",
8927
8959
  "",
8928
- `**Detected by:** ${agentId} (${agentRole})`,
8929
- `**Tool:** ${toolName}`,
8930
- `**Timestamp:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
8931
- `**Fingerprint:** ${fingerprint}`,
8960
+ `**Schema:** bug-intake/v${record.schemaVersion}`,
8961
+ `**Source:** ${record.source}`,
8962
+ `**Detected by:** ${record.reporterAgentId} (${record.reporterAgentRole})`,
8963
+ `**Tool:** ${record.toolName}`,
8964
+ `**Timestamp:** ${record.createdAt}`,
8965
+ `**Fingerprint:** ${record.fingerprint}`,
8966
+ `**Severity:** ${record.severity}`,
8967
+ record.runtime ? `**Runtime:** ${record.runtime}` : void 0,
8968
+ record.repo ? `**Repo:** ${record.repo}` : void 0,
8969
+ record.licenseKeyHash ? `**License hash:** ${record.licenseKeyHash}` : void 0,
8932
8970
  "",
8933
8971
  "## Error output",
8934
8972
  "",
8935
8973
  "```",
8936
- errorText.slice(0, 1e3),
8974
+ record.errorText.slice(0, 2e3),
8937
8975
  "```",
8938
8976
  "",
8939
8977
  "## Tool input (reproduction context)",
8940
8978
  "",
8941
8979
  "```json",
8942
- toolInput.slice(0, 500),
8980
+ record.toolInput.slice(0, 1e3),
8981
+ "```",
8982
+ "",
8983
+ "## Normalized intake JSON",
8984
+ "",
8985
+ "```json",
8986
+ JSON.stringify(record, null, 2),
8943
8987
  "```",
8944
8988
  "",
8945
8989
  "## Triage notes",
@@ -8947,19 +8991,44 @@ async function main() {
8947
8991
  "- Classification: system bug (auto-detected)",
8948
8992
  "- Review this error \u2014 fix if real, close if false positive",
8949
8993
  "- If false positive: add the error pattern to USER_ERROR_PATTERNS in error-detector.ts"
8950
- ].join("\n");
8994
+ ].filter((line) => line !== void 0).join("\n");
8995
+ }
8996
+
8997
+ // src/adapters/claude/hooks/bug-report-worker.ts
8998
+ async function main() {
8999
+ const intake = buildBugIntakeFromEnv(process.env);
9000
+ await fastDbInit();
9001
+ const client = getClient();
9002
+ const { loadEmployeesSync: loadEmployeesSync2, getEmployeeByRole: getEmployeeByRole2, getCoordinatorName: getCoordinatorName2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
9003
+ const employees = loadEmployeesSync2();
9004
+ const coordinatorName = getCoordinatorName2(employees);
9005
+ const ctoName = getEmployeeByRole2(employees, "CTO")?.name ?? coordinatorName;
9006
+ const existing = await client.execute({
9007
+ sql: `SELECT id FROM tasks
9008
+ WHERE assigned_to = ?
9009
+ AND status IN ('open', 'in_progress')
9010
+ AND title LIKE '[auto-bug]%'
9011
+ AND (context LIKE ? OR task_file LIKE ?)
9012
+ LIMIT 1`,
9013
+ args: [ctoName, `%${intake.fingerprint}%`, `%${intake.fingerprint.slice(0, 8)}%`]
9014
+ });
9015
+ if (existing.rows.length > 0) {
9016
+ process.stderr.write(`[bug-report-worker] Duplicate found for fingerprint ${intake.fingerprint}, skipping
9017
+ `);
9018
+ return;
9019
+ }
8951
9020
  await createTask({
8952
- title: `[auto-bug] ${toolName}: ${errorSummary}`,
9021
+ title: intake.title,
8953
9022
  assignedTo: ctoName,
8954
9023
  assignedBy: "system",
8955
- projectName,
8956
- priority: "p1",
8957
- context,
9024
+ projectName: intake.projectName,
9025
+ priority: intake.severity,
9026
+ context: formatBugIntakeTaskContext(intake),
8958
9027
  baseDir: process.cwd(),
8959
9028
  skipDispatch: true,
8960
9029
  reviewer: coordinatorName
8961
9030
  });
8962
- process.stderr.write(`[bug-report-worker] Created auto-bug task for ${toolName}: ${errorSummary}
9031
+ process.stderr.write(`[bug-report-worker] Created auto-bug task for ${intake.toolName}: ${intake.summary}
8963
9032
  `);
8964
9033
  }
8965
9034
  main().catch((err) => {