aegis-desktop 1.0.10 → 1.0.13

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.
@@ -89,16 +89,25 @@ const DEEPSEEK_REASONING_MODEL_RE = /^deepseek-(v4(\.\d+)?-(flash|pro)|flash|pro
89
89
  const EFFORT_TOKEN_BUDGET = { low: 8192, medium: 16384, high: 32768 };
90
90
 
91
91
  /**
92
- * DeepSeek native reasoning level per config rung. The field is
93
- * `reasoning_effort` (the API 200s and ignores a body key named `effort`), and
94
- * its enum — none|minimal|low|medium|high|xhigh|ultra|max — names every rung
95
- * of this knob, so the map is 1:1. Mirrors DEEPSEEK_EFFORT in
96
- * desktop/lib/local/engine.js, which mirrors the upstream engine's
97
- * src/backend.js — carried here so the no-drift guarantee for
98
- * DEEPSEEK_REASONING_MODEL_RE and EFFORT_TOKEN_BUDGET extends to the wire knob
99
- * too.
92
+ * DeepSeek native reasoning level per config rung.
93
+ *
94
+ * The field is `effort`, not `reasoning_effort`. Measured live 2026-10-21 on the
95
+ * surface this client actually reaches (the relay's
96
+ * `https://api.deepseek.com/anthropic` transport), interleaved paired samples,
97
+ * n=12, output tokens: absent 818 · low 772 · medium 836 · high 818 · max 1494.
98
+ * Only `max` separates from sending nothing, and `GET /models` reports
99
+ * `supported_levels: ["low","high","max"]` on both live ids — so `medium` is
100
+ * not a level these models have, and it is folded upward here rather than sent
101
+ * to be absorbed into the vendor's default (a 200 that changes nothing, which
102
+ * is the one failure this knob cannot surface). `max` stays unreachable: it is
103
+ * not a rung of this knob.
104
+ *
105
+ * Mirrors DEEPSEEK_EFFORT in desktop/lib/local/engine.js — carried here so the
106
+ * no-drift guarantee for DEEPSEEK_REASONING_MODEL_RE and EFFORT_TOKEN_BUDGET
107
+ * extends to the wire knob too. That guard was missing, which is how the two
108
+ * copies agreed on a field name that reached no vendor at all.
100
109
  */
101
- const DEEPSEEK_EFFORT = { low: 'low', medium: 'medium', high: 'high' };
110
+ const DEEPSEEK_EFFORT = { low: 'low', medium: 'high', high: 'high' };
102
111
 
103
112
  /**
104
113
  * There is no longer a "class that requires a stated max_tokens".
@@ -0,0 +1,225 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
6
+ 'use strict';
7
+
8
+ /**
9
+ * companion.js — the one greeting the app writes to you, once a day.
10
+ *
11
+ * WHAT THIS IS NOT. It is not a notification, a pop-up, a badge, or anything
12
+ * that interrupts. It is one line in the sidebar that changes at most once per
13
+ * local day and can be dismissed for that day. A "daily reminder" that steals
14
+ * focus is a nag; the whole value here is that it is quiet enough to read.
15
+ *
16
+ * THE RULE THAT SHAPES EVERY MESSAGE: it may only say things this process
17
+ * actually knows. There is no engagement metric in this file, no invented
18
+ * streak, no "you usually work on Tuesdays". Every candidate message reads its
19
+ * facts from the `facts` object the caller passes, and a message whose facts
20
+ * are absent is not a candidate at all — it is never shown with a guess in the
21
+ * blank. That is why `pickMessage` returns `{ kind, ... }` rather than a
22
+ * pre-baked string: the copy is chosen by which facts exist, and each branch
23
+ * says exactly which ones it used.
24
+ *
25
+ * Determinism matters for the daily behaviour: the same local day and the same
26
+ * facts must select the same message, or a re-render (which happens on every
27
+ * status read) would flip the suggestion while the user is looking at it. The
28
+ * rotation between equally-valid tips is therefore seeded from the day key,
29
+ * never from Math.random().
30
+ *
31
+ * Pure: no DOM, no Electron, no fs, no clock of its own — `now` is passed in.
32
+ */
33
+
34
+ /**
35
+ * The local calendar day, as YYYY-MM-DD.
36
+ *
37
+ * Local, not UTC, on purpose: "a daily reminder" means the user's day. A UTC
38
+ * key would roll the message over at 01:00 or 02:00 for most of Europe, which
39
+ * is the middle of the evening for the people most likely to be running this.
40
+ */
41
+ function dayKey(now) {
42
+ const d = now instanceof Date ? now : new Date(now == null ? Date.now() : now);
43
+ if (Number.isNaN(d.getTime())) return '';
44
+ const p = (n) => String(n).padStart(2, '0');
45
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
46
+ }
47
+
48
+ /** Whole local days between two timestamps; null when either is unusable. */
49
+ function daysBetween(a, b) {
50
+ const t1 = a instanceof Date ? a.getTime() : Number(a);
51
+ const t2 = b instanceof Date ? b.getTime() : Number(b);
52
+ if (!Number.isFinite(t1) || !Number.isFinite(t2)) return null;
53
+ const d1 = new Date(t1); d1.setHours(0, 0, 0, 0);
54
+ const d2 = new Date(t2); d2.setHours(0, 0, 0, 0);
55
+ return Math.round((d2.getTime() - d1.getTime()) / 86400000);
56
+ }
57
+
58
+ /** Stable small hash, so tip rotation is deterministic per day. */
59
+ function hash(s) {
60
+ let h = 2166136261;
61
+ const str = String(s);
62
+ for (let i = 0; i < str.length; i += 1) {
63
+ h ^= str.charCodeAt(i);
64
+ h = Math.imul(h, 16777619);
65
+ }
66
+ return h >>> 0;
67
+ }
68
+
69
+ /**
70
+ * Tips that are TRUE OF THIS BUILD — each one names a real affordance. They are
71
+ * the fallback rotation once nothing needs attention; none of them is a "come
72
+ * back and use the app more" nudge, because that would be the app asking for
73
+ * the user's time rather than giving them something back.
74
+ */
75
+ const TIPS = Object.freeze([
76
+ { id: 'effort', text: 'Effort sizes the call — low for quick edits, high when the answer has to be complete.' },
77
+ { id: 'tools', text: 'Turn on "confirm before running tools" in Tool approvals to see every command before it runs.' },
78
+ { id: 'quick', text: 'The Quick Launcher shortcut opens a prompt from anywhere — no need to switch windows.' },
79
+ { id: 'sessions', text: 'Sessions → Export writes the thread out as a file you keep, not something held in the app.' },
80
+ { id: 'queue', text: 'Queued tasks run on their own class — they can stay on Aegis Cloud while you work locally.' },
81
+ { id: 'memory', text: 'Memory entries are yours: the inspector shows the source and tags for every one.' },
82
+ { id: 'local', text: 'Local model needs no key and no network — useful for anything you would rather not send anywhere.' },
83
+ ]);
84
+
85
+ /**
86
+ * Choose today's message.
87
+ *
88
+ * `facts` (all optional; each one absent merely removes its branch):
89
+ * keyConfigured {boolean} an AEGIS API key exists on this machine
90
+ * modelCount {number} how many models the current class listed
91
+ * lastTurnAt {number} epoch ms of the last finished turn, if any
92
+ * firstSeenAt {number} epoch ms this install first ran
93
+ * queuePending {number} unfinished queued tasks
94
+ * memoryCount {number} memory entries visible in the inspector
95
+ * providerNeedsKey {boolean} byok class has models but no provider key
96
+ * updatePending {string} version string of a downloaded update, if any
97
+ *
98
+ * Returns `{ id, kind, text, usedFacts }` — `kind` is 'action' when the message
99
+ * points at something concrete to do and 'tip' when it is just the rotation.
100
+ */
101
+ function pickMessage(facts, now) {
102
+ const f = facts && typeof facts === 'object' ? facts : {};
103
+ const day = dayKey(now);
104
+ const used = [];
105
+ const pick = (id, kind, text, keys) => ({
106
+ id,
107
+ kind,
108
+ text,
109
+ day,
110
+ usedFacts: keys.filter((k) => f[k] !== undefined && f[k] !== null),
111
+ });
112
+
113
+ // 1. An update that is downloaded and waiting is the single most actionable
114
+ // thing the app can know: it is finished work sitting idle until restart.
115
+ if (typeof f.updatePending === 'string' && f.updatePending) {
116
+ return pick(
117
+ 'update-ready',
118
+ 'action',
119
+ `Update ${f.updatePending} is downloaded — restart when it suits you, it installs on quit.`,
120
+ ['updatePending'],
121
+ );
122
+ }
123
+
124
+ // 2. No key at all: the app can barely do anything, and the remedy is one
125
+ // paste. Not an error message — a reminder is allowed to be the first
126
+ // place the user hears it, so it is phrased as an invitation.
127
+ if (f.keyConfigured === false) {
128
+ return pick(
129
+ 'connect',
130
+ 'action',
131
+ 'Paste your AEGIS API key in Status to run turns on Aegis Cloud — or pick Local model below for no key at all.',
132
+ ['keyConfigured'],
133
+ );
134
+ }
135
+
136
+ // 3. A provider class chosen with models listed but no provider key: the user
137
+ // has already walked to the door and has not opened it.
138
+ if (f.providerNeedsKey === true) {
139
+ return pick(
140
+ 'provider-key',
141
+ 'action',
142
+ 'Your provider class has models listed but no key saved — add one in Provider settings to use them.',
143
+ ['providerNeedsKey'],
144
+ );
145
+ }
146
+
147
+ // 4. Queued work still waiting. Counted, never estimated: "3 queued" is a
148
+ // fact, "your queue is busy" would be a guess about progress.
149
+ if (typeof f.queuePending === 'number' && f.queuePending > 0) {
150
+ return pick(
151
+ 'queue',
152
+ 'action',
153
+ `${f.queuePending} queued task${f.queuePending === 1 ? '' : 's'} still waiting — the queue card runs ${f.queuePending === 1 ? 'it' : 'them'} on its own class.`,
154
+ ['queuePending'],
155
+ );
156
+ }
157
+
158
+ // 4b. Memory this install already holds. Documented in the facts list from
159
+ // the day this file was written, but with no branch behind it — so a user
160
+ // with twenty saved entries was told nothing about them and got a generic
161
+ // tip instead. Counted exactly, and it points at the inspector, which is
162
+ // the only place in the app where the provenance of each entry is shown.
163
+ if (typeof f.memoryCount === 'number' && f.memoryCount > 0) {
164
+ return pick(
165
+ 'memory-held',
166
+ 'action',
167
+ `${f.memoryCount} memory entr${f.memoryCount === 1 ? 'y is' : 'ies are'} already saved here — the inspector shows the source and tags for every one.`,
168
+ ['memoryCount'],
169
+ );
170
+ }
171
+
172
+ // 5. First-run, no key required path: say what the app is, once. Only on the
173
+ // FIRST local day, so it does not become a permanent fixture.
174
+ const age = daysBetween(f.firstSeenAt, now);
175
+ if (age === 0 && f.lastTurnAt == null) {
176
+ return pick(
177
+ 'first-day',
178
+ 'tip',
179
+ 'First day. Pick a class and a model in the Model card, then send a turn — everything else here is optional.',
180
+ ['firstSeenAt', 'lastTurnAt'],
181
+ );
182
+ }
183
+
184
+ // 6. A gap after real use. Reported only when there is a genuine last turn,
185
+ // and only past a threshold where the observation is worth stating; below
186
+ // it, nothing is said at all (fall through to the rotation).
187
+ const since = daysBetween(f.lastTurnAt, now);
188
+ if (since != null && since >= 3) {
189
+ return pick(
190
+ 'returning',
191
+ 'tip',
192
+ 'Last turn was a few days ago — your sessions and queued tasks are still here.',
193
+ ['lastTurnAt'],
194
+ );
195
+ }
196
+
197
+ // 7. Nothing needs attention: rotate one true tip, seeded by the day so it is
198
+ // stable for the whole day and different tomorrow.
199
+ const tip = TIPS[hash(day) % TIPS.length];
200
+ return pick(`tip-${tip.id}`, 'tip', tip.text, ['tipRotation']);
201
+ }
202
+
203
+ /** True when this message was already dismissed today. */
204
+ function dismissedToday(dismissed, now) {
205
+ const d = dismissed && typeof dismissed === 'object' ? dismissed : null;
206
+ if (!d) return false;
207
+ return d.day === dayKey(now);
208
+ }
209
+
210
+ if (typeof module !== 'undefined' && module.exports) {
211
+ module.exports = { pickMessage, dayKey, daysBetween, dismissedToday, TIPS };
212
+ }
213
+
214
+ // Also a page global, because index.html loads this file as a classic script
215
+ // (`<script src="companion.js">`) and app.js reads window.aegisCompanion. The
216
+ // module branch above is for Node (test/renderer-providers.test.mjs requires
217
+ // the file directly); the two are mutually exclusive in practice — a classic
218
+ // script has no `module` — so this cannot clobber the module export.
219
+ if (typeof window !== 'undefined') {
220
+ (window.aegisCompanion = window.aegisCompanion || {}).pickMessage = pickMessage;
221
+ window.aegisCompanion.dayKey = dayKey;
222
+ window.aegisCompanion.daysBetween = daysBetween;
223
+ window.aegisCompanion.dismissedToday = dismissedToday;
224
+ window.aegisCompanion.TIPS = TIPS;
225
+ }
@@ -54,6 +54,22 @@
54
54
  display: none;
55
55
  }
