instar 1.3.401 → 1.3.403
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/dashboard/index.html +74 -0
- package/dashboard/subscriptions.js +264 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +59 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/EnrollmentWizard.d.ts +84 -0
- package/dist/core/EnrollmentWizard.d.ts.map +1 -0
- package/dist/core/EnrollmentWizard.js +107 -0
- package/dist/core/EnrollmentWizard.js.map +1 -0
- package/dist/core/FrameworkLoginDriver.d.ts +85 -0
- package/dist/core/FrameworkLoginDriver.d.ts.map +1 -0
- package/dist/core/FrameworkLoginDriver.js +110 -0
- package/dist/core/FrameworkLoginDriver.js.map +1 -0
- package/dist/core/PendingLoginStore.d.ts +118 -0
- package/dist/core/PendingLoginStore.d.ts.map +1 -0
- package/dist/core/PendingLoginStore.js +201 -0
- package/dist/core/PendingLoginStore.js.map +1 -0
- package/dist/core/types.d.ts +8 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/server/AgentServer.d.ts +1 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +2 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +59 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +47 -47
- package/upgrades/1.3.402.md +64 -0
- package/upgrades/1.3.403.md +50 -0
- package/upgrades/side-effects/subscription-auth-enrollment.md +91 -0
package/dashboard/index.html
CHANGED
|
@@ -2692,6 +2692,7 @@
|
|
|
2692
2692
|
<button class="tab" data-tab="threadline" onclick="switchTab('threadline')">Threadline</button>
|
|
2693
2693
|
<button class="tab" data-tab="evidence" onclick="switchTab('evidence')">Evidence</button>
|
|
2694
2694
|
<button class="tab" data-tab="process-health" onclick="switchTab('process-health')">Process Health</button>
|
|
2695
|
+
<button class="tab" data-tab="subscriptions" onclick="switchTab('subscriptions')">Subscriptions</button>
|
|
2695
2696
|
<button class="tab" data-tab="preferences-learning" onclick="switchTab('preferences-learning')">Preferences</button>
|
|
2696
2697
|
<button class="tab" data-tab="machines" onclick="switchTab('machines')">Machines</button>
|
|
2697
2698
|
<button class="tab" data-tab="mandates" onclick="switchTab('mandates')">Mandates</button>
|
|
@@ -3243,6 +3244,44 @@
|
|
|
3243
3244
|
</details>
|
|
3244
3245
|
</div>
|
|
3245
3246
|
|
|
3247
|
+
<!-- subscriptions tab (Subscription & Auth Standard P2.2 — live quota + pending logins) -->
|
|
3248
|
+
<style>
|
|
3249
|
+
.sub-account { border:1px solid var(--border,#2a2a2a); border-radius:10px; padding:14px 16px; margin-bottom:12px; }
|
|
3250
|
+
.sub-account-head { display:flex; justify-content:space-between; align-items:baseline; gap:12px; }
|
|
3251
|
+
.sub-account-nick { font-weight:600; font-size:15px; }
|
|
3252
|
+
.sub-account-status { font-size:12px; opacity:.75; }
|
|
3253
|
+
.sub-account-meta { font-size:12px; opacity:.6; margin:2px 0 10px; }
|
|
3254
|
+
.sub-account-noquota { font-size:12px; opacity:.6; }
|
|
3255
|
+
.sub-quota { margin:8px 0; }
|
|
3256
|
+
.sub-quota-head { display:flex; justify-content:space-between; font-size:12px; margin-bottom:4px; }
|
|
3257
|
+
.sub-quota-label { opacity:.8; }
|
|
3258
|
+
.sub-quota-pct { opacity:.65; }
|
|
3259
|
+
.sub-quota-track { height:8px; border-radius:4px; background:var(--border,#2a2a2a); overflow:hidden; }
|
|
3260
|
+
.sub-quota-fill { height:100%; background:linear-gradient(90deg,#4caf80,#3a9); }
|
|
3261
|
+
.sub-pending { border:1px solid var(--border,#2a2a2a); border-radius:10px; padding:12px 16px; margin-bottom:10px; }
|
|
3262
|
+
.sub-pending-head { display:flex; justify-content:space-between; align-items:baseline; gap:12px; }
|
|
3263
|
+
.sub-pending-label { font-weight:600; }
|
|
3264
|
+
.sub-pending-ttl { font-size:12px; opacity:.7; }
|
|
3265
|
+
.sub-pending-code { font-family:monospace; font-size:14px; margin-top:6px; }
|
|
3266
|
+
.sub-pending-url { font-size:12px; opacity:.7; word-break:break-all; margin-top:4px; }
|
|
3267
|
+
.sub-pending-reissue { font-size:11px; opacity:.55; margin-top:4px; }
|
|
3268
|
+
.sub-empty, .sub-disabled { font-size:13px; opacity:.7; padding:8px 0; }
|
|
3269
|
+
</style>
|
|
3270
|
+
<div id="subscriptionsPanel" class="ph-root" style="display:none;flex-direction:column;padding:28px;gap:24px;overflow-y:auto">
|
|
3271
|
+
<h2 class="ph-title">Subscriptions</h2>
|
|
3272
|
+
<p class="ph-intro">Your subscription accounts and how much of each is left before it resets — plus any logins waiting for you to approve on your phone.</p>
|
|
3273
|
+
<section class="ph-section">
|
|
3274
|
+
<h3 class="ph-h">Accounts</h3>
|
|
3275
|
+
<p class="ph-h-sub">Each account’s live usage (5-hour and weekly) and when it resets.</p>
|
|
3276
|
+
<div id="subAccounts"></div>
|
|
3277
|
+
</section>
|
|
3278
|
+
<section class="ph-section">
|
|
3279
|
+
<h3 class="ph-h">Pending logins</h3>
|
|
3280
|
+
<p class="ph-h-sub">Logins waiting for approval — open the link on your phone and confirm. An expired code is re-issued automatically.</p>
|
|
3281
|
+
<div id="subPending"></div>
|
|
3282
|
+
</section>
|
|
3283
|
+
</div>
|
|
3284
|
+
|
|
3246
3285
|
<!-- mandates tab (coordination-mandate spec, decision 2A — the operator's PIN-gated surface) -->
|
|
3247
3286
|
<div id="mandatesPanel" class="ph-root" style="display:none;flex-direction:column;padding:28px;gap:24px;overflow-y:auto">
|
|
3248
3287
|
<div style="display:flex;justify-content:space-between;align-items:baseline">
|
|
@@ -4650,6 +4689,13 @@
|
|
|
4650
4689
|
onActivate: () => { if (typeof startProcessHealth === 'function') startProcessHealth(); },
|
|
4651
4690
|
onDeactivate: () => { if (typeof stopProcessHealth === 'function') stopProcessHealth(); },
|
|
4652
4691
|
},
|
|
4692
|
+
{
|
|
4693
|
+
id: 'subscriptions',
|
|
4694
|
+
panels: ['subscriptionsPanel'],
|
|
4695
|
+
display: ['flex'],
|
|
4696
|
+
onActivate: () => { if (typeof startSubscriptions === 'function') startSubscriptions(); },
|
|
4697
|
+
onDeactivate: () => { if (typeof stopSubscriptions === 'function') stopSubscriptions(); },
|
|
4698
|
+
},
|
|
4653
4699
|
{
|
|
4654
4700
|
id: 'preferences-learning',
|
|
4655
4701
|
panels: ['preferencesLearningPanel'],
|
|
@@ -8602,6 +8648,34 @@
|
|
|
8602
8648
|
if (__phController) __phController.stop();
|
|
8603
8649
|
}
|
|
8604
8650
|
|
|
8651
|
+
// ── Subscriptions tab (Subscription & Auth Standard P2.2) ──
|
|
8652
|
+
// Same lazy dynamic-import pattern as Process Health: the controller lives in
|
|
8653
|
+
// this (classic) scope so it can read `token`; the module ships the pure
|
|
8654
|
+
// renderers + the polling controller.
|
|
8655
|
+
let __subController = null;
|
|
8656
|
+
let __subModule = null;
|
|
8657
|
+
async function startSubscriptions() {
|
|
8658
|
+
if (!__subModule) {
|
|
8659
|
+
try { __subModule = await import('/dashboard/subscriptions.js'); }
|
|
8660
|
+
catch (e) { console.error('[subscriptions] module load failed', e); return; }
|
|
8661
|
+
}
|
|
8662
|
+
if (!__subController) {
|
|
8663
|
+
const els = {
|
|
8664
|
+
accounts: document.getElementById('subAccounts'),
|
|
8665
|
+
pending: document.getElementById('subPending'),
|
|
8666
|
+
};
|
|
8667
|
+
const fetchImpl = (url, opts = {}) => fetch(url, {
|
|
8668
|
+
headers: { 'Authorization': `Bearer ${token}`, ...(opts.headers || {}) },
|
|
8669
|
+
signal: opts.signal,
|
|
8670
|
+
});
|
|
8671
|
+
__subController = __subModule.createController({ doc: document, els, fetchImpl });
|
|
8672
|
+
}
|
|
8673
|
+
__subController.start();
|
|
8674
|
+
}
|
|
8675
|
+
function stopSubscriptions() {
|
|
8676
|
+
if (__subController) __subController.stop();
|
|
8677
|
+
}
|
|
8678
|
+
|
|
8605
8679
|
// ── Mandates tab (coordination-mandate spec, decision 2A) ──
|
|
8606
8680
|
// Same lazy dynamic-import pattern as Process Health. The PIN typed in this
|
|
8607
8681
|
// tab is sent once per action and never stored anywhere.
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// Subscriptions tab — a read surface for the multi-account Subscription & Auth
|
|
2
|
+
// pool: per-account live quota bars (5h / weekly + reset countdown), status, and
|
|
3
|
+
// the Pending Logins panel (device codes / verification URLs awaiting approval,
|
|
4
|
+
// with TTL). Spec: docs/specs/_drafts/subscription-auth-standard-master-spec.md.
|
|
5
|
+
//
|
|
6
|
+
// Browser-native ESM (no build step; served at /dashboard/subscriptions.js and
|
|
7
|
+
// loaded by index.html via <script type="module">). The pure functions are
|
|
8
|
+
// exported so the 3-tier jsdom tests exercise the SHIPPED code, not a copy; the
|
|
9
|
+
// controller is attached to window.Subscriptions so index.html drives start/stop
|
|
10
|
+
// on tab activation.
|
|
11
|
+
//
|
|
12
|
+
// Load-bearing safety contract (mirrors the Process Health tab §4.6): every
|
|
13
|
+
// dynamic value flows through sanitizeForDisplay before the DOM; all DOM writes
|
|
14
|
+
// are textContent only (never innerHTML); the only dynamic ATTRIBUTE written is a
|
|
15
|
+
// quota-bar width, set from a clamped NUMBER (0–100) — never a string from data.
|
|
16
|
+
// No verification URL is ever rendered as a live href (defense-in-depth): it is
|
|
17
|
+
// shown as sanitized TEXT for the operator to copy.
|
|
18
|
+
|
|
19
|
+
const CAPS = { label: 64, code: 48, url: 320, summary: 240 };
|
|
20
|
+
|
|
21
|
+
// Structural presentation-glyph class (NFKC-fold THEN strip), identical to the
|
|
22
|
+
// Process Health tab: \p{So} + arrows + geometric + box-drawing + dingbats +
|
|
23
|
+
// variation-selectors + bullet/middot — so a confusable can't impersonate chrome.
|
|
24
|
+
const CHROME_GLYPH_RE = new RegExp(
|
|
25
|
+
'[\\p{So}\\u2190-\\u21FF\\u25A0-\\u25FF\\u2500-\\u257F\\u2700-\\u27BF\\uFE00-\\uFE0F\\u2022\\u00B7\\u2027\\u2043]',
|
|
26
|
+
'gu',
|
|
27
|
+
);
|
|
28
|
+
const CONTROL_RE = new RegExp('[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F-\\u009F]', 'g');
|
|
29
|
+
const BIDI_RE = new RegExp('[\\u202A-\\u202E\\u2066-\\u2069]', 'g');
|
|
30
|
+
|
|
31
|
+
/** Sanitize a dynamic value before it touches the DOM (see contract above). */
|
|
32
|
+
export function sanitizeForDisplay(value, fieldKind = 'summary') {
|
|
33
|
+
let s = value == null ? '' : String(value);
|
|
34
|
+
s = s.normalize('NFKC');
|
|
35
|
+
s = s.replace(CONTROL_RE, '');
|
|
36
|
+
s = s.replace(BIDI_RE, '');
|
|
37
|
+
s = s.replace(/\n{2,}/g, '\n').replace(/[ \t]{5,}/g, ' ');
|
|
38
|
+
s = s.replace(CHROME_GLYPH_RE, '');
|
|
39
|
+
s = capGraphemes(s, CAPS[fieldKind] ?? CAPS.summary);
|
|
40
|
+
return s;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function capGraphemes(s, max) {
|
|
44
|
+
if (s.length <= max) return s;
|
|
45
|
+
if (typeof Intl !== 'undefined' && Intl.Segmenter) {
|
|
46
|
+
const arr = Array.from(new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(s), (x) => x.segment);
|
|
47
|
+
if (arr.length <= max) return s;
|
|
48
|
+
return arr.slice(0, max - 1).join('') + '…';
|
|
49
|
+
}
|
|
50
|
+
let cut = max - 1;
|
|
51
|
+
const c = s.charCodeAt(cut - 1);
|
|
52
|
+
if (c >= 0xd800 && c <= 0xdbff) cut -= 1;
|
|
53
|
+
return s.slice(0, cut) + '…';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Clamp a utilization value to an integer 0–100 (the only dynamic attribute). */
|
|
57
|
+
export function clampPct(value) {
|
|
58
|
+
const n = Number(value);
|
|
59
|
+
if (!Number.isFinite(n)) return 0;
|
|
60
|
+
return Math.max(0, Math.min(100, Math.round(n)));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const STATUS_WORDS = {
|
|
64
|
+
active: 'Active',
|
|
65
|
+
warming: 'Warming up',
|
|
66
|
+
'rate-limited': 'At its limit',
|
|
67
|
+
'needs-reauth': 'Needs sign-in',
|
|
68
|
+
disabled: 'Disabled',
|
|
69
|
+
};
|
|
70
|
+
export function friendlyStatus(status) {
|
|
71
|
+
return STATUS_WORDS[typeof status === 'string' ? status : ''] || 'Unknown';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const PROVIDER_WORDS = { anthropic: 'Claude', openai: 'Codex', 'github-copilot': 'Copilot', google: 'Gemini' };
|
|
75
|
+
export function friendlyProvider(provider) {
|
|
76
|
+
return PROVIDER_WORDS[typeof provider === 'string' ? provider : ''] || sanitizeForDisplay(provider, 'label');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Human countdown to a reset/expiry instant: "resets in 2h 15m" / "expired". */
|
|
80
|
+
export function countdown(iso, now = Date.now(), { expiredWord = 'expired' } = {}) {
|
|
81
|
+
const t = typeof iso === 'string' ? Date.parse(iso) : NaN;
|
|
82
|
+
if (Number.isNaN(t)) return '';
|
|
83
|
+
const sec = Math.floor((t - now) / 1000);
|
|
84
|
+
if (sec <= 0) return expiredWord;
|
|
85
|
+
const hr = Math.floor(sec / 3600);
|
|
86
|
+
const min = Math.floor((sec % 3600) / 60);
|
|
87
|
+
if (hr >= 24) { const d = Math.floor(hr / 24); return `${d}d ${hr % 24}h`; }
|
|
88
|
+
if (hr >= 1) return `${hr}h ${min}m`;
|
|
89
|
+
if (min >= 1) return `${min}m`;
|
|
90
|
+
return `${sec}s`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── DOM helpers (textContent ONLY — never innerHTML) ────────────────────────
|
|
94
|
+
function el(doc, tag, cls, text) {
|
|
95
|
+
const node = doc.createElement(tag);
|
|
96
|
+
if (cls) node.setAttribute('class', cls); // static literal
|
|
97
|
+
if (text != null) node.textContent = text; // dynamic text → textContent ONLY
|
|
98
|
+
return node;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** A labelled quota bar. `pct` is clamped to a 0–100 NUMBER before it reaches the
|
|
102
|
+
* only dynamic attribute (style width); the percent text is also from that number. */
|
|
103
|
+
export function quotaBar(doc, label, pct, resetIso, now = Date.now()) {
|
|
104
|
+
const wrap = el(doc, 'div', 'sub-quota');
|
|
105
|
+
const used = clampPct(pct);
|
|
106
|
+
const head = el(doc, 'div', 'sub-quota-head');
|
|
107
|
+
head.appendChild(el(doc, 'span', 'sub-quota-label', sanitizeForDisplay(label, 'label')));
|
|
108
|
+
const resetTxt = resetIso ? countdown(resetIso, now, { expiredWord: 'resetting' }) : '';
|
|
109
|
+
head.appendChild(el(doc, 'span', 'sub-quota-pct', `${used}% used${resetTxt ? ` · resets in ${resetTxt}` : ''}`));
|
|
110
|
+
wrap.appendChild(head);
|
|
111
|
+
const track = el(doc, 'div', 'sub-quota-track');
|
|
112
|
+
const fill = el(doc, 'div', 'sub-quota-fill');
|
|
113
|
+
fill.style.width = `${used}%`; // safe: `used` is a clamped integer
|
|
114
|
+
track.appendChild(fill);
|
|
115
|
+
wrap.appendChild(track);
|
|
116
|
+
return wrap;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Per-account rows: nickname, status, provider·framework, 5h + weekly quota bars. */
|
|
120
|
+
export function renderAccounts(doc, target, accounts, now = Date.now()) {
|
|
121
|
+
if (!target) return;
|
|
122
|
+
target.replaceChildren();
|
|
123
|
+
if (!Array.isArray(accounts) || accounts.length === 0) {
|
|
124
|
+
target.appendChild(el(doc, 'div', 'sub-empty', 'No subscription accounts enrolled yet.'));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
for (const a of accounts) {
|
|
128
|
+
const card = el(doc, 'div', 'sub-account');
|
|
129
|
+
const head = el(doc, 'div', 'sub-account-head');
|
|
130
|
+
head.appendChild(el(doc, 'span', 'sub-account-nick', sanitizeForDisplay(a && a.nickname, 'label')));
|
|
131
|
+
head.appendChild(el(doc, 'span', 'sub-account-status', friendlyStatus(a && a.status)));
|
|
132
|
+
card.appendChild(head);
|
|
133
|
+
card.appendChild(el(doc, 'div', 'sub-account-meta',
|
|
134
|
+
`${friendlyProvider(a && a.provider)} · ${sanitizeForDisplay(a && a.framework, 'label')}`));
|
|
135
|
+
const q = (a && a.lastQuota) || null;
|
|
136
|
+
if (q && (q.fiveHour || q.sevenDay)) {
|
|
137
|
+
if (q.fiveHour) card.appendChild(quotaBar(doc, '5-hour', q.fiveHour.utilizationPct, q.fiveHour.resetsAt, now));
|
|
138
|
+
if (q.sevenDay) card.appendChild(quotaBar(doc, 'Weekly', q.sevenDay.utilizationPct, q.sevenDay.resetsAt, now));
|
|
139
|
+
} else {
|
|
140
|
+
card.appendChild(el(doc, 'div', 'sub-account-noquota', 'No quota reading yet.'));
|
|
141
|
+
}
|
|
142
|
+
target.appendChild(card);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Pending Logins panel: device code / verification URL (as TEXT) + TTL + reissues. */
|
|
147
|
+
export function renderPendingLogins(doc, target, logins, now = Date.now()) {
|
|
148
|
+
if (!target) return;
|
|
149
|
+
target.replaceChildren();
|
|
150
|
+
if (!Array.isArray(logins) || logins.length === 0) {
|
|
151
|
+
target.appendChild(el(doc, 'div', 'sub-empty', 'No logins waiting for approval.'));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
for (const l of logins) {
|
|
155
|
+
const row = el(doc, 'div', 'sub-pending');
|
|
156
|
+
const head = el(doc, 'div', 'sub-pending-head');
|
|
157
|
+
head.appendChild(el(doc, 'span', 'sub-pending-label', sanitizeForDisplay(l && l.label, 'label')));
|
|
158
|
+
const ttl = l && l.ttlExpiresAt ? countdown(l.ttlExpiresAt, now) : '';
|
|
159
|
+
head.appendChild(el(doc, 'span', 'sub-pending-ttl', ttl ? `expires in ${ttl}` : 'expired'));
|
|
160
|
+
row.appendChild(head);
|
|
161
|
+
if (l && l.userCode) {
|
|
162
|
+
row.appendChild(el(doc, 'div', 'sub-pending-code', `Code: ${sanitizeForDisplay(l.userCode, 'code')}`));
|
|
163
|
+
}
|
|
164
|
+
// Verification URL shown as TEXT for the operator to copy — never a live href.
|
|
165
|
+
row.appendChild(el(doc, 'div', 'sub-pending-url', sanitizeForDisplay(l && l.verificationUrl, 'url')));
|
|
166
|
+
const rc = l && Number(l.reissueCount);
|
|
167
|
+
if (Number.isFinite(rc) && rc > 0) {
|
|
168
|
+
row.appendChild(el(doc, 'div', 'sub-pending-reissue', `Re-issued ${rc} time${rc === 1 ? '' : 's'}`));
|
|
169
|
+
}
|
|
170
|
+
target.appendChild(row);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function renderDisabled(doc, els) {
|
|
175
|
+
if (els && els.accounts) {
|
|
176
|
+
els.accounts.replaceChildren(
|
|
177
|
+
el(doc, 'div', 'sub-disabled', 'The subscription pool isn’t set up yet. Enroll an account to get started.'),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
if (els && els.pending) els.pending.replaceChildren();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ── Controller (fetch /subscription-pool + /pending-logins, render) ─────────
|
|
184
|
+
const URLS = {
|
|
185
|
+
accounts: '/subscription-pool',
|
|
186
|
+
pending: '/subscription-pool/pending-logins',
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export function createController(opts) {
|
|
190
|
+
const {
|
|
191
|
+
doc,
|
|
192
|
+
els = {},
|
|
193
|
+
fetchImpl,
|
|
194
|
+
now = () => Date.now(),
|
|
195
|
+
cadenceMs = 30_000,
|
|
196
|
+
schedule = (fn, ms) => setTimeout(fn, ms),
|
|
197
|
+
cancel = (id) => clearTimeout(id),
|
|
198
|
+
} = opts;
|
|
199
|
+
|
|
200
|
+
const state = { timerId: null, active: false, inFlight: null };
|
|
201
|
+
|
|
202
|
+
async function fetchJson(url, controller) {
|
|
203
|
+
const resp = await fetchImpl(url, { signal: controller.signal });
|
|
204
|
+
if (!resp.ok) throw new Error(`${url} ${resp.status}`);
|
|
205
|
+
return resp.json();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function tick() {
|
|
209
|
+
if (!state.active) return;
|
|
210
|
+
if (state.inFlight) { try { state.inFlight.abort(); } catch { /* superseded */ } }
|
|
211
|
+
const controller = typeof AbortController !== 'undefined' ? new AbortController() : { signal: undefined, abort() {} };
|
|
212
|
+
state.inFlight = controller;
|
|
213
|
+
let accountsBody, pendingBody;
|
|
214
|
+
try {
|
|
215
|
+
[accountsBody, pendingBody] = await Promise.all([
|
|
216
|
+
fetchJson(URLS.accounts, controller),
|
|
217
|
+
fetchJson(URLS.pending, controller),
|
|
218
|
+
]);
|
|
219
|
+
} catch {
|
|
220
|
+
if (controller.signal && controller.signal.aborted) return;
|
|
221
|
+
state.inFlight = null;
|
|
222
|
+
reschedule();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (controller.signal && controller.signal.aborted) return;
|
|
226
|
+
state.inFlight = null;
|
|
227
|
+
// Feature dark → both routes answer { enabled:false }. Show the friendly copy.
|
|
228
|
+
if (accountsBody && accountsBody.enabled === false && pendingBody && pendingBody.enabled === false) {
|
|
229
|
+
renderDisabled(doc, els);
|
|
230
|
+
reschedule();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
render(accountsBody, pendingBody);
|
|
234
|
+
reschedule();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function render(accountsBody, pendingBody) {
|
|
238
|
+
const accounts = accountsBody && Array.isArray(accountsBody.accounts) ? accountsBody.accounts : [];
|
|
239
|
+
const logins = pendingBody && Array.isArray(pendingBody.logins) ? pendingBody.logins : [];
|
|
240
|
+
renderAccounts(doc, els.accounts, accounts, now());
|
|
241
|
+
renderPendingLogins(doc, els.pending, logins, now());
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function reschedule() {
|
|
245
|
+
if (!state.active) return;
|
|
246
|
+
if (state.timerId != null) cancel(state.timerId);
|
|
247
|
+
state.timerId = schedule(() => { void tick(); }, cadenceMs);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function start() { if (state.active) return; state.active = true; void tick(); }
|
|
251
|
+
function stop() {
|
|
252
|
+
state.active = false;
|
|
253
|
+
if (state.timerId != null) { cancel(state.timerId); state.timerId = null; }
|
|
254
|
+
if (state.inFlight) { try { state.inFlight.abort(); } catch { /* ignore */ } state.inFlight = null; }
|
|
255
|
+
}
|
|
256
|
+
function onVisible() { if (!state.active) start(); }
|
|
257
|
+
function onHidden() { stop(); }
|
|
258
|
+
|
|
259
|
+
return { start, stop, onVisible, onHidden, tick, render, _state: state };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (typeof window !== 'undefined') {
|
|
263
|
+
window.Subscriptions = { createController, sanitizeForDisplay, renderAccounts, renderPendingLogins };
|
|
264
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAQ3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAiG7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AA6JtD,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA05BD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,GAC/F,IAAI,CAyUN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAQ3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAiG7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AA6JtD,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA05BD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,GAC/F,IAAI,CAyUN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA0gUtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
|
package/dist/commands/server.js
CHANGED
|
@@ -6494,6 +6494,64 @@ export async function startServer(options) {
|
|
|
6494
6494
|
quotaPoller.start();
|
|
6495
6495
|
console.log(pc.green(' Subscription quota poller started'));
|
|
6496
6496
|
}
|
|
6497
|
+
// EnrollmentWizard — mobile-first new-account login (P2.1 of the Subscription
|
|
6498
|
+
// & Auth Standard). Dark with the pool: the /subscription-pool/enroll routes
|
|
6499
|
+
// do nothing until an operator starts an enrollment. The login driver reuses
|
|
6500
|
+
// the proven tmux-spawn + capture-pane primitive (the same one /login uses) to
|
|
6501
|
+
// scrape the PUBLIC verification URL / device code — NEVER a token. The new
|
|
6502
|
+
// account's CLAUDE_CONFIG_DIR isolates the login to its own slot, so enrolling
|
|
6503
|
+
// a 2nd account never clobbers the 1st.
|
|
6504
|
+
const { PendingLoginStore } = await import('../core/PendingLoginStore.js');
|
|
6505
|
+
const { EnrollmentWizard } = await import('../core/EnrollmentWizard.js');
|
|
6506
|
+
const { FrameworkLoginDriver } = await import('../core/FrameworkLoginDriver.js');
|
|
6507
|
+
const DEFAULT_ENROLL_LOGIN_COMMANDS = {
|
|
6508
|
+
'claude-code': 'claude auth login',
|
|
6509
|
+
'codex-cli': 'codex login',
|
|
6510
|
+
'gemini-cli': 'gemini',
|
|
6511
|
+
'pi-cli': 'pi login',
|
|
6512
|
+
};
|
|
6513
|
+
const enrollLoginCommands = {
|
|
6514
|
+
...DEFAULT_ENROLL_LOGIN_COMMANDS,
|
|
6515
|
+
...(config.subscriptionPool?.enrollment?.loginCommands ?? {}),
|
|
6516
|
+
};
|
|
6517
|
+
const pendingLoginStore = new PendingLoginStore({ stateDir: config.stateDir });
|
|
6518
|
+
const enrollmentWizard = new EnrollmentWizard({
|
|
6519
|
+
store: pendingLoginStore,
|
|
6520
|
+
logger: { log: (m) => console.log(m), warn: (m) => console.warn(m) },
|
|
6521
|
+
driveLogin: new FrameworkLoginDriver({
|
|
6522
|
+
capture: async (session) => sessionManager.captureOutput(session, 120) || '',
|
|
6523
|
+
spawn: async ({ framework, configHome }) => {
|
|
6524
|
+
const tmuxPath = detectTmuxPath();
|
|
6525
|
+
if (!tmuxPath)
|
|
6526
|
+
throw new Error('tmux not available for enrollment login');
|
|
6527
|
+
const baseCmd = enrollLoginCommands[framework] ?? `${framework} login`;
|
|
6528
|
+
// env-prefix sets the per-account config home for the login process so
|
|
6529
|
+
// the credential lands in its own slot (CLAUDE_CONFIG_DIR isolation).
|
|
6530
|
+
const cmd = configHome
|
|
6531
|
+
? `env CLAUDE_CONFIG_DIR=${JSON.stringify(configHome)} ${baseCmd}`
|
|
6532
|
+
: baseCmd;
|
|
6533
|
+
const slug = (configHome ?? framework).replace(/[^a-zA-Z0-9]+/g, '-').slice(-24);
|
|
6534
|
+
const session = `instar-enroll-${framework}-${slug}`;
|
|
6535
|
+
try {
|
|
6536
|
+
execFileSync(tmuxPath, ['kill-session', '-t', `=${session}`], { stdio: 'ignore' });
|
|
6537
|
+
}
|
|
6538
|
+
catch { /* @silent-fallback-ok: no prior enroll session for this slot */ }
|
|
6539
|
+
execFileSync(tmuxPath, ['new-session', '-d', '-s', session, cmd], { timeout: 10000 });
|
|
6540
|
+
return { session };
|
|
6541
|
+
},
|
|
6542
|
+
logger: { log: (m) => console.log(m), warn: (m) => console.warn(m) },
|
|
6543
|
+
}).asLoginDriver(),
|
|
6544
|
+
});
|
|
6545
|
+
// Background auto-reissue sweep — refreshes an expired login code without the
|
|
6546
|
+
// operator asking (the pi-live-test gap). Inert with no pending logins; the
|
|
6547
|
+
// timer is unref'd so it never holds the process open.
|
|
6548
|
+
const enrollReissueTimer = setInterval(() => {
|
|
6549
|
+
enrollmentWizard
|
|
6550
|
+
.reissueExpired()
|
|
6551
|
+
.catch(() => { });
|
|
6552
|
+
}, config.subscriptionPool?.enrollment?.reissueSweepMs ?? 5 * 60_000);
|
|
6553
|
+
if (enrollReissueTimer.unref)
|
|
6554
|
+
enrollReissueTimer.unref();
|
|
6497
6555
|
// Commitment Sentinel — LLM-powered scanner that finds unregistered commitments.
|
|
6498
6556
|
let commitmentSentinel;
|
|
6499
6557
|
if (sharedIntelligence) {
|
|
@@ -11282,7 +11340,7 @@ export async function startServer(options) {
|
|
|
11282
11340
|
catch (err) {
|
|
11283
11341
|
console.log(pc.dim(` [session-pool] rollout gate not wired: ${err instanceof Error ? err.message : String(err)}`));
|
|
11284
11342
|
}
|
|
11285
|
-
const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, subscriptionPool, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, leaseTransport, onLeasePullRequest: () => leaseCoordinatorRef?.currentLease() ?? null, liveTailReceiver, handoffWireTransport, onHandoffBegin, onHandoffInitiate: handoffInitiate, handoffInProgress: handoffSentinelInProgress, messageLedger, currentInboundByTopic, replyMarkerTransport, onReplyMarker: messageLedger ? (marker) => { const m = marker; messageLedger.applyRemoteReplyMarker(m.dedupeKey, { platform: m.platform, replyIdempotencyKey: m.replyIdempotencyKey, epoch: m.epoch, topic: m.topic ?? null }); } : undefined, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, meshRpcDispatcher, workingSetPullCoordinator, commitmentReplicaStore, forwardCommitmentMutate, sessionOwnershipRegistry, topicPinStore: _topicPinStore ?? undefined, streamTicketStore: _streamTicketStore ?? undefined, poolStreamAllowRemoteInput: config.dashboard?.poolStream?.allowRemoteInput ?? false, poolStreamConnector: _poolStreamConnector ?? undefined, secretSync: _secretSyncHandle ?? undefined, meshSelfId: _meshSelfId ?? undefined, resolveRouterUrl: _resolveRouterUrl ?? undefined, resolvePeerUrls: _resolvePeerUrls ?? undefined, sessionPoolE2EResultStore, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, agentWorktreeReaper, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier });
|
|
11343
|
+
const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, subscriptionPool, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, enrollmentWizard, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, leaseTransport, onLeasePullRequest: () => leaseCoordinatorRef?.currentLease() ?? null, liveTailReceiver, handoffWireTransport, onHandoffBegin, onHandoffInitiate: handoffInitiate, handoffInProgress: handoffSentinelInProgress, messageLedger, currentInboundByTopic, replyMarkerTransport, onReplyMarker: messageLedger ? (marker) => { const m = marker; messageLedger.applyRemoteReplyMarker(m.dedupeKey, { platform: m.platform, replyIdempotencyKey: m.replyIdempotencyKey, epoch: m.epoch, topic: m.topic ?? null }); } : undefined, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, meshRpcDispatcher, workingSetPullCoordinator, commitmentReplicaStore, forwardCommitmentMutate, sessionOwnershipRegistry, topicPinStore: _topicPinStore ?? undefined, streamTicketStore: _streamTicketStore ?? undefined, poolStreamAllowRemoteInput: config.dashboard?.poolStream?.allowRemoteInput ?? false, poolStreamConnector: _poolStreamConnector ?? undefined, secretSync: _secretSyncHandle ?? undefined, meshSelfId: _meshSelfId ?? undefined, resolveRouterUrl: _resolveRouterUrl ?? undefined, resolvePeerUrls: _resolvePeerUrls ?? undefined, sessionPoolE2EResultStore, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, agentWorktreeReaper, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier });
|
|
11286
11344
|
// Resolve the late-bound topic-operator getter (increment 2e): routing was
|
|
11287
11345
|
// wired before the server existed; from here on inbound binds use the
|
|
11288
11346
|
// server's own store instance.
|