@eddyskywalker/dsh-chatgpt-subscription 0.46.1 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +22 -1
  3. package/lib/client.js +2639 -571
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +15598 -5902
  6. package/lib/plan-mode-pipeline.js +419 -0
  7. package/lib/plan-mode.js +783 -0
  8. package/lib/tool-result-pruner.js +400 -0
  9. package/lib/types/client/hub/brand-icons.d.ts +1 -1
  10. package/lib/types/client/hub/brand-icons.d.ts.map +1 -1
  11. package/lib/types/client/hub/brand-svg.d.ts +4 -0
  12. package/lib/types/client/hub/brand-svg.d.ts.map +1 -1
  13. package/lib/types/client/hub/providers.d.ts.map +1 -1
  14. package/lib/types/client/index.d.ts +2 -0
  15. package/lib/types/client/index.d.ts.map +1 -1
  16. package/lib/types/client/locales-ru/index.d.ts.map +1 -1
  17. package/lib/types/client/locales-ru/progress-updates.d.ts.map +1 -1
  18. package/lib/types/client/locales-ru/workbuddy.d.ts +10 -0
  19. package/lib/types/client/locales-ru/workbuddy.d.ts.map +1 -0
  20. package/lib/types/client/progress-updates/locales.d.ts +23 -5
  21. package/lib/types/client/progress-updates/locales.d.ts.map +1 -1
  22. package/lib/types/client/progress-updates/settings-binding.d.ts +13 -1
  23. package/lib/types/client/progress-updates/settings-binding.d.ts.map +1 -1
  24. package/lib/types/client/progress-updates/settings-section.d.ts +1 -1
  25. package/lib/types/client/progress-updates/settings-section.d.ts.map +1 -1
  26. package/lib/types/client/workbuddy/WorkBuddyComposerQuota.d.ts +34 -0
  27. package/lib/types/client/workbuddy/WorkBuddyComposerQuota.d.ts.map +1 -0
  28. package/lib/types/client/workbuddy/WorkBuddySection.d.ts +91 -0
  29. package/lib/types/client/workbuddy/WorkBuddySection.d.ts.map +1 -0
  30. package/lib/types/client/workbuddy/locales.d.ts +768 -0
  31. package/lib/types/client/workbuddy/locales.d.ts.map +1 -0
  32. package/lib/types/client/workbuddy/styles.d.ts +21 -0
  33. package/lib/types/client/workbuddy/styles.d.ts.map +1 -0
  34. package/lib/types/client/workbuddy/useWorkBuddySection.d.ts +149 -0
  35. package/lib/types/client/workbuddy/useWorkBuddySection.d.ts.map +1 -0
  36. package/lib/types/compat.d.ts +1 -1
  37. package/lib/types/entry-plan-mode-pipeline.d.ts +206 -0
  38. package/lib/types/entry-plan-mode-pipeline.d.ts.map +1 -0
  39. package/lib/types/entry-plan-mode.d.ts +353 -0
  40. package/lib/types/entry-plan-mode.d.ts.map +1 -0
  41. package/lib/types/entry-tool-result-pruner.d.ts +255 -0
  42. package/lib/types/entry-tool-result-pruner.d.ts.map +1 -0
  43. package/lib/types/host/agent-preset.d.ts.map +1 -1
  44. package/lib/types/host/cache-warmup/contracts.d.ts +5 -1
  45. package/lib/types/host/cache-warmup/contracts.d.ts.map +1 -1
  46. package/lib/types/host/cache-warmup/install.d.ts.map +1 -1
  47. package/lib/types/host/cache-warmup/projection.d.ts.map +1 -1
  48. package/lib/types/host/cache-warmup/retrieval-intent.d.ts +18 -0
  49. package/lib/types/host/cache-warmup/retrieval-intent.d.ts.map +1 -0
  50. package/lib/types/host/cache-warmup/settings.d.ts +9 -2
  51. package/lib/types/host/cache-warmup/settings.d.ts.map +1 -1
  52. package/lib/types/host/common/capabilities.d.ts.map +1 -1
  53. package/lib/types/host/common/model-request-control.d.ts +1 -1
  54. package/lib/types/host/common/model-request-control.d.ts.map +1 -1
  55. package/lib/types/host/common/request-diagnostics.d.ts +1 -1
  56. package/lib/types/host/common/request-diagnostics.d.ts.map +1 -1
  57. package/lib/types/host/compaction-policy-guard.d.ts +58 -0
  58. package/lib/types/host/compaction-policy-guard.d.ts.map +1 -0
  59. package/lib/types/host/compaction-prompt.d.ts +129 -0
  60. package/lib/types/host/compaction-prompt.d.ts.map +1 -0
  61. package/lib/types/host/plan-mode-cache-guard.d.ts +16 -0
  62. package/lib/types/host/plan-mode-cache-guard.d.ts.map +1 -0
  63. package/lib/types/host/preset-sync.d.ts.map +1 -1
  64. package/lib/types/host/ptc-sdk-cache-guard.d.ts +80 -0
  65. package/lib/types/host/ptc-sdk-cache-guard.d.ts.map +1 -0
  66. package/lib/types/host/tool-result-pruner-guard.d.ts +34 -0
  67. package/lib/types/host/tool-result-pruner-guard.d.ts.map +1 -0
  68. package/lib/types/host/workbuddy/account-pool.d.ts +389 -0
  69. package/lib/types/host/workbuddy/account-pool.d.ts.map +1 -0
  70. package/lib/types/host/workbuddy/adapter.d.ts +249 -0
  71. package/lib/types/host/workbuddy/adapter.d.ts.map +1 -0
  72. package/lib/types/host/workbuddy/atrest.d.ts +98 -0
  73. package/lib/types/host/workbuddy/atrest.d.ts.map +1 -0
  74. package/lib/types/host/workbuddy/checkin.d.ts +150 -0
  75. package/lib/types/host/workbuddy/checkin.d.ts.map +1 -0
  76. package/lib/types/host/workbuddy/client.d.ts +214 -0
  77. package/lib/types/host/workbuddy/client.d.ts.map +1 -0
  78. package/lib/types/host/workbuddy/dsml.d.ts +55 -0
  79. package/lib/types/host/workbuddy/dsml.d.ts.map +1 -0
  80. package/lib/types/host/workbuddy/guardrails.d.ts +111 -0
  81. package/lib/types/host/workbuddy/guardrails.d.ts.map +1 -0
  82. package/lib/types/host/workbuddy/identity.d.ts +71 -0
  83. package/lib/types/host/workbuddy/identity.d.ts.map +1 -0
  84. package/lib/types/host/workbuddy/mapper.d.ts +238 -0
  85. package/lib/types/host/workbuddy/mapper.d.ts.map +1 -0
  86. package/lib/types/host/workbuddy/model-catalog.d.ts +217 -0
  87. package/lib/types/host/workbuddy/model-catalog.d.ts.map +1 -0
  88. package/lib/types/host/workbuddy/oauth.d.ts +47 -0
  89. package/lib/types/host/workbuddy/oauth.d.ts.map +1 -0
  90. package/lib/types/host/workbuddy/outbound-identity.d.ts +139 -0
  91. package/lib/types/host/workbuddy/outbound-identity.d.ts.map +1 -0
  92. package/lib/types/host/workbuddy/routes.d.ts +56 -0
  93. package/lib/types/host/workbuddy/routes.d.ts.map +1 -0
  94. package/lib/types/host/workbuddy/token-store.d.ts +370 -0
  95. package/lib/types/host/workbuddy/token-store.d.ts.map +1 -0
  96. package/lib/types/host/workbuddy/types.d.ts +179 -0
  97. package/lib/types/host/workbuddy/types.d.ts.map +1 -0
  98. package/lib/types/index.d.ts +35 -0
  99. package/lib/types/index.d.ts.map +1 -1
  100. package/lib/types/shared/workbuddy-contracts.d.ts +531 -0
  101. package/lib/types/shared/workbuddy-contracts.d.ts.map +1 -0
  102. package/package.json +14 -2
  103. package/presets/dispatch/README.md +57 -3
  104. package/presets/dispatch/agent.cordis.yml +63 -3