56
56
 
57
+ /* Which of the three destinations a publish/backup goes to. A row of
58
+ buttons, not a <select>: `aria-pressed` on the active one is the whole
59
+ state fingerprint.js's paintVaultBackend() paints, and three named options
60
+ read faster than a dropdown you have to open to see the current one. */
61
+ .fp-vault-backend {
62
+ display: flex;
63
+ flex-wrap: wrap;
64
+ gap: 6px;
65
+ padding: 8px 16px 0;
66
+ }
67
+
68
+ .fp-backend-btn[aria-pressed='true'] {
69
+ border-color: var(--teal);
70
+ color: var(--teal);
71
+ }
72
+
57
73
  .fp-controls {
58
74
  display: flex;
59
75
  flex-direction: column;
@@ -57,6 +57,9 @@
57
57
  keyTag: 'fp-key-tag',
58
58
  keyWarning: 'fp-key-warning',
59
59
  vaultTarget: 'fp-vault-target',
60
+ vaultBackendDrive: 'fp-vault-backend-drive',
61
+ vaultBackendCloud: 'fp-vault-backend-aegiscloud',
62
+ vaultBackendOff: 'fp-vault-backend-off',
60
63
  label: 'fp-label',
61
64
  publishOnMint: 'fp-publish-on-mint',
62
65
  autoSeal: 'fp-auto-seal',
@@ -75,6 +78,10 @@
75
78
  audit: 'fp-audit',
76
79
  vaultList: 'fp-vault-list',
77
80
  out: 'fp-out',
81
+ driveStatus: 'fp-drive-status',
82
+ driveSignIn: 'fp-drive-signin',
83
+ driveConnect: 'fp-drive-connect',
84
+ driveSyncNow: 'fp-drive-sync',
78
85
  };
