@cmmd-center/forge 0.13.13 → 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.cjs CHANGED
@@ -27208,6 +27208,44 @@ function encodeCmmdRuntimeWorktreeLaunchActivityPayload(kind, payload) {
27208
27208
  function isCmmdRuntimeWorktreeLaunchActivityKind(kind) {
27209
27209
  return WORKTREE_LAUNCH_ACTIVITY_KINDS.has(kind);
27210
27210
  }
27211
+ /**
27212
+ * A plan update rides the `tool_call` event kind because the forge-context
27213
+ * contract has no plan event kind, the same trade the launch rows make. The
27214
+ * steps are carried verbatim under a versioned marker so a cloud client can
27215
+ * rebuild the composer plan banner from the durable replica. (#3319)
27216
+ */
27217
+ const CMMD_RUNTIME_PLAN_ACTIVITY_KIND = "turn.plan.updated";
27218
+ function encodeCmmdRuntimePlanActivityPayload(kind, payload) {
27219
+ return {
27220
+ ...isRecord$23(payload) ? payload : { sourcePayload: payload },
27221
+ forgePlanActivity: {
27222
+ schemaVersion: 1,
27223
+ source: "native",
27224
+ kind
27225
+ }
27226
+ };
27227
+ }
27228
+ function decodeCmmdRuntimePlanActivity(payload) {
27229
+ if (!isRecord$23(payload)) return null;
27230
+ const metadata = payload["forgePlanActivity"];
27231
+ if (!isRecord$23(metadata)) return null;
27232
+ if (metadata["schemaVersion"] !== 1 || metadata["source"] !== "native" || metadata["kind"] !== "turn.plan.updated") return null;
27233
+ const rawPlan = payload["plan"];
27234
+ return {
27235
+ kind: CMMD_RUNTIME_PLAN_ACTIVITY_KIND,
27236
+ plan: Array.isArray(rawPlan) ? rawPlan.flatMap((entry) => {
27237
+ if (!isRecord$23(entry)) return [];
27238
+ const step = entry["step"];
27239
+ const status = entry["status"];
27240
+ if (typeof step !== "string" || step.trim().length === 0) return [];
27241
+ if (status !== "pending" && status !== "inProgress" && status !== "completed") return [];
27242
+ return [{
27243
+ step,
27244
+ status
27245
+ }];
27246
+ }) : []
27247
+ };
27248
+ }
27211
27249
  //#endregion
27212
27250
  //#region ../../packages/contracts/src/cmmdProjectPatch.ts
27213
27251
  const CMMD_PROJECT_PATCH_FIELDS = {
@@ -66380,7 +66418,7 @@ function normalizeNumberish(value) {
66380
66418
  }
66381
66419
  //#endregion
66382
66420
  //#region package.json
66383
- var version$1 = "0.13.13";
66421
+ var version$1 = "0.13.15";
66384
66422
  //#endregion
66385
66423
  //#region src/sentry.ts
66386
66424
  const SERVER_APP_NAME = "forge-server";
@@ -67260,6 +67298,121 @@ function verifyCmmdForgeCloudOwnerAssertion(token, publicKeyPem, expectedKeyId,
67260
67298
  };
67261
67299
  }
67262
67300
  //#endregion
67301
+ //#region src/observability/Attributes.ts
67302
+ function isPlainObject$1(value) {
67303
+ return typeof value === "object" && value !== null && !Array.isArray(value);
67304
+ }
67305
+ function markSeen(value, seen) {
67306
+ if (seen.has(value)) return true;
67307
+ seen.add(value);
67308
+ return false;
67309
+ }
67310
+ function normalizeJsonValue(value, seen = /* @__PURE__ */ new WeakSet()) {
67311
+ if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value ?? null;
67312
+ if (typeof value === "bigint") return value.toString();
67313
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString();
67314
+ if (value instanceof Error) return {
67315
+ name: value.name,
67316
+ message: value.message,
67317
+ ...value.stack ? { stack: value.stack } : {}
67318
+ };
67319
+ if (Array.isArray(value)) {
67320
+ if (markSeen(value, seen)) return "[Circular]";
67321
+ return value.map((entry) => normalizeJsonValue(entry, seen));
67322
+ }
67323
+ if (value instanceof Map) {
67324
+ if (markSeen(value, seen)) return "[Circular]";
67325
+ return Object.fromEntries(Array.from(value.entries(), ([key, entryValue]) => [String(key), normalizeJsonValue(entryValue, seen)]));
67326
+ }
67327
+ if (value instanceof Set) {
67328
+ if (markSeen(value, seen)) return "[Circular]";
67329
+ return Array.from(value.values(), (entry) => normalizeJsonValue(entry, seen));
67330
+ }
67331
+ if (!isPlainObject$1(value)) return String(value);
67332
+ if (markSeen(value, seen)) return "[Circular]";
67333
+ return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, normalizeJsonValue(entryValue, seen)]));
67334
+ }
67335
+ function compactTraceAttributes(attributes) {
67336
+ return Object.fromEntries(Object.entries(attributes).filter(([, value]) => value !== void 0).map(([key, value]) => [key, normalizeJsonValue(value)]));
67337
+ }
67338
+ function compactMetricAttributes(attributes) {
67339
+ return Object.fromEntries(Object.entries(attributes).flatMap(([key, value]) => {
67340
+ if (value === void 0 || value === null) return [];
67341
+ if (typeof value === "string") return [[key, value]];
67342
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return [[key, String(value)]];
67343
+ return [];
67344
+ }));
67345
+ }
67346
+ function outcomeFromExit(exit) {
67347
+ if (require_Schema$1.isSuccess(exit)) return "success";
67348
+ return require_Schema$1.hasInterruptsOnly(exit.cause) ? "interrupt" : "failure";
67349
+ }
67350
+ function normalizeModelMetricLabel(model) {
67351
+ const normalized = model?.trim().toLowerCase();
67352
+ if (!normalized) return;
67353
+ if (normalized.includes("gpt")) return "gpt";
67354
+ if (normalized.includes("claude")) return "claude";
67355
+ if (normalized.includes("gemini")) return "gemini";
67356
+ return "other";
67357
+ }
67358
+ //#endregion
67359
+ //#region src/observability/Metrics.ts
67360
+ const rpcRequestsTotal = require_Schema$1.counter("t3_rpc_requests_total", { description: "Total RPC requests handled by the websocket RPC server." });
67361
+ const rpcRequestDuration = require_Schema$1.timer("t3_rpc_request_duration", { description: "RPC request handling duration." });
67362
+ const orchestrationCommandsTotal = require_Schema$1.counter("t3_orchestration_commands_total", { description: "Total orchestration commands dispatched." });
67363
+ const orchestrationCommandDuration = require_Schema$1.timer("t3_orchestration_command_duration", { description: "Orchestration command dispatch duration." });
67364
+ const orchestrationCommandAckDuration = require_Schema$1.timer("t3_orchestration_command_ack_duration", { description: "Time from orchestration command dispatch to the first committed domain event emitted for that command." });
67365
+ const orchestrationEventsProcessedTotal = require_Schema$1.counter("t3_orchestration_events_processed_total", { description: "Total orchestration intent events processed by runtime reactors." });
67366
+ const providerSessionsTotal = require_Schema$1.counter("t3_provider_sessions_total", { description: "Total provider session lifecycle operations." });
67367
+ const providerTurnsTotal = require_Schema$1.counter("t3_provider_turns_total", { description: "Total provider turn lifecycle operations." });
67368
+ const providerTurnDuration = require_Schema$1.timer("t3_provider_turn_duration", { description: "Provider turn request duration." });
67369
+ const providerRuntimeEventsTotal = require_Schema$1.counter("t3_provider_runtime_events_total", { description: "Total canonical provider runtime events processed." });
67370
+ const gitCommandsTotal = require_Schema$1.counter("t3_git_commands_total", { description: "Total git commands executed by the server runtime." });
67371
+ const gitCommandDuration = require_Schema$1.timer("t3_git_command_duration", { description: "Git command execution duration." });
67372
+ const terminalSessionsTotal = require_Schema$1.counter("t3_terminal_sessions_total", { description: "Total terminal sessions started." });
67373
+ const browserSessionsReapedTotal = require_Schema$1.counter("browser_sessions_reaped_total", { description: "Browser sessions and Chromium processes reclaimed by the orphan reaper, by reason (orphan | ttl | stale_profile)." });
67374
+ const terminalRestartsTotal = require_Schema$1.counter("t3_terminal_restarts_total", { description: "Total terminal restart requests handled." });
67375
+ const runtimeReplicationOutboxDepth = require_Schema$1.gauge("t3_runtime_replication_outbox_depth", { description: "Current runtime replication outbox items by durable state." });
67376
+ const runtimeReplicationOutboxOldestEventAge = require_Schema$1.gauge("t3_runtime_replication_outbox_oldest_event_age_seconds", { description: "Age in seconds of the oldest undelivered runtime replication payload." });
67377
+ const runtimeReplicationPublicationAttemptsTotal = require_Schema$1.counter("t3_runtime_replication_publication_attempts_total", { description: "Total canonical runtime replication publication attempts." });
67378
+ const runtimeReplicationPublicationRetriesTotal = require_Schema$1.counter("t3_runtime_replication_publication_retries_total", { description: "Total runtime replication retries scheduled after recoverable failures." });
67379
+ const runtimeReplicationPublicationLatency = require_Schema$1.timer("t3_runtime_replication_publication_latency", { description: "Time from durable enqueue to canonical CMMD acknowledgement." });
67380
+ const cmmdCredentialRefreshTotal = require_Schema$1.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)." });
67381
+ const cmmdRotationFenceAppliedTotal = require_Schema$1.counter("t3_cmmd_rotation_fence_applied_total", { description: "Sessions Forge refused to rotate CMMD credentials for, because the rotation fence applied." });
67382
+ const cmmdCredentialRowDeletedTotal = require_Schema$1.counter("t3_cmmd_credential_row_deleted_total", { description: "command_access_tokens rows actually deleted by clearUnrecoverableCmmdAuthForSession, by reason." });
67383
+ const metricAttributes = (attributes) => Object.entries(compactMetricAttributes(attributes));
67384
+ const increment = (metric, attributes, amount = 1) => require_Schema$1.update(require_Schema$1.withAttributes(metric, metricAttributes(attributes)), amount);
67385
+ const withMetricsImpl = (effect, options) => require_Schema$1.gen(function* () {
67386
+ const startedAt = Date.now();
67387
+ const exit$2 = yield* require_Schema$1.exit(effect);
67388
+ const duration = require_Schema$1.millis(Math.max(0, Date.now() - startedAt));
67389
+ const baseAttributes = typeof options.attributes === "function" ? options.attributes() : options.attributes ?? {};
67390
+ if (options.timer) yield* require_Schema$1.update(require_Schema$1.withAttributes(options.timer, metricAttributes(baseAttributes)), duration);
67391
+ if (options.counter) {
67392
+ const outcome = outcomeFromExit(exit$2);
67393
+ yield* require_Schema$1.update(require_Schema$1.withAttributes(options.counter, metricAttributes({
67394
+ ...baseAttributes,
67395
+ outcome,
67396
+ ...options.outcomeAttributes ? options.outcomeAttributes(outcome) : {}
67397
+ })), 1);
67398
+ }
67399
+ if (require_Schema$1.isSuccess(exit$2)) return exit$2.value;
67400
+ return yield* require_Schema$1.failCause(exit$2.cause);
67401
+ });
67402
+ const withMetrics = require_Schema$1.dual(2, withMetricsImpl);
67403
+ const providerMetricAttributes = (provider, extra) => compactMetricAttributes({
67404
+ provider,
67405
+ ...extra
67406
+ });
67407
+ const providerTurnMetricAttributes = (input) => {
67408
+ const modelFamily = normalizeModelMetricLabel(input.model);
67409
+ return compactMetricAttributes({
67410
+ provider: input.provider,
67411
+ ...modelFamily ? { modelFamily } : {},
67412
+ ...input.extra
67413
+ });
67414
+ };
67415
+ //#endregion
67263
67416
  //#region src/persistence/Errors.ts