@@ -0,0 +1,98 @@
1
+ /**
2
+ * `$wbEncrypted` at-rest credential envelope.
3
+ *
4
+ * The CodeBuddy/WorkBuddy desktop client stopped writing plaintext tokens on
5
+ * 2026-09-24: `auth.accessToken` / `auth.refreshToken` became
6
+ * `{"$wbEncrypted": 1, "envelope": base64}` (the domestic build also seals
7
+ * `nickname` / `phoneNumber`). Reading such a file the way this plugin used to
8
+ * yields the envelope TEXT as the bearer token, so every request answers 401 —
9
+ * the adoption path has to decrypt before it can use the credential.
10
+ *
11
+ * The scheme below is transcribed from the client's `packages/at-rest-crypto`
12
+ * as reimplemented by workbuddy2api-hub (`wb_atrest.py`, MIT); the framing
13
+ * constants are the part that must match byte for byte:
14
+ *
15
+ * key = sha256(atRestSecretKey as an ASCII string) # the protector key
16
+ * keyId = sha256(key).hex[0..16] # `protectorKeyId`
17
+ * AAD = "WB-AAD\0" | 0x01 | LP(fileMagic) | LP("sym-v1")
18
+ * | u32(suite) | LP(keyId) | framingCode | 0x00 | 0x00
19
+ * envelope = base64(JSON{suite, keyId, nonce, authTag, ciphertext}) # AES-256-GCM
20
+ * framing : the keyblob's wrapped master key is `WBEF1`/1, a sealed `.info`
21
+ * field is `WBEV1`/2 — the SAME protector key opens both.
22
+ *
23
+ * Key material never leaves the process and is never logged: the user pastes it
24
+ * once, it is stored through the platform credential store (DPAPI / Keychain /
25
+ * Secret Service), and every read re-validates it against the client's own
26
+ * keyblob when that file exists.
27
+ */
28
+ /** Field name the client uses to mark a sealed value. */
29
+ export declare const ENVELOPE_KEY = "$wbEncrypted";
30
+ /** Framing kinds and the code each carries in the AAD. */
31
+ declare const FRAMING: {
32
+ readonly file: {
33
+ readonly magic: "WBEF1";
34
+ readonly code: 1;
35
+ };
36
+ readonly field: {
37
+ readonly magic: "WBEV1";
38
+ readonly code: 2;
39
+ };
40
+ };
41
+ type Framing = keyof typeof FRAMING;
42
+ /** Every failure the at-rest path can report to a user. */
43
+ export declare class AtRestError extends Error {
44
+ constructor(message: string);
45
+ }
46
+ /** The GCM associated data, byte-identical to the client's. */
47
+ export declare function associatedData(keyId: string, suite: number, framing: Framing): Buffer;
48
+ /** `protectorKeyId` / `envelope.keyId`: the first 16 hex characters of sha256(key). */
49
+ export declare function deriveKeyId(key: Buffer): string;
50
+ /**
51
+ * Whether a value is a sealed envelope rather than plaintext.
52
+ *
53
+ * Older clients sometimes stored the whole envelope as a JSON STRING in the
54
+ * field, so that shape is recognised too.
55
+ */
56
+ export declare function isSealedValue(value: unknown): boolean;
57
+ interface SealedEnvelope {
58
+ suite: number;
59
+ keyId: string;
60
+ nonce: Buffer;
61
+ authTag: Buffer;
62
+ ciphertext: Buffer;
63
+ }
64
+ /** Split one envelope into its GCM parts. */
65
+ export declare function parseEnvelope(value: unknown): SealedEnvelope;
66
+ /** Open one envelope with the protector key, returning the plaintext bytes. */
67
+ export declare function openEnvelope(key: Buffer, value: unknown, framing: Framing): Buffer;
68
+ /** Decrypt a field that may be sealed, leaving plaintext values untouched. */
69
+ export declare function openSealedField(key: Buffer, value: unknown): string;
70
+ /**
71
+ * Normalise a pasted secret into the 32-byte protector key.
72
+ *
73
+ * Two spellings are accepted, neither requiring the user to compute anything:
74
+ * a 64-character hex protector key (used as-is), or the 44-character base64
75
+ * `atRestSecretKey` the client carries in its payload — that one is hashed as
76
+ * the STRING, which is what produces the protector key.
77
+ */
78
+ export declare function parseKeyText(text: string): Buffer;
79
+ /** Path of the client's keyblob, or null when the client is not installed here. */
80
+ export declare function keyBlobPath(): string | null;
81
+ /**
82
+ * Validate a protector key against the client's own keyblob.
83
+ *
84
+ * This is the check that turns a typo into an error instead of a mystery: the
85
+ * key must match the `static-v1` slot's `protectorKeyId` and must unwrap a
86
+ * 32-byte master whose derived id matches the blob's own `keyId`. Returns
87
+ * `false` when there is no keyblob to check against (the desktop client is not
88
+ * installed on this machine) — absence of evidence, not evidence of a bad key.
89
+ */
90
+ export declare function verifyKeyAgainstKeyBlob(key: Buffer): Promise<boolean>;
91
+ /** Install the protector key for this process (already persisted by the caller). */
92
+ export declare function setActiveAtRestKey(key: Buffer | null): void;
93
+ /** The protector key this process holds, if any. */
94
+ export declare function activeAtRestKey(): Buffer | null;
95
+ /** Whether sealed credentials can be opened right now. */
96
+ export declare function hasAtRestKey(): boolean;
97
+ export {};
98
+ //# sourceMappingURL=atrest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atrest.d.ts","sourceRoot":"","sources":["../../../../src/host/workbuddy/atrest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAQH,yDAAyD;AACzD,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAE3C,0DAA0D;AAC1D,QAAA,MAAM,OAAO;;;;;;;;;CAGH,CAAC;AAEX,KAAK,OAAO,GAAG,MAAM,OAAO,OAAO,CAAC;AAQpC,2DAA2D;AAC3D,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM;CAI5B;AAeD,+DAA+D;AAC/D,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAWrF;AAED,uFAAuF;AACvF,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAUrD;AAED,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAOD,6CAA6C;AAC7C,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAsB5D;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAkBlF;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAKnE;AAKD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQjD;AAUD,mFAAmF;AACnF,wBAAgB,WAAW,IAAI,MAAM,GAAG,IAAI,CAM3C;AAwBD;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAkB3E;AAID,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAE3D;AAED,oDAAoD;AACpD,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAE/C;AAED,0DAA0D;AAC1D,wBAAgB,YAAY,IAAI,OAAO,CAEtC"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Daily check-in scheduler for the CN-region billing activity.
3
+ *
4
+ * Ported from workbuddy2api's `daily_checkin.py` and adapted to this host:
5
+ * the cron job becomes an in-process timer (the plugin runs inside the DSH
6
+ * host, so a day the host never runs is a day nothing signs in — accepted and
7
+ * documented), and the per-file state JSON becomes one state document under
8
+ * the harness home. The semantics are kept exactly:
9
+ *
10
+ * - **CN only.** The international deployment exposes no check-in surface, so
11
+ * those accounts are skipped rather than failing daily.
12
+ * - **Idempotent.** The activity status is queried first; an account already
13
+ * signed in (by anyone) is only recorded. An account whose activity reports
14
+ * no entitlement is parked (see {@link CHECKIN_INACTIVE_RECHECK_MS}) rather
15
+ * than abandoned for the day: the host often starts before the activity's
16
+ * daily opening, and treating that first answer as final cost the account its
17
+ * only chance to sign in. Once everyone is settled, a tick sends no requests.
18
+ * - **Bounded.** A failing account is retried at most {@link CHECKIN_ATTEMPT_CAP}
19
+ * times a day, so a broken account cannot turn the ten-minute tick into a
20
+ * request loop. A manual run ignores the cap and the window, like the
21
+ * reference script's `--now`, and is queued behind an in-flight run rather
22
+ * than dropped into it (a click must actually mean "try again now").
23
+ *
24
+ * Tokens stay host-side: the only write beyond the state file is the existing
25
+ * credential store's refresh write-back, reused here unchanged.
26
+ */
27
+ import { type FileCredentialStore } from './token-store.ts';
28
+ import type { WorkBuddyCheckinSettings, WorkBuddyCheckinSummary } from '../../shared/workbuddy-contracts.ts';
29
+ /** Activity status surface; answers whether today is signed in already. */
30
+ export declare const CHECKIN_STATUS_PATH = "/billing/meter/checkin-activity-status";
31
+ /** Check-in surface; credits are granted by posting here once a day. */
32
+ export declare const CHECKIN_PATH = "/billing/meter/daily-checkin";
33
+ /**
34
+ * Scheduler cadence. The first tick at host startup is the daily run; the
35
+ * interval keeps a long-lived process signing in on later days without a
36
+ * restart. Idle ticks cost no requests — the day state short-circuits them.
37
+ */
38
+ export declare const CHECKIN_TICK_MS: number;
39
+ /** Daily retry ceiling for one account after a failed attempt. */
40
+ export declare const CHECKIN_ATTEMPT_CAP = 3;
41
+ export declare const CHECKIN_TIMEOUT_MS = 20000;
42
+ /**
43
+ * How long an unentitled account waits before the activity is polled again.
44
+ *
45
+ * The activity opens on its own schedule, so the first inactive answer of the
46
+ * day is frequently 'not open yet' rather than 'no entitlement today'. Hourly
47
+ * re-checks keep a host that booted early from missing the day, while still
48
+ * costing at most one status request per hour per account.
49
+ */
50
+ export declare const CHECKIN_INACTIVE_RECHECK_MS: number;
51
+ /**
52
+ * Random pause between two accounts in one pass.
53
+ *
54
+ * Sequential accounts already serialise their requests, but they still arrive as
55
+ * one tight cluster from one outbound IP; a dozen-account pool doing that at the
56
+ * same minute is the burst pattern the sibling gateway added jitter for after
57
+ * seeing upstream rate-limit whole pools.
58
+ */
59
+ export declare const CHECKIN_JITTER_MIN_MS = 2000;
60
+ export declare const CHECKIN_JITTER_MAX_MS = 5000;
61
+ /** State document the scheduler keeps beside the other plugin stores. */
62
+ export declare function checkinStatePath(): string;
63
+ /** Local calendar day an instant belongs to; state entries are keyed by it. */
64
+ export declare function localDateString(nowMs: number): string;
65
+ export interface WorkBuddyCheckinServiceOptions {
66
+ fetchFn?: typeof fetch;
67
+ /** State document path; tests redirect it, production uses {@link checkinStatePath}. */
68
+ statePath?: string;
69
+ /** Live preferences, re-read on every tick so the toggle applies at once. */
70
+ settings: () => WorkBuddyCheckinSettings;
71
+ /**
72
+ * Resolves once the preference store has read its stored document.
73
+ *
74
+ * Optional for callers whose settings are already in memory; the host passes
75
+ * the preference store's own warmup so the startup pass cannot act on a
76
+ * default that the stored document is about to overwrite.
77
+ */
78
+ ready?: () => Promise<void>;
79
+ /** Clock injection for tests. */
80
+ now?: () => number;
81
+ /** Wait between two accounts in one pass; injected so tests run without delays. */
82
+ pause?: (ms: number) => Promise<void>;
83
+ /** Jitter source; injected so a test can pin the delay it caused. */
84
+ random?: () => number;
85
+ logger?: {
86
+ info(message: string): void;
87
+ warn(message: string): void;
88
+ };
89
+ }
90
+ /**
91
+ * The check-in scheduler. One instance per plugin load; the host owns the
92
+ * interval and clears it from the same `ctx.effect` that created it.
93
+ */
94
+ export declare class WorkBuddyCheckinService {
95
+ private readonly store;
96
+ private readonly options;
97
+ private readonly fetchFn;
98
+ private readonly statePath;
99
+ private readonly now;
100
+ private readonly pause;
101
+ private readonly random;
102
+ private readonly logger;
103
+ private state;
104
+ private loaded;
105
+ /** Passes waiting to run, in order; `true` marks a manual pass. */
106
+ private readonly queue;
107
+ /** The drain loop currently working the queue, if any. */
108
+ private draining;
109
+ private lastRunAt;
110
+ /** Memoized preference warmup, so it is awaited once however many passes run. */
111
+ private readyWait;
112
+ constructor(store: FileCredentialStore, options: WorkBuddyCheckinServiceOptions);
113
+ /**
114
+ * One scheduler pass. Automatic passes honour the toggle and the retry cap;
115
+ * a manual pass (`manual: true`, the card's "check in now") ignores both
116
+ * but still respects an account already signed in today.
117
+ *
118
+ * A manual pass that lands while a run is already in flight is *queued*, not
119
+ * absorbed: joining the running pass would silently discard the one thing the
120
+ * button promises, that the retry cap and the toggle are bypassed now.
121
+ */
122
+ tick(manual?: boolean): Promise<void>;
123
+ /** Run queued passes one after another until the queue empties. */
124
+ private drain;
125
+ private runQueue;
126
+ private run;
127
+ /** Wait for the preference store's warmup read, once. */
128
+ private ready;
129
+ /** Check one account in, recording the outcome in its today's entry. */
130
+ private checkinOne;
131
+ /** Record a confirmed sign-in, clearing the transient failure bookkeeping. */
132
+ private markSignedIn;
133
+ /**
134
+ * Refresh a token the server rejected, bypassing the store's expiry check.
135
+ *
136
+ * `ensureFresh` only acts on a locally expired token, so a session the server
137
+ * revoked early would otherwise fail every retry until the file's own clock
138
+ * caught up. The rotation is written back through the store either way.
139
+ */
140
+ private forceRefresh;
141
+ /**
142
+ * The aggregate the settings card renders. Reads only local state and the
143
+ * account list; it never spends a network request.
144
+ */
145
+ summary(): Promise<WorkBuddyCheckinSummary>;
146
+ private load;
147
+ private save;
148
+ private post;
149
+ }
150
+ //# sourceMappingURL=checkin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkin.d.ts","sourceRoot":"","sources":["../../../../src/host/workbuddy/checkin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,214 @@
1
+ import type { WorkBuddyAccount, WorkBuddyAccountQuota, WorkBuddyMeter } from '../../shared/workbuddy-contracts.ts';
2
+ import { type WorkBuddyOutboundProduct } from './outbound-identity.ts';
3
+ import { PROVIDER_ID, PROVIDER_NAME } from './types.ts';
4
+ import { FileCredentialStore, type WorkBuddyCredentials } from './token-store.ts';
5
+ import type { WorkBuddyTokenIdentity } from './identity.ts';
6
+ import { type WorkBuddyModelEntry } from './model-catalog.ts';
7
+ export interface WorkBuddyRequestOptions {
8
+ fetchFn?: typeof fetch;
9
+ signal?: AbortSignal;
10
+ }
11
+ /**
12
+ * Which chat surface a call is, for the headers only that surface needs.
13
+ *
14
+ * Passing this opts the request into the conversation header group. It is
15
+ * deliberately a separate argument rather than a consequence of `sessionId`
16
+ * being present: the group belongs to the chat endpoint's channel check, and
17
+ * the config, billing, refresh and check-in surfaces keep the exact header set
18
+ * they already worked with.
19
+ */
20
+ export interface WorkBuddyChannelOptions {
21
+ /**
22
+ * Which client identity this request presents.
23
+ *
24
+ * Omitted means the line's shipped identity (`cli`). Only the chat call ever
25
+ * sets it: the config, billing and refresh surfaces are the ones measured to
26
+ * accept the CLI identity, and the choice exists for the chat fingerprint.
27
+ */
28
+ product?: WorkBuddyOutboundProduct;
29
+ /**
30
+ * Harness session the chat call belongs to.
31
+ *
32
+ * Omit it for a call with no conversation behind it (the connection-test
33
+ * probe). The group is still sent — the gateway requires it either way — but
34
+ * the scoped ids fall back to random values.
35
+ */
36
+ sessionId?: string;
37
+ /**
38
+ * Stable per-account key the conversation ids are derived from.
39
+ *
40
+ * Load-bearing for the pool: two accounts sending the SAME conversation id
41
+ * would present one device signing in as two identities, which is exactly
42
+ * what the gateway's channel check exists to catch. Naming the account here is
43
+ * what keeps every account's ids its own.
44
+ */
45
+ accountKey?: string;
46
+ /**
47
+ * One user send, as `userTurnKey` in `mapper.ts` derives it.
48
+ *
49
+ * Every tool step, retry and renewal replay of the SAME user turn keeps this
50
+ * value, so the gateway reads one send rather than N unrelated requests; the
51
+ * user's next message changes it. Absent or empty means "no turn to name" and
52
+ * degrades to a fresh id, which is the behaviour every non-chat surface keeps.
53
+ */
54
+ turnKey?: string;
55
+ /**
56
+ * This call goes to the Anthropic Messages surface.
57
+ *
58
+ * Adds its protocol headers. Deliberately explicit rather than inferred from
59
+ * the URL: the two surfaces want different header sets, and a call that
60
+ * guessed wrong would be refused for a reason the caller could have stated.
61
+ */
62
+ messages?: boolean;
63
+ }
64
+ /**
65
+ * Attribution and identity headers for one backend request.
66
+ *
67
+ * The gateway gates on this header set: the international deployment answers
68
+ * 401 without the CLI user agent and matching `Origin`/`Referer`, and both
69
+ * deployments read the account identity headers rather than deriving it from
70
+ * the bearer token alone.
71
+ *
72
+ * `channel` marks a request to the chat endpoint, which is the only surface
73
+ * whose channel check wants the {@link conversationHeaders} group.
74
+ */
75
+ export declare function workBuddyHeaders(credentials: Pick<WorkBuddyCredentials, 'accessToken' | 'domain' | 'uid' | 'enterpriseId' | 'backend'>, extra?: Record<string, string>, channel?: WorkBuddyChannelOptions): Record<string, string>;
76
+ /**
77
+ * Content coding a chat request's body is sent with.
78
+ *
79
+ * Measured: the official CLI's `GzipRequestProcessor` compresses each POST's
80
+ * body and sets `Content-Encoding: gzip`, and both builds turn that feature on.
81
+ * The saving is not marginal — a long agent conversation re-sends its whole
82
+ * history on every step, which plain-JSON uploads made bytes-on-the-wire the
83
+ * dominant cost of using this route.
84
+ *
85
+ * Only a chat completion is compressed: the same fetch also serves surfaces
86
+ * whose parser reads plain JSON (the images API answers 400 `code 11101`
87
+ * "invalid character '\\x1f'" to a gzipped body), so the caller opts in by
88
+ * calling this rather than every request getting it.
89
+ */
90
+ export declare const GZIP_CONTENT_ENCODING = "gzip";
91
+ /** One chat body, compact JSON, gzipped for the wire. */
92
+ export declare function gzipChatBody(body: string): Uint8Array;
93
+ /** Header set that declares one request's body coding. */
94
+ export declare function encodedBodyHeaders(encoding: string | undefined): Record<string, string>;
95
+ /**
96
+ * Refresh an access token against the endpoint the official IDE uses.
97
+ *
98
+ * The response carries a whole new `auth` block rather than just a token, so it
99
+ * is merged over the current one: fields the endpoint omits (the domain, the
100
+ * refresh lifetime) must survive, or the next refresh would lose its target.
101
+ */
102
+ export declare function refreshCredentials(credentials: WorkBuddyCredentials, options?: WorkBuddyRequestOptions): Promise<WorkBuddyCredentials>;
103
+ /** Public account facts for the settings card. */
104
+ export declare function accountFromCredentials(credentials: WorkBuddyCredentials): WorkBuddyAccount;
105
+ /**
106
+ * Read the signed-in account for a credential.
107
+ *
108
+ * The browser-authorization token response states the access token and the
109
+ * refresh token but **not** which account they belong to, so a login that
110
+ * relied on it alone could only key the account by the display name it also
111
+ * did not have — which is where the duplicate row and the `账号 1` label came
112
+ * from. The official client reads the account from this same endpoint right
113
+ * after polling the token; measured live, it returns the uid, the nickname,
114
+ * the UIN and the enterprise id for a valid bearer.
115
+ *
116
+ * A failure is not fatal to the sign-in: the token is usable, so the caller
117
+ * keeps the credential it has and lets the token's own claims fill in what
118
+ * they can.
119
+ */
120
+ export declare function fetchAccountIdentity(credentials: WorkBuddyCredentials, options?: WorkBuddyRequestOptions): Promise<WorkBuddyTokenIdentity | null>;
121
+ /**
122
+ * Parse one `/v3/config` payload's model list.
123
+ *
124
+ * Each entry carries the facts this route needs directly, so nothing is
125
+ * inferred from the model's name. Image-generation tools (`tags` naming
126
+ * `text-to-image`/`image-to-image`) are skipped: they are not chat models and
127
+ * carry no context window.
128
+ *
129
+ * `credits` is read here rather than left to the shipped table because a
130
+ * reachable gateway **replaces** that table entirely: a multiplier transcribed
131
+ * offline would be the only one a signed-in user ever saw, and it would stay
132
+ * wrong the moment the subscription repriced a model. A string this route cannot
133
+ * turn into a number (`''`, which `default-model` publishes, or a value a
134
+ * future gateway invents) leaves the field off the entry altogether instead of
135
+ * writing a plausible-looking `0` — see `parseWorkBuddyCreditMultiplier`.
136
+ */
137
+ export declare function parseConfigModels(payload: unknown, region: WorkBuddyCredentials['region']): WorkBuddyModelEntry[];
138
+ export declare function getCachedCatalog(): WorkBuddyModelEntry[];
139
+ export declare function clearCachedCatalog(): void;
140
+ /**
141
+ * Fetch the live catalog for one account.
142
+ *
143
+ * The gateway is the authority on capabilities, so a successful read replaces
144
+ * the shipped table entirely; a failure returns an empty list and the caller
145
+ * falls back, which keeps a first run usable before sign-in.
146
+ */
147
+ export declare function fetchConfigCatalog(credentials: WorkBuddyCredentials, options?: WorkBuddyRequestOptions): Promise<WorkBuddyModelEntry[]>;
148
+ /** Cached catalog with a TTL; a failed refresh keeps the previous snapshot.
149
+ *
150
+ * On the first cache miss of a process the persisted snapshot is rehydrated
151
+ * first, so a restart serves the previous listing without waiting on the
152
+ * network. An expired snapshot still answers immediately (stale) while a
153
+ * refresh runs in the background — the catalog is not fast-moving data, and
154
+ * waiting on an unreachable gateway turned a restart into the full discovery
155
+ * timeout on every model resolution.
156
+ */
157
+ export declare function loadConfigCatalog(credentials: WorkBuddyCredentials, options?: WorkBuddyRequestOptions & {
158
+ force?: boolean;
159
+ }): Promise<WorkBuddyModelEntry[]>;
160
+ /** Parse one `Capacity*` account record into the public quota shape. */
161
+ export interface ParsedBilling {
162
+ packageName: string | null;
163
+ totalCredits: number | null;
164
+ remainingCredits: number | null;
165
+ cycleUsedCredits: number | null;
166
+ cycleCredits: number | null;
167
+ cycleStartsAt: number | null;
168
+ cycleEndsAt: number | null;
169
+ }
170
+ /**
171
+ * Parse the billing payload's account list.
172
+ *
173
+ * The response nests as `data.Response.Data.Accounts[]`; each entry describes
174
+ * one purchased package with its own capacity counters. The first entry is the
175
+ * active subscription, and the totals are summed across all of them because a
176
+ * user can hold several packages at once.
177
+ */
178
+ export declare function parseBilling(payload: unknown): ParsedBilling;
179
+ /**
180
+ * Parse a cycle boundary.
181
+ *
182
+ * The service sends these as local-time strings without a zone
183
+ * (`2026-09-01 00:00:00`), which `Date.parse` reads as local time — the same
184
+ * reading the IDE's own panel uses, so the card agrees with it.
185
+ */
186
+ export declare function parseCycleTime(value: unknown): number | null;
187
+ /** Turn a parsed billing snapshot into the meters the card renders. */
188
+ export declare function billingMeters(billing: ParsedBilling): WorkBuddyMeter[];
189
+ /**
190
+ * The snapshot read most recently, whichever account it belongs to.
191
+ *
192
+ * Kept with a no-argument signature for backward compatibility; the page-level
193
+ * facts use {@link getCachedQuotaFor} instead, because they describe one
194
+ * account and must never be another account's numbers.
195
+ */
196
+ export declare function getCachedQuota(): WorkBuddyAccountQuota | undefined;
197
+ /**
198
+ * The snapshot remembered for one account, if it was read before.
199
+ *
200
+ * This is the lookup the account pool calls while it builds its summaries: a
201
+ * pure memory read, never a fetch. Passing no account keeps the pre-pool
202
+ * behavior of returning whatever was fetched last.
203
+ */
204
+ export declare function getCachedQuotaFor(accountId?: string | null): WorkBuddyAccountQuota | undefined;
205
+ export declare function clearCachedQuota(): void;
206
+ /**
207
+ * Read the account's credit allowance.
208
+ *
209
+ * Only one billing route exists here, so a failure is a real failure rather
210
+ * than a partially-answered snapshot: the caller decides whether to surface it.
211
+ */
212
+ export declare function fetchAccountQuota(store?: FileCredentialStore, fetchFn?: typeof fetch, force?: boolean, accountId?: string | null, hiddenAccountIds?: readonly string[]): Promise<WorkBuddyAccountQuota>;
213
+ export { PROVIDER_ID, PROVIDER_NAME };
214
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/host/workbuddy/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Recovery of tool calls the model PRINTED instead of emitting structurally.
3
+ *
4
+ * Measured upstream behaviour (and the reason workbuddy2api-hub has the same
5
+ * recovery, `wb_proxy.py` "Printed tool calls on the Chat Completions path"):
6
+ * a tools request sometimes answers with the call spelled out inside `content`
7
+ * — either the full-width DSML form
8
+ *
9
+ * <||DSML|| calls><||DSML|| invoke name="run_code">
10
+ * <||DSML|| parameter name="code">print(1)</||DSML|| parameter>
11
+ * </||DSML|| invoke></||DSML|| calls>
12
+ *
13
+ * or a hybrid the model mixes on its own: a plain opening tag closed by DSML
14
+ * tags, with the first parameter's close tag swallowed. Forwarded verbatim the
15
+ * client sees prose, finds no call, and the turn simply ends — reported by
16
+ * users as "it keeps stopping by itself".
17
+ *
18
+ * A structured `tool_calls` delta always wins; this only runs for text, which
19
+ * is why the recovery is a pure function over the accumulated content.
20
+ */
21
+ /** One recovered call, in the shape the mapper emits. */
22
+ export interface PrintedToolCall {
23
+ id: string;
24
+ name: string;
25
+ arguments: string;
26
+ }
27
+ /** A successful recovery: the calls plus the prose left behind. */
28
+ export interface PrintedRecovery {
29
+ calls: PrintedToolCall[];
30
+ clean: string;
31
+ }
32
+ /** Recover the full-width `<||DSML|| …>` spelling. */
33
+ export declare function parseDsmlToolCalls(text: string): PrintedRecovery | null;
34
+ /**
35
+ * Recover the hybrid spelling, driven by the `<parameter>` elements.
36
+ *
37
+ * Parameter elements are spelled the same way in both shapes and a parameter
38
+ * name is the one thing a call cannot do without, so they are the anchor; the
39
+ * nearest preceding opening tag that is neither a wrapper nor a parameter names
40
+ * the tool.
41
+ */
42
+ export declare function parseLeakedToolCalls(text: string): PrintedRecovery | null;
43
+ /** Recover a printed call under either spelling. */
44
+ export declare function recoverPrintedToolCalls(text: string): PrintedRecovery | null;
45
+ /**
46
+ * Index from which a streaming text buffer must be held back.
47
+ *
48
+ * The earliest `<` that either opens a recognisable tag or could still grow
49
+ * into one (a delta may split `<parameter` across frames), or -1 when the
50
+ * whole buffer is safe to emit as prose.
51
+ */
52
+ export declare function printedCallHoldIndex(text: string): number;
53
+ /** Whether a held buffer has already reached a closing tag. */
54
+ export declare function printedCallLooksComplete(text: string): boolean;
55
+ //# sourceMappingURL=dsml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dsml.d.ts","sourceRoot":"","sources":["../../../../src/host/workbuddy/dsml.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,yDAAyD;AACzD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;CACf;AA2CD,sDAAsD;AACtD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAiBvE;AAiBD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAyFzE;AAED,oDAAoD;AACpD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAE5E;AAgBD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASzD;AAED,+DAA+D;AAC/D,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Local quota guardrails for the WorkBuddy line.
3
+ *
4
+ * The subscription meters an account × model over a 24h window (429 `code
5
+ * 6004`), but it only says so AFTER the window is gone — and on the shared
6
+ * account that window is minutes of a large model or a day of a small one. These
7
+ * guardrails stop serving BEFORE that happens, so an exhausted window never has
8
+ * to be discovered by a user staring at a 429.
9
+ *
10
+ * Three of the four guardrails workbuddy2api-hub ships are reproduced here, and
11
+ * all three are EXACT — they read numbers this plugin already has:
12
+ *
13
+ * reserveCredits stop the account when its remaining credits fall below N
14
+ * (the billing snapshot already reports the remainder)
15
+ * dailyTokens stop the account for the rest of the local day at N tokens
16
+ * modelDailyTokens stop ONE model for the rest of the local day at N tokens
17
+ *
18
+ * The fourth one there — a daily CREDITS budget — is deliberately NOT
19
+ * reproduced: a request's credit cost is not on the wire this route reads, and
20
+ * deriving it from the catalog multiplier × tokens would invent a number that
21
+ * silently diverges from the bill. A guardrail that lies is worse than none.
22
+ *
23
+ * `0` means OFF for every field, and the default is all-off: an install that
24
+ * never opens this setting behaves exactly as before.
25
+ */
26
+ import type { WorkBuddyGuardrailsDto } from '../../shared/workbuddy-contracts.ts';
27
+ /**
28
+ * The three exact guardrails.
29
+ *
30
+ * The shape has ONE definition — the shared wire contract — so the card and the
31
+ * enforcement can never disagree about a field name.
32
+ */
33
+ export type WorkBuddyGuardrails = WorkBuddyGuardrailsDto;
34
+ /** Shipped default: every guardrail off. */
35
+ export declare const DEFAULT_GUARDRAILS: WorkBuddyGuardrails;
36
+ /** Narrow a stored or patched value into guardrails; anything unusable means off. */
37
+ export declare function normalizeGuardrails(value: unknown): WorkBuddyGuardrails;
38
+ /** Whether any guardrail is armed; a no-op check must stay cheap. */
39
+ export declare function guardrailsArmed(guardrails: WorkBuddyGuardrails): boolean;
40
+ /**
41
+ * The local calendar day a counter belongs to.
42
+ *
43
+ * Local, not UTC, because the reset the user reasons about is their own
44
+ * midnight — the same choice this line makes for the CN check-in day.
45
+ */
46
+ export declare function localDayKey(now?: number): string;
47
+ /** Per-account counters for one day. */
48
+ export interface UsageLedgerEntry {
49
+ tokens: number;
50
+ perModel: Record<string, number>;
51
+ }
52
+ /** Where the local usage counters live. */
53
+ export declare function usageLedgerPath(): string;
54
+ /**
55
+ * Token counters per account per local day.
56
+ *
57
+ * Deliberately a small persisted document rather than an accounting system: it
58
+ * exists to answer one question ("has this account / model used its day?") and a
59
+ * restart must not forget it, or a guardrail would reset by restarting DSH.
60
+ */
61
+ export declare class WorkBuddyUsageLedger {
62
+ private readonly filePath;
63
+ private readonly now;
64
+ private document;
65
+ private loaded;
66
+ private writing;
67
+ constructor(filePath?: string, now?: () => number);
68
+ /** Read the counters once; a missing or unreadable file starts a fresh day. */
69
+ load(): Promise<void>;
70
+ /** Drop yesterday's counters the moment a read notices the day changed. */
71
+ private rollIfNewDay;
72
+ /** Tokens one account has used today, across models. */
73
+ tokensToday(accountId: string): number;
74
+ /** Tokens one account has used today on one model. */
75
+ modelTokensToday(accountId: string, model: string): number;
76
+ /** Today's counters, for the settings card and for tests. */
77
+ snapshot(): Readonly<Record<string, UsageLedgerEntry>>;
78
+ /**
79
+ * Add one request's tokens.
80
+ *
81
+ * Serialized through a promise chain: two concurrent requests on different
82
+ * accounts would otherwise read the same document, add their own counts and
83
+ * write, losing one of them.
84
+ */
85
+ record(accountId: string, model: string, tokens: number): Promise<void>;
86
+ private persist;
87
+ }
88
+ /** Why an account may not serve a request. */
89
+ export type GuardrailReason = 'reserve-credits' | 'daily-tokens' | 'model-daily-tokens';
90
+ /** The verdict for one account on one model. */
91
+ export type GuardrailVerdict = {
92
+ allowed: true;
93
+ } | {
94
+ allowed: false;
95
+ reason: GuardrailReason;
96
+ detail: string;
97
+ };
98
+ /**
99
+ * Decide whether one account may serve one model right now.
100
+ *
101
+ * Pure on purpose: every number it reads is passed in, so the decision can be
102
+ * tested without a pool, a clock or a network.
103
+ */
104
+ export declare function evaluateGuardrails(input: {
105
+ guardrails: WorkBuddyGuardrails;
106
+ tokensToday: number;
107
+ modelTokensToday: number;
108
+ /** Remaining credits from the billing snapshot; null when never read. */
109
+ remainingCredits: number | null | undefined;
110
+ }): GuardrailVerdict;
111
+ //# sourceMappingURL=guardrails.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guardrails.d.ts","sourceRoot":"","sources":["../../../../src/host/workbuddy/guardrails.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAA;AAEjF;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,sBAAsB,CAAA;AAExD,4CAA4C;AAC5C,eAAO,MAAM,kBAAkB,EAAE,mBAIhC,CAAA;AAOD,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CAQvE;AAED,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,UAAU,EAAE,mBAAmB,GAAG,OAAO,CAExE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,SAAa,GAAG,MAAM,CAKpD;AAED,wCAAwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACjC;AAQD,2CAA2C;AAC3C,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;;;;;GAMG;AACH,qBAAa,oBAAoB;IAM7B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG;IANtB,OAAO,CAAC,QAAQ,CAAmE;IACnF,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,OAAO,CAAmC;gBAG/B,QAAQ,SAAoB,EAC5B,GAAG,GAAE,MAAM,MAAyB;IAGvD,+EAA+E;IACzE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAqC3B,2EAA2E;IAC3E,OAAO,CAAC,YAAY;IAMpB,wDAAwD;IACxD,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAKtC,sDAAsD;IACtD,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IAK1D,6DAA6D;IAC7D,QAAQ,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAKtD;;;;;;OAMG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAY/D,OAAO;CAMtB;AAED,8CAA8C;AAC9C,MAAM,MAAM,eAAe,GAAG,iBAAiB,GAAG,cAAc,GAAG,oBAAoB,CAAA;AAEvF,gDAAgD;AAChD,MAAM,MAAM,gBAAgB,GACxB;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GACjB;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAE/D;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,UAAU,EAAE,mBAAmB,CAAA;IAC/B,WAAW,EAAE,MAAM,CAAA;IACnB,gBAAgB,EAAE,MAAM,CAAA;IACxB,yEAAyE;IACzE,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CAC5C,GAAG,gBAAgB,CA4BnB"}