79
86
 
80
87
  // ------------------------------------------------------------- formatting
@@ -136,6 +143,23 @@
136
143
  });
137
144
  }
138
145
 
146
+ /**
147
+ * The one-line summary of the Google Drive card — `desktop/lib/
148
+ * google-connect.js`'s `status()` shape, `{signedIn, connected, email}`.
149
+ *
150
+ * Three distinct states, because "not connected" means something different
151
+ * depending on whether this machine has signed in with Google at all:
152
+ * connecting Drive before signing in attaches the tokens to a different
153
+ * account than the one this machine polls (see google-connect.js's header
154
+ * comment), so the line has to say WHICH step is next, not just "no".
155
+ */
156
+ function driveLine(status) {
157
+ if (!status || status.ok === false) return (status && status.error) || 'unavailable';
158
+ if (!status.signedIn) return 'not signed in — sign in with Google first';
159
+ if (!status.connected) return 'signed in · Google Drive not connected yet';
160
+ return `Google Drive connected${status.email ? ` · ${status.email}` : ''}`;
161
+ }
162
+
139
163
  /** The anchor answer, kept as three separate claims on purpose: "the file is
140
164
  * gone", "the file was replaced" and "the key that sealed it has moved on"
141
165
  * are three different situations, and one boolean hides the interesting one
@@ -276,7 +300,19 @@
276
300
  warn.hidden = !w;
277
301
  }
278
302
  if (vt && s && s.vault) {
279
- vt.textContent = `${dash(s.vault.remote)}:${dash(s.vault.recordsDir || s.vault.root)}`;
303
+ // The default backend is the account (`aegiscloud`), where "gdrive" is
304
+ // not where anything goes — printing `gdrive:AEGIS-AGPL/records` for a
305
+ // cloud publish would name a folder the record never touches. Say the
306
+ // backend for that case, and keep the remembered rclone target for the
307
+ // case where it IS a remote. 'off' names neither — nothing is resolved
308
+ // behind it (desktop/lib/fingerprint.js's status() short-circuits
309
+ // before ever calling vaultTarget()), so the line says so plainly
310
+ // rather than printing the leftover "–:–" two dashes would read as.
311
+ vt.textContent = s.vault.backend === 'off'
312
+ ? 'nothing — sync is off'
313
+ : s.vault.backend === 'aegiscloud'
314
+ ? `aegiscloud:${dash(s.vault.recordsDir)}`
315
+ : `${dash(s.vault.remote)}:${dash(s.vault.recordsDir || s.vault.root)}`;
280
316
  } else if (vt) {
281
317
  vt.textContent = '–';
282
318
  }
@@ -287,6 +323,90 @@
287
323
  if (kg) kg.textContent = s && s.configured ? 'Replace key…' : 'Mint key';
288
324
  }
289
325
 
326
+ /**
327
+ * The three backend buttons' pressed state.
328
+ *
329
+ * Deliberately read from `getVaultBackend` (the preference the user set),
330
+ * NOT from `status().vault.backend` (the resolved record-vault.js
331
+ * backend id): the preference vocabulary is 'aegiscloud' | 'drive' | 'off',
332
+ * the resolved one is 'aegiscloud' | 'rclone' | 'off' — a 'drive'
333
+ * preference resolves to 'rclone', so pressing "Google Drive" from status
334
+ * alone would never read back as pressed.
335
+ */
336
+ function paintVaultBackend(backend) {
337
+ const map = {
338
+ drive: el(IDS.vaultBackendDrive),
339
+ aegiscloud: el(IDS.vaultBackendCloud),
340
+ off: el(IDS.vaultBackendOff),
341
+ };
342
+ for (const [key, btn] of Object.entries(map)) {
343
+ if (btn) btn.setAttribute('aria-pressed', String(key === backend));
344
+ }
345
+ }
346
+
347
+ async function loadVaultBackend() {
348
+ const r = await call('getVaultBackend');
349
+ state.vaultBackend = r && r.backend ? r.backend : 'aegiscloud';
350
+ paintVaultBackend(state.vaultBackend);
351
+ return state.vaultBackend;
352
+ }
353
+
354
+ const doSetVaultBackend = (backend) =>
355
+ action(`vault: ${backend === 'drive' ? 'Google Drive' : backend === 'off' ? 'no sync' : 'AE'}`, async () => {
356
+ const r = await call('setVaultBackend', backend);
357
+ return r && r.backend ? { ok: true, ...r } : { ok: false, error: 'could not save that' };
358
+ }, {
359
+ // The "publishes to" line names the backend too, so both it and the
360
+ // three buttons must repaint the moment the preference changes — not
361
+ // just on the next open().
362
+ after: (r) => (r && r.ok ? refresh() : null),
363
+ say: (r) => (r.ok ? `vault backend set to ${dash(r.backend)}` : `could not save that: ${r.error}`),
364
+ });
365
+
366
+ /**
367
+ * Google Drive — memory backup (desktop/lib/google-connect.js, exposed
368
+ * over `aegis.driveSignIn`/`driveConnect`/`driveStatus`/`driveSyncMemory`).
369
+ *
370
+ * Distinct from the vault backend buttons above: those choose where a
371
+ * SEALED φ(α) RECORD is published (rclone or the account vault). This is
372
+ * the account's AEGIS CLOUD MEMORY, backed up to the user's own Drive
373
+ * through real Google OAuth — unrelated data, unrelated credential.
374
+ */
375
+ function paintDrive(status) {
376
+ const line = el(IDS.driveStatus);
377
+ if (line) line.textContent = driveLine(status);
378
+ const connectBtn = el(IDS.driveConnect);
379
+ if (connectBtn) connectBtn.disabled = Boolean(status && !status.signedIn);
380
+ const syncBtn = el(IDS.driveSyncNow);
381
+ if (syncBtn) syncBtn.disabled = Boolean(!status || !status.connected);
382
+ }
383
+
384
+ async function loadDriveStatus() {
385
+ const r = await call('driveStatus');
386
+ state.drive = r;
387
+ paintDrive(r);
388
+ return r;
389
+ }
390
+
391
+ const doDriveSignIn = () =>
392
+ action('sign in with Google', () => call('driveSignIn'), {
393
+ after: (r) => (r && r.ok ? loadDriveStatus() : null),
394
+ say: (r) => (r.ok ? `signed in${r.username ? ` as ${r.username}` : ''}` : `sign-in failed: ${r.error}`),
395
+ });
396
+
397
+ const doDriveConnect = () =>
398
+ action('connect Google Drive', () => call('driveConnect'), {
399
+ after: (r) => (r && r.ok ? loadDriveStatus() : null),
400
+ say: (r) => (r.ok ? `Google Drive connected${r.email ? ` · ${r.email}` : ''}` : `could not connect: ${r.error}`),
401
+ });
402
+
403
+ const doDriveSyncNow = () =>
404
+ action('back up memory to Drive', () => call('driveSyncMemory'), {
405
+ say: (r) => (r.ok
406
+ ? `backed up ${r.count} memory entr${r.count === 1 ? 'y' : 'ies'}${r.webViewLink ? ` → ${r.webViewLink}` : ''}`
407
+ : `backup failed: ${r.error}`),
408
+ });
409
+
290
410
  function paintVault(list) {
291
411
  const ul = el(IDS.vaultList);
292
412
  if (!ul) return;
@@ -312,6 +432,8 @@
312
432
  state.status = await call('fingerprintStatus');
313
433
  paintStatus();
314
434
  await loadAutoMint();
435
+ await loadVaultBackend();
436
+ await loadDriveStatus();
315
437
  return state.status;
316
438
  }