67264
67417
  var PersistenceSqlError = class extends require_Schema$1.TaggedErrorClass()("PersistenceSqlError", {
67265
67418
  operation: require_Schema$1.String,
@@ -67950,8 +68103,14 @@ const scheduleExpiredForgeCloudOwnerCredentialMaintenance = require_Schema$1.sle
67950
68103
  *
67951
68104
  * Clearing every active owner row unconditionally (the pre-CMMD-1871 behaviour)
67952
68105
  * turned a single expired tab into a sign-out on every browser and device.
68106
+ *
68107
+ * Destroys a refresh token good for months, so every call is logged with a
68108
+ * `reason` naming the caller's own decision (never the credential itself) and
68109
+ * the actual row count deleted, at info level so it shows up without raising
68110
+ * the server's log level. `reason` is optional only so a caller that has not
68111
+ * been updated yet still compiles; every current caller passes one.
67953
68112
  */
67954
- const clearUnrecoverableCmmdAuthForSession = (sessionId) => require_Schema$1.gen(function* () {
68113
+ const clearUnrecoverableCmmdAuthForSession = (sessionId, reason) => require_Schema$1.gen(function* () {
67955
68114
  const sql = yield* require_SqlError.SqlClient;
67956
68115
  const rows = yield* sql`
67957
68116
  DELETE FROM command_access_tokens
@@ -67976,6 +68135,14 @@ const clearUnrecoverableCmmdAuthForSession = (sessionId) => require_Schema$1.gen
67976
68135
  `.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.clearUnrecoverableCmmdAuth:query")));
67977
68136
  forgetCommandAccessTokenCachesForSession(sessionId);
67978
68137
  for (const row of rows) forgetCommandAccessTokenCachesForSession(row.sessionId);
68138
+ if (rows.length > 0) {
68139
+ yield* require_Schema$1.logInfo("CMMD credential row deleted.", {
68140
+ sessionId,
68141
+ reason: reason ?? "unspecified",
68142
+ rowsDeleted: rows.length
68143
+ });
68144
+ yield* require_Schema$1.update(require_Schema$1.withAttributes(cmmdCredentialRowDeletedTotal, metricAttributes({ reason: reason ?? "unspecified" })), rows.length);
68145
+ }
67979
68146
  });
67980
68147
  /**
67981
68148
  * Retire refresh credentials copied into historical desktop bearer sessions.
@@ -68031,6 +68198,23 @@ const isDesktopBearerAuthSession = (sessionId) => require_Schema$1.gen(function*
68031
68198
  `.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.isDesktopBearerSession:query")));
68032
68199
  return rows[0]?.method === "bearer-session-token" && rows[0].clientDeviceType === "desktop";
68033
68200
  });
68201
+ /**
68202
+ * The device type a session's bearer bootstrap declared, for log lines only.
68203
+ *
68204
+ * Mirrors `isDesktopBearerAuthSession`'s read but returns the raw column
68205
+ * instead of collapsing it into a rotation verdict — a log line wants to say
68206
+ * what made the request, not whether Forge may rotate for it. Never fails:
68207
+ * an unreadable or missing row logs as `"unknown"` rather than losing the
68208
+ * decision-point log line it is attached to.
68209
+ */
68210
+ const readClientDeviceTypeForSession = (sessionId) => require_Schema$1.gen(function* () {
68211
+ return (yield* (yield* require_SqlError.SqlClient)`
68212
+ SELECT client_device_type AS "clientDeviceType"
68213
+ FROM auth_sessions
68214
+ WHERE session_id = ${sessionId}
68215
+ LIMIT 1
68216
+ `.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.readClientDeviceType:query"))))[0]?.clientDeviceType ?? "unknown";
68217
+ }).pipe(require_Schema$1.orElseSucceed(() => "unknown"));
68034
68218
  //#endregion
68035
68219
  //#region src/auth/http/oauthCookies.ts
68036
68220
  const OAUTH_STATE_COOKIE = "forge_oauth_state";
@@ -68233,9 +68417,21 @@ const authLogoutRouteLayer = add$1("POST", "/api/auth/logout", require_Schema$1.
68233
68417
  onSuccess: (session) => session.sessionId
68234
68418
  }));
68235
68419
  if (sessionId) {
68420
+ const deviceType = yield* readClientDeviceTypeForSession(sessionId);
68421
+ yield* require_Schema$1.logInfo("Auth logout.", {
68422
+ sessionId,
68423
+ deviceType,
68424
+ outcome: "session-cleared",
68425
+ reason: cloudShell ? "forge-cloud-shell-session" : "local-session"
68426
+ });
68236
68427
  if (!cloudShell) yield* sessions.revoke(sessionId).pipe(require_Schema$1.ignore({ log: false }));
68237
68428
  yield* clearCommandAccessTokenForSession(sessionId).pipe(require_Schema$1.ignore({ log: false }));
68238
- }
68429
+ } else yield* require_Schema$1.logInfo("Auth logout.", {
68430
+ sessionId: null,
68431
+ deviceType: "unknown",
68432
+ outcome: "no-op",
68433
+ reason: "no authenticated session presented"
68434
+ });
68239
68435
  const secure = shouldUseSecureCookie(forgePublicUrlForRequest(request));
68240
68436
  let response = yield* require_HttpServer.jsonUnsafe({ loggedOut: true }, { status: 200 }).pipe(require_HttpServer.setCookie(sessions.cookieName, "", {
68241
68437
  expires: /* @__PURE__ */ new Date(0),
@@ -68834,16 +69030,32 @@ const authCommandBearerBootstrapRouteLayer = add$1("POST", "/api/auth/bootstrap/
68834
69030
  if (limited) return limited;
68835
69031
  const serverAuth = yield* ServerAuth;
68836
69032
  const sessions = yield* SessionCredentialService;
68837
- if (serverAuth.isForgeCloudShell?.() === true) return yield* new AuthError({
68838
- message: "Forge Cloud accepts owner assertions from CMMD only.",
68839
- status: 403
68840
- });
69033
+ if (serverAuth.isForgeCloudShell?.() === true) {
69034
+ yield* require_Schema$1.logWarning("CMMD bearer bootstrap failed.", {
69035
+ deviceType: "unknown",
69036
+ outcome: "failed",
69037
+ reason: "forge-cloud-shell-rejects-non-cmmd-bootstrap"
69038
+ });
69039
+ return yield* new AuthError({
69040
+ message: "Forge Cloud accepts owner assertions from CMMD only.",
69041
+ status: 403
69042
+ });
69043
+ }
68841
69044
  const payload = yield* require_HttpServer.schemaBodyJson(CommandBootstrapInput).pipe(require_Schema$1.mapError((cause) => new AuthError({
68842
69045
  message: "Invalid CMMD OAuth bearer bootstrap payload.",
68843
69046
  status: 400,
68844
69047
  cause
69048
+ })), require_Schema$1.tapError(() => require_Schema$1.logWarning("CMMD bearer bootstrap failed.", {
69049
+ deviceType: "unknown",
69050
+ outcome: "failed",
69051
+ reason: "invalid-payload"
69052
+ })));
69053
+ const deviceType = payload.clientDeviceType ?? "desktop";
69054
+ const validated = yield* validateCommandBootstrapPayload(payload).pipe(require_Schema$1.tapError(() => require_Schema$1.logWarning("CMMD bearer bootstrap failed.", {
69055
+ deviceType,
69056
+ outcome: "failed",
69057
+ reason: "invalid-command-bootstrap-payload"
68845
69058
  })));
68846
- const validated = yield* validateCommandBootstrapPayload(payload);
68847
69059
  const sessionSubject = resolveCommandOAuthSubject(validated.subjectUserId);
68848
69060
  const issued = yield* sessions.issue({
68849
69061
  method: "bearer-session-token",
@@ -68856,20 +69068,34 @@ const authCommandBearerBootstrapRouteLayer = add$1("POST", "/api/auth/bootstrap/
68856
69068
  }).pipe(require_Schema$1.mapError((cause) => new AuthError({
68857
69069
  message: "Failed to issue authenticated bearer session.",
68858
69070
  cause
69071
+ })), require_Schema$1.tapError(() => require_Schema$1.logWarning("CMMD bearer bootstrap failed.", {
69072
+ deviceType,
69073
+ outcome: "failed",
69074
+ reason: "session-issuance-failed"
68859
69075
  })));
68860
69076
  const cmmdIdentity = yield* persistCommandBootstrapSessionArtifacts({
68861
69077
  sessionId: issued.sessionId,
68862
69078
  payload,
68863
69079
  cmmdIdentity: validated.cmmdIdentity
68864
69080
  });
68865
- if ((payload.clientDeviceType ?? "desktop") === "desktop" && !payload.refreshToken?.trim()) yield* clearDesktopBearerRefreshTokensForSubject({
69081
+ if (deviceType === "desktop" && !payload.refreshToken?.trim()) yield* clearDesktopBearerRefreshTokensForSubject({
68866
69082
  subject: sessionSubject,
68867
69083
  clientUserAgent: request.headers["user-agent"]
68868
69084
  }).pipe(require_Schema$1.mapError((cause) => new AuthError({
68869
69085
  message: "Failed to retire legacy desktop refresh credentials.",
68870
69086
  status: 500,
68871
69087
  cause
69088
+ })), require_Schema$1.tapError(() => require_Schema$1.logWarning("CMMD bearer bootstrap failed.", {
69089
+ sessionId: issued.sessionId,
69090
+ deviceType,
69091
+ outcome: "failed",
69092
+ reason: "legacy-desktop-refresh-retirement-failed"
68872
69093
  })));
69094
+ yield* require_Schema$1.logInfo("CMMD bearer bootstrap succeeded.", {
69095
+ sessionId: issued.sessionId,
69096
+ deviceType,
69097
+ outcome: "succeeded"
69098
+ });
68873
69099
  return require_HttpServer.jsonUnsafe({
68874
69100
  authenticated: true,
68875
69101
  role: issued.role,
@@ -70177,64 +70403,6 @@ function parseAttachmentIdFromRelativePath(relativePath) {
70177
70403
  return id.length > 0 && !id.includes(".") ? id : null;
70178
70404
  }
70179
70405
  //#endregion
70180
- //#region src/observability/Attributes.ts
70181
- function isPlainObject$1(value) {
70182
- return typeof value === "object" && value !== null && !Array.isArray(value);
70183
- }
70184
- function markSeen(value, seen) {
70185
- if (seen.has(value)) return true;
70186
- seen.add(value);
70187
- return false;
70188
- }
70189
- function normalizeJsonValue(value, seen = /* @__PURE__ */ new WeakSet()) {
70190
- if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value ?? null;
70191
- if (typeof value === "bigint") return value.toString();
70192
- if (value instanceof Date) return Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString();
70193
- if (value instanceof Error) return {
70194
- name: value.name,
70195
- message: value.message,
70196
- ...value.stack ? { stack: value.stack } : {}
70197
- };
70198
- if (Array.isArray(value)) {
70199
- if (markSeen(value, seen)) return "[Circular]";
70200
- return value.map((entry) => normalizeJsonValue(entry, seen));
70201
- }
70202
- if (value instanceof Map) {
70203
- if (markSeen(value, seen)) return "[Circular]";
70204
- return Object.fromEntries(Array.from(value.entries(), ([key, entryValue]) => [String(key), normalizeJsonValue(entryValue, seen)]));
70205
- }
70206
- if (value instanceof Set) {
70207
- if (markSeen(value, seen)) return "[Circular]";
70208
- return Array.from(value.values(), (entry) => normalizeJsonValue(entry, seen));
70209
- }
70210
- if (!isPlainObject$1(value)) return String(value);
70211
- if (markSeen(value, seen)) return "[Circular]";
70212
- return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, normalizeJsonValue(entryValue, seen)]));
70213
- }
70214
- function compactTraceAttributes(attributes) {
70215
- return Object.fromEntries(Object.entries(attributes).filter(([, value]) => value !== void 0).map(([key, value]) => [key, normalizeJsonValue(value)]));
70216
- }
70217
- function compactMetricAttributes(attributes) {
70218
- return Object.fromEntries(Object.entries(attributes).flatMap(([key, value]) => {
70219
- if (value === void 0 || value === null) return [];
70220
- if (typeof value === "string") return [[key, value]];
70221
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return [[key, String(value)]];
70222
- return [];
70223
- }));
70224
- }
70225
- function outcomeFromExit(exit) {
70226
- if (require_Schema$1.isSuccess(exit)) return "success";
70227
- return require_Schema$1.hasInterruptsOnly(exit.cause) ? "interrupt" : "failure";
70228
- }
70229
- function normalizeModelMetricLabel(model) {
70230
- const normalized = model?.trim().toLowerCase();
70231
- if (!normalized) return;
70232
- if (normalized.includes("gpt")) return "gpt";
70233
- if (normalized.includes("claude")) return "claude";
70234
- if (normalized.includes("gemini")) return "gemini";
70235
- return "other";
70236
- }
70237
- //#endregion
70238
70406
  //#region src/observability/TraceRecord.ts
70239
70407
  function formatTraceExit(exit) {
70240
70408
  if (require_Schema$1.isSuccess(exit)) return { _tag: "Success" };
@@ -71297,6 +71465,22 @@ async function readOAuthErrorCode(response) {
71297
71465
  //#endregion
71298
71466
  //#region src/command/cmmdRotationFence.ts
71299
71467
  /**
71468
+ * Log and count a fence application, without changing the verdict it reports.
71469
+ *
71470
+ * The two facts named in the log line are exactly the ones the predicate
71471
+ * above the caller already computed — whether this session ever deposited a
71472
+ * refresh token of its own, and (only when that was false) whether the
71473
+ * session row reads as a desktop bearer session. Neither fact nor the log
71474
+ * line changes what the caller returns.
71475
+ */
71476
+ function logRotationFenceApplied(input) {
71477
+ return require_Schema$1.logInfo("CMMD rotation fence applied: session may not rotate its own credential.", {
71478
+ sessionId: input.sessionId,
71479
+ hasStoredRefreshToken: input.hasStoredRefreshToken,
71480
+ isDesktopBearerSession: input.isDesktopBearerSession
71481
+ }).pipe(require_Schema$1.andThen(() => require_Schema$1.update(require_Schema$1.withAttributes(cmmdRotationFenceAppliedTotal, metricAttributes({})), 1)));
71482
+ }
71483
+ /**
71300
71484
  * True when Forge must not rotate this session's CMMD credentials.
71301
71485
  *
71302
71486
  * Fails closed: if the session row cannot be read, assume the fence applies.
@@ -71304,8 +71488,22 @@ async function readOAuthErrorCode(response) {
71304
71488
  * reconnect.
71305
71489
  */
71306
71490
  function isRotationFencedSession(sessionId) {
71307
- if (readCommandRefreshTokenForSession(sessionId)) return require_Schema$1.succeed(false);
71308
- return isDesktopBearerAuthSession(sessionId).pipe(require_Schema$1.orElseSucceed(() => true));
71491
+ const hasStoredRefreshToken = readCommandRefreshTokenForSession(sessionId) !== null;
71492
+ if (hasStoredRefreshToken) return require_Schema$1.succeed(false);
71493
+ return isDesktopBearerAuthSession(sessionId).pipe(require_Schema$1.match({
71494
+ onFailure: () => ({
71495
+ fenced: true,
71496
+ isDesktopBearerSession: null
71497
+ }),
71498
+ onSuccess: (isDesktopBearerSession) => ({
71499
+ fenced: isDesktopBearerSession,
71500
+ isDesktopBearerSession
71501
+ })
71502
+ }), require_Schema$1.tap(({ fenced, isDesktopBearerSession }) => fenced ? logRotationFenceApplied({
71503
+ sessionId,
71504
+ hasStoredRefreshToken,
71505
+ isDesktopBearerSession
71506
+ }) : require_Schema$1.void_), require_Schema$1.map(({ fenced }) => fenced));
71309
71507
  }
71310
71508
  //#endregion
71311
71509
  //#region src/cmmdConnection/cmmdCredentialRejection.ts
@@ -71342,7 +71540,7 @@ function forgetCmmdCredentialRejection(sessionId) {
71342
71540
  */
71343
71541
  const clearCmmdAuthAfterCmmdRejection = (sessionId) => require_Schema$1.gen(function* () {
71344
71542
  if (!hasCmmdCredentialRejection(sessionId)) return;
71345
- yield* clearUnrecoverableCmmdAuthForSession(sessionId);
71543
+ yield* clearUnrecoverableCmmdAuthForSession(sessionId, "cmmd-rejected-credential");
71346
71544
  forgetCmmdCredentialRejection(sessionId);
71347
71545
  });
71348
71546
  //#endregion
@@ -71353,6 +71551,23 @@ function usableCmmdUserJwt(token) {
71353
71551
  function storeResolvedJwt(sessionId, accessToken, refreshToken) {
71354
71552
  return setCommandJwtForSession(sessionId, accessToken, refreshToken ? { refreshToken } : {}).pipe(require_Schema$1.ignore({ log: true }));
71355
71553
  }
71554
+ function logCmmdJwtRung(context, rung, outcome, reason) {
71555
+ return (outcome === "failed" ? require_Schema$1.logWarning : require_Schema$1.logInfo)("CMMD JWT resolver rung.", {
71556
+ sessionId: context.sessionId,
71557
+ subject: context.subject,
71558
+ deviceType: context.deviceType,
71559
+ caller: context.caller,
71560
+ rung,
71561
+ outcome,
71562
+ reason
71563
+ });
71564
+ }
71565
+ function recordCmmdCredentialRefreshMetric(rung, outcome) {
71566
+ return require_Schema$1.update(require_Schema$1.withAttributes(cmmdCredentialRefreshTotal, metricAttributes({
71567
+ rung,
71568
+ outcome
71569
+ })), 1);
71570
+ }
71356
71571
  const inFlightRefreshes = /* @__PURE__ */ new Map();
71357
71572
  function refreshOnceForSession(sessionId, refreshToken) {
71358
71573
  return require_Schema$1.promise(() => {
@@ -71374,15 +71589,21 @@ function refreshOnceForSession(sessionId, refreshToken) {
71374
71589
  * sibling session's token silently invalidates that sibling's stored copy and
71375
71590
  * signs it out.
71376
71591
  */
71377
- function trySelfRefresh(sessionId, logPrefix) {
71592
+ function trySelfRefresh(sessionId, context) {
71378
71593
  const currentRefreshToken = readCommandRefreshTokenForSession(sessionId);
71379
- if (!currentRefreshToken) return require_Schema$1.succeed(null);
71594
+ if (!currentRefreshToken) return logCmmdJwtRung(context, "self-refresh", "skipped", "no refresh token stored for this session").pipe(require_Schema$1.as(null));
71380
71595
  return require_Schema$1.gen(function* () {
71596
+ yield* logCmmdJwtRung(context, "self-refresh", "attempted", "rotating this session's own stored refresh token");
71597
+ yield* recordCmmdCredentialRefreshMetric("self-refresh", "attempted");
71381
71598
  const refreshed = yield* refreshOnceForSession(sessionId, currentRefreshToken);
71382
71599
  const accessToken = usableCmmdUserJwt(refreshed?.accessToken);
71383
- if (!accessToken) return null;
71600
+ if (!accessToken) {
71601
+ yield* logCmmdJwtRung(context, "self-refresh", "failed", "CMMD refresh did not return a usable user access JWT");
71602
+ return null;
71603
+ }
71384
71604
  yield* storeResolvedJwt(sessionId, accessToken, refreshed?.refreshToken);
71385
- console.log(`[${logPrefix}] CMMD JWT refreshed for session ${sessionId}`);
71605
+ yield* recordCmmdCredentialRefreshMetric("self-refresh", "succeeded");
71606
+ yield* logCmmdJwtRung(context, "self-refresh", "succeeded", "CMMD rotated the stored refresh token");
71386
71607
  return accessToken;
71387
71608
  });
71388
71609
  }
@@ -71399,16 +71620,22 @@ function trySelfRefresh(sessionId, logPrefix) {
71399
71620
  * CMMD owns its lifetime, and a transient network failure must not throw away
71400
71621
  * the one credential that can still recover the session.
71401
71622
  */
71402
- function tryGrantRedemption(sessionId, logPrefix) {
71623
+ function tryGrantRedemption(sessionId, context) {
71403
71624
  const grantToken = readCommandSessionGrantForSession(sessionId);
71404
- if (!grantToken) return require_Schema$1.succeed(null);
71625
+ if (!grantToken) return logCmmdJwtRung(context, "grant-redemption", "skipped", "no session grant stored for this session").pipe(require_Schema$1.as(null));
71405
71626
  return require_Schema$1.gen(function* () {
71627
+ yield* logCmmdJwtRung(context, "grant-redemption", "attempted", "redeeming this session's durable CMMD session grant");
71628
+ yield* recordCmmdCredentialRefreshMetric("grant-redemption", "attempted");
71406
71629
  const redeemed = yield* require_Schema$1.promise(() => redeemCmmdSessionGrant(grantToken)).pipe(require_Schema$1.orElseSucceed(() => null));
71407
71630
  const accessToken = usableCmmdUserJwt(redeemed?.accessToken);
71408
- if (!accessToken) return null;
71631
+ if (!accessToken) {
71632
+ yield* logCmmdJwtRung(context, "grant-redemption", "failed", "session grant redemption did not return a usable user access JWT");
71633
+ return null;
71634
+ }
71409
71635
  if (redeemed?.mcpToken) yield* setCommandAccessTokenForSession(sessionId, redeemed.mcpToken).pipe(require_Schema$1.ignore({ log: true }));
71410
71636
  yield* storeResolvedJwt(sessionId, accessToken, redeemed?.refreshToken);
71411
- console.log(`[${logPrefix}] CMMD credentials recovered via session grant for ${sessionId}`);
71637
+ yield* recordCmmdCredentialRefreshMetric("grant-redemption", "succeeded");
71638
+ yield* logCmmdJwtRung(context, "grant-redemption", "succeeded", "session grant redeemed");
71412
71639
  return accessToken;
71413
71640
  });
71414
71641
  }
@@ -71419,23 +71646,28 @@ function tryGrantRedemption(sessionId, logPrefix) {
71419
71646
  * fallback for when that rotation is already gone. Neither touches another
71420
71647
  * session's credentials, so this ladder is safe to run from any path.
71421
71648
  */
71422
- function tryOwnCredentialRecovery(sessionId, logPrefix) {
71649
+ function tryOwnCredentialRecovery(sessionId, context) {
71423
71650
  return require_Schema$1.gen(function* () {
71424
- const refreshed = yield* trySelfRefresh(sessionId, logPrefix);
71651
+ const refreshed = yield* trySelfRefresh(sessionId, context);
71425
71652
  if (refreshed) return refreshed;
71426
- return yield* tryGrantRedemption(sessionId, logPrefix);
71653
+ return yield* tryGrantRedemption(sessionId, context);
71427
71654
  });
71428
71655
  }
71429
- function tryPromoteBootstrap(sessionId, subject, forceRefresh) {
71430
- if (subject !== "owner-bootstrap" && subject !== "desktop-bootstrap") return require_Schema$1.succeed(null);
71656
+ function tryPromoteBootstrap(sessionId, subject, forceRefresh, context) {
71657
+ if (subject !== "owner-bootstrap" && subject !== "desktop-bootstrap") return logCmmdJwtRung(context, "promote-bootstrap", "skipped", "subject is not a bootstrap subject").pipe(require_Schema$1.as(null));
71431
71658
  return require_Schema$1.gen(function* () {
71659
+ yield* logCmmdJwtRung(context, "promote-bootstrap", "attempted", "promoting the most recent owner CMMD auth to this bootstrap session");
71432
71660
  yield* promoteMostRecentOwnerCommandAuthToSession(sessionId).pipe(require_Schema$1.catch_((error) => require_Schema$1.logWarning("Failed to promote owner CMMD auth for REST JWT resolution.", {
71433
71661
  cause: error,
71434
71662
  sessionId,
71435
71663
  subject
71436
71664
  })));
71437
71665
  const promotedJwt = usableCmmdUserJwt(readCommandJwtForSession(sessionId));
71438
- if (!forceRefresh && promotedJwt && !isJwtExpiredForSession(sessionId)) return promotedJwt;
71666
+ if (!forceRefresh && promotedJwt && !isJwtExpiredForSession(sessionId)) {
71667
+ yield* logCmmdJwtRung(context, "promote-bootstrap", "succeeded", "promoted owner JWT is usable");
71668
+ return promotedJwt;
71669
+ }
71670
+ 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");
71439
71671
  return null;
71440
71672
  });
71441
71673
  }
@@ -71448,17 +71680,50 @@ function tryPromoteBootstrap(sessionId, subject, forceRefresh) {
71448
71680
  * must stay private to the session it was issued to (see `trySelfRefresh`).
71449
71681
  * Copying it is what let one expired tab sign the user out everywhere.
71450
71682
  */
71451
- function trySubjectFallback(sessionId, subject, logPrefix) {
71683
+ function trySubjectFallback(sessionId, subject, context) {
71452
71684
  return require_Schema$1.gen(function* () {
71453
- const fallback = yield* readMostRecentJwtForSubject(subject).pipe(require_Schema$1.orElseSucceed(() => null));
71454
- const fallbackJwt = usableCmmdUserJwt(fallback?.jwt);
71455
- if (!fallbackJwt) return null;
71685
+ yield* logCmmdJwtRung(context, "subject-fallback", "attempted", "looking for a sibling session's still-valid JWT for this subject");
71686
+ const fallbackJwt = usableCmmdUserJwt((yield* readMostRecentJwtForSubject(subject).pipe(require_Schema$1.orElseSucceed(() => null)))?.jwt);
71687
+ if (!fallbackJwt) {
71688
+ yield* logCmmdJwtRung(context, "subject-fallback", "failed", "no sibling session holds a still-valid JWT for this subject");
71689
+ return null;
71690
+ }
71456
71691
  yield* storeResolvedJwt(sessionId, fallbackJwt, null);
71457
- console.log(`[${logPrefix}] JWT subject-fallback promoted for session ${sessionId} (owner: ${fallback?.sessionId})`);
71692
+ yield* logCmmdJwtRung(context, "subject-fallback", "succeeded", "borrowed a sibling session's still-valid JWT");
71458
71693
  return fallbackJwt;
71459
71694
  });
71460
71695
  }
71461
71696
  /**
71697
+ * The rungs below the rotation fence: forced own-credential recovery,
71698
+ * bootstrap promotion, subject fallback, then own-credential recovery again
71699
+ * as the last resort. Split out of `resolveCmmdJwtForSession` so each half of
71700
+ * the ladder stays independently readable (and independently testable) —
71701
+ * the fence check above is the one decision the split does not touch.
71702
+ */
71703
+ function runRecoveryLadder(sessionId, normalizedSubject, forceRefresh, context) {
71704
+ return require_Schema$1.gen(function* () {
71705
+ if (forceRefresh) {
71706
+ const forced = yield* tryOwnCredentialRecovery(sessionId, context);
71707
+ if (forced) return forced;
71708
+ }
71709
+ if (normalizedSubject) {
71710
+ const promoted = yield* tryPromoteBootstrap(sessionId, normalizedSubject, forceRefresh, context);
71711
+ if (promoted) return promoted;
71712
+ }
71713
+ if (!normalizedSubject) {
71714
+ yield* logCmmdJwtRung(context, "cache", "failed", "no subject to resolve and forced recovery already exhausted");
71715
+ return null;
71716
+ }
71717
+ if (!forceRefresh) {
71718
+ const fallback = yield* trySubjectFallback(sessionId, normalizedSubject, context);
71719
+ if (fallback) return fallback;
71720
+ }
71721
+ const recovered = yield* tryOwnCredentialRecovery(sessionId, context);
71722
+ if (!recovered) yield* logCmmdJwtRung(context, "cache", "failed", "recovery ladder exhausted: no rung produced a usable JWT");
71723
+ return recovered;
71724
+ });
71725
+ }
71726
+ /**
71462
71727
  * Resolve the CMMD REST JWT for a Forge session.
71463
71728
  *
71464
71729
  * Forge session auth is long-lived compared with the short-lived CMMD JWT.
@@ -71472,26 +71737,30 @@ function resolveCmmdJwtForSession({ sessionId, subject, logPrefix = "cmmd-auth",
71472
71737
  const cached = usableCmmdUserJwt(readCommandJwtForSession(sessionId));
71473
71738
  if (cached && !isJwtExpiredForSession(sessionId)) {
71474
71739
  if (!forceRefresh) return require_Schema$1.succeed(cached);
71475
- if (staleAccessToken && cached !== staleAccessToken) return require_Schema$1.succeed(cached);
71740
+ if (staleAccessToken && cached !== staleAccessToken) return require_Schema$1.succeed(cached).pipe(require_Schema$1.tap(() => require_Schema$1.gen(function* () {
71741
+ const deviceType = yield* readClientDeviceTypeForSession(sessionId);
71742
+ yield* logCmmdJwtRung({
71743
+ sessionId,
71744
+ subject: subject?.trim() || null,
71745
+ deviceType,
71746
+ caller: logPrefix
71747
+ }, "cache", "skipped", "forced refresh skipped: cache already holds a token another caller rotated");
71748
+ })));
71476
71749
  }
71477
71750
  const normalizedSubject = subject?.trim() || null;
71478
71751
  if (!normalizedSubject && !forceRefresh) return require_Schema$1.succeed(null);
71479
71752
  return require_Schema$1.gen(function* () {
71480
- if (yield* isRotationFencedSession(sessionId)) return null;
71481
- if (forceRefresh) {
71482
- const forced = yield* tryOwnCredentialRecovery(sessionId, logPrefix);
71483
- if (forced) return forced;
71484
- }
71485
- if (normalizedSubject) {
71486
- const promoted = yield* tryPromoteBootstrap(sessionId, normalizedSubject, forceRefresh);
71487
- if (promoted) return promoted;
71488
- }
71489
- if (!normalizedSubject) return null;
71490
- if (!forceRefresh) {
71491
- const fallback = yield* trySubjectFallback(sessionId, normalizedSubject, logPrefix);
71492
- if (fallback) return fallback;
71753
+ const context = {
71754
+ sessionId,
71755
+ subject: normalizedSubject,
71756
+ deviceType: yield* readClientDeviceTypeForSession(sessionId),
71757
+ caller: logPrefix
71758
+ };
71759
+ if (yield* isRotationFencedSession(sessionId)) {
71760
+ yield* logCmmdJwtRung(context, "rotation-fence", "failed", "session is fenced from rotating CMMD credentials");
71761
+ return null;
71493
71762
  }
71494
- return yield* tryOwnCredentialRecovery(sessionId, logPrefix);
71763
+ return yield* runRecoveryLadder(sessionId, normalizedSubject, forceRefresh, context);
71495
71764
  });
71496
71765
  }
71497
71766
  //#endregion
@@ -72228,9 +72497,14 @@ function buildProxyErrorResponse(error) {
72228
72497
  * warning rather than failing the request if the clear itself errors — a
72229
72498
  * session that already has no usable credential must still get its 401,
72230
72499
  * not a 500 from a failed best-effort cleanup.
72500
+ *
72501
+ * Reached only after CMMD itself rejected the request with both the original
72502
+ * and (if retried) the refreshed JWT, so — unlike `respondCmmdConnectionRequired`
72503
+ * below — this always deletes the row. `clearUnrecoverableCmmdAuthForSession`
72504
+ * logs the row count and the reason itself; this only names the reason.
72231
72505
  */
72232
72506
  function clearCmmdAuthAfterFailure(sessionId) {
72233
- return clearUnrecoverableCmmdAuthForSession(sessionId).pipe(require_Schema$1.catch_((error) => require_Schema$1.logWarning("Failed to clear unrecoverable CMMD auth after CMMD auth failure.", {
72507
+ return clearUnrecoverableCmmdAuthForSession(sessionId, "cmmd-proxy-auth-error").pipe(require_Schema$1.catch_((error) => require_Schema$1.logWarning("Failed to clear unrecoverable CMMD auth after CMMD auth failure.", {
72234
72508
  cause: error,
72235
72509
  sessionId
72236
72510
  })));
@@ -72251,7 +72525,7 @@ function clearCmmdAuthAfterFailure(sessionId) {
72251
72525
  * refused the credential, which `clearCmmdAuthAfterCmmdRejection` checks.
72252
72526
  */
72253
72527
  function respondCmmdConnectionRequired(sessionId) {
72254
- return clearCmmdAuthAfterCmmdRejection(sessionId).pipe(require_Schema$1.catch_((error) => require_Schema$1.logWarning("Failed to clear CMMD auth that CMMD itself rejected.", {
72528
+ return require_Schema$1.logInfo("CMMD proxy request answered CMMD_AUTH_REQUIRED.", { sessionId }).pipe(require_Schema$1.andThen(() => clearCmmdAuthAfterCmmdRejection(sessionId)), require_Schema$1.catch_((error) => require_Schema$1.logWarning("Failed to clear CMMD auth that CMMD itself rejected.", {
72255
72529
  cause: error,
72256
72530
  sessionId
72257
72531
  })), require_Schema$1.as(buildProxyErrorResponse(new CmmdProxyAuthenticationRequiredError("CMMD connection required. Reconnect CMMD to continue."))));
@@ -95034,60 +95308,6 @@ const dispatchBootstrapTurnStart = (input, command) => require_Schema$1.gen(func
95034
95308
  */
95035
95309
  var OrchestrationEngineService = class extends require_Schema$1.Service()("forge/orchestration/Services/OrchestrationEngine/OrchestrationEngineService") {};
95036
95310
  //#endregion
95037
- //#region src/observability/Metrics.ts
95038
- const rpcRequestsTotal = require_Schema$1.counter("t3_rpc_requests_total", { description: "Total RPC requests handled by the websocket RPC server." });
95039
- const rpcRequestDuration = require_Schema$1.timer("t3_rpc_request_duration", { description: "RPC request handling duration." });
95040
- const orchestrationCommandsTotal = require_Schema$1.counter("t3_orchestration_commands_total", { description: "Total orchestration commands dispatched." });
95041
- const orchestrationCommandDuration = require_Schema$1.timer("t3_orchestration_command_duration", { description: "Orchestration command dispatch duration." });
95042
- const orchestrationCommandAckDuration = require_Schema$1.timer("t3_orchestration_command_ack_duration", { description: "Time from orchestration command dispatch to the first committed domain event emitted for that command." });
95043
- const orchestrationEventsProcessedTotal = require_Schema$1.counter("t3_orchestration_events_processed_total", { description: "Total orchestration intent events processed by runtime reactors." });
95044
- const providerSessionsTotal = require_Schema$1.counter("t3_provider_sessions_total", { description: "Total provider session lifecycle operations." });
95045
- const providerTurnsTotal = require_Schema$1.counter("t3_provider_turns_total", { description: "Total provider turn lifecycle operations." });
95046
- const providerTurnDuration = require_Schema$1.timer("t3_provider_turn_duration", { description: "Provider turn request duration." });
95047
- const providerRuntimeEventsTotal = require_Schema$1.counter("t3_provider_runtime_events_total", { description: "Total canonical provider runtime events processed." });
95048
- const gitCommandsTotal = require_Schema$1.counter("t3_git_commands_total", { description: "Total git commands executed by the server runtime." });
95049
- const gitCommandDuration = require_Schema$1.timer("t3_git_command_duration", { description: "Git command execution duration." });
95050
- const terminalSessionsTotal = require_Schema$1.counter("t3_terminal_sessions_total", { description: "Total terminal sessions started." });
95051
- const browserSessionsReapedTotal = require_Schema$1.counter("browser_sessions_reaped_total", { description: "Browser sessions and Chromium processes reclaimed by the orphan reaper, by reason (orphan | ttl | stale_profile)." });
95052
- const terminalRestartsTotal = require_Schema$1.counter("t3_terminal_restarts_total", { description: "Total terminal restart requests handled." });
95053
- const runtimeReplicationOutboxDepth = require_Schema$1.gauge("t3_runtime_replication_outbox_depth", { description: "Current runtime replication outbox items by durable state." });
95054
- const runtimeReplicationOutboxOldestEventAge = require_Schema$1.gauge("t3_runtime_replication_outbox_oldest_event_age_seconds", { description: "Age in seconds of the oldest undelivered runtime replication payload." });
95055
- const runtimeReplicationPublicationAttemptsTotal = require_Schema$1.counter("t3_runtime_replication_publication_attempts_total", { description: "Total canonical runtime replication publication attempts." });
95056
- const runtimeReplicationPublicationRetriesTotal = require_Schema$1.counter("t3_runtime_replication_publication_retries_total", { description: "Total runtime replication retries scheduled after recoverable failures." });
95057
- const runtimeReplicationPublicationLatency = require_Schema$1.timer("t3_runtime_replication_publication_latency", { description: "Time from durable enqueue to canonical CMMD acknowledgement." });
95058
- const metricAttributes = (attributes) => Object.entries(compactMetricAttributes(attributes));
95059
- const increment = (metric, attributes, amount = 1) => require_Schema$1.update(require_Schema$1.withAttributes(metric, metricAttributes(attributes)), amount);
95060
- const withMetricsImpl = (effect, options) => require_Schema$1.gen(function* () {
95061
- const startedAt = Date.now();
95062
- const exit$2 = yield* require_Schema$1.exit(effect);
95063
- const duration = require_Schema$1.millis(Math.max(0, Date.now() - startedAt));
95064
- const baseAttributes = typeof options.attributes === "function" ? options.attributes() : options.attributes ?? {};
95065
- if (options.timer) yield* require_Schema$1.update(require_Schema$1.withAttributes(options.timer, metricAttributes(baseAttributes)), duration);
95066
- if (options.counter) {
95067
- const outcome = outcomeFromExit(exit$2);
95068
- yield* require_Schema$1.update(require_Schema$1.withAttributes(options.counter, metricAttributes({
95069
- ...baseAttributes,
95070
- outcome,
95071
- ...options.outcomeAttributes ? options.outcomeAttributes(outcome) : {}
95072
- })), 1);
95073
- }
95074
- if (require_Schema$1.isSuccess(exit$2)) return exit$2.value;
95075
- return yield* require_Schema$1.failCause(exit$2.cause);
95076
- });
95077
- const withMetrics = require_Schema$1.dual(2, withMetricsImpl);
95078
- const providerMetricAttributes = (provider, extra) => compactMetricAttributes({
95079
- provider,
95080
- ...extra
95081
- });
95082
- const providerTurnMetricAttributes = (input) => {
95083
- const modelFamily = normalizeModelMetricLabel(input.model);
95084
- return compactMetricAttributes({
95085
- provider: input.provider,
95086
- ...modelFamily ? { modelFamily } : {},
95087
- ...input.extra
95088
- });
95089
- };
95090
- //#endregion
95091
95311
  //#region src/observability/RpcInstrumentation.ts
95092
95312
  const annotateRpcSpan = (method, traceAttributes) => require_Schema$1.annotateCurrentSpan({
95093
95313
  "rpc.method": method,
@@ -98299,6 +98519,28 @@ function serializeWorktreeLaunchActivity(activity) {
98299
98519
  function decodeWorktreeLaunchActivityKind(kind) {
98300
98520
  return isCmmdRuntimeWorktreeLaunchActivityKind(kind) ? kind : null;
98301
98521
  }
98522
+ function planStepsOf(activity) {
98523
+ const decoded = decodeCmmdRuntimePlanActivity(encodeCmmdRuntimePlanActivityPayload(activity.kind, activity.payload));
98524
+ return decoded !== null && decoded.plan.length > 0 ? decoded.plan : null;
98525
+ }
98526
+ function serializePlanActivity(activity) {
98527
+ if (activity.kind !== "turn.plan.updated") return null;
98528
+ const plan = planStepsOf(activity);
98529
+ if (plan === null) return null;
98530
+ const completed = plan.filter((step) => step.status === "completed").length;
98531
+ return {
98532
+ sourceSequence: null,
98533
+ event: {
98534
+ eventId: `activity:${activity.id}`,
98535
+ occurredAt: canonicalRuntimeEventTimestamp(activity.createdAt),
98536
+ ...eventTurn(activity.turnId),
98537
+ kind: "tool_call",
98538
+ summary: `Plan: ${completed} of ${plan.length} steps done`,
98539
+ status: "completed",
98540
+ payload: encodeCmmdRuntimePlanActivityPayload(activity.kind, activity.payload)
98541
+ }
98542
+ };
98543
+ }
98302
98544
  function isReplicatedFailureActivity(activity) {
98303
98545
  return activity.kind === "runtime.error" || activity.kind.startsWith("provider.") && activity.kind.endsWith(".failed");
98304
98546
  }
@@ -98683,6 +98925,10 @@ function serializeRuntimeThreadEvents(thread) {
98683
98925
  const event = serializeWorktreeLaunchActivity(activity);
98684
98926
  return event === null ? [] : [event];
98685
98927
  });
98928
+ const planActivities = thread.activities.flatMap((activity) => {
98929
+ const event = serializePlanActivity(activity);
98930
+ return event === null ? [] : [event];
98931
+ });
98686
98932
  const recordedTurns = serializeRecordedTurns(thread);
98687
98933
  const textGenerations = serializeTextGenerations(thread);
98688
98934
  return [
@@ -98690,6 +98936,7 @@ function serializeRuntimeThreadEvents(thread) {
98690
98936
  ...replicaActivities,
98691
98937
  ...toolActivities,
98692
98938
  ...worktreeLaunchActivities,
98939
+ ...planActivities,
98693
98940
  ...failureActivities,
98694
98941
  ...recordedTurns,
98695
98942
  ...textGenerations
@@ -215938,6 +216185,7 @@ function buildClaudeSessionContext(input) {
215938
216185
  pendingUserInputs: input.pendingUserInputs,
215939
216186
  turns: [],
215940
216187
  inFlightTools: input.inFlightTools,
216188
+ taskPlan: makeClaudeTaskPlanState(),
215941
216189
  turnState: void 0,
215942
216190
  lastKnownContextWindow: void 0,
215943
216191
  lastKnownTokenUsage: void 0,
@@ -216447,6 +216695,7 @@ function classifyToolItemType(toolName) {
216447
216695
  if (normalized.includes("agent")) return "collab_agent_tool_call";
216448
216696
  if (normalized === "task" || normalized === "agent" || normalized.includes("subagent") || normalized.includes("sub-agent")) return "collab_agent_tool_call";
216449
216697
  if (normalized.includes("bash") || normalized.includes("command") || normalized.includes("shell") || normalized.includes("terminal")) return "command_execution";
216698
+ if (isTaskBookkeepingTool(normalized)) return "dynamic_tool_call";
216450
216699
  if (normalized.includes("edit") || normalized.includes("write") || normalized.includes("file") || normalized.includes("patch") || normalized.includes("replace") || normalized.includes("create") || normalized.includes("delete")) return "file_change";
216451
216700
  if (normalized.includes("browser")) return "browser_automation";
216452
216701
  if (normalized.includes("mcp")) return "mcp_tool_call";
@@ -216466,6 +216715,88 @@ function classifyRequestType(toolName) {
216466
216715
  function isTodoTool(toolName) {
216467
216716
  return toolName.toLowerCase().includes("todowrite");
216468
216717
  }
216718
+ function makeClaudeTaskPlanState() {
216719
+ return {
216720
+ entries: [],
216721
+ createsByToolUseId: /* @__PURE__ */ new Map()
216722
+ };
216723
+ }
216724
+ function taskPlanToolFor(toolName) {
216725
+ const normalized = toolName.toLowerCase();
216726
+ if (normalized === "taskcreate") return "TaskCreate";
216727
+ if (normalized === "taskupdate") return "TaskUpdate";
216728
+ }
216729
+ /** Task bookkeeping tools (create, update, list, get) are not file changes. */
216730
+ function isTaskBookkeepingTool(normalizedToolName) {
216731
+ return /^task(create|update|list|get)$/.test(normalizedToolName);
216732
+ }
216733
+ function taskPlanStatus(value) {
216734
+ return value === "completed" ? "completed" : value === "in_progress" ? "inProgress" : "pending";
216735
+ }
216736
+ function taskPlanSnapshot(state) {
216737
+ return state.entries.map((entry) => ({
216738
+ step: entry.step,
216739
+ status: entry.status
216740
+ }));
216741
+ }
216742
+ /**
216743
+ * Applies one TaskCreate or TaskUpdate input to the session's task list and
216744
+ * returns the plan to publish, or null when the input carries nothing yet.
216745
+ * Input JSON streams in pieces, so the same tool use can arrive more than once;
216746
+ * creates are keyed by tool use id so a repeat updates the entry in place.
216747
+ */
216748
+ function applyTaskPlanToolInput(state, tool, toolUseId, input) {
216749
+ if (tool === "TaskCreate") {
216750
+ const subject = typeof input.subject === "string" ? input.subject.trim() : "";
216751
+ const existing = state.createsByToolUseId.get(toolUseId);
216752
+ if (existing) {
216753
+ if (subject.length > 0) existing.step = subject;
216754
+ return taskPlanSnapshot(state);
216755
+ }
216756
+ const entry = {
216757
+ id: void 0,
216758
+ step: subject.length > 0 ? subject : "Task",
216759
+ status: taskPlanStatus(input.status)
216760
+ };
216761
+ state.entries.push(entry);
216762
+ state.createsByToolUseId.set(toolUseId, entry);
216763
+ return taskPlanSnapshot(state);
216764
+ }
216765
+ const taskId = typeof input.taskId === "string" ? input.taskId.trim() : typeof input.taskId === "number" ? String(input.taskId) : "";
216766
+ if (taskId.length === 0) return null;
216767
+ let entry = state.entries.find((candidate) => candidate.id === taskId);
216768
+ if (!entry) {
216769
+ const position = Number.parseInt(taskId, 10);
216770
+ const candidate = Number.isInteger(position) ? state.entries[position - 1] : void 0;
216771
+ if (candidate && candidate.id === void 0) {
216772
+ candidate.id = taskId;
216773
+ entry = candidate;
216774
+ }
216775
+ }
216776
+ if (input.status === "deleted") {
216777
+ if (entry) state.entries.splice(state.entries.indexOf(entry), 1);
216778
+ return taskPlanSnapshot(state);
216779
+ }
216780
+ if (!entry) {
216781
+ entry = {
216782
+ id: taskId,
216783
+ step: `Task #${taskId}`,
216784
+ status: "pending"
216785
+ };
216786
+ state.entries.push(entry);
216787
+ }
216788
+ if (typeof input.subject === "string" && input.subject.trim().length > 0) entry.step = input.subject.trim();
216789
+ if (input.status !== void 0) entry.status = taskPlanStatus(input.status);
216790
+ return taskPlanSnapshot(state);
216791
+ }
216792
+ const TASK_CREATE_RESULT_ID = /Task #(\S+) created/;
216793
+ /** Binds the id Claude Code assigned in the TaskCreate result to the entry. */
216794
+ function bindTaskPlanCreateResult(state, toolUseId, resultText) {
216795
+ const entry = state.createsByToolUseId.get(toolUseId);
216796
+ if (!entry || entry.id !== void 0) return;
216797
+ const match = TASK_CREATE_RESULT_ID.exec(resultText);
216798
+ if (match?.[1]) entry.id = match[1];
216799
+ }
216469
216800
  function extractPlanStepsFromTodoInput(input) {
216470
216801
  const todos = input.todos;
216471
216802
  if (!Array.isArray(todos) || todos.length === 0) return null;
@@ -217183,9 +217514,9 @@ const makeClaudeAdapter = require_Schema$1.fn("makeClaudeAdapter")(function* (op
217183
217514
  }
217184
217515
  });
217185
217516
  });
217186
- const emitPlanUpdateFromToolInput = require_Schema$1.fn("emitPlanUpdateFromToolInput")(function* (context, toolName, parsedInput) {
217187
- if (!isTodoTool(toolName)) return;
217188
- const planSteps = extractPlanStepsFromTodoInput(parsedInput);
217517
+ const emitPlanUpdateFromToolInput = require_Schema$1.fn("emitPlanUpdateFromToolInput")(function* (context, toolName, toolUseId, parsedInput) {
217518
+ const taskPlanTool = taskPlanToolFor(toolName);
217519
+ const planSteps = taskPlanTool ? applyTaskPlanToolInput(context.taskPlan, taskPlanTool, toolUseId, parsedInput) : isTodoTool(toolName) ? extractPlanStepsFromTodoInput(parsedInput) : null;
217189
217520
  if (!planSteps || planSteps.length === 0) return;
217190
217521
  const planStamp = yield* makeEventStamp();
217191
217522
  yield* offerRuntimeEvent({
@@ -217243,7 +217574,7 @@ const makeClaudeAdapter = require_Schema$1.fn("makeClaudeAdapter")(function* (op
217243
217574
  };
217244
217575
  context.inFlightTools.set(event.index, nextTool);
217245
217576
  yield* emitToolInputUpdated(context, message, nextTool);
217246
- if (state.parsedInput) yield* emitPlanUpdateFromToolInput(context, nextTool.toolName, state.parsedInput);
217577
+ if (state.parsedInput) yield* emitPlanUpdateFromToolInput(context, nextTool.toolName, nextTool.itemId, state.parsedInput);
217247
217578
  });
217248
217579
  const handleContentBlockDeltaEvent = require_Schema$1.fn("handleContentBlockDeltaEvent")(function* (context, message, event) {
217249
217580
  yield* handleTextOrThinkingDelta(context, message, event);
@@ -217296,6 +217627,7 @@ const makeClaudeAdapter = require_Schema$1.fn("makeClaudeAdapter")(function* (op
217296
217627
  if (!toolEntry) continue;
217297
217628
  const [index, tool] = toolEntry;
217298
217629
  const itemStatus = toolResult.isError ? "failed" : "completed";
217630
+ if (taskPlanToolFor(tool.toolName) === "TaskCreate" && !toolResult.isError) bindTaskPlanCreateResult(context.taskPlan, tool.itemId, toolResult.text);
217299
217631
  const toolData = {
217300
217632
  toolName: tool.toolName,
217301
217633
  input: tool.input,
@@ -292722,7 +293054,7 @@ const make$8 = require_Schema$1.gen(function* () {
292722
293054
  summary: "Provider turn start failed",
292723
293055
  detail: `User message '${event.payload.messageId}' was not found for turn start request.`,
292724
293056
  turnId: null,
292725
- createdAt: event.payload.createdAt
293057
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
292726
293058
  });
292727
293059
  return;
292728
293060
  }
@@ -292775,7 +293107,7 @@ const make$8 = require_Schema$1.gen(function* () {
292775
293107
  summary: "Provider turn start failed",
292776
293108
  detail: publicProviderFailureDetail(require_Schema$1.pretty(cause)),
292777
293109
  turnId: null,
292778
- createdAt: event.payload.createdAt
293110
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
292779
293111
  })));
292780
293112
  });
292781
293113
  const processTurnInterruptRequested = require_Schema$1.fn("processTurnInterruptRequested")(function* (event) {
@@ -298458,7 +298790,7 @@ function resolveBuildCommitFromEnv(env) {
298458
298790
  * environment descriptor down instead of reporting an honest "unknown".
298459
298791
  */
298460
298792
  function readBakedBuildCommit() {
298461
- return "81ce9360f406bcb5ae9fbdc9f90dd55d33f25f0c";
298793
+ return "78b0e072680ed734c4eb8337a6d38c87e7e34393";
298462
298794
  }
298463
298795
  async function resolveServerBuildCommit(input) {
298464
298796
  if (isFullCommitSha(input.baked)) return input.baked;
@@ -309882,6 +310214,33 @@ registerProxyRoute({
309882
310214
  cmmdPath: "/api/projects/campaigns/:id",
309883
310215
  orgIdSource: "none"
309884
310216
  });
310217
+ /**
310218
+ * Apple App Store guideline 5.1.1(v): an app that supports account creation
310219
+ * or sign-in must let the user start account deletion from inside the app.
310220
+ * CMMD owns the account, so this proxies to its existing deactivation route.
310221
+ *
310222
+ * `cmmdPath` is `/api/settings/delete-account`, not a `/api/forge/context/*`
310223
+ * route: CMMD never built a forge-context-scoped deletion endpoint. It is
310224
+ * still reachable through the same owner JWT this gateway already resolves
310225
+ * per session: CMMD's `optionalJwtAuth` runs ahead of every `/api/*` route
310226
+ * (not only forge-context ones), so the same Bearer that reaches
310227
+ * `/api/cmmd/bootstrap` also authenticates here.
310228
+ *
310229
+ * CMMD's route soft-deletes: it flips the user to `active: false` and
310230
+ * `lifecycleState: 'deactivated'`, revokes CMMD's own refresh tokens, and
310231
+ * destroys its session. It does not purge data on this call. The Forge
310232
+ * client is responsible for the rest: revoking the Forge session
310233
+ * (`/api/auth/logout`) and clearing local state, the same as a manual
310234
+ * sign-out.
310235
+ */
310236
+ registerProxyRoute({
310237
+ forgePath: "/api/cmmd/settings/delete-account",
310238
+ method: "DELETE",
310239
+ cmmdPath: "/api/settings/delete-account",
310240
+ orgIdSource: "none",
310241
+ responseShape: "unwrapData",
310242
+ requestBody: "none"
310243
+ });
309885
310244
  registerProxyRoute({
309886
310245
  forgePath: "/api/cmmd/bootstrap",
309887
310246
  method: "GET",