@cmmd-center/forge 0.13.14 → 0.13.15

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/dist/bin.mjs CHANGED
@@ -22,7 +22,7 @@ import { createCipheriv, createDecipheriv, createHash, createHmac, createPublicK
22
22
  import * as FS$1 from "node:fs";
23
23
  import fs, { accessSync, chmodSync, constants, createReadStream, existsSync, mkdirSync, mkdtempSync, promises, readFile, readFileSync, readdir, readdirSync, realpathSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
24
24
  import * as NodeOS from "node:os";
25
- import $U, { homedir, networkInterfaces, tmpdir } from "node:os";
25
+ import os, { homedir, networkInterfaces, tmpdir } from "node:os";
26
26
  import * as Path$1 from "node:path";
27
27
  import path, { basename, delimiter, dirname, extname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
28
28
  import { pathToFileURL } from "node:url";
@@ -26921,6 +26921,44 @@ function encodeCmmdRuntimeWorktreeLaunchActivityPayload(kind, payload) {
26921
26921
  function isCmmdRuntimeWorktreeLaunchActivityKind(kind) {
26922
26922
  return WORKTREE_LAUNCH_ACTIVITY_KINDS.has(kind);
26923
26923
  }
26924
+ /**
26925
+ * A plan update rides the `tool_call` event kind because the forge-context
26926
+ * contract has no plan event kind, the same trade the launch rows make. The
26927
+ * steps are carried verbatim under a versioned marker so a cloud client can
26928
+ * rebuild the composer plan banner from the durable replica. (#3319)
26929
+ */
26930
+ const CMMD_RUNTIME_PLAN_ACTIVITY_KIND = "turn.plan.updated";
26931
+ function encodeCmmdRuntimePlanActivityPayload(kind, payload) {
26932
+ return {
26933
+ ...isRecord$23(payload) ? payload : { sourcePayload: payload },
26934
+ forgePlanActivity: {
26935
+ schemaVersion: 1,
26936
+ source: "native",
26937
+ kind
26938
+ }
26939
+ };
26940
+ }
26941
+ function decodeCmmdRuntimePlanActivity(payload) {
26942
+ if (!isRecord$23(payload)) return null;
26943
+ const metadata = payload["forgePlanActivity"];
26944
+ if (!isRecord$23(metadata)) return null;
26945
+ if (metadata["schemaVersion"] !== 1 || metadata["source"] !== "native" || metadata["kind"] !== "turn.plan.updated") return null;
26946
+ const rawPlan = payload["plan"];
26947
+ return {
26948
+ kind: CMMD_RUNTIME_PLAN_ACTIVITY_KIND,
26949
+ plan: Array.isArray(rawPlan) ? rawPlan.flatMap((entry) => {
26950
+ if (!isRecord$23(entry)) return [];
26951
+ const step = entry["step"];
26952
+ const status = entry["status"];
26953
+ if (typeof step !== "string" || step.trim().length === 0) return [];
26954
+ if (status !== "pending" && status !== "inProgress" && status !== "completed") return [];
26955
+ return [{
26956
+ step,
26957
+ status
26958
+ }];
26959
+ }) : []
26960
+ };
26961
+ }
26924
26962
  //#endregion
26925
26963
  //#region ../../packages/contracts/src/cmmdProjectPatch.ts
26926
26964
  const CMMD_PROJECT_PATCH_FIELDS = {
@@ -50697,7 +50735,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
50697
50735
  const min = forceColor || 0;
50698
50736
  if (env.TERM === "dumb") return min;
50699
50737
  if (process$1.platform === "win32") {
50700
- const osRelease = $U.release().split(".");
50738
+ const osRelease = os.release().split(".");
50701
50739
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
50702
50740
  return 1;
50703
50741
  }
@@ -53140,7 +53178,7 @@ var require_ProcessDetector = /* @__PURE__ */ __commonJSMin(((exports) => {
53140
53178
  exports.processDetector = void 0;
53141
53179
  const api_1 = (init_esm$1(), __toCommonJS(esm_exports$1));
53142
53180
  const semconv_1 = require_semconv$1();
53143
- const os$3 = __require("os");
53181
+ const os$4 = __require("os");
53144
53182
  /**
53145
53183
  * ProcessDetector will be used to detect the resources related current process running
53146
53184
  * and being instrumented from the NodeJS Process module.
@@ -53162,7 +53200,7 @@ var require_ProcessDetector = /* @__PURE__ */ __commonJSMin(((exports) => {
53162
53200
  };
53163
53201
  if (process.argv.length > 1) attributes[semconv_1.ATTR_PROCESS_COMMAND] = process.argv[1];
53164
53202
  try {
53165
- const userInfo = os$3.userInfo();
53203
+ const userInfo = os$4.userInfo();
53166
53204
  attributes[semconv_1.ATTR_PROCESS_OWNER] = userInfo.username;
53167
53205
  } catch (e) {
53168
53206
  api_1.diag.debug(`error obtaining process owner: ${e}`);
@@ -66085,7 +66123,7 @@ function normalizeNumberish(value) {
66085
66123
  }
66086
66124
  //#endregion
66087
66125
  //#region package.json
66088
- var version$1 = "0.13.14";
66126
+ var version$1 = "0.13.15";
66089
66127
  //#endregion
66090
66128
  //#region src/sentry.ts
66091
66129
  const SERVER_APP_NAME = "forge-server";
@@ -66965,6 +67003,121 @@ function verifyCmmdForgeCloudOwnerAssertion(token, publicKeyPem, expectedKeyId,
66965
67003
  };
66966
67004
  }
66967
67005
  //#endregion
67006
+ //#region src/observability/Attributes.ts
67007
+ function isPlainObject$1(value) {
67008
+ return typeof value === "object" && value !== null && !Array.isArray(value);
67009
+ }
67010
+ function markSeen(value, seen) {
67011
+ if (seen.has(value)) return true;
67012
+ seen.add(value);
67013
+ return false;
67014
+ }
67015
+ function normalizeJsonValue(value, seen = /* @__PURE__ */ new WeakSet()) {
67016
+ if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value ?? null;
67017
+ if (typeof value === "bigint") return value.toString();
67018
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString();
67019
+ if (value instanceof Error) return {
67020
+ name: value.name,
67021
+ message: value.message,
67022
+ ...value.stack ? { stack: value.stack } : {}
67023
+ };
67024
+ if (Array.isArray(value)) {
67025
+ if (markSeen(value, seen)) return "[Circular]";
67026
+ return value.map((entry) => normalizeJsonValue(entry, seen));
67027
+ }
67028
+ if (value instanceof Map) {
67029
+ if (markSeen(value, seen)) return "[Circular]";
67030
+ return Object.fromEntries(Array.from(value.entries(), ([key, entryValue]) => [String(key), normalizeJsonValue(entryValue, seen)]));
67031
+ }
67032
+ if (value instanceof Set) {
67033
+ if (markSeen(value, seen)) return "[Circular]";
67034
+ return Array.from(value.values(), (entry) => normalizeJsonValue(entry, seen));
67035
+ }
67036
+ if (!isPlainObject$1(value)) return String(value);
67037
+ if (markSeen(value, seen)) return "[Circular]";
67038
+ return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, normalizeJsonValue(entryValue, seen)]));
67039
+ }
67040
+ function compactTraceAttributes(attributes) {
67041
+ return Object.fromEntries(Object.entries(attributes).filter(([, value]) => value !== void 0).map(([key, value]) => [key, normalizeJsonValue(value)]));
67042
+ }
67043
+ function compactMetricAttributes(attributes) {
67044
+ return Object.fromEntries(Object.entries(attributes).flatMap(([key, value]) => {
67045
+ if (value === void 0 || value === null) return [];
67046
+ if (typeof value === "string") return [[key, value]];
67047
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return [[key, String(value)]];
67048
+ return [];
67049
+ }));
67050
+ }
67051
+ function outcomeFromExit(exit) {
67052
+ if (isSuccess(exit)) return "success";
67053
+ return hasInterruptsOnly(exit.cause) ? "interrupt" : "failure";
67054
+ }
67055
+ function normalizeModelMetricLabel(model) {
67056
+ const normalized = model?.trim().toLowerCase();
67057
+ if (!normalized) return;
67058
+ if (normalized.includes("gpt")) return "gpt";
67059
+ if (normalized.includes("claude")) return "claude";
67060
+ if (normalized.includes("gemini")) return "gemini";
67061
+ return "other";
67062
+ }
67063
+ //#endregion
67064
+ //#region src/observability/Metrics.ts
67065
+ const rpcRequestsTotal = counter("t3_rpc_requests_total", { description: "Total RPC requests handled by the websocket RPC server." });
67066
+ const rpcRequestDuration = timer("t3_rpc_request_duration", { description: "RPC request handling duration." });
67067
+ const orchestrationCommandsTotal = counter("t3_orchestration_commands_total", { description: "Total orchestration commands dispatched." });
67068
+ const orchestrationCommandDuration = timer("t3_orchestration_command_duration", { description: "Orchestration command dispatch duration." });
67069
+ const orchestrationCommandAckDuration = timer("t3_orchestration_command_ack_duration", { description: "Time from orchestration command dispatch to the first committed domain event emitted for that command." });
67070
+ const orchestrationEventsProcessedTotal = counter("t3_orchestration_events_processed_total", { description: "Total orchestration intent events processed by runtime reactors." });
67071
+ const providerSessionsTotal = counter("t3_provider_sessions_total", { description: "Total provider session lifecycle operations." });
67072
+ const providerTurnsTotal = counter("t3_provider_turns_total", { description: "Total provider turn lifecycle operations." });
67073
+ const providerTurnDuration = timer("t3_provider_turn_duration", { description: "Provider turn request duration." });
67074
+ const providerRuntimeEventsTotal = counter("t3_provider_runtime_events_total", { description: "Total canonical provider runtime events processed." });
67075
+ const gitCommandsTotal = counter("t3_git_commands_total", { description: "Total git commands executed by the server runtime." });
67076
+ const gitCommandDuration = timer("t3_git_command_duration", { description: "Git command execution duration." });
67077
+ const terminalSessionsTotal = counter("t3_terminal_sessions_total", { description: "Total terminal sessions started." });
67078
+ const browserSessionsReapedTotal = counter("browser_sessions_reaped_total", { description: "Browser sessions and Chromium processes reclaimed by the orphan reaper, by reason (orphan | ttl | stale_profile)." });
67079
+ const terminalRestartsTotal = counter("t3_terminal_restarts_total", { description: "Total terminal restart requests handled." });
67080
+ const runtimeReplicationOutboxDepth = gauge("t3_runtime_replication_outbox_depth", { description: "Current runtime replication outbox items by durable state." });
67081
+ const runtimeReplicationOutboxOldestEventAge = gauge("t3_runtime_replication_outbox_oldest_event_age_seconds", { description: "Age in seconds of the oldest undelivered runtime replication payload." });
67082
+ const runtimeReplicationPublicationAttemptsTotal = counter("t3_runtime_replication_publication_attempts_total", { description: "Total canonical runtime replication publication attempts." });
67083
+ const runtimeReplicationPublicationRetriesTotal = counter("t3_runtime_replication_publication_retries_total", { description: "Total runtime replication retries scheduled after recoverable failures." });
67084
+ const runtimeReplicationPublicationLatency = timer("t3_runtime_replication_publication_latency", { description: "Time from durable enqueue to canonical CMMD acknowledgement." });
67085
+ const cmmdCredentialRefreshTotal = counter("t3_cmmd_credential_refresh_total", { description: "CMMD JWT refresh attempts for a Forge session, by rung (self-refresh | grant-redemption) and outcome (attempted | succeeded)." });
67086
+ const cmmdRotationFenceAppliedTotal = counter("t3_cmmd_rotation_fence_applied_total", { description: "Sessions Forge refused to rotate CMMD credentials for, because the rotation fence applied." });
67087
+ const cmmdCredentialRowDeletedTotal = counter("t3_cmmd_credential_row_deleted_total", { description: "command_access_tokens rows actually deleted by clearUnrecoverableCmmdAuthForSession, by reason." });
67088
+ const metricAttributes = (attributes) => Object.entries(compactMetricAttributes(attributes));
67089
+ const increment = (metric, attributes, amount = 1) => update$3(withAttributes(metric, metricAttributes(attributes)), amount);
67090
+ const withMetricsImpl = (effect, options) => gen(function* () {
67091
+ const startedAt = Date.now();
67092
+ const exit$2 = yield* exit(effect);
67093
+ const duration = millis(Math.max(0, Date.now() - startedAt));
67094
+ const baseAttributes = typeof options.attributes === "function" ? options.attributes() : options.attributes ?? {};
67095
+ if (options.timer) yield* update$3(withAttributes(options.timer, metricAttributes(baseAttributes)), duration);
67096
+ if (options.counter) {
67097
+ const outcome = outcomeFromExit(exit$2);
67098
+ yield* update$3(withAttributes(options.counter, metricAttributes({
67099
+ ...baseAttributes,
67100
+ outcome,
67101
+ ...options.outcomeAttributes ? options.outcomeAttributes(outcome) : {}
67102
+ })), 1);
67103
+ }
67104
+ if (isSuccess(exit$2)) return exit$2.value;
67105
+ return yield* failCause$1(exit$2.cause);
67106
+ });
67107
+ const withMetrics = dual(2, withMetricsImpl);
67108
+ const providerMetricAttributes = (provider, extra) => compactMetricAttributes({
67109
+ provider,
67110
+ ...extra
67111
+ });
67112
+ const providerTurnMetricAttributes = (input) => {
67113
+ const modelFamily = normalizeModelMetricLabel(input.model);
67114
+ return compactMetricAttributes({
67115
+ provider: input.provider,
67116
+ ...modelFamily ? { modelFamily } : {},
67117
+ ...input.extra
67118
+ });
67119
+ };
67120
+ //#endregion
66968
67121
  //#region src/persistence/Errors.ts
66969
67122
  var PersistenceSqlError = class extends TaggedErrorClass()("PersistenceSqlError", {
66970
67123
  operation: String$1,
@@ -67655,8 +67808,14 @@ const scheduleExpiredForgeCloudOwnerCredentialMaintenance = sleep$4(minutes(5)).
67655
67808
  *
67656
67809
  * Clearing every active owner row unconditionally (the pre-CMMD-1871 behaviour)
67657
67810
  * turned a single expired tab into a sign-out on every browser and device.
67811
+ *
67812
+ * Destroys a refresh token good for months, so every call is logged with a
67813
+ * `reason` naming the caller's own decision (never the credential itself) and
67814
+ * the actual row count deleted, at info level so it shows up without raising
67815
+ * the server's log level. `reason` is optional only so a caller that has not
67816
+ * been updated yet still compiles; every current caller passes one.
67658
67817
  */
67659
- const clearUnrecoverableCmmdAuthForSession = (sessionId) => gen(function* () {
67818
+ const clearUnrecoverableCmmdAuthForSession = (sessionId, reason) => gen(function* () {
67660
67819
  const sql = yield* SqlClient;
67661
67820
  const rows = yield* sql`
67662
67821
  DELETE FROM command_access_tokens
@@ -67681,6 +67840,14 @@ const clearUnrecoverableCmmdAuthForSession = (sessionId) => gen(function* () {
67681
67840
  `.pipe(mapError(toPersistenceSqlError("CommandAccessTokenStore.clearUnrecoverableCmmdAuth:query")));
67682
67841
  forgetCommandAccessTokenCachesForSession(sessionId);
67683
67842
  for (const row of rows) forgetCommandAccessTokenCachesForSession(row.sessionId);
67843
+ if (rows.length > 0) {
67844
+ yield* logInfo("CMMD credential row deleted.", {
67845
+ sessionId,
67846
+ reason: reason ?? "unspecified",
67847
+ rowsDeleted: rows.length
67848
+ });
67849
+ yield* update$3(withAttributes(cmmdCredentialRowDeletedTotal, metricAttributes({ reason: reason ?? "unspecified" })), rows.length);
67850
+ }
67684
67851
  });
67685
67852
  /**
67686
67853
  * Retire refresh credentials copied into historical desktop bearer sessions.
@@ -67736,6 +67903,23 @@ const isDesktopBearerAuthSession = (sessionId) => gen(function* () {
67736
67903
  `.pipe(mapError(toPersistenceSqlError("CommandAccessTokenStore.isDesktopBearerSession:query")));
67737
67904
  return rows[0]?.method === "bearer-session-token" && rows[0].clientDeviceType === "desktop";
67738
67905
  });
67906
+ /**
67907
+ * The device type a session's bearer bootstrap declared, for log lines only.
67908
+ *
67909
+ * Mirrors `isDesktopBearerAuthSession`'s read but returns the raw column
67910
+ * instead of collapsing it into a rotation verdict — a log line wants to say
67911
+ * what made the request, not whether Forge may rotate for it. Never fails:
67912
+ * an unreadable or missing row logs as `"unknown"` rather than losing the
67913
+ * decision-point log line it is attached to.
67914
+ */
67915
+ const readClientDeviceTypeForSession = (sessionId) => gen(function* () {
67916
+ return (yield* (yield* SqlClient)`
67917
+ SELECT client_device_type AS "clientDeviceType"
67918
+ FROM auth_sessions
67919
+ WHERE session_id = ${sessionId}
67920
+ LIMIT 1
67921
+ `.pipe(mapError(toPersistenceSqlError("CommandAccessTokenStore.readClientDeviceType:query"))))[0]?.clientDeviceType ?? "unknown";
67922
+ }).pipe(orElseSucceed(() => "unknown"));
67739
67923
  //#endregion
67740
67924
  //#region src/auth/http/oauthCookies.ts
67741
67925
  const OAUTH_STATE_COOKIE = "forge_oauth_state";
@@ -67938,9 +68122,20 @@ const authLogoutRouteLayer = add$1("POST", "/api/auth/logout", gen(function* ()
67938
68122
  onSuccess: (session) => session.sessionId
67939
68123
  }));
67940
68124
  if (sessionId) {
68125
+ yield* logInfo("Auth logout.", {
68126
+ sessionId,
68127
+ deviceType: yield* readClientDeviceTypeForSession(sessionId),
68128
+ outcome: "session-cleared",
68129
+ reason: cloudShell ? "forge-cloud-shell-session" : "local-session"
68130
+ });
67941
68131
  if (!cloudShell) yield* sessions.revoke(sessionId).pipe(ignore({ log: false }));
67942
68132
  yield* clearCommandAccessTokenForSession(sessionId).pipe(ignore({ log: false }));
67943
- }
68133
+ } else yield* logInfo("Auth logout.", {
68134
+ sessionId: null,
68135
+ deviceType: "unknown",
68136
+ outcome: "no-op",
68137
+ reason: "no authenticated session presented"
68138
+ });
67944
68139
  const secure = shouldUseSecureCookie(forgePublicUrlForRequest(request));
67945
68140
  let response = yield* jsonUnsafe({ loggedOut: true }, { status: 200 }).pipe(setCookie(sessions.cookieName, "", {
67946
68141
  expires: /* @__PURE__ */ new Date(0),
@@ -68539,16 +68734,32 @@ const authCommandBearerBootstrapRouteLayer = add$1("POST", "/api/auth/bootstrap/
68539
68734
  if (limited) return limited;
68540
68735
  const serverAuth = yield* ServerAuth;
68541
68736
  const sessions = yield* SessionCredentialService;
68542
- if (serverAuth.isForgeCloudShell?.() === true) return yield* new AuthError({
68543
- message: "Forge Cloud accepts owner assertions from CMMD only.",
68544
- status: 403
68545
- });
68737
+ if (serverAuth.isForgeCloudShell?.() === true) {
68738
+ yield* logWarning$1("CMMD bearer bootstrap failed.", {
68739
+ deviceType: "unknown",
68740
+ outcome: "failed",
68741
+ reason: "forge-cloud-shell-rejects-non-cmmd-bootstrap"
68742
+ });
68743
+ return yield* new AuthError({
68744
+ message: "Forge Cloud accepts owner assertions from CMMD only.",
68745
+ status: 403
68746
+ });
68747
+ }
68546
68748
  const payload = yield* schemaBodyJson(CommandBootstrapInput).pipe(mapError((cause) => new AuthError({
68547
68749
  message: "Invalid CMMD OAuth bearer bootstrap payload.",
68548
68750
  status: 400,
68549
68751
  cause
68752
+ })), tapError(() => logWarning$1("CMMD bearer bootstrap failed.", {
68753
+ deviceType: "unknown",
68754
+ outcome: "failed",
68755
+ reason: "invalid-payload"
68756
+ })));
68757
+ const deviceType = payload.clientDeviceType ?? "desktop";
68758
+ const validated = yield* validateCommandBootstrapPayload(payload).pipe(tapError(() => logWarning$1("CMMD bearer bootstrap failed.", {
68759
+ deviceType,
68760
+ outcome: "failed",
68761
+ reason: "invalid-command-bootstrap-payload"
68550
68762
  })));
68551
- const validated = yield* validateCommandBootstrapPayload(payload);
68552
68763
  const sessionSubject = resolveCommandOAuthSubject(validated.subjectUserId);
68553
68764
  const issued = yield* sessions.issue({
68554
68765
  method: "bearer-session-token",
@@ -68561,20 +68772,34 @@ const authCommandBearerBootstrapRouteLayer = add$1("POST", "/api/auth/bootstrap/
68561
68772
  }).pipe(mapError((cause) => new AuthError({
68562
68773
  message: "Failed to issue authenticated bearer session.",
68563
68774
  cause
68775
+ })), tapError(() => logWarning$1("CMMD bearer bootstrap failed.", {
68776
+ deviceType,
68777
+ outcome: "failed",
68778
+ reason: "session-issuance-failed"
68564
68779
  })));
68565
68780
  const cmmdIdentity = yield* persistCommandBootstrapSessionArtifacts({
68566
68781
  sessionId: issued.sessionId,
68567
68782
  payload,
68568
68783
  cmmdIdentity: validated.cmmdIdentity
68569
68784
  });
68570
- if ((payload.clientDeviceType ?? "desktop") === "desktop" && !payload.refreshToken?.trim()) yield* clearDesktopBearerRefreshTokensForSubject({
68785
+ if (deviceType === "desktop" && !payload.refreshToken?.trim()) yield* clearDesktopBearerRefreshTokensForSubject({
68571
68786
  subject: sessionSubject,
68572
68787
  clientUserAgent: request.headers["user-agent"]
68573
68788
  }).pipe(mapError((cause) => new AuthError({
68574
68789
  message: "Failed to retire legacy desktop refresh credentials.",
68575
68790
  status: 500,
68576
68791
  cause
68792
+ })), tapError(() => logWarning$1("CMMD bearer bootstrap failed.", {
68793
+ sessionId: issued.sessionId,
68794
+ deviceType,
68795
+ outcome: "failed",
68796
+ reason: "legacy-desktop-refresh-retirement-failed"
68577
68797
  })));
68798
+ yield* logInfo("CMMD bearer bootstrap succeeded.", {
68799
+ sessionId: issued.sessionId,
68800
+ deviceType,
68801
+ outcome: "succeeded"
68802
+ });
68578
68803
  return jsonUnsafe({
68579
68804
  authenticated: true,
68580
68805
  role: issued.role,
@@ -69865,64 +70090,6 @@ function parseAttachmentIdFromRelativePath(relativePath) {
69865
70090
  return id.length > 0 && !id.includes(".") ? id : null;
69866
70091
  }
69867
70092
  //#endregion
69868
- //#region src/observability/Attributes.ts
69869
- function isPlainObject$1(value) {
69870
- return typeof value === "object" && value !== null && !Array.isArray(value);
69871
- }
69872
- function markSeen(value, seen) {
69873
- if (seen.has(value)) return true;
69874
- seen.add(value);
69875
- return false;
69876
- }
69877
- function normalizeJsonValue(value, seen = /* @__PURE__ */ new WeakSet()) {
69878
- if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value ?? null;
69879
- if (typeof value === "bigint") return value.toString();
69880
- if (value instanceof Date) return Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString();
69881
- if (value instanceof Error) return {
69882
- name: value.name,
69883
- message: value.message,
69884
- ...value.stack ? { stack: value.stack } : {}
69885
- };
69886
- if (Array.isArray(value)) {
69887
- if (markSeen(value, seen)) return "[Circular]";
69888
- return value.map((entry) => normalizeJsonValue(entry, seen));
69889
- }
69890
- if (value instanceof Map) {
69891
- if (markSeen(value, seen)) return "[Circular]";
69892
- return Object.fromEntries(Array.from(value.entries(), ([key, entryValue]) => [String(key), normalizeJsonValue(entryValue, seen)]));
69893
- }
69894
- if (value instanceof Set) {
69895
- if (markSeen(value, seen)) return "[Circular]";
69896
- return Array.from(value.values(), (entry) => normalizeJsonValue(entry, seen));
69897
- }
69898
- if (!isPlainObject$1(value)) return String(value);
69899
- if (markSeen(value, seen)) return "[Circular]";
69900
- return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, normalizeJsonValue(entryValue, seen)]));
69901
- }
69902
- function compactTraceAttributes(attributes) {
69903
- return Object.fromEntries(Object.entries(attributes).filter(([, value]) => value !== void 0).map(([key, value]) => [key, normalizeJsonValue(value)]));
69904
- }
69905
- function compactMetricAttributes(attributes) {
69906
- return Object.fromEntries(Object.entries(attributes).flatMap(([key, value]) => {
69907
- if (value === void 0 || value === null) return [];
69908
- if (typeof value === "string") return [[key, value]];
69909
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return [[key, String(value)]];
69910
- return [];
69911
- }));
69912
- }
69913
- function outcomeFromExit(exit) {
69914
- if (isSuccess(exit)) return "success";
69915
- return hasInterruptsOnly(exit.cause) ? "interrupt" : "failure";
69916
- }
69917
- function normalizeModelMetricLabel(model) {
69918
- const normalized = model?.trim().toLowerCase();
69919
- if (!normalized) return;
69920
- if (normalized.includes("gpt")) return "gpt";
69921
- if (normalized.includes("claude")) return "claude";
69922
- if (normalized.includes("gemini")) return "gemini";
69923
- return "other";
69924
- }
69925
- //#endregion
69926
70093
  //#region src/observability/TraceRecord.ts
69927
70094
  function formatTraceExit(exit) {
69928
70095
  if (isSuccess(exit)) return { _tag: "Success" };
@@ -70978,6 +71145,22 @@ async function readOAuthErrorCode(response) {
70978
71145
  //#endregion
70979
71146
  //#region src/command/cmmdRotationFence.ts
70980
71147
  /**
71148
+ * Log and count a fence application, without changing the verdict it reports.
71149
+ *
71150
+ * The two facts named in the log line are exactly the ones the predicate
71151
+ * above the caller already computed — whether this session ever deposited a
71152
+ * refresh token of its own, and (only when that was false) whether the
71153
+ * session row reads as a desktop bearer session. Neither fact nor the log
71154
+ * line changes what the caller returns.
71155
+ */
71156
+ function logRotationFenceApplied(input) {
71157
+ return logInfo("CMMD rotation fence applied: session may not rotate its own credential.", {
71158
+ sessionId: input.sessionId,
71159
+ hasStoredRefreshToken: input.hasStoredRefreshToken,
71160
+ isDesktopBearerSession: input.isDesktopBearerSession
71161
+ }).pipe(andThen(() => update$3(withAttributes(cmmdRotationFenceAppliedTotal, metricAttributes({})), 1)));
71162
+ }
71163
+ /**
70981
71164
  * True when Forge must not rotate this session's CMMD credentials.
70982
71165
  *
70983
71166
  * Fails closed: if the session row cannot be read, assume the fence applies.
@@ -70985,8 +71168,22 @@ async function readOAuthErrorCode(response) {
70985
71168
  * reconnect.
70986
71169
  */
70987
71170
  function isRotationFencedSession(sessionId) {
70988
- if (readCommandRefreshTokenForSession(sessionId)) return succeed(false);
70989
- return isDesktopBearerAuthSession(sessionId).pipe(orElseSucceed(() => true));
71171
+ const hasStoredRefreshToken = readCommandRefreshTokenForSession(sessionId) !== null;
71172
+ if (hasStoredRefreshToken) return succeed(false);
71173
+ return isDesktopBearerAuthSession(sessionId).pipe(match$1({
71174
+ onFailure: () => ({
71175
+ fenced: true,
71176
+ isDesktopBearerSession: null
71177
+ }),
71178
+ onSuccess: (isDesktopBearerSession) => ({
71179
+ fenced: isDesktopBearerSession,
71180
+ isDesktopBearerSession
71181
+ })
71182
+ }), tap(({ fenced, isDesktopBearerSession }) => fenced ? logRotationFenceApplied({
71183
+ sessionId,
71184
+ hasStoredRefreshToken,
71185
+ isDesktopBearerSession
71186
+ }) : void_$1), map$3(({ fenced }) => fenced));
70990
71187
  }
70991
71188
  //#endregion
70992
71189
  //#region src/cmmdConnection/cmmdCredentialRejection.ts
@@ -71023,7 +71220,7 @@ function forgetCmmdCredentialRejection(sessionId) {
71023
71220
  */
71024
71221
  const clearCmmdAuthAfterCmmdRejection = (sessionId) => gen(function* () {
71025
71222
  if (!hasCmmdCredentialRejection(sessionId)) return;
71026
- yield* clearUnrecoverableCmmdAuthForSession(sessionId);
71223
+ yield* clearUnrecoverableCmmdAuthForSession(sessionId, "cmmd-rejected-credential");
71027
71224
  forgetCmmdCredentialRejection(sessionId);
71028
71225
  });
71029
71226
  //#endregion
@@ -71034,6 +71231,23 @@ function usableCmmdUserJwt(token) {
71034
71231
  function storeResolvedJwt(sessionId, accessToken, refreshToken) {
71035
71232
  return setCommandJwtForSession(sessionId, accessToken, refreshToken ? { refreshToken } : {}).pipe(ignore({ log: true }));
71036
71233
  }
71234
+ function logCmmdJwtRung(context, rung, outcome, reason) {
71235
+ return (outcome === "failed" ? logWarning$1 : logInfo)("CMMD JWT resolver rung.", {
71236
+ sessionId: context.sessionId,
71237
+ subject: context.subject,
71238
+ deviceType: context.deviceType,
71239
+ caller: context.caller,
71240
+ rung,
71241
+ outcome,
71242
+ reason
71243
+ });
71244
+ }
71245
+ function recordCmmdCredentialRefreshMetric(rung, outcome) {
71246
+ return update$3(withAttributes(cmmdCredentialRefreshTotal, metricAttributes({
71247
+ rung,
71248
+ outcome
71249
+ })), 1);
71250
+ }
71037
71251
  const inFlightRefreshes = /* @__PURE__ */ new Map();
71038
71252
  function refreshOnceForSession(sessionId, refreshToken) {
71039
71253
  return promise(() => {
@@ -71055,15 +71269,21 @@ function refreshOnceForSession(sessionId, refreshToken) {
71055
71269
  * sibling session's token silently invalidates that sibling's stored copy and
71056
71270
  * signs it out.
71057
71271
  */
71058
- function trySelfRefresh(sessionId, logPrefix) {
71272
+ function trySelfRefresh(sessionId, context) {
71059
71273
  const currentRefreshToken = readCommandRefreshTokenForSession(sessionId);
71060
- if (!currentRefreshToken) return succeed(null);
71274
+ if (!currentRefreshToken) return logCmmdJwtRung(context, "self-refresh", "skipped", "no refresh token stored for this session").pipe(as$3(null));
71061
71275
  return gen(function* () {
71276
+ yield* logCmmdJwtRung(context, "self-refresh", "attempted", "rotating this session's own stored refresh token");
71277
+ yield* recordCmmdCredentialRefreshMetric("self-refresh", "attempted");
71062
71278
  const refreshed = yield* refreshOnceForSession(sessionId, currentRefreshToken);
71063
71279
  const accessToken = usableCmmdUserJwt(refreshed?.accessToken);
71064
- if (!accessToken) return null;
71280
+ if (!accessToken) {
71281
+ yield* logCmmdJwtRung(context, "self-refresh", "failed", "CMMD refresh did not return a usable user access JWT");
71282
+ return null;
71283
+ }
71065
71284
  yield* storeResolvedJwt(sessionId, accessToken, refreshed?.refreshToken);
71066
- console.log(`[${logPrefix}] CMMD JWT refreshed for session ${sessionId}`);
71285
+ yield* recordCmmdCredentialRefreshMetric("self-refresh", "succeeded");
71286
+ yield* logCmmdJwtRung(context, "self-refresh", "succeeded", "CMMD rotated the stored refresh token");
71067
71287
  return accessToken;
71068
71288
  });
71069
71289
  }
@@ -71080,16 +71300,22 @@ function trySelfRefresh(sessionId, logPrefix) {
71080
71300
  * CMMD owns its lifetime, and a transient network failure must not throw away
71081
71301
  * the one credential that can still recover the session.
71082
71302
  */
71083
- function tryGrantRedemption(sessionId, logPrefix) {
71303
+ function tryGrantRedemption(sessionId, context) {
71084
71304
  const grantToken = readCommandSessionGrantForSession(sessionId);
71085
- if (!grantToken) return succeed(null);
71305
+ if (!grantToken) return logCmmdJwtRung(context, "grant-redemption", "skipped", "no session grant stored for this session").pipe(as$3(null));
71086
71306
  return gen(function* () {
71307
+ yield* logCmmdJwtRung(context, "grant-redemption", "attempted", "redeeming this session's durable CMMD session grant");
71308
+ yield* recordCmmdCredentialRefreshMetric("grant-redemption", "attempted");
71087
71309
  const redeemed = yield* promise(() => redeemCmmdSessionGrant(grantToken)).pipe(orElseSucceed(() => null));
71088
71310
  const accessToken = usableCmmdUserJwt(redeemed?.accessToken);
71089
- if (!accessToken) return null;
71311
+ if (!accessToken) {
71312
+ yield* logCmmdJwtRung(context, "grant-redemption", "failed", "session grant redemption did not return a usable user access JWT");
71313
+ return null;
71314
+ }
71090
71315
  if (redeemed?.mcpToken) yield* setCommandAccessTokenForSession(sessionId, redeemed.mcpToken).pipe(ignore({ log: true }));
71091
71316
  yield* storeResolvedJwt(sessionId, accessToken, redeemed?.refreshToken);
71092
- console.log(`[${logPrefix}] CMMD credentials recovered via session grant for ${sessionId}`);
71317
+ yield* recordCmmdCredentialRefreshMetric("grant-redemption", "succeeded");
71318
+ yield* logCmmdJwtRung(context, "grant-redemption", "succeeded", "session grant redeemed");
71093
71319
  return accessToken;
71094
71320
  });
71095
71321
  }
@@ -71100,23 +71326,28 @@ function tryGrantRedemption(sessionId, logPrefix) {
71100
71326
  * fallback for when that rotation is already gone. Neither touches another
71101
71327
  * session's credentials, so this ladder is safe to run from any path.
71102
71328
  */
71103
- function tryOwnCredentialRecovery(sessionId, logPrefix) {
71329
+ function tryOwnCredentialRecovery(sessionId, context) {
71104
71330
  return gen(function* () {
71105
- const refreshed = yield* trySelfRefresh(sessionId, logPrefix);
71331
+ const refreshed = yield* trySelfRefresh(sessionId, context);
71106
71332
  if (refreshed) return refreshed;
71107
- return yield* tryGrantRedemption(sessionId, logPrefix);
71333
+ return yield* tryGrantRedemption(sessionId, context);
71108
71334
  });
71109
71335
  }
71110
- function tryPromoteBootstrap(sessionId, subject, forceRefresh) {
71111
- if (subject !== "owner-bootstrap" && subject !== "desktop-bootstrap") return succeed(null);
71336
+ function tryPromoteBootstrap(sessionId, subject, forceRefresh, context) {
71337
+ if (subject !== "owner-bootstrap" && subject !== "desktop-bootstrap") return logCmmdJwtRung(context, "promote-bootstrap", "skipped", "subject is not a bootstrap subject").pipe(as$3(null));
71112
71338
  return gen(function* () {
71339
+ yield* logCmmdJwtRung(context, "promote-bootstrap", "attempted", "promoting the most recent owner CMMD auth to this bootstrap session");
71113
71340
  yield* promoteMostRecentOwnerCommandAuthToSession(sessionId).pipe(catch_((error) => logWarning$1("Failed to promote owner CMMD auth for REST JWT resolution.", {
71114
71341
  cause: error,
71115
71342
  sessionId,
71116
71343
  subject
71117
71344
  })));
71118
71345
  const promotedJwt = usableCmmdUserJwt(readCommandJwtForSession(sessionId));
71119
- if (!forceRefresh && promotedJwt && !isJwtExpiredForSession(sessionId)) return promotedJwt;
71346
+ if (!forceRefresh && promotedJwt && !isJwtExpiredForSession(sessionId)) {
71347
+ yield* logCmmdJwtRung(context, "promote-bootstrap", "succeeded", "promoted owner JWT is usable");
71348
+ return promotedJwt;
71349
+ }
71350
+ yield* logCmmdJwtRung(context, "promote-bootstrap", "failed", forceRefresh ? "forced refresh: a promoted JWT is never reused as-is" : "no promoted JWT available, or it is already expired");
71120
71351
  return null;
71121
71352
  });
71122
71353
  }
@@ -71129,17 +71360,50 @@ function tryPromoteBootstrap(sessionId, subject, forceRefresh) {
71129
71360
  * must stay private to the session it was issued to (see `trySelfRefresh`).
71130
71361
  * Copying it is what let one expired tab sign the user out everywhere.
71131
71362
  */
71132
- function trySubjectFallback(sessionId, subject, logPrefix) {
71363
+ function trySubjectFallback(sessionId, subject, context) {
71133
71364
  return gen(function* () {
71134
- const fallback = yield* readMostRecentJwtForSubject(subject).pipe(orElseSucceed(() => null));
71135
- const fallbackJwt = usableCmmdUserJwt(fallback?.jwt);
71136
- if (!fallbackJwt) return null;
71365
+ yield* logCmmdJwtRung(context, "subject-fallback", "attempted", "looking for a sibling session's still-valid JWT for this subject");
71366
+ const fallbackJwt = usableCmmdUserJwt((yield* readMostRecentJwtForSubject(subject).pipe(orElseSucceed(() => null)))?.jwt);
71367
+ if (!fallbackJwt) {
71368
+ yield* logCmmdJwtRung(context, "subject-fallback", "failed", "no sibling session holds a still-valid JWT for this subject");
71369
+ return null;
71370
+ }
71137
71371
  yield* storeResolvedJwt(sessionId, fallbackJwt, null);
71138
- console.log(`[${logPrefix}] JWT subject-fallback promoted for session ${sessionId} (owner: ${fallback?.sessionId})`);
71372
+ yield* logCmmdJwtRung(context, "subject-fallback", "succeeded", "borrowed a sibling session's still-valid JWT");
71139
71373
  return fallbackJwt;
71140
71374
  });
71141
71375
  }
71142
71376
  /**
71377
+ * The rungs below the rotation fence: forced own-credential recovery,
71378
+ * bootstrap promotion, subject fallback, then own-credential recovery again
71379
+ * as the last resort. Split out of `resolveCmmdJwtForSession` so each half of
71380
+ * the ladder stays independently readable (and independently testable) —
71381
+ * the fence check above is the one decision the split does not touch.
71382
+ */
71383
+ function runRecoveryLadder(sessionId, normalizedSubject, forceRefresh, context) {
71384
+ return gen(function* () {
71385
+ if (forceRefresh) {
71386
+ const forced = yield* tryOwnCredentialRecovery(sessionId, context);
71387
+ if (forced) return forced;
71388
+ }
71389
+ if (normalizedSubject) {
71390
+ const promoted = yield* tryPromoteBootstrap(sessionId, normalizedSubject, forceRefresh, context);
71391
+ if (promoted) return promoted;
71392
+ }
71393
+ if (!normalizedSubject) {
71394
+ yield* logCmmdJwtRung(context, "cache", "failed", "no subject to resolve and forced recovery already exhausted");
71395
+ return null;
71396
+ }
71397
+ if (!forceRefresh) {
71398
+ const fallback = yield* trySubjectFallback(sessionId, normalizedSubject, context);
71399
+ if (fallback) return fallback;
71400
+ }
71401
+ const recovered = yield* tryOwnCredentialRecovery(sessionId, context);
71402
+ if (!recovered) yield* logCmmdJwtRung(context, "cache", "failed", "recovery ladder exhausted: no rung produced a usable JWT");
71403
+ return recovered;
71404
+ });
71405
+ }
71406
+ /**
71143
71407
  * Resolve the CMMD REST JWT for a Forge session.
71144
71408
  *
71145
71409
  * Forge session auth is long-lived compared with the short-lived CMMD JWT.
@@ -71153,26 +71417,30 @@ function resolveCmmdJwtForSession({ sessionId, subject, logPrefix = "cmmd-auth",
71153
71417
  const cached = usableCmmdUserJwt(readCommandJwtForSession(sessionId));
71154
71418
  if (cached && !isJwtExpiredForSession(sessionId)) {
71155
71419
  if (!forceRefresh) return succeed(cached);
71156
- if (staleAccessToken && cached !== staleAccessToken) return succeed(cached);
71420
+ if (staleAccessToken && cached !== staleAccessToken) return succeed(cached).pipe(tap(() => gen(function* () {
71421
+ const deviceType = yield* readClientDeviceTypeForSession(sessionId);
71422
+ yield* logCmmdJwtRung({
71423
+ sessionId,
71424
+ subject: subject?.trim() || null,
71425
+ deviceType,
71426
+ caller: logPrefix
71427
+ }, "cache", "skipped", "forced refresh skipped: cache already holds a token another caller rotated");
71428
+ })));
71157
71429
  }
71158
71430
  const normalizedSubject = subject?.trim() || null;
71159
71431
  if (!normalizedSubject && !forceRefresh) return succeed(null);
71160
71432
  return gen(function* () {
71161
- if (yield* isRotationFencedSession(sessionId)) return null;
71162
- if (forceRefresh) {
71163
- const forced = yield* tryOwnCredentialRecovery(sessionId, logPrefix);
71164
- if (forced) return forced;
71165
- }
71166
- if (normalizedSubject) {
71167
- const promoted = yield* tryPromoteBootstrap(sessionId, normalizedSubject, forceRefresh);
71168
- if (promoted) return promoted;
71169
- }
71170
- if (!normalizedSubject) return null;
71171
- if (!forceRefresh) {
71172
- const fallback = yield* trySubjectFallback(sessionId, normalizedSubject, logPrefix);
71173
- if (fallback) return fallback;
71433
+ const context = {
71434
+ sessionId,
71435
+ subject: normalizedSubject,
71436
+ deviceType: yield* readClientDeviceTypeForSession(sessionId),
71437
+ caller: logPrefix
71438
+ };
71439
+ if (yield* isRotationFencedSession(sessionId)) {
71440
+ yield* logCmmdJwtRung(context, "rotation-fence", "failed", "session is fenced from rotating CMMD credentials");
71441
+ return null;
71174
71442
  }
71175
- return yield* tryOwnCredentialRecovery(sessionId, logPrefix);
71443
+ return yield* runRecoveryLadder(sessionId, normalizedSubject, forceRefresh, context);
71176
71444
  });
71177
71445
  }
71178
71446
  //#endregion
@@ -71909,9 +72177,14 @@ function buildProxyErrorResponse(error) {
71909
72177
  * warning rather than failing the request if the clear itself errors — a
71910
72178
  * session that already has no usable credential must still get its 401,
71911
72179
  * not a 500 from a failed best-effort cleanup.
72180
+ *
72181
+ * Reached only after CMMD itself rejected the request with both the original
72182
+ * and (if retried) the refreshed JWT, so — unlike `respondCmmdConnectionRequired`
72183
+ * below — this always deletes the row. `clearUnrecoverableCmmdAuthForSession`
72184
+ * logs the row count and the reason itself; this only names the reason.
71912
72185
  */
71913
72186
  function clearCmmdAuthAfterFailure(sessionId) {
71914
- return clearUnrecoverableCmmdAuthForSession(sessionId).pipe(catch_((error) => logWarning$1("Failed to clear unrecoverable CMMD auth after CMMD auth failure.", {
72187
+ return clearUnrecoverableCmmdAuthForSession(sessionId, "cmmd-proxy-auth-error").pipe(catch_((error) => logWarning$1("Failed to clear unrecoverable CMMD auth after CMMD auth failure.", {
71915
72188
  cause: error,
71916
72189
  sessionId
71917
72190
  })));
@@ -71932,7 +72205,7 @@ function clearCmmdAuthAfterFailure(sessionId) {
71932
72205
  * refused the credential, which `clearCmmdAuthAfterCmmdRejection` checks.
71933
72206
  */
71934
72207
  function respondCmmdConnectionRequired(sessionId) {
71935
- return clearCmmdAuthAfterCmmdRejection(sessionId).pipe(catch_((error) => logWarning$1("Failed to clear CMMD auth that CMMD itself rejected.", {
72208
+ return logInfo("CMMD proxy request answered CMMD_AUTH_REQUIRED.", { sessionId }).pipe(andThen(() => clearCmmdAuthAfterCmmdRejection(sessionId)), catch_((error) => logWarning$1("Failed to clear CMMD auth that CMMD itself rejected.", {
71936
72209
  cause: error,
71937
72210
  sessionId
71938
72211
  })), as$3(buildProxyErrorResponse(new CmmdProxyAuthenticationRequiredError("CMMD connection required. Reconnect CMMD to continue."))));
@@ -75698,7 +75971,7 @@ var require_node_gyp_build = /* @__PURE__ */ __commonJSMin(((exports, module) =>
75698
75971
  var fs$6 = __require("fs");
75699
75972
  var path$5 = __require("path");
75700
75973
  var url$1 = __require("url");
75701
- var os$2 = __require("os");
75974
+ var os$3 = __require("os");
75702
75975
  var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
75703
75976
  var vars = process.config && process.config.variables || {};
75704
75977
  var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
@@ -75706,8 +75979,8 @@ var require_node_gyp_build = /* @__PURE__ */ __commonJSMin(((exports, module) =>
75706
75979
  var abi = versions.modules;
75707
75980
  if (versions.deno || process.isBun) abi = "unsupported";
75708
75981
  var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
75709
- var arch = process.env.npm_config_arch || os$2.arch();
75710
- var platform = process.env.npm_config_platform || os$2.platform();
75982
+ var arch = process.env.npm_config_arch || os$3.arch();
75983
+ var platform = process.env.npm_config_platform || os$3.platform();
75711
75984
  var libc = process.env.LIBC || (isMusl(platform) ? "musl" : "glibc");
75712
75985
  var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
75713
75986
  var uv = (versions.uv || "").split(".")[0];
@@ -93889,7 +94162,7 @@ const launchDetached = (launch) => gen(function* () {
93889
94162
  });
93890
94163
  const OpenLive = effect(Open, gen(function* () {
93891
94164
  const open = yield* tryPromise({
93892
- try: () => import("./open-D8mGyWmt.mjs"),
94165
+ try: () => import("./open-FIgTDosf.mjs"),
93893
94166
  catch: (cause) => new OpenError$1({
93894
94167
  message: "failed to load browser opener",
93895
94168
  cause
@@ -94672,60 +94945,6 @@ const dispatchBootstrapTurnStart = (input, command) => gen(function* () {
94672
94945
  */
94673
94946
  var OrchestrationEngineService = class extends Service$1()("forge/orchestration/Services/OrchestrationEngine/OrchestrationEngineService") {};
94674
94947
  //#endregion
94675
- //#region src/observability/Metrics.ts
94676
- const rpcRequestsTotal = counter("t3_rpc_requests_total", { description: "Total RPC requests handled by the websocket RPC server." });
94677
- const rpcRequestDuration = timer("t3_rpc_request_duration", { description: "RPC request handling duration." });
94678
- const orchestrationCommandsTotal = counter("t3_orchestration_commands_total", { description: "Total orchestration commands dispatched." });
94679
- const orchestrationCommandDuration = timer("t3_orchestration_command_duration", { description: "Orchestration command dispatch duration." });
94680
- const orchestrationCommandAckDuration = timer("t3_orchestration_command_ack_duration", { description: "Time from orchestration command dispatch to the first committed domain event emitted for that command." });
94681
- const orchestrationEventsProcessedTotal = counter("t3_orchestration_events_processed_total", { description: "Total orchestration intent events processed by runtime reactors." });
94682
- const providerSessionsTotal = counter("t3_provider_sessions_total", { description: "Total provider session lifecycle operations." });
94683
- const providerTurnsTotal = counter("t3_provider_turns_total", { description: "Total provider turn lifecycle operations." });
94684
- const providerTurnDuration = timer("t3_provider_turn_duration", { description: "Provider turn request duration." });
94685
- const providerRuntimeEventsTotal = counter("t3_provider_runtime_events_total", { description: "Total canonical provider runtime events processed." });
94686
- const gitCommandsTotal = counter("t3_git_commands_total", { description: "Total git commands executed by the server runtime." });
94687
- const gitCommandDuration = timer("t3_git_command_duration", { description: "Git command execution duration." });
94688
- const terminalSessionsTotal = counter("t3_terminal_sessions_total", { description: "Total terminal sessions started." });
94689
- const browserSessionsReapedTotal = counter("browser_sessions_reaped_total", { description: "Browser sessions and Chromium processes reclaimed by the orphan reaper, by reason (orphan | ttl | stale_profile)." });
94690
- const terminalRestartsTotal = counter("t3_terminal_restarts_total", { description: "Total terminal restart requests handled." });
94691
- const runtimeReplicationOutboxDepth = gauge("t3_runtime_replication_outbox_depth", { description: "Current runtime replication outbox items by durable state." });
94692
- const runtimeReplicationOutboxOldestEventAge = gauge("t3_runtime_replication_outbox_oldest_event_age_seconds", { description: "Age in seconds of the oldest undelivered runtime replication payload." });
94693
- const runtimeReplicationPublicationAttemptsTotal = counter("t3_runtime_replication_publication_attempts_total", { description: "Total canonical runtime replication publication attempts." });
94694
- const runtimeReplicationPublicationRetriesTotal = counter("t3_runtime_replication_publication_retries_total", { description: "Total runtime replication retries scheduled after recoverable failures." });
94695
- const runtimeReplicationPublicationLatency = timer("t3_runtime_replication_publication_latency", { description: "Time from durable enqueue to canonical CMMD acknowledgement." });
94696
- const metricAttributes = (attributes) => Object.entries(compactMetricAttributes(attributes));
94697
- const increment = (metric, attributes, amount = 1) => update$3(withAttributes(metric, metricAttributes(attributes)), amount);
94698
- const withMetricsImpl = (effect, options) => gen(function* () {
94699
- const startedAt = Date.now();
94700
- const exit$2 = yield* exit(effect);
94701
- const duration = millis(Math.max(0, Date.now() - startedAt));
94702
- const baseAttributes = typeof options.attributes === "function" ? options.attributes() : options.attributes ?? {};
94703
- if (options.timer) yield* update$3(withAttributes(options.timer, metricAttributes(baseAttributes)), duration);
94704
- if (options.counter) {
94705
- const outcome = outcomeFromExit(exit$2);
94706
- yield* update$3(withAttributes(options.counter, metricAttributes({
94707
- ...baseAttributes,
94708
- outcome,
94709
- ...options.outcomeAttributes ? options.outcomeAttributes(outcome) : {}
94710
- })), 1);
94711
- }
94712
- if (isSuccess(exit$2)) return exit$2.value;
94713
- return yield* failCause$1(exit$2.cause);
94714
- });
94715
- const withMetrics = dual(2, withMetricsImpl);
94716
- const providerMetricAttributes = (provider, extra) => compactMetricAttributes({
94717
- provider,
94718
- ...extra
94719
- });
94720
- const providerTurnMetricAttributes = (input) => {
94721
- const modelFamily = normalizeModelMetricLabel(input.model);
94722
- return compactMetricAttributes({
94723
- provider: input.provider,
94724
- ...modelFamily ? { modelFamily } : {},
94725
- ...input.extra
94726
- });
94727
- };
94728
- //#endregion
94729
94948
  //#region src/observability/RpcInstrumentation.ts
94730
94949
  const annotateRpcSpan = (method, traceAttributes) => annotateCurrentSpan({
94731
94950
  "rpc.method": method,
@@ -97906,6 +98125,28 @@ function serializeWorktreeLaunchActivity(activity) {
97906
98125
  function decodeWorktreeLaunchActivityKind(kind) {
97907
98126
  return isCmmdRuntimeWorktreeLaunchActivityKind(kind) ? kind : null;
97908
98127
  }
98128
+ function planStepsOf(activity) {
98129
+ const decoded = decodeCmmdRuntimePlanActivity(encodeCmmdRuntimePlanActivityPayload(activity.kind, activity.payload));
98130
+ return decoded !== null && decoded.plan.length > 0 ? decoded.plan : null;
98131
+ }
98132
+ function serializePlanActivity(activity) {
98133
+ if (activity.kind !== "turn.plan.updated") return null;
98134
+ const plan = planStepsOf(activity);
98135
+ if (plan === null) return null;
98136
+ const completed = plan.filter((step) => step.status === "completed").length;
98137
+ return {
98138
+ sourceSequence: null,
98139
+ event: {
98140
+ eventId: `activity:${activity.id}`,
98141
+ occurredAt: canonicalRuntimeEventTimestamp(activity.createdAt),
98142
+ ...eventTurn(activity.turnId),
98143
+ kind: "tool_call",
98144
+ summary: `Plan: ${completed} of ${plan.length} steps done`,
98145
+ status: "completed",
98146
+ payload: encodeCmmdRuntimePlanActivityPayload(activity.kind, activity.payload)
98147
+ }
98148
+ };
98149
+ }
97909
98150
  function isReplicatedFailureActivity(activity) {
97910
98151
  return activity.kind === "runtime.error" || activity.kind.startsWith("provider.") && activity.kind.endsWith(".failed");
97911
98152
  }
@@ -98290,6 +98531,10 @@ function serializeRuntimeThreadEvents(thread) {
98290
98531
  const event = serializeWorktreeLaunchActivity(activity);
98291
98532
  return event === null ? [] : [event];
98292
98533
  });
98534
+ const planActivities = thread.activities.flatMap((activity) => {
98535
+ const event = serializePlanActivity(activity);
98536
+ return event === null ? [] : [event];
98537
+ });
98293
98538
  const recordedTurns = serializeRecordedTurns(thread);
98294
98539
  const textGenerations = serializeTextGenerations(thread);
98295
98540
  return [
@@ -98297,6 +98542,7 @@ function serializeRuntimeThreadEvents(thread) {
98297
98542
  ...replicaActivities,
98298
98543
  ...toolActivities,
98299
98544
  ...worktreeLaunchActivities,
98545
+ ...planActivities,
98300
98546
  ...failureActivities,
98301
98547
  ...recordedTurns,
98302
98548
  ...textGenerations
@@ -191252,7 +191498,7 @@ var Vce = {
191252
191498
  function Ve() {
191253
191499
  return Vce;
191254
191500
  }
191255
- var Us = $U.homedir(), lk = $U.tmpdir(), { env: Vc } = process$1, Yce = (e) => {
191501
+ var Us = os.homedir(), lk = os.tmpdir(), { env: Vc } = process$1, Yce = (e) => {
191256
191502
  let t = path.join(Us, "Library");
191257
191503
  return {
191258
191504
  data: path.join(t, "Application Support", e),
@@ -201452,12 +201698,12 @@ function qZ(e, t) {
201452
201698
  }
201453
201699
  return n;
201454
201700
  }
201455
- var os$1 = "https://code.claude.com/docs/en", kKe = [
201701
+ var os$2 = "https://code.claude.com/docs/en", kKe = [
201456
201702
  {
201457
201703
  matches: (e) => e.path === "permissions.defaultMode" && e.code === "invalid_value",
201458
201704
  tip: {
201459
201705
  suggestion: "Valid modes: \"acceptEdits\" (ask before file changes), \"plan\" (analysis only), \"bypassPermissions\" (auto-accept all), or \"default\" (standard behavior)",
201460
- docLink: `${os$1}/iam#permission-modes`
201706
+ docLink: `${os$2}/iam#permission-modes`
201461
201707
  }
201462
201708
  },
201463
201709
  {
@@ -201472,7 +201718,7 @@ var os$1 = "https://code.claude.com/docs/en", kKe = [
201472
201718
  matches: (e) => e.path.startsWith("env.") && e.code === "invalid_type",
201473
201719
  tip: {
201474
201720
  suggestion: "Environment variables must be strings. Wrap numbers and booleans in quotes. Example: \"DEBUG\": \"true\", \"PORT\": \"3000\"",
201475
- docLink: `${os$1}/settings#environment-variables`
201721
+ docLink: `${os$2}/settings#environment-variables`
201476
201722
  }
201477
201723
  },
201478
201724
  {
@@ -201483,14 +201729,14 @@ var os$1 = "https://code.claude.com/docs/en", kKe = [
201483
201729
  matches: (e) => e.path.startsWith("hooks.") && e.code === "invalid_key",
201484
201730
  tip: {
201485
201731
  suggestion: "Not a recognized hook event. Common events: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SessionEnd, Stop. Check spelling and capitalization.",
201486
- docLink: `${os$1}/hooks`
201732
+ docLink: `${os$2}/hooks`
201487
201733
  }
201488
201734
  },
201489
201735
  {
201490
201736
  matches: (e) => /\.hooks\.\d+\.command$/.test(e.path) && e.code === "invalid_type" && e.received === "undefined",
201491
201737
  tip: {
201492
201738
  suggestion: "Command hooks require `command`. For exec form (no shell), set `command` to the executable and `args` to its arguments: {\"type\": \"command\", \"command\": \"echo\", \"args\": [\"hi\"]}. For shell form, set `command` to the full shell string: {\"type\": \"command\", \"command\": \"echo hi\"}.",
201493
- docLink: `${os$1}/hooks#exec-form-and-shell-form`
201739
+ docLink: `${os$2}/hooks#exec-form-and-shell-form`
201494
201740
  }
201495
201741
  },
201496
201742
  {
@@ -201505,7 +201751,7 @@ var os$1 = "https://code.claude.com/docs/en", kKe = [
201505
201751
  matches: (e) => e.code === "unrecognized_keys",
201506
201752
  tip: {
201507
201753
  suggestion: "Check for typos or refer to the documentation for valid fields",
201508
- docLink: `${os$1}/settings`
201754
+ docLink: `${os$2}/settings`
201509
201755
  }
201510
201756
  },
201511
201757
  {
@@ -201520,13 +201766,13 @@ var os$1 = "https://code.claude.com/docs/en", kKe = [
201520
201766
  matches: (e) => e.path === "permissions.additionalDirectories" && e.code === "invalid_type",
201521
201767
  tip: {
201522
201768
  suggestion: "Must be an array of directory paths. Example: [\"~/projects\", \"/tmp/workspace\"]. You can also use --add-dir flag or /add-dir command",
201523
- docLink: `${os$1}/iam#working-directories`
201769
+ docLink: `${os$2}/iam#working-directories`
201524
201770
  }
201525
201771
  }
201526
201772
  ], AKe = {
201527
- permissions: `${os$1}/iam#configuring-permissions`,
201528
- env: `${os$1}/settings#environment-variables`,
201529
- hooks: `${os$1}/hooks`
201773
+ permissions: `${os$2}/iam#configuring-permissions`,
201774
+ env: `${os$2}/settings#environment-variables`,
201775
+ hooks: `${os$2}/hooks`
201530
201776
  };
201531
201777
  function GZ(e) {
201532
201778
  let t = kKe.find((r) => r.matches(e));
@@ -268836,7 +269082,7 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
268836
269082
  var dgram = __require("dgram");
268837
269083
  var thunky = require_thunky();
268838
269084
  var events = __require("events");
268839
- var os = __require("os");
269085
+ var os$1 = __require("os");
268840
269086
  var noop = function() {};
268841
269087
  module.exports = function(opts) {
268842
269088
  if (!opts) opts = {};
@@ -268967,14 +269213,14 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
268967
269213
  return that;
268968
269214
  };
268969
269215
  function defaultInterface() {
268970
- var networks = os.networkInterfaces();
269216
+ var networks = os$1.networkInterfaces();
268971
269217
  var names = Object.keys(networks);
268972
269218
  for (var i = 0; i < names.length; i++) {
268973
269219
  var net = networks[names[i]];
268974
269220
  for (var j = 0; j < net.length; j++) {
268975
269221
  var iface = net[j];
268976
269222
  if (isIPv4(iface.family) && !iface.internal) {
268977
- if (os.platform() === "darwin" && names[i] === "en0") return iface.address;
269223
+ if (os$1.platform() === "darwin" && names[i] === "en0") return iface.address;
268978
269224
  return "0.0.0.0";
268979
269225
  }
268980
269226
  }
@@ -268982,7 +269228,7 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
268982
269228
  return "127.0.0.1";
268983
269229
  }
268984
269230
  function allInterfaces() {
268985
- var networks = os.networkInterfaces();
269231
+ var networks = os$1.networkInterfaces();
268986
269232
  var names = Object.keys(networks);
268987
269233
  var res = [];
268988
269234
  for (var i = 0; i < names.length; i++) {
@@ -292287,7 +292533,7 @@ const ProviderCommandReactorLive = effect(ProviderCommandReactor, gen(function*
292287
292533
  summary: "Provider turn start failed",
292288
292534
  detail: `User message '${event.payload.messageId}' was not found for turn start request.`,
292289
292535
  turnId: null,
292290
- createdAt: event.payload.createdAt
292536
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
292291
292537
  });
292292
292538
  return;
292293
292539
  }
@@ -292340,7 +292586,7 @@ const ProviderCommandReactorLive = effect(ProviderCommandReactor, gen(function*
292340
292586
  summary: "Provider turn start failed",
292341
292587
  detail: publicProviderFailureDetail(pretty(cause)),
292342
292588
  turnId: null,
292343
- createdAt: event.payload.createdAt
292589
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
292344
292590
  })));
292345
292591
  });
292346
292592
  const processTurnInterruptRequested = fn$1("processTurnInterruptRequested")(function* (event) {
@@ -297975,7 +298221,7 @@ function resolveBuildCommitFromEnv(env) {
297975
298221
  * environment descriptor down instead of reporting an honest "unknown".
297976
298222
  */
297977
298223
  function readBakedBuildCommit() {
297978
- return "dea91e954d3d2f23154802ae304412fabe90dbbd";
298224
+ return "78b0e072680ed734c4eb8337a6d38c87e7e34393";
297979
298225
  }
297980
298226
  async function resolveServerBuildCommit(input) {
297981
298227
  if (isFullCommitSha(input.baked)) return input.baked;
@@ -309327,6 +309573,33 @@ registerProxyRoute({
309327
309573
  cmmdPath: "/api/projects/campaigns/:id",
309328
309574
  orgIdSource: "none"
309329
309575
  });
309576
+ /**
309577
+ * Apple App Store guideline 5.1.1(v): an app that supports account creation
309578
+ * or sign-in must let the user start account deletion from inside the app.
309579
+ * CMMD owns the account, so this proxies to its existing deactivation route.
309580
+ *
309581
+ * `cmmdPath` is `/api/settings/delete-account`, not a `/api/forge/context/*`
309582
+ * route: CMMD never built a forge-context-scoped deletion endpoint. It is
309583
+ * still reachable through the same owner JWT this gateway already resolves
309584
+ * per session: CMMD's `optionalJwtAuth` runs ahead of every `/api/*` route
309585
+ * (not only forge-context ones), so the same Bearer that reaches
309586
+ * `/api/cmmd/bootstrap` also authenticates here.
309587
+ *
309588
+ * CMMD's route soft-deletes: it flips the user to `active: false` and
309589
+ * `lifecycleState: 'deactivated'`, revokes CMMD's own refresh tokens, and
309590
+ * destroys its session. It does not purge data on this call. The Forge
309591
+ * client is responsible for the rest: revoking the Forge session
309592
+ * (`/api/auth/logout`) and clearing local state, the same as a manual
309593
+ * sign-out.
309594
+ */
309595
+ registerProxyRoute({
309596
+ forgePath: "/api/cmmd/settings/delete-account",
309597
+ method: "DELETE",
309598
+ cmmdPath: "/api/settings/delete-account",
309599
+ orgIdSource: "none",
309600
+ responseShape: "unwrapData",
309601
+ requestBody: "none"
309602
+ });
309330
309603
  registerProxyRoute({
309331
309604
  forgePath: "/api/cmmd/bootstrap",
309332
309605
  method: "GET",
@@ -322304,7 +322577,7 @@ const connectLoginCommand = make$34("login", {
322304
322577
  })).json();
322305
322578
  },
322306
322579
  openBrowser: async (url) => {
322307
- const open = (await import("./open-D8mGyWmt.mjs")).default;
322580
+ const open = (await import("./open-FIgTDosf.mjs")).default;
322308
322581
  await open(url);
322309
322582
  return true;
322310
322583
  },