317
439
 
@@ -547,6 +669,12 @@
547
669
  bind(IDS.backup, 'click', doBackup);
548
670
  bind(IDS.vault, 'click', doVault);
549
671
  bind(IDS.audit, 'click', doAudit);
672
+ bind(IDS.vaultBackendDrive, 'click', () => doSetVaultBackend('drive'));
673
+ bind(IDS.vaultBackendCloud, 'click', () => doSetVaultBackend('aegiscloud'));
674
+ bind(IDS.vaultBackendOff, 'click', () => doSetVaultBackend('off'));
675
+ bind(IDS.driveSignIn, 'click', doDriveSignIn);
676
+ bind(IDS.driveConnect, 'click', doDriveConnect);
677
+ bind(IDS.driveSyncNow, 'click', doDriveSyncNow);
550
678
  // The auto-seal switch is the ONE control on this panel that can file a
551
679
  // record with nobody watching, so it is the one that must repaint from
552
680
  // main's answer rather than from the checkbox the user just clicked.
@@ -567,7 +695,12 @@
567
695
  state,
568
696
  // Exposed for the test and for app.js's on-demand repaint. Every one is
569
697
  // idempotent, so calling them from a console is safe.
570
- actions: { doKeygen, doSeal, doSealNow, doVerify, doPublish, doAnchor, doBackup, doVault, doAudit },
698
+ actions: {
699
+ doKeygen, doSeal, doSealNow, doVerify, doPublish, doAnchor, doBackup, doVault, doAudit,
700
+ doSetVaultBackend, doDriveSignIn, doDriveConnect, doDriveSyncNow,
701
+ },
702
+ loadVaultBackend,
703
+ loadDriveStatus,
571
704
  };
572
705
  }
573
706
 
@@ -583,6 +716,7 @@
583
716
  auditVerdict,
584
717
  sealLine,
585
718
  autoMintLine,
719
+ driveLine,
586
720
  dash,
587
721
  };
588
722
  });