dsh-codex-subscription 2.2.2 → 2.2.4

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/lib/index.js CHANGED
@@ -369,6 +369,54 @@ const PREFERENCE_FIELDS = Object.freeze({
369
369
  }
370
370
  });
371
371
  //#endregion
372
+ //#region src/diagnostic-events.js
373
+ const areas$1 = /* @__PURE__ */ new Set([
374
+ "login",
375
+ "model",
376
+ "catalog",
377
+ "quota",
378
+ "quota-reset",
379
+ "search",
380
+ "image"
381
+ ]);
382
+ const codes$1 = /* @__PURE__ */ new Set([
383
+ "timeout",
384
+ "dns",
385
+ "tls",
386
+ "connection",
387
+ "network",
388
+ "http-error"
389
+ ]);
390
+ /** Process-local bounded evidence. No bodies, URLs, exception strings or identities. */
391
+ function createDiagnosticEvents({ limit = 32, now = Date.now } = {}) {
392
+ const events = [];
393
+ const capacity = Math.max(1, Math.min(128, Number.isInteger(limit) ? limit : 32));
394
+ let dropped = 0, sequence = 0;
395
+ return {
396
+ record(area, value) {
397
+ if (!areas$1.has(area) || !["ok", "failed"].includes(value?.status)) return;
398
+ events.push({
399
+ sequence: ++sequence,
400
+ area,
401
+ status: value.status,
402
+ observedAt: now(),
403
+ ...codes$1.has(value.code) ? { code: value.code } : {},
404
+ ...Number.isInteger(value.httpStatus) && value.httpStatus >= 100 && value.httpStatus <= 599 ? { httpStatus: value.httpStatus } : {}
405
+ });
406
+ if (events.length > capacity) {
407
+ events.shift();
408
+ dropped++;
409
+ }
410
+ },
411
+ snapshot: () => ({
412
+ scope: "plugin-process",
413
+ capacity,
414
+ dropped,
415
+ events: events.map((value) => ({ ...value }))
416
+ })
417
+ };
418
+ }
419
+ //#endregion
372
420
  //#region src/oauth-network.js
373
421
  const execFileAsync = promisify(execFile);
374
422
  const CODEX_AUTH_HOST = "auth.openai.com";
@@ -612,6 +660,11 @@ const elapsedBucket = (elapsed) => elapsed < 1e3 ? "under-1s" : elapsed < 5e3 ?
612
660
  function createCodexNetworkTransport(options = {}) {
613
661
  const attempts = /* @__PURE__ */ new Map();
614
662
  const now = options.now ?? Date.now;
663
+ const evidence = createDiagnosticEvents({ now });
664
+ const remember = (area, value) => {
665
+ attempts.set(area, value);
666
+ evidence.record(area, value);
667
+ };
615
668
  const run = async (area, operation, connection = {}) => {
616
669
  const startedAt = now();
617
670
  let route = attempts.get(area)?.route ?? "direct";
@@ -625,7 +678,7 @@ function createCodexNetworkTransport(options = {}) {
625
678
  routed = true;
626
679
  }
627
680
  });
