instar 1.3.518 → 1.3.520
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 +40 -10
- package/dashboard/mandates.js +115 -34
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +26 -2
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +29 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/BackupManager.d.ts.map +1 -1
- package/dist/core/BackupManager.js +4 -0
- package/dist/core/BackupManager.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +29 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +6 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +7 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +3 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +2 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +7 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +310 -7
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/ConversationStore.d.ts +90 -0
- package/dist/threadline/ConversationStore.d.ts.map +1 -1
- package/dist/threadline/ConversationStore.js +133 -0
- package/dist/threadline/ConversationStore.js.map +1 -1
- package/dist/threadline/ThreadLog.d.ts +200 -0
- package/dist/threadline/ThreadLog.d.ts.map +1 -0
- package/dist/threadline/ThreadLog.js +434 -0
- package/dist/threadline/ThreadLog.js.map +1 -0
- package/dist/threadline/ThreadlineEndpoints.d.ts +10 -1
- package/dist/threadline/ThreadlineEndpoints.d.ts.map +1 -1
- package/dist/threadline/ThreadlineEndpoints.js +63 -12
- package/dist/threadline/ThreadlineEndpoints.js.map +1 -1
- package/dist/threadline/canonicalHistoryRead.d.ts +63 -0
- package/dist/threadline/canonicalHistoryRead.d.ts.map +1 -0
- package/dist/threadline/canonicalHistoryRead.js +123 -0
- package/dist/threadline/canonicalHistoryRead.js.map +1 -0
- package/dist/threadline/recordThreadMessage.d.ts +143 -0
- package/dist/threadline/recordThreadMessage.d.ts.map +1 -0
- package/dist/threadline/recordThreadMessage.js +222 -0
- package/dist/threadline/recordThreadMessage.js.map +1 -0
- package/dist/threadline/threadDigest.d.ts +90 -0
- package/dist/threadline/threadDigest.d.ts.map +1 -0
- package/dist/threadline/threadDigest.js +130 -0
- package/dist/threadline/threadDigest.js.map +1 -0
- package/dist/threadline/threadSymmetry.d.ts +108 -0
- package/dist/threadline/threadSymmetry.d.ts.map +1 -0
- package/dist/threadline/threadSymmetry.js +225 -0
- package/dist/threadline/threadSymmetry.js.map +1 -0
- package/package.json +1 -1
- package/scripts/instar-dev-precommit.js +46 -0
- package/scripts/lib/operator-surface.mjs +54 -0
- package/skills/instar-dev/templates/side-effects-artifact.md +15 -0
- package/src/data/builtin-manifest.json +65 -65
- package/src/scaffold/templates.ts +7 -0
- package/upgrades/1.3.519.md +92 -0
- package/upgrades/1.3.520.md +50 -0
- package/upgrades/side-effects/operator-surface-quality-and-mandates-redesign.md +200 -0
- package/upgrades/side-effects/threadline-canonical-history.md +122 -0
package/dashboard/index.html
CHANGED
|
@@ -815,18 +815,34 @@
|
|
|
815
815
|
.mnd-bad { color: #f85149; border-color: #f8514955; font-weight: 700; }
|
|
816
816
|
.mnd-deny { color: #d29922; border-color: #d2992255; }
|
|
817
817
|
.mnd-dead { color: var(--text-dim); }
|
|
818
|
-
|
|
819
|
-
|
|
818
|
+
/* Internals (id, fingerprints, scope slug, action slugs) are SUPPORT
|
|
819
|
+
metadata only — de-emphasized, never the headline (Operator-Surface
|
|
820
|
+
Quality item 2). */
|
|
821
|
+
.mnd-meta { font-size: 12px; color: var(--text-dim); }
|
|
822
|
+
/* The card's plain-language headline sentence — the primary content. */
|
|
823
|
+
.mnd-summary { margin: 0; font-size: 15px; line-height: 1.5; color: var(--text-bright); }
|
|
820
824
|
.mnd-empty { font-size: 16px; color: var(--text-dim); line-height: 1.6; padding: 12px 0; }
|
|
821
|
-
.mnd-revoke-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
|
825
|
+
.mnd-revoke-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin: 8px 0 4px; }
|
|
822
826
|
.mnd-pin { width: 110px; } .mnd-reason { flex: 1; min-width: 160px; }
|
|
823
827
|
.mnd-btn { padding: 6px 14px; cursor: pointer; }
|
|
824
|
-
.mnd-btn-
|
|
828
|
+
.mnd-btn-primary { font-weight: 600; }
|
|
829
|
+
/* Destructive action: quiet by design, never featured (item 3). */
|
|
830
|
+
.mnd-btn-danger { color: #f85149; font-weight: 400; }
|
|
825
831
|
.mnd-dead-note { font-size: 13px; color: var(--text-dim); font-style: italic; }
|
|
826
832
|
.mnd-grants-head { font-size: 13px; color: var(--text-dim); }
|
|
827
|
-
.mnd-grants { margin: 0; padding-left: 18px; font-size:
|
|
828
|
-
.mnd-grant-
|
|
829
|
-
|
|
833
|
+
.mnd-grants { margin: 0; padding-left: 18px; font-size: 14px; line-height: 1.7; }
|
|
834
|
+
.mnd-grant-id { color: var(--text-dim); font-size: 12px; }
|
|
835
|
+
/* The grant form is the card's PRIMARY action — an open, titled block, never
|
|
836
|
+
a collapsed toggle (Operator-Surface Quality item 1). */
|
|
837
|
+
.mnd-grant-block { border-top: 1px solid var(--border); padding-top: 10px; margin-top: 2px; }
|
|
838
|
+
.mnd-grant-title { font-size: 14px; font-weight: 600; color: var(--text-bright); margin-bottom: 6px; }
|
|
839
|
+
.mnd-grant-sep { font-size: 13px; color: var(--text-dim); }
|
|
840
|
+
/* Revoke is demoted to a quiet, collapsed secondary control (item 3). */
|
|
841
|
+
.mnd-revoke-details { font-size: 13px; margin-top: 4px; }
|
|
842
|
+
.mnd-revoke-summary { cursor: pointer; color: var(--text-dim); font-size: 13px; }
|
|
843
|
+
.mnd-about { font-size: 14px; color: var(--text-dim); margin-top: -8px; }
|
|
844
|
+
.mnd-about summary { cursor: pointer; }
|
|
845
|
+
.mnd-about p { margin: 8px 0 0; line-height: 1.55; }
|
|
830
846
|
.mnd-grant-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin: 8px 0 4px; }
|
|
831
847
|
.mnd-grant-field { min-width: 130px; max-width: 100%; padding: 6px 8px; }
|
|
832
848
|
.mnd-notice { min-height: 20px; font-size: 14px; color: #3fb950; }
|
|
@@ -842,9 +858,19 @@
|
|
|
842
858
|
.mnd-notice-ok { border: 1px solid #15803d; background: rgba(21, 128, 61, 0.12); color: #86efac; padding: 12px 14px; border-radius: 8px; font-size: 17px; font-weight: 600; line-height: 1.5; }
|
|
843
859
|
.mnd-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
844
860
|
.mnd-table th, .mnd-table td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
|
|
845
|
-
.mnd-reason-cell { color: var(--text-dim);
|
|
846
|
-
.mnd-audit-head { display: flex; gap: 12px; align-items: center; margin-bottom: 8px; }
|
|
861
|
+
.mnd-reason-cell { color: var(--text-dim); }
|
|
862
|
+
.mnd-audit-head { display: flex; gap: 12px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }
|
|
847
863
|
.mnd-dim { font-size: 13px; color: var(--text-dim); }
|
|
864
|
+
/* Mobile-first (item 5): at phone width the 5-column audit table stacks into
|
|
865
|
+
labelled rows so the reason — the useful column — is never truncated. */
|
|
866
|
+
@media (max-width: 640px) {
|
|
867
|
+
.mnd-table thead { position: absolute; left: -9999px; }
|
|
868
|
+
.mnd-table tr { display: block; border: 1px solid var(--border); border-radius: 8px; margin-bottom: 10px; padding: 6px 4px; }
|
|
869
|
+
.mnd-table td { display: flex; justify-content: space-between; gap: 12px; border-bottom: none; padding: 4px 8px; }
|
|
870
|
+
.mnd-table td::before { content: attr(data-label); font-weight: 600; color: var(--text-dim); flex: 0 0 auto; }
|
|
871
|
+
.mnd-reason-cell { text-align: right; word-break: break-word; }
|
|
872
|
+
.mnd-grant-field, .mnd-pin, .mnd-reason { width: 100%; min-width: 0; flex: 1 1 100%; }
|
|
873
|
+
}
|
|
848
874
|
|
|
849
875
|
/* ── process-health tab (PROCESS-HEALTH-DASHBOARD-TAB-SPEC §4.1) ──
|
|
850
876
|
Large, readable, calm. No monospace, no tables, no alarm colors.
|
|
@@ -3384,7 +3410,11 @@
|
|
|
3384
3410
|
<h2 class="ph-title">Coordination Mandates</h2>
|
|
3385
3411
|
<div id="mndStamp" class="ph-stamp" aria-live="polite"></div>
|
|
3386
3412
|
</div>
|
|
3387
|
-
<p class="ph-intro">
|
|
3413
|
+
<p class="ph-intro">Permission slips you sign with your PIN to let your agents do bounded work together.</p>
|
|
3414
|
+
<details class="mnd-about">
|
|
3415
|
+
<summary>What is this?</summary>
|
|
3416
|
+
<p>These are your permission slips for autonomous agent-to-agent work — each one authorizes a specific, bounded task. The slip — never the agent — is the authorizer. Issuing and revoking require <strong>your dashboard PIN</strong>; an agent's own credentials are refused by design. With no slip issued, every delegated action is denied.</p>
|
|
3417
|
+
</details>
|
|
3388
3418
|
<div id="mndNotice" class="mnd-notice" aria-live="polite"></div>
|
|
3389
3419
|
<section class="ph-section">
|
|
3390
3420
|
<h3 class="ph-h">Issued mandates</h3>
|
package/dashboard/mandates.js
CHANGED
|
@@ -61,12 +61,72 @@ function fmtWhen(iso) {
|
|
|
61
61
|
} catch { return String(iso); }
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
/** The registry name for a Slack id, or the id itself when we don't know them. */
|
|
65
|
+
function userName(slackUserId, slackUsers) {
|
|
65
66
|
const u = (slackUsers || []).find((x) => x.slackUserId === slackUserId);
|
|
66
|
-
return u ?
|
|
67
|
+
return u ? u.name : slackUserId;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
// Operator-Surface Quality (constitution): the card speaks plain language, never
|
|
71
|
+
// the slugs/JSON the data model stores. These maps turn the stored enum/bounds
|
|
72
|
+
// into the sentence a non-engineer would say. Unknown values fall back to a
|
|
73
|
+
// de-slugified phrase — never a raw token dumped at the operator.
|
|
74
|
+
|
|
75
|
+
// Both the coordination-mandate authority actions AND the RolePolicy floor
|
|
76
|
+
// actions, in plain language. Returns the RAW human string; callers esc() it.
|
|
77
|
+
const ACTION_PHRASES = {
|
|
78
|
+
'exchange-read-credential': 'exchange a read-only credential',
|
|
79
|
+
'sign-code-review': 'co-sign a code review',
|
|
80
|
+
'money-movement': 'move money',
|
|
81
|
+
'prod-deploy': 'deploy to production',
|
|
82
|
+
'credential-access': 'use a credential',
|
|
83
|
+
'destructive-data': 'run a destructive data operation',
|
|
84
|
+
'external-send': 'send something externally',
|
|
85
|
+
'grant-authority': 'grant authority to others',
|
|
86
|
+
};
|
|
87
|
+
function humanAction(slug) {
|
|
88
|
+
const s = String(slug ?? '');
|
|
89
|
+
return ACTION_PHRASES[s] || s.replace(/-/g, ' ');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** "you" when the authorizer is the operator-via-PIN; otherwise the raw name. */
|
|
93
|
+
function humanAuthorizer(authorizedBy) {
|
|
94
|
+
const s = String(authorizedBy ?? '');
|
|
95
|
+
return /operator|dashboard pin/i.test(s) ? 'you' : s;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** A short title from the operator's scope name (de-slugified, not the raw slug). */
|
|
99
|
+
function humanScope(scope) {
|
|
100
|
+
const s = String(scope ?? '').trim();
|
|
101
|
+
if (!s) return 'Permission slip';
|
|
102
|
+
const words = s.replace(/[-_]+/g, ' ');
|
|
103
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Join phrases the way a person speaks them: "a, b and c". */
|
|
107
|
+
function joinHuman(parts) {
|
|
108
|
+
if (parts.length === 0) return '';
|
|
109
|
+
if (parts.length === 1) return parts[0];
|
|
110
|
+
return parts.slice(0, -1).join(', ') + ' and ' + parts[parts.length - 1];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* One plain sentence describing what the mandate authorizes + when it ends —
|
|
115
|
+
* the card's primary, human-language headline (no slugs, no JSON, no
|
|
116
|
+
* fingerprints). Each interpolated action is escaped at the call site.
|
|
117
|
+
*/
|
|
118
|
+
function humanSummary(m) {
|
|
119
|
+
const acts = (m.authorities || []).map((a) => esc(humanAction(a.action)));
|
|
120
|
+
const agents = (m.agents || []).filter(Boolean);
|
|
121
|
+
const who = agents.length >= 2 ? 'two agents' : 'an agent';
|
|
122
|
+
const when = fmtWhen(m.expiresAt);
|
|
123
|
+
if (acts.length === 0) {
|
|
124
|
+
return `A permission slip for ${who} that delegates no actions yet. Expires ${when}.`;
|
|
125
|
+
}
|
|
126
|
+
return `Lets ${who} ${joinHuman(acts)}. Expires ${when}.`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** The grants a mandate already carries, in plain operator language. */
|
|
70
130
|
export function renderGrants(m, slackUsers) {
|
|
71
131
|
const grants = Array.isArray(m.grants) ? m.grants : [];
|
|
72
132
|
if (grants.length === 0) return '';
|
|
@@ -75,16 +135,22 @@ export function renderGrants(m, slackUsers) {
|
|
|
75
135
|
const badge = expired
|
|
76
136
|
? '<span class="mnd-badge mnd-dead">expired</span>'
|
|
77
137
|
: '<span class="mnd-badge mnd-ok">active</span>';
|
|
78
|
-
|
|
138
|
+
const name = userName(g.grantedTo, slackUsers);
|
|
139
|
+
// The Slack id is support metadata, never the headline — shown muted only
|
|
140
|
+
// when it differs from the name we render (an unknown id IS the name).
|
|
141
|
+
const idDetail = name !== g.grantedTo ? ` <span class="mnd-grant-id">(${esc(g.grantedTo)})</span>` : '';
|
|
142
|
+
return `<li>${badge} <strong>${esc(name)}</strong>${idDetail} can ${esc(humanAction(g.floorAction))} until ${fmtWhen(g.expiresAt)} — authorized by ${esc(humanAuthorizer(g.authorizedBy))}</li>`;
|
|
79
143
|
}).join('');
|
|
80
|
-
return `<div class="mnd-grants-head">
|
|
144
|
+
return `<div class="mnd-grants-head">Who this slip already lets act:</div><ul class="mnd-grants">${rows}</ul>`;
|
|
81
145
|
}
|
|
82
146
|
|
|
83
147
|
/**
|
|
84
|
-
* The add-grant form for an ACTIVE mandate
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
148
|
+
* The add-grant form for an ACTIVE mandate — the card's PRIMARY action, always
|
|
149
|
+
* visible (Operator-Surface Quality: lead with the primary action, never behind
|
|
150
|
+
* a toggle). Mobile-first (the 2026-06-12 lesson, instar#1080): the operator
|
|
151
|
+
* PICKS a person and a duration — the only thing typed is the PIN. A free-text
|
|
152
|
+
* Slack-id input appears only when the user registry has nobody to offer. Action
|
|
153
|
+
* labels read in plain language; the slug is the option VALUE the server needs.
|
|
88
154
|
*/
|
|
89
155
|
export function renderGrantForm(m, slackUsers) {
|
|
90
156
|
const users = (slackUsers || []).filter((u) => u.slackUserId);
|
|
@@ -94,21 +160,23 @@ export function renderGrantForm(m, slackUsers) {
|
|
|
94
160
|
}</select>`
|
|
95
161
|
: `<input type="text" class="mnd-grant-field" data-grant-user="${esc(m.id)}" placeholder="Slack user id (e.g. U0…)" />`;
|
|
96
162
|
const actionField = `<select class="mnd-grant-field" data-grant-action="${esc(m.id)}">${
|
|
97
|
-
FLOOR_ACTIONS.map((a) => `<option value="${a}"${a === 'prod-deploy' ? ' selected' : ''}>${a}</option>`).join('')
|
|
163
|
+
FLOOR_ACTIONS.map((a) => `<option value="${a}"${a === 'prod-deploy' ? ' selected' : ''}>${esc(humanAction(a))}</option>`).join('')
|
|
98
164
|
}</select>`;
|
|
99
165
|
const durationField = `<select class="mnd-grant-field" data-grant-duration="${esc(m.id)}">${
|
|
100
166
|
GRANT_DURATIONS.map((d) => `<option value="${d.minutes}"${d.minutes === 60 ? ' selected' : ''}>${d.label}</option>`).join('')
|
|
101
167
|
}</select>`;
|
|
102
|
-
return `<
|
|
168
|
+
return `<div class="mnd-grant-block">
|
|
169
|
+
<div class="mnd-grant-title">Grant a person an action</div>
|
|
103
170
|
<div class="mnd-grant-row">
|
|
104
171
|
${granteeField}
|
|
105
172
|
${actionField}
|
|
173
|
+
<span class="mnd-grant-sep">for</span>
|
|
106
174
|
${durationField}
|
|
107
|
-
<input type="password" class="mnd-pin" data-grant-pin="${esc(m.id)}" placeholder="PIN" autocomplete="off" />
|
|
108
|
-
<button class="mnd-btn" data-grant="${esc(m.id)}">Grant</button>
|
|
175
|
+
<input type="password" class="mnd-pin" data-grant-pin="${esc(m.id)}" placeholder="Your PIN" autocomplete="off" />
|
|
176
|
+
<button class="mnd-btn mnd-btn-primary" data-grant="${esc(m.id)}">Grant</button>
|
|
109
177
|
</div>
|
|
110
|
-
<span class="mnd-hint">
|
|
111
|
-
</
|
|
178
|
+
<span class="mnd-hint">Lets the person you pick take that one action for the window you choose. Confirmed with your PIN — never stored. A grant can never outlive this slip, and revoking the slip ends it.</span>
|
|
179
|
+
</div>`;
|
|
112
180
|
}
|
|
113
181
|
|
|
114
182
|
export function renderMandates(list, slackUsers = []) {
|
|
@@ -119,30 +187,39 @@ export function renderMandates(list, slackUsers = []) {
|
|
|
119
187
|
const expired = Date.parse(m.expiresAt) < Date.now();
|
|
120
188
|
const state = m.revoked ? 'revoked' : expired ? 'expired' : 'active';
|
|
121
189
|
const stateCls = state === 'active' ? 'mnd-ok' : 'mnd-dead';
|
|
190
|
+
const stateLabel = state === 'active' ? 'active' : state === 'revoked' ? 'revoked' : 'expired';
|
|
122
191
|
const authBadge = m.authorshipValid
|
|
123
192
|
? '<span class="mnd-badge mnd-ok">authorship verified</span>'
|
|
124
193
|
: '<span class="mnd-badge mnd-bad">AUTHORSHIP INVALID</span>';
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
<button class="mnd-btn mnd-btn-danger" data-revoke="${esc(m.id)}">Revoke</button>
|
|
133
|
-
</div>`
|
|
134
|
-
: `<div class="mnd-dead-note">${m.revoked ? `revoked ${fmtWhen(m.revoked.at)} — ${esc(m.revoked.reason)}` : `expired ${fmtWhen(m.expiresAt)}`}</div>`;
|
|
194
|
+
// Internals (id, agent fingerprints, scope slug, raw action names, bounds)
|
|
195
|
+
// are SUPPORT metadata, never the headline — kept on one muted line so the
|
|
196
|
+
// operator can quote an id when asking for help, but never has to read JSON.
|
|
197
|
+
const actionSlugs = (m.authorities || []).map((a) => esc(a.action)).filter(Boolean).join(', ') || 'none';
|
|
198
|
+
const referenceLine = `<div class="mnd-meta">For support: id <code>${esc(m.id)}</code> · agents <code>${esc(shortFp(m.agents?.[0]))}</code> + <code>${esc(shortFp(m.agents?.[1]))}</code> · scope <code>${esc(m.scope)}</code> · authorizes ${actionSlugs} · issued by ${esc(m.author)}</div>`;
|
|
199
|
+
// The grant form is the PRIMARY action and renders ABOVE revoke. Revoke is
|
|
200
|
+
// destructive, so it is demoted to a quiet collapsed control, never featured.
|
|
135
201
|
const grantUi = state === 'active' ? renderGrantForm(m, slackUsers) : '';
|
|
202
|
+
const revokeUi = state === 'active'
|
|
203
|
+
? `<details class="mnd-revoke-details">
|
|
204
|
+
<summary class="mnd-revoke-summary">Revoke this permission slip</summary>
|
|
205
|
+
<div class="mnd-revoke-row">
|
|
206
|
+
<input type="password" class="mnd-pin" data-revoke-pin="${esc(m.id)}" placeholder="Your PIN" autocomplete="off" />
|
|
207
|
+
<input type="text" class="mnd-reason" data-revoke-reason="${esc(m.id)}" placeholder="reason (optional)" />
|
|
208
|
+
<button class="mnd-btn mnd-btn-danger" data-revoke="${esc(m.id)}">Revoke</button>
|
|
209
|
+
</div>
|
|
210
|
+
<span class="mnd-hint">Ends the slip and everything it authorized, right away. This cannot be undone.</span>
|
|
211
|
+
</details>`
|
|
212
|
+
: `<div class="mnd-dead-note">${m.revoked ? `Revoked ${fmtWhen(m.revoked.at)} — ${esc(m.revoked.reason)}` : `Expired ${fmtWhen(m.expiresAt)}`}</div>`;
|
|
136
213
|
return `<div class="mnd-card">
|
|
137
214
|
<div class="mnd-card-head">
|
|
138
|
-
<span class="mnd-scope">${esc(m.scope)}</span>
|
|
139
|
-
<span class="mnd-badge ${stateCls}">${
|
|
215
|
+
<span class="mnd-scope">${esc(humanScope(m.scope))}</span>
|
|
216
|
+
<span class="mnd-badge ${stateCls}">${stateLabel}</span>
|
|
140
217
|
${authBadge}
|
|
141
218
|
</div>
|
|
142
|
-
<
|
|
143
|
-
<ul class="mnd-authorities">${authorities}</ul>
|
|
219
|
+
<p class="mnd-summary">${humanSummary(m)}</p>
|
|
144
220
|
${renderGrants(m, slackUsers)}
|
|
145
221
|
${grantUi}
|
|
222
|
+
${referenceLine}
|
|
146
223
|
${revokeUi}
|
|
147
224
|
</div>`;
|
|
148
225
|
}).join('');
|
|
@@ -157,13 +234,17 @@ export function renderAudit(payload) {
|
|
|
157
234
|
if (entries.length === 0) {
|
|
158
235
|
return `<div class="mnd-audit-head">${chainBadge}</div><div class="mnd-empty">No decisions recorded yet.</div>`;
|
|
159
236
|
}
|
|
237
|
+
// Mobile-first (Operator-Surface Quality item 5): each cell carries a
|
|
238
|
+
// data-label so the table can stack into labelled rows at phone width (the
|
|
239
|
+
// CSS media query in index.html), instead of cramming five columns and
|
|
240
|
+
// truncating the most useful one — the reason — into an unreadable sliver.
|
|
160
241
|
const rows = entries.slice(-25).reverse().map((e) =>
|
|
161
242
|
`<tr>
|
|
162
|
-
<td>${fmtWhen(e.ts)}</td>
|
|
163
|
-
<td><span class="mnd-badge ${e.decision === 'allow' ? 'mnd-ok' : 'mnd-deny'}">${esc(e.decision)}</span></td>
|
|
164
|
-
<td><code>${esc(e.action)}</code></td>
|
|
165
|
-
<td><code>${esc(shortFp(e.agentFp))}</code></td>
|
|
166
|
-
<td class="mnd-reason-cell">${esc(e.reason)}</td>
|
|
243
|
+
<td data-label="When">${fmtWhen(e.ts)}</td>
|
|
244
|
+
<td data-label="Decision"><span class="mnd-badge ${e.decision === 'allow' ? 'mnd-ok' : 'mnd-deny'}">${esc(e.decision)}</span></td>
|
|
245
|
+
<td data-label="Action"><code>${esc(e.action)}</code></td>
|
|
246
|
+
<td data-label="Agent"><code>${esc(shortFp(e.agentFp))}</code></td>
|
|
247
|
+
<td data-label="Reason" class="mnd-reason-cell">${esc(e.reason)}</td>
|
|
167
248
|
</tr>`,
|
|
168
249
|
).join('');
|
|
169
250
|
return `<div class="mnd-audit-head">${chainBadge}<span class="mnd-dim">${entries.length} total decisions · newest first</span></div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgCH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgCH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AA4G7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsBtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAm1CD,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,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CA8eN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAuuZtE;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
|
@@ -144,6 +144,8 @@ import { WarmSessionPool } from '../threadline/WarmSessionPool.js';
|
|
|
144
144
|
import { resolveThreadlineMcpEntry } from '../threadline/mcpEntry.js';
|
|
145
145
|
import { ThreadResumeMap } from '../threadline/ThreadResumeMap.js';
|
|
146
146
|
import { ConversationStore } from '../threadline/ConversationStore.js';
|
|
147
|
+
import { ThreadLog } from '../threadline/ThreadLog.js';
|
|
148
|
+
import { ThreadMessageRecorder } from '../threadline/recordThreadMessage.js';
|
|
147
149
|
import { recordInboundAck } from '../threadline/recordInboundAck.js';
|
|
148
150
|
import { WarrantsReplyGate, evaluateAndRecordInbound } from '../threadline/WarrantsReplyGate.js';
|
|
149
151
|
import { CollaborationSurfacer } from '../threadline/CollaborationSurfacer.js';
|
|
@@ -9504,6 +9506,29 @@ export async function startServer(options) {
|
|
|
9504
9506
|
const cj = coherenceJournal;
|
|
9505
9507
|
conversationStore.setCoherenceJournalSeam((d) => cj.emitThreadlineConversation(d));
|
|
9506
9508
|
}
|
|
9509
|
+
// ── Robustness Phase 2 (D-A/D-B): the canonical per-thread log + append funnel ──
|
|
9510
|
+
// CORE + UNGATED: the log, funnel, read-source union, and symmetry DETECTION
|
|
9511
|
+
// are additive/observability and ship live (history can only gain). Only the
|
|
9512
|
+
// D-E resolver JOIN is dev-gated + dry-run-first, resolved per-send at the route.
|
|
9513
|
+
const _canonHist = config.threadline?.canonicalHistory ?? {};
|
|
9514
|
+
const threadLog = new ThreadLog(config.stateDir, {
|
|
9515
|
+
maxEntriesPerThread: _canonHist.maxEntriesPerThread,
|
|
9516
|
+
seenSetMaxPerThread: _canonHist.seenSetMaxPerThread,
|
|
9517
|
+
seenSetMaxThreads: _canonHist.seenSetMaxThreads,
|
|
9518
|
+
});
|
|
9519
|
+
const threadMessageRecorder = new ThreadMessageRecorder({
|
|
9520
|
+
threadLog,
|
|
9521
|
+
conversationStore,
|
|
9522
|
+
attention: telegram ?? null,
|
|
9523
|
+
logDir: path.join(config.stateDir, 'logs'),
|
|
9524
|
+
headCacheCoalesceMs: _canonHist.headCacheCoalesceMs,
|
|
9525
|
+
appendFailureAlertThreshold: _canonHist.appendFailureAlertThreshold,
|
|
9526
|
+
inlineMaxBytes: _canonHist.inlineMaxBytes,
|
|
9527
|
+
});
|
|
9528
|
+
// SA5/E1: close-only canonical-log retention — a non-pinned conversation
|
|
9529
|
+
// transitioning INTO resolved/failed deletes its log (via SafeFsExecutor inside
|
|
9530
|
+
// ThreadLog.deleteThread); a COLD archive/LRU eviction never fires this seam.
|
|
9531
|
+
conversationStore.setLogRetentionSeam((threadId) => threadLog.deleteThread(threadId));
|
|
9507
9532
|
const warrantsReplyGate = new WarrantsReplyGate({ intelligence: sharedIntelligence });
|
|
9508
9533
|
// CMT-509 §2: surface PARENTLESS Threadline conversations into a single
|
|
9509
9534
|
// dedicated topic so a peer reaching out cold is visible (not an invisible
|
|
@@ -14437,8 +14462,7 @@ export async function startServer(options) {
|
|
|
14437
14462
|
carrier: _topicProfileCarrier,
|
|
14438
14463
|
}
|
|
14439
14464
|
: null;
|
|
14440
|
-
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, cartographer: cartographer ?? undefined, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, subscriptionPool, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, proactiveSwapMonitor: _proactiveSwapMonitor ?? undefined, inUseAccountResolver, enrollmentWizard, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, greenPrAutoMerger: greenPrAutoMerger ?? undefined, guardLatchStore: guardLatchStore ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, topicProfile: _topicProfileCtx ?? 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, getInboundQueue: () => _inboundQueue, meshRpcDispatcher, workingSetPullCoordinator, commitmentReplicaStore, preferenceReplicaStore, 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, guardRegistry, listPoolMachines: _listPoolMachines ?? undefined, poolLink: _poolLink ?? 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, resumeQueue, resumeDrainer, operatorStopRecorder: recordOperatorStop, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier });
|
|
14441
|
-
// Resolve the late-bound topic-operator getter (increment 2e): routing was
|
|
14465
|
+
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, cartographer: cartographer ?? undefined, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, subscriptionPool, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, proactiveSwapMonitor: _proactiveSwapMonitor ?? undefined, inUseAccountResolver, enrollmentWizard, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, greenPrAutoMerger: greenPrAutoMerger ?? undefined, guardLatchStore: guardLatchStore ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, topicProfile: _topicProfileCtx ?? 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, threadLog, threadMessageRecorder, 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, getInboundQueue: () => _inboundQueue, meshRpcDispatcher, workingSetPullCoordinator, commitmentReplicaStore, preferenceReplicaStore, 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, guardRegistry, listPoolMachines: _listPoolMachines ?? undefined, poolLink: _poolLink ?? 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, resumeQueue, resumeDrainer, operatorStopRecorder: recordOperatorStop, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier }); // Resolve the late-bound topic-operator getter (increment 2e): routing was
|
|
14442
14466
|
// wired before the server existed; from here on inbound binds use the
|
|
14443
14467
|
// server's own store instance.
|
|
14444
14468
|
_agentServerRef = server;
|