instar 1.3.1309 → 1.3.1310
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 +471 -5
- package/dist/commands/server.d.ts +21 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +82 -13
- package/dist/commands/server.js.map +1 -1
- package/dist/core/FileClassifier.d.ts.map +1 -1
- package/dist/core/FileClassifier.js +4 -0
- package/dist/core/FileClassifier.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +25 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/TopicProfileResolver.d.ts +20 -1
- package/dist/core/TopicProfileResolver.d.ts.map +1 -1
- package/dist/core/TopicProfileResolver.js +34 -11
- package/dist/core/TopicProfileResolver.js.map +1 -1
- package/dist/core/TopicProfileStore.d.ts +13 -0
- package/dist/core/TopicProfileStore.d.ts.map +1 -1
- package/dist/core/TopicProfileStore.js +25 -0
- package/dist/core/TopicProfileStore.js.map +1 -1
- package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
- package/dist/core/WriteDomainRegistry.js +16 -0
- package/dist/core/WriteDomainRegistry.js.map +1 -1
- package/dist/core/dashboardTopicProfile.d.ts +254 -0
- package/dist/core/dashboardTopicProfile.d.ts.map +1 -0
- package/dist/core/dashboardTopicProfile.js +314 -0
- package/dist/core/dashboardTopicProfile.js.map +1 -0
- package/dist/core/topicProfileWriteSurface.d.ts +2 -0
- package/dist/core/topicProfileWriteSurface.d.ts.map +1 -1
- package/dist/core/topicProfileWriteSurface.js +4 -0
- package/dist/core/topicProfileWriteSurface.js.map +1 -1
- package/dist/data/standards-guard-index.json +1 -1
- package/dist/data/standards-guard-index.meta.json +2 -2
- package/dist/data/standards-registry.meta.json +1 -1
- package/dist/messaging/telegram-origin/OriginDeterministicSend.d.ts +1 -1
- package/dist/messaging/telegram-origin/OriginDeterministicSend.d.ts.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +1 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/routes.d.ts +15 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +289 -26
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +63 -63
- package/src/data/standards-guard-index.json +1 -1
- package/src/data/standards-guard-index.meta.json +2 -2
- package/src/data/standards-registry.meta.json +1 -1
- package/src/data/state-coherence-registry.json +17 -0
- package/src/scaffold/templates.ts +1 -0
- package/upgrades/1.3.1310.md +70 -0
- package/upgrades/side-effects/dashboard-door-model-controls.md +227 -0
- package/upgrades/side-effects/window-run-liveness-register-lock-retry.md +65 -0
package/dashboard/index.html
CHANGED
|
@@ -1731,6 +1731,50 @@
|
|
|
1731
1731
|
color: var(--text);
|
|
1732
1732
|
}
|
|
1733
1733
|
|
|
1734
|
+
/* Door + model controls (dashboard-door-model-controls §3.6) — native
|
|
1735
|
+
selects, full width at phone width, no horizontal scroll. */
|
|
1736
|
+
.modal-field select {
|
|
1737
|
+
width: 100%;
|
|
1738
|
+
max-width: 100%;
|
|
1739
|
+
box-sizing: border-box;
|
|
1740
|
+
padding: 8px 12px;
|
|
1741
|
+
background: var(--bg-panel);
|
|
1742
|
+
color: var(--text-bright);
|
|
1743
|
+
border: 1px solid var(--border);
|
|
1744
|
+
border-radius: 6px;
|
|
1745
|
+
font-size: 14px;
|
|
1746
|
+
}
|
|
1747
|
+
.modal-field select:disabled { opacity: 0.5; }
|
|
1748
|
+
.modal-note {
|
|
1749
|
+
font-size: 12px;
|
|
1750
|
+
color: var(--text);
|
|
1751
|
+
margin: 6px 0 0;
|
|
1752
|
+
overflow-wrap: anywhere;
|
|
1753
|
+
}
|
|
1754
|
+
.modal-note.warn { color: var(--yellow, #e5c07b); }
|
|
1755
|
+
.modal-note.err { color: var(--red, #e06c75); }
|
|
1756
|
+
.ntd-row {
|
|
1757
|
+
margin: 0 12px 8px;
|
|
1758
|
+
padding: 6px 10px;
|
|
1759
|
+
border: 1px solid var(--border);
|
|
1760
|
+
border-radius: 6px;
|
|
1761
|
+
font-size: 12px;
|
|
1762
|
+
color: var(--text);
|
|
1763
|
+
cursor: pointer;
|
|
1764
|
+
overflow-wrap: anywhere;
|
|
1765
|
+
}
|
|
1766
|
+
.ntd-row:hover { background: var(--bg-hover); }
|
|
1767
|
+
.ntd-row .ntd-note { color: var(--text-dim); font-size: 11px; }
|
|
1768
|
+
.door-model-btn {
|
|
1769
|
+
font-size: 11px;
|
|
1770
|
+
padding: 2px 8px;
|
|
1771
|
+
border-radius: 10px;
|
|
1772
|
+
border: 1px solid var(--border);
|
|
1773
|
+
background: var(--bg);
|
|
1774
|
+
color: var(--text);
|
|
1775
|
+
cursor: pointer;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1734
1778
|
.modal-hint {
|
|
1735
1779
|
font-size: 11px;
|
|
1736
1780
|
color: var(--text-dim);
|
|
@@ -3027,6 +3071,7 @@
|
|
|
3027
3071
|
</div>
|
|
3028
3072
|
</div>
|
|
3029
3073
|
<div class="tab-purpose" style="padding:0 12px 8px">Your agent's live sessions — pick one to watch its terminal in real time.</div>
|
|
3074
|
+
<div class="ntd-row" id="newTopicDefaultRow" style="display:none" onclick="openNewTopicDefaultSheet()" role="button" tabindex="0"></div>
|
|
3030
3075
|
<div class="session-list" id="sessionList">
|
|
3031
3076
|
<div class="empty-state" id="emptyState">
|
|
3032
3077
|
<div class="icon">💭</div>
|
|
@@ -3048,6 +3093,7 @@
|
|
|
3048
3093
|
<h3 id="termSessionName">—</h3>
|
|
3049
3094
|
<span class="type-badge" id="termTypeBadge" style="display:none"></span>
|
|
3050
3095
|
<span class="model-badge" id="termModelBadge" style="display:none"></span>
|
|
3096
|
+
<button class="door-model-btn" id="termDoorModelBtn" style="display:none" onclick="openDoorModelSheet()" title="Switch this topic's door and model">Door + model</button>
|
|
3051
3097
|
</div>
|
|
3052
3098
|
<div class="terminal-actions">
|
|
3053
3099
|
<button class="action-btn" onclick="sendKey('Enter')" title="Send Enter">Enter</button>
|
|
@@ -3126,7 +3172,7 @@
|
|
|
3126
3172
|
</div>
|
|
3127
3173
|
<div class="modal-field">
|
|
3128
3174
|
<label for="newSessionPlatform">Platform</label>
|
|
3129
|
-
<select id="newSessionPlatform" style="width:100%;padding:8px 12px;background:var(--bg-panel);color:var(--text-bright);border:1px solid var(--border);border-radius:6px;font-size:14px;">
|
|
3175
|
+
<select id="newSessionPlatform" onchange="onNewSessionPlatformChange()" style="width:100%;padding:8px 12px;background:var(--bg-panel);color:var(--text-bright);border:1px solid var(--border);border-radius:6px;font-size:14px;">
|
|
3130
3176
|
<option value="auto">Auto (best available)</option>
|
|
3131
3177
|
<option value="telegram">✈️ Telegram</option>
|
|
3132
3178
|
<option value="slack">💬 Slack</option>
|
|
@@ -3134,6 +3180,17 @@
|
|
|
3134
3180
|
</select>
|
|
3135
3181
|
<div class="modal-hint">Choose which messaging platform to link this session to.</div>
|
|
3136
3182
|
</div>
|
|
3183
|
+
<div class="modal-field">
|
|
3184
|
+
<label for="newSessionDoor">Door</label>
|
|
3185
|
+
<select id="newSessionDoor" onchange="onNewSessionDoorChange()"></select>
|
|
3186
|
+
</div>
|
|
3187
|
+
<div class="modal-field">
|
|
3188
|
+
<label for="newSessionModel">Model</label>
|
|
3189
|
+
<select id="newSessionModel" onchange="updateCreateButtonLabel()"></select>
|
|
3190
|
+
<div class="modal-hint" id="newSessionDoorHint">You can change this again after the topic's first message.</div>
|
|
3191
|
+
<div class="modal-note warn" id="newSessionRegimeNote" style="display:none"></div>
|
|
3192
|
+
<div class="modal-note err" id="newSessionError" style="display:none"></div>
|
|
3193
|
+
</div>
|
|
3137
3194
|
<!-- Keep hidden checkbox for backward compat -->
|
|
3138
3195
|
<input type="hidden" id="newSessionHeadless">
|
|
3139
3196
|
<div class="modal-actions">
|
|
@@ -3143,6 +3200,54 @@
|
|
|
3143
3200
|
</div>
|
|
3144
3201
|
</div>
|
|
3145
3202
|
|
|
3203
|
+
<!-- Default for dashboard-created topics (dashboard-door-model-controls §3.2/§3.6) -->
|
|
3204
|
+
<div class="modal-backdrop" id="newTopicDefaultSheet" style="display:none" onclick="closeSheet(event, 'newTopicDefaultSheet')">
|
|
3205
|
+
<div class="modal-dialog" onclick="event.stopPropagation()">
|
|
3206
|
+
<h3>Default for dashboard-created topics</h3>
|
|
3207
|
+
<div class="modal-note">Applies to topics you create from this dashboard. Topics you start in Telegram and topics that already exist are not changed.</div>
|
|
3208
|
+
<div class="modal-field" style="margin-top:12px">
|
|
3209
|
+
<label for="ntdDoor">Door</label>
|
|
3210
|
+
<select id="ntdDoor" onchange="onSheetDoorChange('ntdDoor','ntdModel')"></select>
|
|
3211
|
+
</div>
|
|
3212
|
+
<div class="modal-field">
|
|
3213
|
+
<label for="ntdModel">Model</label>
|
|
3214
|
+
<select id="ntdModel"></select>
|
|
3215
|
+
</div>
|
|
3216
|
+
<div class="modal-note" id="ntdCount"></div>
|
|
3217
|
+
<div class="modal-note warn" id="ntdRegimeNote" style="display:none"></div>
|
|
3218
|
+
<div class="modal-note" id="ntdCaveat"></div>
|
|
3219
|
+
<div class="modal-note" id="ntdStatus" style="display:none"></div>
|
|
3220
|
+
<div class="modal-actions">
|
|
3221
|
+
<button class="modal-btn-cancel" onclick="clearNewTopicDefault()">Clear</button>
|
|
3222
|
+
<button class="modal-btn-cancel" onclick="closeSheet(null, 'newTopicDefaultSheet')">Close</button>
|
|
3223
|
+
<button class="modal-btn-create" id="ntdSaveBtn" onclick="saveNewTopicDefault()">Save</button>
|
|
3224
|
+
</div>
|
|
3225
|
+
</div>
|
|
3226
|
+
</div>
|
|
3227
|
+
|
|
3228
|
+
<!-- Switch an existing topic's door + model (dashboard-door-model-controls §3.4/§3.6) -->
|
|
3229
|
+
<div class="modal-backdrop" id="doorModelSheet" style="display:none" onclick="closeSheet(event, 'doorModelSheet')">
|
|
3230
|
+
<div class="modal-dialog" onclick="event.stopPropagation()">
|
|
3231
|
+
<h3>Door + model</h3>
|
|
3232
|
+
<div class="modal-note" id="dmCurrent">Loading…</div>
|
|
3233
|
+
<div class="modal-field" style="margin-top:12px">
|
|
3234
|
+
<label for="dmDoor">Door</label>
|
|
3235
|
+
<select id="dmDoor" onchange="onSheetDoorChange('dmDoor','dmModel')"></select>
|
|
3236
|
+
</div>
|
|
3237
|
+
<div class="modal-field">
|
|
3238
|
+
<label for="dmModel">Model</label>
|
|
3239
|
+
<select id="dmModel"></select>
|
|
3240
|
+
</div>
|
|
3241
|
+
<div class="modal-note">Switching the door restarts this topic's session; the conversation is kept.</div>
|
|
3242
|
+
<div class="modal-note warn" id="dmRegimeNote" style="display:none"></div>
|
|
3243
|
+
<div class="modal-note" id="dmStatus" style="display:none"></div>
|
|
3244
|
+
<div class="modal-actions">
|
|
3245
|
+
<button class="modal-btn-cancel" onclick="closeSheet(null, 'doorModelSheet')">Close</button>
|
|
3246
|
+
<button class="modal-btn-create" id="dmSwitchBtn" onclick="switchDoorModel()">Switch</button>
|
|
3247
|
+
</div>
|
|
3248
|
+
</div>
|
|
3249
|
+
</div>
|
|
3250
|
+
|
|
3146
3251
|
<!-- Send Content tab (was Drop Zone) -->
|
|
3147
3252
|
<div class="dropzone-container" id="dropzoneTab" style="display:none">
|
|
3148
3253
|
<div class="dropzone-panel">
|
|
@@ -4200,6 +4305,11 @@
|
|
|
4200
4305
|
// API token is intentionally insufficient for operator-only actions.
|
|
4201
4306
|
let operatorSessionToken = '';
|
|
4202
4307
|
let sessions = [];
|
|
4308
|
+
// Door + model controls (dashboard-door-model-controls §3.6).
|
|
4309
|
+
let topicProfileOptions = null;
|
|
4310
|
+
const switchingModel = new Set(); // tmux sessions whose topic is mid-switch
|
|
4311
|
+
let pendingDoorSwitch = null; // { topicId, model, since, tmuxSession }
|
|
4312
|
+
let doorModelSheetTopic = null; // { topicId, tmuxSession }
|
|
4203
4313
|
// Sessions running on OTHER machines in the pool (GET /sessions?scope=pool).
|
|
4204
4314
|
// Kept separate from `sessions` because the WebSocket replaces that array
|
|
4205
4315
|
// with LOCAL sessions on every broadcast — merging would be wiped each tick.
|
|
@@ -4265,6 +4375,7 @@
|
|
|
4265
4375
|
startWaPolling();
|
|
4266
4376
|
startVitalSigns();
|
|
4267
4377
|
startPoolSessionsPolling();
|
|
4378
|
+
loadTopicProfileOptions();
|
|
4268
4379
|
} else {
|
|
4269
4380
|
showAuthError('Incorrect PIN');
|
|
4270
4381
|
}
|
|
@@ -4291,6 +4402,7 @@
|
|
|
4291
4402
|
startWaPolling();
|
|
4292
4403
|
startVitalSigns();
|
|
4293
4404
|
startPoolSessionsPolling();
|
|
4405
|
+
loadTopicProfileOptions();
|
|
4294
4406
|
}
|
|
4295
4407
|
})
|
|
4296
4408
|
.catch(() => {});
|
|
@@ -4567,7 +4679,7 @@
|
|
|
4567
4679
|
</div>
|
|
4568
4680
|
<div class="session-meta">
|
|
4569
4681
|
<span class="type-badge ${sType}">${sType}</span>
|
|
4570
|
-
<span class="model-badge ${model}">${model}</span>
|
|
4682
|
+
<span class="model-badge ${escapeHtml(model)}">${switchingModel.has(session.tmuxSession) && !session.remote ? 'switching…' : escapeHtml(model)}</span>
|
|
4571
4683
|
<span class="platform-badge ${platform}" title="${platformLabel}">${platformIcon}</span>
|
|
4572
4684
|
${session.machineNickname ? `<span class="machine-badge" title="Machine this session runs on">${escapeHtml(session.machineNickname)}</span>` : ''}
|
|
4573
4685
|
${(session.duplicateTopic === true || poolDuplicateSessionNames.has(session.name)) ? `<span class="duplicate-badge" title="Genuine cross-machine duplicate: this conversation has a live session on more than one machine at once">⚠ duplicate</span>` : ''}
|
|
@@ -4653,6 +4765,9 @@
|
|
|
4653
4765
|
badge.style.display = 'none';
|
|
4654
4766
|
}
|
|
4655
4767
|
|
|
4768
|
+
if (switchingModel.has(tmuxSession) && !session.remote) badge.textContent = 'switching…';
|
|
4769
|
+
refreshDoorModelButton(session);
|
|
4770
|
+
|
|
4656
4771
|
// Show session type badge
|
|
4657
4772
|
const sType = session.type || (session.jobSlug ? 'job' : 'interactive');
|
|
4658
4773
|
const typeEl = document.getElementById('termTypeBadge');
|
|
@@ -4948,7 +5063,17 @@
|
|
|
4948
5063
|
const nameInput = document.getElementById('newSessionName');
|
|
4949
5064
|
nameInput.value = '';
|
|
4950
5065
|
document.getElementById('newSessionHeadless').checked = false;
|
|
5066
|
+
showNote('newSessionError', '');
|
|
4951
5067
|
setTimeout(() => nameInput.focus(), 50);
|
|
5068
|
+
// Door/Model preselect from the default (§3.6) — refreshed on every open.
|
|
5069
|
+
loadTopicProfileOptions().then(() => {
|
|
5070
|
+
const def = topicProfileOptions && topicProfileOptions.newTopicDefault;
|
|
5071
|
+
fillDoorSelect('newSessionDoor', def && def.framework ? def.framework : '', true);
|
|
5072
|
+
fillModelSelect('newSessionModel', document.getElementById('newSessionDoor').value,
|
|
5073
|
+
def && def.framework ? def.model : null);
|
|
5074
|
+
showRegimeNote('newSessionRegimeNote');
|
|
5075
|
+
onNewSessionPlatformChange();
|
|
5076
|
+
});
|
|
4952
5077
|
}
|
|
4953
5078
|
|
|
4954
5079
|
function closeNewSessionModal(e) {
|
|
@@ -4973,6 +5098,16 @@
|
|
|
4973
5098
|
|
|
4974
5099
|
btn.disabled = true;
|
|
4975
5100
|
btn.textContent = 'Creating...';
|
|
5101
|
+
showNote('newSessionError', '');
|
|
5102
|
+
|
|
5103
|
+
// A pick that differs from the dashboard default is sent explicitly; an
|
|
5104
|
+
// unchanged pick sends nothing and the server seeds the default (§3.2).
|
|
5105
|
+
const pick = newSessionPick();
|
|
5106
|
+
const payload = { name, headless, platform: platform === 'auto' ? undefined : platform };
|
|
5107
|
+
if (pick && pickDiffersFromDefault(pick)) {
|
|
5108
|
+
payload.framework = pick.framework;
|
|
5109
|
+
payload.model = pick.model;
|
|
5110
|
+
}
|
|
4976
5111
|
|
|
4977
5112
|
try {
|
|
4978
5113
|
const resp = await fetch('/sessions/create', {
|
|
@@ -4980,23 +5115,354 @@
|
|
|
4980
5115
|
headers: {
|
|
4981
5116
|
'Authorization': `Bearer ${token}`,
|
|
4982
5117
|
'Content-Type': 'application/json',
|
|
5118
|
+
// Required for a door/model pick (403 without it) — §3.6.
|
|
5119
|
+
'X-Instar-Request': '1',
|
|
4983
5120
|
},
|
|
4984
|
-
body: JSON.stringify(
|
|
5121
|
+
body: JSON.stringify(payload),
|
|
4985
5122
|
});
|
|
4986
5123
|
const data = await resp.json();
|
|
4987
5124
|
|
|
4988
5125
|
if (!resp.ok) {
|
|
5126
|
+
// Server copy verbatim, rendered as text (§3.6).
|
|
5127
|
+
showNote('newSessionError', data.error || data.message || 'Failed to create session');
|
|
4989
5128
|
showToast(data.error || 'Failed to create session', 'error');
|
|
4990
5129
|
return;
|
|
4991
5130
|
}
|
|
4992
5131
|
|
|
4993
|
-
|
|
5132
|
+
const where = data.profile ? ` on ${data.profile.label}${data.profile.model ? ' · ' + data.profile.model : ''}` : '';
|
|
5133
|
+
showToast(`Session "${name}" created${data.topicId ? ' with Telegram topic' + where : ' (headless)'}`, 'success');
|
|
4994
5134
|
document.getElementById('newSessionModal').style.display = 'none';
|
|
5135
|
+
if (data.seed && data.seed.outcome === 'seeded') loadTopicProfileOptions();
|
|
4995
5136
|
} catch (err) {
|
|
4996
5137
|
showToast('Network error: ' + (err.message || 'Cannot reach server'), 'error');
|
|
4997
5138
|
} finally {
|
|
4998
5139
|
btn.disabled = false;
|
|
4999
|
-
|
|
5140
|
+
updateCreateButtonLabel();
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
|
|
5144
|
+
// ── Door + model controls (docs/specs/dashboard-door-model-controls.md) ──
|
|
5145
|
+
// Every server-supplied string is rendered with textContent / option.text.
|
|
5146
|
+
|
|
5147
|
+
function showNote(id, text) {
|
|
5148
|
+
const el = document.getElementById(id);
|
|
5149
|
+
if (!el) return;
|
|
5150
|
+
el.textContent = text || '';
|
|
5151
|
+
el.style.display = text ? 'block' : 'none';
|
|
5152
|
+
}
|
|
5153
|
+
|
|
5154
|
+
async function loadTopicProfileOptions() {
|
|
5155
|
+
try {
|
|
5156
|
+
const r = await fetch('/topic-profile/options', { headers: { 'Authorization': `Bearer ${token}` } });
|
|
5157
|
+
topicProfileOptions = r.ok ? await r.json() : null;
|
|
5158
|
+
} catch {
|
|
5159
|
+
topicProfileOptions = null;
|
|
5160
|
+
}
|
|
5161
|
+
renderNewTopicDefaultRow();
|
|
5162
|
+
return topicProfileOptions;
|
|
5163
|
+
}
|
|
5164
|
+
|
|
5165
|
+
function doorOf(fw) {
|
|
5166
|
+
return ((topicProfileOptions && topicProfileOptions.doors) || []).find(d => d.framework === fw) || null;
|
|
5167
|
+
}
|
|
5168
|
+
|
|
5169
|
+
function doorModelLabel(fw, model) {
|
|
5170
|
+
const d = doorOf(fw);
|
|
5171
|
+
return (d ? d.label : fw) + ' · ' + (model || 'Account default');
|
|
5172
|
+
}
|
|
5173
|
+
|
|
5174
|
+
// withDefaultOption: the modal offers "Agent default" (no pick) when no
|
|
5175
|
+
// dashboard default is set, so an untouched create stays exactly as today.
|
|
5176
|
+
function fillDoorSelect(id, selected, withDefaultOption) {
|
|
5177
|
+
const sel = document.getElementById(id);
|
|
5178
|
+
sel.textContent = '';
|
|
5179
|
+
const hasDefault = topicProfileOptions && topicProfileOptions.newTopicDefault && topicProfileOptions.newTopicDefault.framework;
|
|
5180
|
+
if (withDefaultOption && !hasDefault) {
|
|
5181
|
+
const o = document.createElement('option');
|
|
5182
|
+
o.value = '';
|
|
5183
|
+
o.text = 'Agent default';
|
|
5184
|
+
sel.appendChild(o);
|
|
5185
|
+
}
|
|
5186
|
+
for (const d of ((topicProfileOptions && topicProfileOptions.doors) || [])) {
|
|
5187
|
+
const o = document.createElement('option');
|
|
5188
|
+
o.value = d.framework;
|
|
5189
|
+
let text = d.label;
|
|
5190
|
+
if (d.availability === 'assumed') text += ' (not verified)';
|
|
5191
|
+
if (!d.selectable) {
|
|
5192
|
+
text += ' — ' + (d.reason || 'unavailable');
|
|
5193
|
+
if (d.chatOnly) text += ' (you can still pin this in chat)';
|
|
5194
|
+
o.disabled = true;
|
|
5195
|
+
}
|
|
5196
|
+
o.text = text;
|
|
5197
|
+
sel.appendChild(o);
|
|
5198
|
+
}
|
|
5199
|
+
const canSelect = [...sel.options].some(o => o.value === selected && !o.disabled);
|
|
5200
|
+
sel.value = canSelect ? selected : (sel.options[0] && !sel.options[0].disabled ? sel.options[0].value : '');
|
|
5201
|
+
}
|
|
5202
|
+
|
|
5203
|
+
function fillModelSelect(id, fw, selected) {
|
|
5204
|
+
const sel = document.getElementById(id);
|
|
5205
|
+
sel.textContent = '';
|
|
5206
|
+
const d = doorOf(fw);
|
|
5207
|
+
if (!d) {
|
|
5208
|
+
const o = document.createElement('option');
|
|
5209
|
+
o.value = '';
|
|
5210
|
+
o.text = '—';
|
|
5211
|
+
sel.appendChild(o);
|
|
5212
|
+
sel.disabled = true;
|
|
5213
|
+
return;
|
|
5214
|
+
}
|
|
5215
|
+
sel.disabled = false;
|
|
5216
|
+
const acct = document.createElement('option');
|
|
5217
|
+
acct.value = '';
|
|
5218
|
+
acct.text = 'Account default';
|
|
5219
|
+
sel.appendChild(acct);
|
|
5220
|
+
for (const m of d.models) {
|
|
5221
|
+
const o = document.createElement('option');
|
|
5222
|
+
o.value = m;
|
|
5223
|
+
o.text = m;
|
|
5224
|
+
sel.appendChild(o);
|
|
5225
|
+
}
|
|
5226
|
+
const want = selected !== undefined && selected !== null ? selected : (d.defaultModel || '');
|
|
5227
|
+
sel.value = [...sel.options].some(o => o.value === want) ? want : '';
|
|
5228
|
+
}
|
|
5229
|
+
|
|
5230
|
+
function showRegimeNote(id) {
|
|
5231
|
+
const regime = topicProfileOptions && topicProfileOptions.regime;
|
|
5232
|
+
showNote(id, regime === 'dry-run'
|
|
5233
|
+
? 'On this agent a chosen model is recorded as intent, not applied — the door still switches.'
|
|
5234
|
+
: regime === 'disabled'
|
|
5235
|
+
? 'On this agent the door switch works, but model choice is turned off — the model will be refused.'
|
|
5236
|
+
: '');
|
|
5237
|
+
}
|
|
5238
|
+
|
|
5239
|
+
function onSheetDoorChange(doorId, modelId) {
|
|
5240
|
+
fillModelSelect(modelId, document.getElementById(doorId).value, null);
|
|
5241
|
+
}
|
|
5242
|
+
|
|
5243
|
+
function newSessionPick() {
|
|
5244
|
+
const fw = document.getElementById('newSessionDoor').value;
|
|
5245
|
+
if (!fw) return null;
|
|
5246
|
+
const m = document.getElementById('newSessionModel').value;
|
|
5247
|
+
return { framework: fw, model: m || null };
|
|
5248
|
+
}
|
|
5249
|
+
|
|
5250
|
+
function pickDiffersFromDefault(pick) {
|
|
5251
|
+
const def = topicProfileOptions && topicProfileOptions.newTopicDefault;
|
|
5252
|
+
if (!def || !def.framework) return true;
|
|
5253
|
+
return def.framework !== pick.framework || (def.model || null) !== pick.model;
|
|
5254
|
+
}
|
|
5255
|
+
|
|
5256
|
+
function updateCreateButtonLabel() {
|
|
5257
|
+
const btn = document.getElementById('createSessionBtn');
|
|
5258
|
+
const doorSel = document.getElementById('newSessionDoor');
|
|
5259
|
+
const pick = doorSel && !doorSel.disabled ? newSessionPick() : null;
|
|
5260
|
+
btn.textContent = pick && pickDiffersFromDefault(pick)
|
|
5261
|
+
? 'Create on ' + doorModelLabel(pick.framework, pick.model)
|
|
5262
|
+
: 'Create Session';
|
|
5263
|
+
}
|
|
5264
|
+
|
|
5265
|
+
function onNewSessionDoorChange() {
|
|
5266
|
+
fillModelSelect('newSessionModel', document.getElementById('newSessionDoor').value, null);
|
|
5267
|
+
updateCreateButtonLabel();
|
|
5268
|
+
}
|
|
5269
|
+
|
|
5270
|
+
// Door/Model apply only to Telegram topics in v1 — disabled for Headless/Slack.
|
|
5271
|
+
function onNewSessionPlatformChange() {
|
|
5272
|
+
const p = document.getElementById('newSessionPlatform').value;
|
|
5273
|
+
const off = p === 'headless' || p === 'slack' || !topicProfileOptions;
|
|
5274
|
+
document.getElementById('newSessionDoor').disabled = off;
|
|
5275
|
+
document.getElementById('newSessionModel').disabled = off || !document.getElementById('newSessionDoor').value;
|
|
5276
|
+
document.getElementById('newSessionDoorHint').textContent = off && topicProfileOptions
|
|
5277
|
+
? 'Door and model apply to Telegram topics only.'
|
|
5278
|
+
: 'You can change this again after the topic\'s first message.';
|
|
5279
|
+
updateCreateButtonLabel();
|
|
5280
|
+
}
|
|
5281
|
+
|
|
5282
|
+
function closeSheet(e, id) {
|
|
5283
|
+
if (e && e.target !== e.currentTarget) return;
|
|
5284
|
+
document.getElementById(id).style.display = 'none';
|
|
5285
|
+
}
|
|
5286
|
+
|
|
5287
|
+
// Sessions list header row — "Default for dashboard-created topics".
|
|
5288
|
+
function renderNewTopicDefaultRow() {
|
|
5289
|
+
const row = document.getElementById('newTopicDefaultRow');
|
|
5290
|
+
if (!row) return;
|
|
5291
|
+
if (!topicProfileOptions) { row.style.display = 'none'; return; }
|
|
5292
|
+
const def = topicProfileOptions.newTopicDefault || {};
|
|
5293
|
+
row.textContent = '';
|
|
5294
|
+
const main = document.createElement('div');
|
|
5295
|
+
main.textContent = 'Default for dashboard-created topics: '
|
|
5296
|
+
+ (def.framework ? doorModelLabel(def.framework, def.model) : 'not set') + ' ▸';
|
|
5297
|
+
const note = document.createElement('div');
|
|
5298
|
+
note.className = 'ntd-note';
|
|
5299
|
+
note.textContent = def.replication === 'local-only' ? 'this machine only' : (def.replication || '');
|
|
5300
|
+
row.appendChild(main);
|
|
5301
|
+
row.appendChild(note);
|
|
5302
|
+
row.style.display = 'block';
|
|
5303
|
+
}
|
|
5304
|
+
|
|
5305
|
+
function openNewTopicDefaultSheet() {
|
|
5306
|
+
document.getElementById('newTopicDefaultSheet').style.display = 'flex';
|
|
5307
|
+
showNote('ntdStatus', '');
|
|
5308
|
+
loadTopicProfileOptions().then(() => {
|
|
5309
|
+
const def = (topicProfileOptions && topicProfileOptions.newTopicDefault) || {};
|
|
5310
|
+
fillDoorSelect('ntdDoor', def.framework || '', false);
|
|
5311
|
+
fillModelSelect('ntdModel', document.getElementById('ntdDoor').value, def.framework ? def.model : null);
|
|
5312
|
+
showRegimeNote('ntdRegimeNote');
|
|
5313
|
+
const n = def.seedsSinceBoot || 0;
|
|
5314
|
+
document.getElementById('ntdCount').textContent =
|
|
5315
|
+
`${n} dashboard topic${n === 1 ? '' : 's'} started on this default since the server started.`;
|
|
5316
|
+
document.getElementById('ntdCaveat').textContent =
|
|
5317
|
+
`Applies to new topics created on ${selfMachineNickname || 'this machine'}. Your other machines keep their own default until replication lands. `
|
|
5318
|
+
+ 'Topics you start in Telegram are not covered yet (coming with the cross-machine work).';
|
|
5319
|
+
});
|
|
5320
|
+
}
|
|
5321
|
+
|
|
5322
|
+
async function postNewTopicDefault(body) {
|
|
5323
|
+
const headers = {
|
|
5324
|
+
'Authorization': `Bearer ${token}`,
|
|
5325
|
+
'Content-Type': 'application/json',
|
|
5326
|
+
'X-Instar-Request': '1',
|
|
5327
|
+
};
|
|
5328
|
+
// Attribution only (never a refusal): lets the audit tell a dashboard human
|
|
5329
|
+
// from an agent session holding the same Bearer.
|
|
5330
|
+
if (operatorSessionToken) headers['X-Instar-Operator-Session'] = operatorSessionToken;
|
|
5331
|
+
const r = await fetch('/topic-profile/new-topic-default', { method: 'POST', headers, body: JSON.stringify(body) });
|
|
5332
|
+
let data = {};
|
|
5333
|
+
try { data = await r.json(); } catch { /* non-JSON error body */ }
|
|
5334
|
+
return { ok: r.ok, data };
|
|
5335
|
+
}
|
|
5336
|
+
|
|
5337
|
+
async function saveNewTopicDefault() {
|
|
5338
|
+
const fw = document.getElementById('ntdDoor').value;
|
|
5339
|
+
if (!fw) return;
|
|
5340
|
+
const model = document.getElementById('ntdModel').value || null;
|
|
5341
|
+
const { ok, data } = await postNewTopicDefault({ framework: fw, model });
|
|
5342
|
+
if (!ok) { showNote('ntdStatus', data.error || 'Could not save the default'); return; }
|
|
5343
|
+
await loadTopicProfileOptions();
|
|
5344
|
+
const saved = data.newTopicDefault;
|
|
5345
|
+
showNote('ntdStatus', 'Saved: ' + doorModelLabel(saved.framework, saved.model));
|
|
5346
|
+
}
|
|
5347
|
+
|
|
5348
|
+
async function clearNewTopicDefault() {
|
|
5349
|
+
const { ok, data } = await postNewTopicDefault({ clear: true });
|
|
5350
|
+
if (!ok) { showNote('ntdStatus', data.error || 'Could not clear the default'); return; }
|
|
5351
|
+
await loadTopicProfileOptions();
|
|
5352
|
+
fillDoorSelect('ntdDoor', '', false);
|
|
5353
|
+
fillModelSelect('ntdModel', document.getElementById('ntdDoor').value, null);
|
|
5354
|
+
showNote('ntdStatus', 'Cleared — dashboard-created topics start on the agent defaults.');
|
|
5355
|
+
}
|
|
5356
|
+
|
|
5357
|
+
// The session view's "Door + model" button — local Telegram tiles only
|
|
5358
|
+
// (remote and Slack tiles keep the read-only badge, §6).
|
|
5359
|
+
async function refreshDoorModelButton(session) {
|
|
5360
|
+
const btn = document.getElementById('termDoorModelBtn');
|
|
5361
|
+
btn.style.display = 'none';
|
|
5362
|
+
doorModelSheetTopic = null;
|
|
5363
|
+
if (!session || session.remote) return;
|
|
5364
|
+
try {
|
|
5365
|
+
const r = await fetch('/sessions', { headers: { 'Authorization': `Bearer ${token}` } });
|
|
5366
|
+
if (!r.ok) return;
|
|
5367
|
+
const list = await r.json();
|
|
5368
|
+
const arr = Array.isArray(list) ? list : (list.sessions || []);
|
|
5369
|
+
const s = arr.find(x => x.tmuxSession === session.tmuxSession);
|
|
5370
|
+
if (!s || s.platform !== 'telegram' || s.platformId == null) return;
|
|
5371
|
+
if (activeSession !== session.tmuxSession) return;
|
|
5372
|
+
doorModelSheetTopic = { topicId: String(s.platformId), tmuxSession: s.tmuxSession };
|
|
5373
|
+
btn.style.display = 'inline-block';
|
|
5374
|
+
} catch { /* read-only badge stays */ }
|
|
5375
|
+
}
|
|
5376
|
+
|
|
5377
|
+
function describeSource(profile) {
|
|
5378
|
+
const src = profile.resolved && profile.resolved.sources ? profile.resolved.sources.framework : '';
|
|
5379
|
+
const by = profile.pin && profile.pin.updatedBy ? profile.pin.updatedBy : '';
|
|
5380
|
+
if (src === 'profile-pin' && (by === 'system:dashboard-create' || by === 'system:new-topic-default')) return 'seeded at creation';
|
|
5381
|
+
if (src === 'profile-pin') return 'pinned by you';
|
|
5382
|
+
return 'default';
|
|
5383
|
+
}
|
|
5384
|
+
|
|
5385
|
+
async function openDoorModelSheet() {
|
|
5386
|
+
if (!doorModelSheetTopic) return;
|
|
5387
|
+
document.getElementById('doorModelSheet').style.display = 'flex';
|
|
5388
|
+
showNote('dmStatus', '');
|
|
5389
|
+
document.getElementById('dmCurrent').textContent = 'Loading…';
|
|
5390
|
+
await loadTopicProfileOptions();
|
|
5391
|
+
try {
|
|
5392
|
+
const r = await fetch('/topic-profile/' + encodeURIComponent(doorModelSheetTopic.topicId), { headers: { 'Authorization': `Bearer ${token}` } });
|
|
5393
|
+
const p = await r.json();
|
|
5394
|
+
if (!r.ok) { document.getElementById('dmCurrent').textContent = p.error || 'Could not read this topic\'s profile'; return; }
|
|
5395
|
+
document.getElementById('dmCurrent').textContent = 'Now: ' + doorModelLabel(p.resolved.framework, p.resolved.model) + ' (' + describeSource(p) + ')';
|
|
5396
|
+
fillDoorSelect('dmDoor', p.resolved.framework, false);
|
|
5397
|
+
fillModelSelect('dmModel', document.getElementById('dmDoor').value, p.resolved.model);
|
|
5398
|
+
showRegimeNote('dmRegimeNote');
|
|
5399
|
+
} catch (err) {
|
|
5400
|
+
document.getElementById('dmCurrent').textContent = 'Network error: ' + (err.message || 'Cannot reach server');
|
|
5401
|
+
}
|
|
5402
|
+
}
|
|
5403
|
+
|
|
5404
|
+
async function switchDoorModel() {
|
|
5405
|
+
if (!doorModelSheetTopic) return;
|
|
5406
|
+
const fw = document.getElementById('dmDoor').value;
|
|
5407
|
+
const model = document.getElementById('dmModel').value || null;
|
|
5408
|
+
const btn = document.getElementById('dmSwitchBtn');
|
|
5409
|
+
btn.disabled = true;
|
|
5410
|
+
try {
|
|
5411
|
+
const r = await fetch('/topic-profile/' + encodeURIComponent(doorModelSheetTopic.topicId), {
|
|
5412
|
+
method: 'POST',
|
|
5413
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', 'X-Instar-Request': '1' },
|
|
5414
|
+
body: JSON.stringify({ framework: fw, model }),
|
|
5415
|
+
});
|
|
5416
|
+
let data = {};
|
|
5417
|
+
try { data = await r.json(); } catch { /* non-JSON error body */ }
|
|
5418
|
+
// The server's reply verbatim — including no-bound-operator (§3.4).
|
|
5419
|
+
showNote('dmStatus', data.message || data.error || (r.ok ? 'Done.' : 'Refused.'));
|
|
5420
|
+
if (r.ok && !data.noop) {
|
|
5421
|
+
pendingDoorSwitch = { topicId: doorModelSheetTopic.topicId, model, since: Date.now(), tmuxSession: doorModelSheetTopic.tmuxSession };
|
|
5422
|
+
switchingModel.add(doorModelSheetTopic.tmuxSession);
|
|
5423
|
+
renderSessionList();
|
|
5424
|
+
watchDoorSwitch();
|
|
5425
|
+
}
|
|
5426
|
+
} catch (err) {
|
|
5427
|
+
showNote('dmStatus', 'Network error: ' + (err.message || 'Cannot reach server'));
|
|
5428
|
+
} finally {
|
|
5429
|
+
btn.disabled = false;
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
5432
|
+
|
|
5433
|
+
// "switching…" until GET /sessions reports the LAUNCHED model for the topic —
|
|
5434
|
+
// never an echo of the selection (§3.6 / P20 badge declaration).
|
|
5435
|
+
async function watchDoorSwitch() {
|
|
5436
|
+
const job = pendingDoorSwitch;
|
|
5437
|
+
if (!job) return;
|
|
5438
|
+
const badge = document.getElementById('termModelBadge');
|
|
5439
|
+
if (activeSession === job.tmuxSession) { badge.textContent = 'switching…'; badge.style.display = 'inline-block'; }
|
|
5440
|
+
for (let i = 0; i < 60 && pendingDoorSwitch === job; i++) {
|
|
5441
|
+
await new Promise(res => setTimeout(res, 3000));
|
|
5442
|
+
try {
|
|
5443
|
+
const r = await fetch('/sessions', { headers: { 'Authorization': `Bearer ${token}` } });
|
|
5444
|
+
if (!r.ok) continue;
|
|
5445
|
+
const list = await r.json();
|
|
5446
|
+
const arr = Array.isArray(list) ? list : (list.sessions || []);
|
|
5447
|
+
const s = arr.find(x => String(x.platformId) === job.topicId
|
|
5448
|
+
&& new Date(x.startedAt).getTime() >= job.since - 1000
|
|
5449
|
+
&& (job.model === null || x.model === job.model));
|
|
5450
|
+
if (s) {
|
|
5451
|
+
switchingModel.delete(job.tmuxSession);
|
|
5452
|
+
pendingDoorSwitch = null;
|
|
5453
|
+
if (activeSession === job.tmuxSession || activeSession === s.tmuxSession) {
|
|
5454
|
+
badge.textContent = s.model || '';
|
|
5455
|
+
badge.className = 'model-badge ' + (s.model || '');
|
|
5456
|
+
}
|
|
5457
|
+
renderSessionList();
|
|
5458
|
+
return;
|
|
5459
|
+
}
|
|
5460
|
+
} catch { /* retry */ }
|
|
5461
|
+
}
|
|
5462
|
+
if (pendingDoorSwitch === job) {
|
|
5463
|
+
switchingModel.delete(job.tmuxSession);
|
|
5464
|
+
pendingDoorSwitch = null;
|
|
5465
|
+
renderSessionList();
|
|
5000
5466
|
}
|
|
5001
5467
|
}
|
|
5002
5468
|
|
|
@@ -2,7 +2,7 @@ import { SessionManager } from '../core/SessionManager.js';
|
|
|
2
2
|
import { TelegramAdapter } from '../messaging/TelegramAdapter.js';
|
|
3
3
|
import { TopicMemory } from '../memory/TopicMemory.js';
|
|
4
4
|
import { QuotaTracker } from '../monitoring/QuotaTracker.js';
|
|
5
|
-
import type { IntelligenceProvider, InstarConfig } from '../core/types.js';
|
|
5
|
+
import type { IntelligenceProvider, UserProfile, InstarConfig } from '../core/types.js';
|
|
6
6
|
import { UserManager } from '../users/UserManager.js';
|
|
7
7
|
/**
|
|
8
8
|
* Pure: should the emergency "fix command" gate intercept this message?
|
|
@@ -32,6 +32,26 @@ interface StartOptions {
|
|
|
32
32
|
}
|
|
33
33
|
/** Narrow, testable ownership of the stale-generation recovery side effect. */
|
|
34
34
|
export declare function clearGenerationlessCodexResumeBinding(resumeMap: Pick<import('../core/TopicResumeMap.js').TopicResumeMap, 'remove'> | null, topicId: number): void;
|
|
35
|
+
export declare function spawnSessionForTopic(sessionManager: SessionManager, telegram: TelegramAdapter, sessionName: string, topicId: number, latestMessage?: string, topicMemory?: TopicMemory, userProfile?: UserProfile, precomputedContext?: string,
|
|
36
|
+
/** Subscription & Auth Standard P1.3 (additive): launch under this account's
|
|
37
|
+
* config home + record its id (the quota-aware account-swap mechanism).
|
|
38
|
+
* Omitted = unchanged behaviour. */
|
|
39
|
+
accountSwap?: {
|
|
40
|
+
configHome?: string;
|
|
41
|
+
accountId?: string;
|
|
42
|
+
},
|
|
43
|
+
/** Reap-notify spec R2.8 / L13 — explicit per-spawn working directory (the
|
|
44
|
+
* resume-queue drainer passes a queue entry's recorded cwd so interrupted
|
|
45
|
+
* worktree work resumes in ITS tree). Omitted = module project dir. */
|
|
46
|
+
spawnOpts?: {
|
|
47
|
+
cwd?: string;
|
|
48
|
+
awaitInitialInjection?: boolean;
|
|
49
|
+
/** dashboard-door-model-controls §3.3 step 4 — a dashboard-created topic
|
|
50
|
+
* starts IDLE: no bootstrap build (no context read, no temp file, no
|
|
51
|
+
* relay block) and no initial message, exactly what the raw
|
|
52
|
+
* spawnInteractiveSession(undefined, …) path injected before. */
|
|
53
|
+
silentStart?: boolean;
|
|
54
|
+
}): Promise<string>;
|
|
35
55
|
/**
|
|
36
56
|
* Resolve the hub-intent classifier deps (Conversion #3,
|
|
37
57
|
* docs/specs/keyword-intent-conversions-1-and-3.md) from live config + the shared
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"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"}
|