628
- if (value instanceof Response && !value.ok) attempts.set(area, {
681
+ if (value instanceof Response && !value.ok) remember(area, {
629
682
  status: "failed",
630
683
  stage: "http",
631
684
  code: "http-error",
@@ -633,14 +686,14 @@ function createCodexNetworkTransport(options = {}) {
633
686
  route,
634
687
  elapsed: elapsedBucket(now() - startedAt)
635
688
  });
636
- else if (routed || value instanceof Response) attempts.set(area, {
689
+ else if (routed || value instanceof Response) remember(area, {
637
690
  status: "ok",
638
691
  route,
639
692
  elapsed: elapsedBucket(now() - startedAt)
640
693
  });
641
694
  return value;
642
695
  } catch (error) {
643
- if (routed) attempts.set(area, {
696
+ if (routed) remember(area, {
644
697
  status: "failed",
645
698
  stage: "transport",
646
699
  code: classifyTransportError(error),
@@ -652,6 +705,7 @@ function createCodexNetworkTransport(options = {}) {
652
705
  };
653
706
  return Object.freeze({
654
707
  run,
708
+ history: evidence.snapshot,
655
709
  fetch: (area, input, init) => run(area, () => globalThis.fetch(input, init)),
656
710
  snapshot: () => Object.fromEntries([...attempts].map(([area, value]) => [area, { ...value }]))
657
711
  });
@@ -1316,6 +1370,21 @@ function createSubagentBackendSwitcher({ entries, prepare, persist }) {
1316
1370
  backgroundMode: "one-shot",
1317
1371
  maxDepth: "provider-managed"
1318
1372
  });
1373
+ const ownedFields = [
1374
+ "provider",
1375
+ "modelSelectionSettings",
1376
+ "backgroundMode",
1377
+ "maxDepth"
1378
+ ];
1379
+ const restore = (current, original) => {
1380
+ const result = { ...current }, applied = convert(original);
1381
+ for (const key of ownedFields) {
1382
+ if (current[key] !== applied[key]) continue;
1383
+ if (Object.hasOwn(original, key)) result[key] = original[key];
1384
+ else delete result[key];
1385
+ }
1386
+ return result;
1387
+ };
1319
1388
  const configure = (fiber, config) => {
1320
1389
  if (!standard(fiber.entry, config)) return config;
1321
1390
  if (!originals.has(fiber)) originals.set(fiber, { ...config });
@@ -1345,7 +1414,8 @@ function createSubagentBackendSwitcher({ entries, prepare, persist }) {
1345
1414
  fiber,
1346
1415
  before
1347
1416
  });
1348
- await fiber.update(mode === "codex" ? convert(original) : original, true);
1417
+ if (mode === "codex" && before.provider !== "codex-subscription-subagent") originals.set(fiber, { ...before });
1418
+ await fiber.update(mode === "codex" ? convert(before) : restore(before, original), true);
1349
1419
  }
1350
1420
  await persist?.(mode);
1351
1421
  } catch (error) {
@@ -1364,7 +1434,7 @@ function createSubagentBackendSwitcher({ entries, prepare, persist }) {
1364
1434
  await tail.catch(() => {});
1365
1435
  disposed = true;
1366
1436
  selected = "dsh";
1367
- for (const [fiber, original] of originals) if (fiber.config?.provider === "codex-subscription-subagent") await fiber.update(original, true);
1437
+ for (const [fiber, original] of originals) if (fiber.config?.provider === "codex-subscription-subagent") await fiber.update(restore(fiber.config, original), true);
1368
1438
  originals.clear();
1369
1439
  }
1370
1440
  };
@@ -2860,8 +2930,28 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
2860
2930
  }
2861
2931
  Object.freeze(["0.82.1", "0.85.1"]);
2862
2932
  //#endregion
2933
+ //#region src/subscription-credentials.js
2934
+ async function readSubscriptionCredentials(getAuth, readCredential, signal) {
2935
+ for (let attempt = 0; attempt < 2; attempt++) {
2936
+ const auth = await getAuth({ signal });
2937
+ signal?.throwIfAborted();
2938
+ if (typeof auth?.auth?.apiKey !== "string" || !auth.auth.apiKey) break;
2939
+ const credential = await readCredential({ signal });
2940
+ signal?.throwIfAborted();
2941
+ if (credential?.type !== "oauth") break;
2942
+ if (credential.access === auth.auth.apiKey) return {
2943
+ access: credential.access,
2944
+ accountId: credential.accountId
2945
+ };
2946
+ }
2947
+ return {
2948
+ access: void 0,
2949
+ accountId: void 0
2950
+ };
2951
+ }
2952
+ //#endregion
2863
2953
  //#region src/version.js
2864
- const PACKAGE_VERSION = "2.2.2";
2954
+ const PACKAGE_VERSION = "2.2.4";
2865
2955
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
2866
2956
  //#endregion
2867
2957
  //#region src/model-catalog.js
@@ -2986,12 +3076,8 @@ function createOfficialModelCatalog(options = {}) {
2986
3076
  let timer;
2987
3077
  const timeoutError = /* @__PURE__ */ new Error("Codex model catalog refresh timed out");
2988
3078
  const work = (async () => {
2989
- const auth = await options.getAuth({ signal: requestSignal });
2990
- if (currentGeneration !== generation || requestSignal.aborted) return false;
2991
- const credential = await options.readCredential({ signal: requestSignal });
3079
+ const { access, accountId } = await readSubscriptionCredentials(options.getAuth, options.readCredential, requestSignal);
2992
3080
  if (currentGeneration !== generation || requestSignal.aborted) return false;
2993
- const access = auth?.auth?.apiKey;
2994
- const accountId = credential?.type === "oauth" ? credential.accountId : void 0;
2995
3081
  if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) return false;
2996
3082
  const headers = {
2997
3083
  authorization: `Bearer ${access}`,
@@ -3148,10 +3234,7 @@ function createCodexSearchProvider(options) {
3148
3234
  signal?.throwIfAborted();
3149
3235
  const preferences = readCapabilitySettings(options.resolvePreferences?.());
3150
3236
  if (preferences.searchMode === "disabled") throw new WebError("Codex search is disabled in subscription settings", "WEB_PROVIDER_UNAVAILABLE");
3151
- const auth = await options.getAuth({ signal });
3152
- const credential = await options.readCredential({ signal });
3153
- const access = auth?.auth?.apiKey;
3154
- const accountId = credential?.type === "oauth" ? credential.accountId : void 0;
3237
+ const { access, accountId } = await readSubscriptionCredentials(options.getAuth, options.readCredential, signal);
3155
3238
  if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) throw new WebError("ChatGPT subscription is not signed in", "WEB_PROVIDER_CREDENTIAL_MISSING");
3156
3239
  const model = nonEmpty$1(options.resolveModel?.()) ?? DEFAULT_MODEL;
3157
3240
  const id = nonEmpty$1(options.resolveSessionId?.()) ?? randomUUID();
@@ -3662,10 +3745,7 @@ function createCodexImageTool(options) {
3662
3745
  const prompt = nonEmpty(args.prompt);
3663
3746
  if (prompt === void 0) throw new Error("prompt must be a non-empty string");
3664
3747
  const imageOptions = normalizeImageOptions(args);
3665
- const auth = await options.getAuth({ signal: exec.signal });
3666
- const credential = await options.readCredential({ signal: exec.signal });
3667
- const access = auth?.auth?.apiKey;
3668
- const accountId = credential?.type === "oauth" ? credential.accountId : void 0;
3748
+ const { access, accountId } = await readSubscriptionCredentials(options.getAuth, options.readCredential, exec.signal);
3669
3749
  if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) throw new Error("ChatGPT subscription is not signed in");
3670
3750
  if (!attachments.imageLimits.mediaTypes.includes("image/png")) throw new Error("This DSH installation does not accept PNG image attachments");
3671
3751
  const maximumBytes = Math.min(attachments.imageLimits.maxImageBytes, attachments.imageLimits.maxMessageImageBytes);
@@ -3907,6 +3987,272 @@ var OriginalImageStore = class {
3907
3987
  }
3908
3988
  };
3909
3989
  //#endregion
3990
+ //#region src/diagnostic-checks.js
3991
+ const states = /* @__PURE__ */ new Set([
3992
+ "pass",
3993
+ "fail",
3994
+ "warn",
3995
+ "unknown",
3996
+ "not-applicable"
3997
+ ]);
3998
+ /** Adapters return bounded reason codes, never exceptions or raw service data. */
3999
+ async function collectChecks(definitions, { timeoutMs = 1500, now = Date.now, source = "server", scope = "plugin-process" } = {}) {
4000
+ return Promise.all(definitions.map(async ({ id, capability, run }) => {
4001
+ const startedAt = now();
4002
+ let timer;
4003
+ const controller = new AbortController();
4004
+ const base = {
4005
+ id,
4006
+ capability,
4007
+ source,
4008
+ scope
4009
+ };
4010
+ try {
4011
+ if (!run) return {
4012
+ ...base,
4013
+ status: "unknown",
4014
+ reason: "not-instrumented",
4015
+ observedAt: now()
4016
+ };
4017
+ const result = await Promise.race([Promise.resolve().then(() => run(controller.signal)), new Promise((resolve) => {
4018
+ timer = setTimeout(() => {
4019
+ controller.abort();
4020
+ resolve({
4021
+ status: "unknown",
4022
+ reason: "inspection-timeout"
4023
+ });
4024
+ }, timeoutMs);
4025
+ })]);
4026
+ const valid = states.has(result?.status) && /^[a-z][a-z0-9-]{0,63}$/u.test(result?.reason ?? "");
4027
+ return {
4028
+ ...base,
4029
+ status: valid ? result.status : "unknown",
4030
+ reason: valid ? result.reason : "invalid-inspection-result",
4031
+ observedAt: now(),
4032
+ elapsedMs: Math.max(0, now() - startedAt)
4033
+ };
4034
+ } catch {
4035
+ return {
4036
+ ...base,
4037
+ status: "unknown",
4038
+ reason: "inspection-failed",
4039
+ observedAt: now(),
4040
+ elapsedMs: Math.max(0, now() - startedAt)
4041
+ };
4042
+ } finally {
4043
+ clearTimeout(timer);
4044
+ }
4045
+ }));
4046
+ }
4047
+ const diagnosticCapabilities = Object.freeze([
4048
+ "account",
4049
+ "accounts",
4050
+ "settings",
4051
+ "models",
4052
+ "quota",
4053
+ "search",
4054
+ "images",
4055
+ "sketch",
4056
+ "subagents",
4057
+ "compaction",
4058
+ "transport",
4059
+ "storage",
4060
+ "host-ui"
4061
+ ]);
4062
+ const areas = {
4063
+ catalog: "models",
4064
+ quota: "quota",
4065
+ search: "search",
4066
+ image: "images",
4067
+ model: "transport",
4068
+ login: "account",
4069
+ "quota-reset": "quota"
4070
+ };
4071
+ function diagnosticEvidence({ operations, history }, now) {
4072
+ return [...(operations?.events ?? []).map((e) => ({
4073
+ capability: e.capability,
4074
+ action: e.action,
4075
+ source: e.source,
4076
+ status: e.status,
4077
+ observedAt: e.observedAt
4078
+ })), ...(history?.events ?? []).map((e) => ({
4079
+ capability: areas[e.area],
4080
+ action: e.area,
4081
+ source: "network",
4082
+ status: e.status === "ok" ? "completed" : "failed",
4083
+ observedAt: e.observedAt
4084
+ }))].filter((e) => Number.isSafeInteger(e.observedAt) && now >= e.observedAt && now - e.observedAt <= 9e5).sort((a, b) => a.observedAt - b.observedAt);
4085
+ }
4086
+ function unresolvedFailures(events) {
4087
+ const failed = /* @__PURE__ */ new Map();
4088
+ for (const event of events) {
4089
+ const key = `${event.source}:${event.action}`;
4090
+ if (event.status === "failed") failed.set(key, event);
4091
+ else if (event.status === "completed") failed.delete(key);
4092
+ }
4093
+ return [...failed.values()];
4094
+ }
4095
+ //#endregion
4096
+ //#region src/diagnostic-capabilities.js
4097
+ function capabilityCoverage({ account, preference, runtime, catalog, tools, storage, checks, operations, history, now = Date.now() }) {
4098
+ const disabled = {
4099
+ images: preference.imageGeneration === false && preference.imageEditing === false,
4100
+ sketch: preference.imageSketch === false || preference.imageEditing === false,
4101
+ search: preference.searchMode === "disabled",
4102
+ compaction: preference.compactionMode === "dsh",
4103
+ subagents: preference.subagentBackend === "dsh"
4104
+ };
4105
+ const observed = diagnosticEvidence({
4106
+ operations,
4107
+ history
4108
+ }, now);
4109
+ return diagnosticCapabilities.map((id) => {
4110
+ let readiness = "unknown", reason = "no-readiness-check", blockedBy;
4111
+ if (disabled[id]) {
4112
+ readiness = "not-applicable";
4113
+ reason = "disabled-or-host-owned";
4114
+ } else if ([
4115
+ "models",
4116
+ "quota",
4117
+ "search",
4118
+ "images",
4119
+ "compaction"
4120
+ ].includes(id) && account.status === "signed-out") {
4121
+ readiness = "warn";
4122
+ reason = "sign-in-required";
4123
+ blockedBy = "account";
4124
+ } else if (id === "account") {
4125
+ readiness = account.status === "signed-in" ? "pass" : "unknown";
4126
+ reason = account.status === "signed-in" ? "local-sign-in-present" : "sign-in-unverified";
4127
+ } else if (id === "settings" && typeof preference.writable === "boolean") {
4128
+ readiness = preference.writable ? "pass" : "warn";
4129
+ reason = preference.writable ? "settings-writable" : "settings-readonly";
4130
+ } else if (id === "subagents" && runtime) {
4131
+ readiness = runtime.installed && !runtime.restartRequired ? "pass" : "warn";
4132
+ reason = runtime.restartRequired ? "restart-required" : runtime.installed ? "runtime-present" : "runtime-missing";
4133
+ } else if (id === "models" && catalog) {
4134
+ readiness = catalog.source === "online" ? "pass" : "warn";
4135
+ reason = catalog.source === "online" ? "catalog-online" : "catalog-fallback";
4136
+ } else if (id === "storage" && storage?.readable) {
4137
+ readiness = "pass";
4138
+ reason = "storage-readable";
4139
+ } else if (tools && (id === "images" || id === "sketch" && preference.imageSketchAgent === true)) {
4140
+ readiness = tools[id] ? "pass" : "warn";
4141
+ reason = tools[id] ? "tool-registered" : "tool-missing";
4142
+ }
4143
+ const evidence = observed.filter((event) => event.capability === id || id === "transport" && event.source === "network");
4144
+ const failures = unresolvedFailures(evidence);
4145
+ const recent = evidence.at(-1);
4146
+ const relatedChecks = checks.filter((check) => check.capability === id || ["images", "sketch"].includes(id) && check.id === "tools");
4147
+ const collectionIssue = relatedChecks.find((check) => ["inspection-failed", "inspection-timeout"].includes(check.reason));
4148
+ if (collectionIssue && readiness === "unknown") reason = collectionIssue.reason;
4149
+ return {
4150
+ id,
4151
+ readiness,
4152
+ reason,
4153
+ ...blockedBy ? { blockedBy } : {},
4154
+ checks: relatedChecks.map((check) => check.id),
4155
+ execution: recent ? "observed" : "not-verified",
4156
+ ...failures.length ? { unresolved: failures } : {},
4157
+ ...recent ? { latest: {
4158
+ action: recent.action,
4159
+ source: recent.source,
4160
+ status: recent.status,
4161
+ observedAt: recent.observedAt
4162
+ } } : {}
4163
+ };
4164
+ });
4165
+ }
4166
+ //#endregion
4167
+ //#region src/diagnostic-operations.js
4168
+ const actions = Object.freeze({
4169
+ "login/start": "account",
4170
+ "login/submit": "account",
4171
+ "login/cancel": "account",
4172
+ logout: "account",
4173
+ "account/select": "accounts",
4174
+ "account/remove": "accounts",
4175
+ "preferences/update": "settings",
4176
+ "account/cleanup": "accounts",
4177
+ usage: "quota",
4178
+ "preferences/models": "models",
4179
+ "storage/status": "storage",
4180
+ "storage/clear-forecast": "storage",
4181
+ "runtime/install": "subagents",
4182
+ "runtime/remove": "subagents",
4183
+ "runtime/cancel": "subagents",
4184
+ codex_image_generate: "images",
4185
+ codex_sketch: "sketch"
4186
+ });
4187
+ function safeOperations(raw) {
4188
+ if (!Array.isArray(raw?.events)) return void 0;
4189
+ return {
4190
+ scope: "plugin-process",
4191
+ capacity: 32,
4192
+ dropped: (Number.isSafeInteger(raw.dropped) && raw.dropped >= 0 ? raw.dropped : 0) + Math.max(0, raw.events.length - 32),
4193
+ events: raw.events.slice(-32).flatMap((value) => {
4194
+ if (!Object.hasOwn(actions, value?.action ?? "") || ![
4195
+ "completed",
4196
+ "failed",
4197
+ "cancelled"
4198
+ ].includes(value.status) || !["rpc", "tool"].includes(value.source) || !Number.isSafeInteger(value.observedAt) || value.observedAt < 0) return [];
4199
+ return [{
4200
+ capability: actions[value.action],
4201
+ action: value.action,
4202
+ status: value.status,
4203
+ source: value.source,
4204
+ observedAt: value.observedAt
4205
+ }];
4206
+ })
4207
+ };
4208
+ }
4209
+ function createDiagnosticOperations({ now = Date.now } = {}) {
4210
+ const events = [];
4211
+ let sequence = 0, dropped = 0;
4212
+ const record = (action, status, source = "rpc") => {
4213
+ if (!Object.hasOwn(actions, action) || ![
4214
+ "completed",
4215
+ "failed",
4216
+ "cancelled"
4217
+ ].includes(status) || !["rpc", "tool"].includes(source)) return;
4218
+ events.push({
4219
+ sequence: ++sequence,
4220
+ capability: actions[action],
4221
+ action,
4222
+ source,
4223
+ status,
4224
+ observedAt: now()
4225
+ });
4226
+ if (events.length > 32) {
4227
+ events.shift();
4228
+ dropped++;
4229
+ }
4230
+ };
4231
+ return {
4232
+ record,
4233
+ wrap: (handler) => async (endpoint, payload, signal) => {
4234
+ try {
4235
+ const result = await handler(endpoint, payload, signal);
4236
+ record(endpoint, result?.ok === true ? "completed" : "failed");
4237
+ return result;
4238
+ } catch (error) {
4239
+ record(endpoint, signal?.aborted ? "cancelled" : "failed");
4240
+ throw error;
4241
+ }
4242
+ },
4243
+ tool: (exec, result) => {
4244
+ if (!["codex_image_generate", "codex_sketch"].includes(exec?.name)) return;
4245
+ record(exec.name, exec.signal?.aborted ? "cancelled" : result?.isError === true ? "failed" : result?.isError === false ? "completed" : "failed", "tool");
4246
+ },
4247
+ snapshot: () => ({
4248
+ scope: "plugin-process",
4249
+ capacity: 32,
4250
+ dropped,
4251
+ events: events.map((value) => ({ ...value }))
4252
+ })
4253
+ };
4254
+ }
4255
+ //#endregion
3910
4256
  //#region src/diagnostics.js
3911
4257
  const requestAreas = /* @__PURE__ */ new Set([
3912
4258
  "login",
@@ -3939,6 +4285,26 @@ const elapsedBuckets = /* @__PURE__ */ new Set([
3939
4285
  "5-15s",
3940
4286
  "over-15s"
3941
4287
  ]);
4288
+ function safeHistory(raw) {
4289
+ if (!raw || !Array.isArray(raw.events)) return void 0;
4290
+ const events = raw.events.slice(-32).flatMap((value) => {
4291
+ if (!value || !requestAreas.has(value.area) || !statuses.has(value.status) || !Number.isSafeInteger(value.sequence) || value.sequence < 1 || !Number.isSafeInteger(value.observedAt) || value.observedAt < 0) return [];
4292
+ return [{
4293
+ area: value.area,
4294
+ status: value.status,
4295
+ sequence: value.sequence,
4296
+ observedAt: value.observedAt,
4297
+ ...codes.has(value.code) ? { code: value.code } : {},
4298
+ ...Number.isInteger(value.httpStatus) && value.httpStatus >= 100 && value.httpStatus <= 599 ? { httpStatus: value.httpStatus } : {}
4299
+ }];
4300
+ });
4301
+ return {
4302
+ scope: "plugin-process",
4303
+ capacity: 32,
4304
+ dropped: (Number.isSafeInteger(raw.dropped) && raw.dropped >= 0 ? raw.dropped : 0) + Math.max(0, raw.events.length - 32),
4305
+ events
4306
+ };
4307
+ }
3942
4308
  function safeRequests(network) {
3943
4309
  const raw = network?.snapshot?.() ?? {};
3944
4310
  const result = {};
@@ -3957,17 +4323,113 @@ function safeRequests(network) {
3957
4323
  return result;
3958
4324
  }
3959
4325
  /** Build a support report that deliberately excludes OAuth and account metadata. */
3960
- async function createSubscriptionDiagnostics({ auth, preferences, login = { phase: "idle" }, network, modelCatalog, connection, compaction }) {
4326
+ async function createSubscriptionDiagnostics({ auth, preferences, login = { phase: "idle" }, network, modelCatalog, connection, compaction, inspectionOptions, operations, runtimeManagement, storage, tools }) {
4327
+ const collected = {};
4328
+ const checks = await collectChecks([
4329
+ [
4330
+ "account",
4331
+ "account",
4332
+ () => auth.status()
4333
+ ],
4334
+ [
4335
+ "preferences",
4336
+ "settings",
4337
+ () => preferences.status()
4338
+ ],
4339
+ [
4340
+ "catalog",
4341
+ "models",
4342
+ () => modelCatalog?.status?.()
4343
+ ],
4344
+ [
4345
+ "gaps",
4346
+ "models",
4347
+ () => modelCatalog?.capabilityGaps?.()
4348
+ ],
4349
+ [
4350
+ "requests",
4351
+ "transport",
4352
+ () => network?.snapshot ? safeRequests(network) : void 0
4353
+ ],
4354
+ [
4355
+ "history",
4356
+ "transport",
4357
+ () => safeHistory(network?.history?.())
4358
+ ],
4359
+ [
4360
+ "websocket",
4361
+ "transport",
4362
+ () => connection ? safeCounters(connection, [
4363
+ "requests",
4364
+ "connectionsCreated",
4365
+ "connectionsReused",
4366
+ "deltaRequests",
4367
+ "websocketFailures",
4368
+ "sseFallbacks"
4369
+ ]) : void 0
4370
+ ],
4371
+ [
4372
+ "compaction",
4373
+ "compaction",
4374
+ () => compaction ? safeCounters(compaction, [
4375
+ "requests",
4376
+ "checkpointsSaved",
4377
+ "checkpointsReused"
4378
+ ]) : void 0
4379
+ ],
4380
+ [
4381
+ "operations",
4382
+ "transport",
4383
+ () => safeOperations(operations?.snapshot?.())
4384
+ ],
4385
+ [
4386
+ "runtime",
4387
+ "subagents",
4388
+ async () => {
4389
+ const value = await runtimeManagement?.status?.();
4390
+ return value ? Object.fromEntries([
4391
+ "available",
4392
+ "installed",
4393
+ "restartRequired"
4394
+ ].filter((key) => typeof value[key] === "boolean").map((key) => [key, value[key]])) : void 0;
4395
+ }
4396
+ ],
4397
+ [
4398
+ "storage",
4399
+ "storage",
4400
+ async () => typeof storage === "function" ? (await storage(), { readable: true }) : void 0
4401
+ ],
4402
+ [
4403
+ "tools",
4404
+ "host-ui",
4405
+ () => typeof tools?.get === "function" ? {
4406
+ images: !!tools.get("codex_image_generate"),
4407
+ sketch: !!tools.get("codex_sketch")
4408
+ } : void 0
4409
+ ]
4410
+ ].map(([id, capability, read]) => ({
4411
+ id,
4412
+ capability,
4413
+ run: read ? async (signal) => {
4414
+ const value = await read();
4415
+ if (value === void 0) return {
4416
+ status: "unknown",
4417
+ reason: "not-instrumented"
4418
+ };
4419
+ if (!signal.aborted) collected[id] = value;
4420
+ return {
4421
+ status: "pass",
4422
+ reason: "inspection-completed"
4423
+ };
4424
+ } : void 0
4425
+ })), inspectionOptions);
3961
4426
  let account = { status: "unknown" };
3962
4427
  const issues = [];
3963
- try {
3964
- account = { status: (await auth.status()).authenticated === true ? "signed-in" : "signed-out" };
3965
- } catch {
3966
- issues.push({ code: "account-status-unavailable" });
3967
- }
3968
- const preference = preferences.status();
3969
- const catalog = modelCatalog?.status?.();
3970
- const gaps = (modelCatalog?.capabilityGaps?.() ?? []).slice(0, 20).flatMap((value) => {
4428
+ if (collected.account) account = { status: collected.account.authenticated === true ? "signed-in" : "signed-out" };
4429
+ else issues.push({ code: "account-status-unavailable" });
4430
+ const preference = collected.preferences ?? {};
4431
+ const catalog = collected.catalog;
4432
+ const gaps = (Array.isArray(collected.gaps) ? collected.gaps : []).slice(0, 20).flatMap((value) => {
3971
4433
  if (!value || typeof value.model !== "string" || !/^[a-z][a-z0-9._-]{0,79}$/u.test(value.model)) return [];
3972
4434
  const fields = Object.fromEntries([
3973
4435
  "reasoning",
@@ -3985,6 +4447,23 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
3985
4447
  if (gaps.length) issues.push({ code: "catalog-capabilities-not-adapted" });
3986
4448
  return {
3987
4449
  schemaVersion: 3,
4450
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4451
+ inspection: {
4452
+ schemaVersion: 1,
4453
+ meaning: "collection-success-is-not-feature-success",
4454
+ checks,
4455
+ capabilities: capabilityCoverage({
4456
+ account,
4457
+ preference,
4458
+ runtime: collected.runtime,
4459
+ catalog,
4460
+ tools: collected.tools,
4461
+ storage: collected.storage,
4462
+ checks,
4463
+ operations: collected.operations,
4464
+ history: collected.history
4465
+ })
4466
+ },
3988
4467
  package: "dsh-codex-subscription",
3989
4468
  version: PACKAGE_VERSION,
3990
4469
  runtime: {
@@ -3994,20 +4473,11 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
3994
4473
  },
3995
4474
  account,
3996
4475
  login,
3997
- requests: safeRequests(network),
3998
- ...connection ? { websocket: safeCounters(connection, [
3999
- "requests",
4000
- "connectionsCreated",
4001
- "connectionsReused",
4002
- "deltaRequests",
4003
- "websocketFailures",
4004
- "sseFallbacks"
4005
- ]) } : {},
4006
- ...compaction ? { compaction: safeCounters(compaction, [
4007
- "requests",
4008
- "checkpointsSaved",
4009
- "checkpointsReused"
4010
- ]) } : {},
4476
+ requests: collected.requests ?? {},
4477
+ ...collected.history ? { requestHistory: collected.history } : {},
4478
+ ...collected.operations ? { operations: collected.operations } : {},
4479
+ ...collected.websocket ? { websocket: collected.websocket } : {},
4480
+ ...collected.compaction ? { compaction: collected.compaction } : {},
4011
4481
  ...catalog && ["fallback", "online"].includes(catalog.source) && [
4012
4482
  "idle",
4013
4483
  "refreshing",
@@ -4025,7 +4495,7 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
4025
4495
  ...typeof preference.outputVerbosity === "string" ? { outputVerbosity: preference.outputVerbosity } : {},
4026
4496
  searchProvider: preference.searchProvider,
4027
4497
  speedMode: preference.speedMode,
4028
- writable: preference.writable === true
4498
+ ...typeof preference.writable === "boolean" ? { writable: preference.writable } : {}
4029
4499
  },
4030
4500
  issues
4031
4501
  };
@@ -4192,10 +4662,7 @@ function createCodexUsageReader(options) {
4192
4662
  let inFlight;
4193
4663
  let generation = 0;
4194
4664
  const load = async (signal) => {
4195
- const auth = await getAuth({ signal });
4196
- const credential = await readCredential({ signal });
4197
- const access = auth?.auth?.apiKey;
4198
- const accountId = credential?.type === "oauth" ? credential.accountId : void 0;
4665
+ const { access, accountId } = await readSubscriptionCredentials(getAuth, readCredential, signal);
4199
4666
  if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) throw new Error("ChatGPT subscription is not signed in");
4200
4667
  const response = await fetchUsage(CODEX_USAGE_URL, {
4201
4668
  method: "GET",
@@ -4851,9 +5318,7 @@ const requestSignal = (signal, timeoutMs) => {
4851
5318
  function safeCopy(value) {
4852
5319
  return typeof value === "string" && value.length > 0 ? value.slice(0, MAX_COPY_LENGTH) : void 0;
4853
5320
  }
4854
- function credentialsOf(auth, credential) {
4855
- const access = auth?.auth?.apiKey;
4856
- const accountId = credential?.type === "oauth" ? credential.accountId : void 0;
5321
+ function credentialsOf({ access, accountId }) {
4857
5322
  if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) throw new Error("ChatGPT subscription is not signed in");
4858
5323
  return {
4859
5324
  access,
@@ -4938,7 +5403,7 @@ function createCodexResetCreditService(options) {
4938
5403
  ...credit.title === void 0 ? {} : { name: credit.title },
4939
5404
  ...credit.creditExpiresAt === void 0 ? {} : { expiresAt: credit.creditExpiresAt }
4940
5405
  });
4941
- const resolveCredentials = async (signal) => credentialsOf(await getAuth({ signal }), await readCredential({ signal }));
5406
+ const resolveCredentials = async (signal) => credentialsOf(await readSubscriptionCredentials(getAuth, readCredential, signal));
4942
5407
  const readDetails = async (signal, credentials) => {
4943
5408
  const { access, accountId } = credentials ?? await resolveCredentials(signal);
4944
5409
  const response = await fetchReset(CODEX_RESET_CREDITS_URL, {
@@ -5129,7 +5594,7 @@ const publicError = (code, message) => ({
5129
5594
  details: { issues: [] }
5130
5595
  }
5131
5596
  });
5132
- function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditService, preferences, runtimeManagement, diagnosticsReader, modelCatalog, originalImages, resolveInheritedOriginal, closeConnections, onAccountChanged }) {
5597
+ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditService, preferences, runtimeManagement, diagnosticsReader, modelCatalog, originalImages, resolveInheritedOriginal, closeConnections, onAccountChanged, onCleanupFailure }) {
5133
5598
  return async (endpoint, payload, signal) => {
5134
5599
  if (endpoint === "storage/status" || endpoint === "storage/clear-forecast") try {
5135
5600
  signal.throwIfAborted();
@@ -5287,23 +5752,20 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
5287
5752
  return publicError("internal", message);
5288
5753
  }
5289
5754
  const result = await authHandler(endpoint, payload, signal);
5290
- if (result.ok === true && [
5291
- "logout",
5292
- "account/select",
5293
- "account/remove"
5294
- ].includes(endpoint)) closeConnections?.();
5295
- if (endpoint === "account/remove" && result.ok === true && typeof payload?.id === "string") await usageReader.clearScope(payload.id);
5296
- if (endpoint === "logout" && result.ok === true) {
5297
- await usageReader.clear();
5298
- resetCreditService.clear();
5299
- modelCatalog?.clear();
5300
- onAccountChanged?.();
5301
- } else if (result.ok === true && (endpoint === "account/select" || endpoint === "account/remove" || endpoint === "login/status" && result.value?.authenticated === true)) {
5302
- usageReader.clearCache();
5303
- resetCreditService.clear();
5304
- modelCatalog?.clear();
5305
- onAccountChanged?.();
5306
- modelCatalog?.refresh({ signal: void 0 }).catch(() => {});
5755
+ if (result.ok === true && (endpoint === "logout" || endpoint === "account/select" || endpoint === "account/remove" || endpoint === "login/status" && result.value?.authenticated === true)) {
5756
+ const tasks = [
5757
+ () => closeConnections?.(),
5758
+ () => usageReader.clearCache(),
5759
+ () => resetCreditService.clear(),
5760
+ () => modelCatalog?.clear(),
5761
+ () => onAccountChanged?.()
5762
+ ];
5763
+ if (endpoint === "logout") tasks.push(() => usageReader.clear());
5764
+ if (endpoint === "account/remove" && typeof payload?.id === "string") tasks.push(() => usageReader.clearScope(payload.id));
5765
+ if ((await Promise.allSettled(tasks.map((task) => Promise.resolve().then(task)))).some((value) => value.status === "rejected")) try {
5766
+ onCleanupFailure?.();
5767
+ } catch {}
5768
+ if (endpoint !== "logout") Promise.resolve().then(() => modelCatalog?.refresh({ signal: void 0 })).catch(() => {});
5307
5769
  } else if (result.ok === true && (endpoint === "status" || result.value?.authenticated === true)) modelCatalog?.refresh({ signal: void 0 }).catch(() => {});
5308
5770
  return result;
5309
5771
  };
@@ -5381,6 +5843,8 @@ function apply(ctx, config = {}) {
5381
5843
  const settings = createSettingsAdapter(ctx, z.object(settingsFields), config, SETTINGS_NAMESPACE);
5382
5844
  const searchProvider = createSearchProviderSwitcher(ctx.loader);
5383
5845
  const network = createCodexNetworkTransport();
5846
+ const diagnosticOperations = createDiagnosticOperations();
5847
+ ctx.effect(() => ctx.on?.("tools/result", (exec, result) => diagnosticOperations.tool(exec, result)), "codex-subscription: operation evidence");
5384
5848
  const originalImages = new OriginalImageStore();
5385
5849
  const accountVault = ACCOUNT_VAULT_KEY !== void 0 && typeof ctx.credentials.readRecord === "function" && typeof ctx.credentials.modifyRecord === "function" && typeof ctx.credentials.deleteRecord === "function" ? new DshOAuthAccountVault(ctx.credentials, {
5386
5850
  key: ACCOUNT_VAULT_KEY,
@@ -5714,14 +6178,19 @@ function apply(ctx, config = {}) {
5714
6178
  network,
5715
6179
  modelCatalog,
5716
6180
  connection,
5717
- compaction
6181
+ compaction,
6182
+ operations: diagnosticOperations,
6183
+ runtimeManagement,
6184
+ storage: () => usageReader.storage(),
6185
+ tools: ctx.tools
5718
6186
  }),
6187
+ onCleanupFailure: () => diagnosticOperations.record("account/cleanup", "failed"),
5719
6188
  modelCatalog,
5720
6189
  closeConnections: () => connection.dispose(),
5721
6190
  originalImages,
5722
6191
  resolveInheritedOriginal: (sessionId, assetId) => inheritedOriginalImageRef(ctx.get?.("sessions")?.get?.(sessionId), assetId)
5723
6192
  });
5724
- const handler = (endpoint, payload, signal) => endpoint.startsWith("sketch/") ? sketchBridge.rpc(endpoint, payload) : subscriptionHandler(endpoint, payload, signal);
6193
+ const handler = diagnosticOperations.wrap((endpoint, payload, signal) => endpoint.startsWith("sketch/") ? sketchBridge.rpc(endpoint, payload) : subscriptionHandler(endpoint, payload, signal));
5725
6194
  ctx.effect(() => {
5726
6195
  modelCatalog.refresh().catch((error) => ctx.logger?.debug?.("could not refresh Codex model catalog: %s", error.message));
5727
6196
  }, "codex-subscription: official model catalog");