privateboard 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +4260 -1660
- package/dist/cli.js.map +1 -1
- package/package.json +2 -1
- package/public/adjourn-overlay.css +8 -4
- package/public/agent-profile.css +345 -0
- package/public/agent-profile.js +222 -3
- package/public/app.js +4990 -149
- package/public/home.html +609 -1
- package/public/i18n.js +49 -385
- package/public/index.html +9549 -5314
- package/public/new-agent.js +148 -37
- package/public/quote-cta.css +7 -3
- package/public/quote-cta.js +21 -0
- package/public/report.html +109 -12
- package/public/room-settings.css +67 -0
- package/public/room-settings.js +72 -6
- package/public/themes.css +38 -0
- package/public/typing-sfx.js +87 -1
- package/public/user-settings.js +3 -1
- package/public/voice-replay.css +70 -1
- package/public/voice-replay.js +265 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "privateboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "PrivateBoard · your private board meeting, on call. Local-first, multi-agent thinking amplifier.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"commander": "^12.1.0",
|
|
46
46
|
"hono": "^4.6.14",
|
|
47
47
|
"open": "^10.1.0",
|
|
48
|
+
"tiny-pinyin": "^1.3.2",
|
|
48
49
|
"yaml": "^2.8.4"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
@@ -29,8 +29,11 @@
|
|
|
29
29
|
.adjourn-modal {
|
|
30
30
|
position: relative;
|
|
31
31
|
z-index: 1;
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
/* Width matches the convene-follow-up overlay (.supplement-modal)
|
|
33
|
+
so the room's two terminal-action overlays read as the same
|
|
34
|
+
family. Was max-width: 880px which felt oversized for a
|
|
35
|
+
short-form action confirm. */
|
|
36
|
+
width: min(560px, calc(100vw - 32px));
|
|
34
37
|
max-height: calc(100vh - 48px);
|
|
35
38
|
background: var(--panel);
|
|
36
39
|
border: 0.5px solid var(--line-strong);
|
|
@@ -441,8 +444,9 @@
|
|
|
441
444
|
pointer-events: none;
|
|
442
445
|
}
|
|
443
446
|
|
|
444
|
-
/* Narrow modal
|
|
447
|
+
/* Narrow viewport · summary rows tighten. The modal width itself
|
|
448
|
+
already collapses gracefully below ~592px via the
|
|
449
|
+
`min(560px, calc(100vw - 32px))` rule on .adjourn-modal. */
|
|
445
450
|
@media (max-width: 640px) {
|
|
446
|
-
.adjourn-modal { max-width: 100%; }
|
|
447
451
|
.adjourn-summary-row { grid-template-columns: 78px 1fr; gap: 10px; padding: 9px 12px; }
|
|
448
452
|
}
|
package/public/agent-profile.css
CHANGED
|
@@ -4046,3 +4046,348 @@
|
|
|
4046
4046
|
.ap-voice-advanced[open] summary::after { content: "−"; }
|
|
4047
4047
|
.ap-voice-advanced summary:hover { color: var(--text, #eee); }
|
|
4048
4048
|
.ap-voice-advanced summary:hover::after { color: var(--text); }
|
|
4049
|
+
|
|
4050
|
+
/* ────────────────────────────────────────────────────────────
|
|
4051
|
+
Persona dossier · Full-mode agents only
|
|
4052
|
+
────────────────────────────────────────────────────────────
|
|
4053
|
+
Reads as a gamified character-sheet entry below the rules
|
|
4054
|
+
section. The card is the entire button — keyboard + click
|
|
4055
|
+
land on the same element. Stat values use the mono register
|
|
4056
|
+
so digits hold a fixed grid; labels are upper-mono kickers
|
|
4057
|
+
for the dossier flavour. */
|
|
4058
|
+
.ap-persona-block .ap-block-h-tag {
|
|
4059
|
+
font-family: var(--mono);
|
|
4060
|
+
font-size: 9px;
|
|
4061
|
+
letter-spacing: 0.16em;
|
|
4062
|
+
text-transform: uppercase;
|
|
4063
|
+
color: var(--lime);
|
|
4064
|
+
padding: 2px 8px;
|
|
4065
|
+
border: 0.5px solid var(--lime-dim, var(--line));
|
|
4066
|
+
border-radius: 999px;
|
|
4067
|
+
}
|
|
4068
|
+
.ap-persona-card {
|
|
4069
|
+
display: flex;
|
|
4070
|
+
flex-direction: column;
|
|
4071
|
+
gap: 14px;
|
|
4072
|
+
width: 100%;
|
|
4073
|
+
padding: 16px 18px 14px;
|
|
4074
|
+
background: var(--panel-2);
|
|
4075
|
+
border: 0.5px solid var(--line);
|
|
4076
|
+
text-align: left;
|
|
4077
|
+
cursor: pointer;
|
|
4078
|
+
font-family: inherit;
|
|
4079
|
+
color: inherit;
|
|
4080
|
+
transition: border-color 0.12s, background 0.12s, transform 0.12s;
|
|
4081
|
+
position: relative;
|
|
4082
|
+
overflow: hidden;
|
|
4083
|
+
}
|
|
4084
|
+
.ap-persona-card::before {
|
|
4085
|
+
/* Faint diagonal hatch behind the card · evokes a stamped
|
|
4086
|
+
dossier surface without competing with content. Pure CSS,
|
|
4087
|
+
pinned to the corner so it doesn't follow the cta. */
|
|
4088
|
+
content: "";
|
|
4089
|
+
position: absolute;
|
|
4090
|
+
top: 0; right: 0;
|
|
4091
|
+
width: 110px; height: 110px;
|
|
4092
|
+
background-image: repeating-linear-gradient(
|
|
4093
|
+
45deg,
|
|
4094
|
+
transparent 0,
|
|
4095
|
+
transparent 6px,
|
|
4096
|
+
var(--line) 6px,
|
|
4097
|
+
var(--line) 7px
|
|
4098
|
+
);
|
|
4099
|
+
opacity: 0.35;
|
|
4100
|
+
pointer-events: none;
|
|
4101
|
+
}
|
|
4102
|
+
.ap-persona-card:hover {
|
|
4103
|
+
border-color: var(--lime);
|
|
4104
|
+
background: var(--panel-3, var(--panel-2));
|
|
4105
|
+
}
|
|
4106
|
+
.ap-persona-card:active { transform: translateY(1px); }
|
|
4107
|
+
.ap-persona-card:focus-visible {
|
|
4108
|
+
outline: 1px solid var(--lime);
|
|
4109
|
+
outline-offset: 2px;
|
|
4110
|
+
}
|
|
4111
|
+
.ap-persona-card-head {
|
|
4112
|
+
display: grid;
|
|
4113
|
+
grid-template-columns: auto 1fr auto;
|
|
4114
|
+
align-items: center;
|
|
4115
|
+
gap: 14px;
|
|
4116
|
+
}
|
|
4117
|
+
.ap-persona-card-glyph {
|
|
4118
|
+
width: 44px; height: 44px;
|
|
4119
|
+
display: flex;
|
|
4120
|
+
align-items: center;
|
|
4121
|
+
justify-content: center;
|
|
4122
|
+
color: var(--lime);
|
|
4123
|
+
border: 0.5px solid var(--lime-dim, var(--line));
|
|
4124
|
+
background: var(--panel);
|
|
4125
|
+
}
|
|
4126
|
+
.ap-persona-card-id { min-width: 0; }
|
|
4127
|
+
.ap-persona-card-kicker {
|
|
4128
|
+
font-family: var(--mono);
|
|
4129
|
+
font-size: 9.5px;
|
|
4130
|
+
letter-spacing: 0.18em;
|
|
4131
|
+
text-transform: uppercase;
|
|
4132
|
+
color: var(--lime);
|
|
4133
|
+
margin-bottom: 4px;
|
|
4134
|
+
}
|
|
4135
|
+
.ap-persona-card-title {
|
|
4136
|
+
font-family: var(--font-human);
|
|
4137
|
+
font-size: 17px;
|
|
4138
|
+
font-weight: 700;
|
|
4139
|
+
color: var(--text);
|
|
4140
|
+
letter-spacing: -0.01em;
|
|
4141
|
+
line-height: 1.2;
|
|
4142
|
+
}
|
|
4143
|
+
.ap-persona-card-meta {
|
|
4144
|
+
font-family: var(--mono);
|
|
4145
|
+
font-size: 10px;
|
|
4146
|
+
letter-spacing: 0.06em;
|
|
4147
|
+
color: var(--text-faint);
|
|
4148
|
+
margin-top: 4px;
|
|
4149
|
+
text-transform: uppercase;
|
|
4150
|
+
}
|
|
4151
|
+
.ap-persona-card-score {
|
|
4152
|
+
text-align: right;
|
|
4153
|
+
padding-left: 12px;
|
|
4154
|
+
border-left: 0.5px solid var(--line);
|
|
4155
|
+
}
|
|
4156
|
+
.ap-persona-card-score-v {
|
|
4157
|
+
font-family: var(--mono);
|
|
4158
|
+
font-size: 26px;
|
|
4159
|
+
font-weight: 600;
|
|
4160
|
+
color: var(--lime);
|
|
4161
|
+
letter-spacing: 0.02em;
|
|
4162
|
+
line-height: 1;
|
|
4163
|
+
}
|
|
4164
|
+
.ap-persona-card-score-l {
|
|
4165
|
+
font-family: var(--mono);
|
|
4166
|
+
font-size: 8.5px;
|
|
4167
|
+
letter-spacing: 0.18em;
|
|
4168
|
+
text-transform: uppercase;
|
|
4169
|
+
color: var(--text-faint);
|
|
4170
|
+
margin-top: 5px;
|
|
4171
|
+
}
|
|
4172
|
+
.ap-persona-card-grid {
|
|
4173
|
+
display: grid;
|
|
4174
|
+
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
4175
|
+
gap: 0;
|
|
4176
|
+
border-top: 0.5px solid var(--line);
|
|
4177
|
+
border-bottom: 0.5px solid var(--line);
|
|
4178
|
+
}
|
|
4179
|
+
.ap-persona-stat {
|
|
4180
|
+
padding: 10px 8px;
|
|
4181
|
+
text-align: center;
|
|
4182
|
+
border-right: 0.5px solid var(--line);
|
|
4183
|
+
}
|
|
4184
|
+
.ap-persona-stat:last-child { border-right: none; }
|
|
4185
|
+
.ap-persona-stat-v {
|
|
4186
|
+
font-family: var(--mono);
|
|
4187
|
+
font-size: 16px;
|
|
4188
|
+
font-weight: 600;
|
|
4189
|
+
color: var(--text);
|
|
4190
|
+
line-height: 1;
|
|
4191
|
+
}
|
|
4192
|
+
.ap-persona-stat-l {
|
|
4193
|
+
font-family: var(--mono);
|
|
4194
|
+
font-size: 8px;
|
|
4195
|
+
letter-spacing: 0.16em;
|
|
4196
|
+
text-transform: uppercase;
|
|
4197
|
+
color: var(--text-faint);
|
|
4198
|
+
margin-top: 5px;
|
|
4199
|
+
}
|
|
4200
|
+
.ap-persona-card-cta {
|
|
4201
|
+
display: flex;
|
|
4202
|
+
justify-content: space-between;
|
|
4203
|
+
align-items: baseline;
|
|
4204
|
+
}
|
|
4205
|
+
.ap-persona-card-cta-label {
|
|
4206
|
+
font-family: var(--mono);
|
|
4207
|
+
font-size: 11px;
|
|
4208
|
+
letter-spacing: 0.18em;
|
|
4209
|
+
text-transform: uppercase;
|
|
4210
|
+
color: var(--lime);
|
|
4211
|
+
font-weight: 600;
|
|
4212
|
+
}
|
|
4213
|
+
.ap-persona-card-cta-hint {
|
|
4214
|
+
font-family: var(--mono);
|
|
4215
|
+
font-size: 9px;
|
|
4216
|
+
letter-spacing: 0.12em;
|
|
4217
|
+
text-transform: uppercase;
|
|
4218
|
+
color: var(--text-faint);
|
|
4219
|
+
}
|
|
4220
|
+
@media (max-width: 720px) {
|
|
4221
|
+
.ap-persona-card-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
4222
|
+
.ap-persona-stat:nth-child(3) { border-right: none; }
|
|
4223
|
+
.ap-persona-stat:nth-child(n+4) { border-top: 0.5px solid var(--line); }
|
|
4224
|
+
}
|
|
4225
|
+
|
|
4226
|
+
/* ────────────────────────────────────────────────────────────
|
|
4227
|
+
Persona dossier overlay · markdown preview + download
|
|
4228
|
+
──────────────────────────────────────────────────────────── */
|
|
4229
|
+
.ap-persona-overlay {
|
|
4230
|
+
position: fixed;
|
|
4231
|
+
inset: 0;
|
|
4232
|
+
z-index: 9500;
|
|
4233
|
+
display: flex;
|
|
4234
|
+
align-items: center;
|
|
4235
|
+
justify-content: center;
|
|
4236
|
+
}
|
|
4237
|
+
.ap-persona-overlay-backdrop {
|
|
4238
|
+
position: absolute;
|
|
4239
|
+
inset: 0;
|
|
4240
|
+
background: rgba(0, 0, 0, 0.6);
|
|
4241
|
+
backdrop-filter: blur(3px);
|
|
4242
|
+
cursor: pointer;
|
|
4243
|
+
}
|
|
4244
|
+
.ap-persona-overlay-modal {
|
|
4245
|
+
position: relative;
|
|
4246
|
+
width: min(820px, calc(100vw - 32px));
|
|
4247
|
+
max-height: calc(100vh - 48px);
|
|
4248
|
+
display: flex;
|
|
4249
|
+
flex-direction: column;
|
|
4250
|
+
background: var(--panel);
|
|
4251
|
+
border: 0.5px solid var(--line-strong, var(--line));
|
|
4252
|
+
box-shadow: 0 24px 56px rgba(0, 0, 0, 0.5);
|
|
4253
|
+
}
|
|
4254
|
+
.ap-persona-overlay-classification {
|
|
4255
|
+
display: flex;
|
|
4256
|
+
justify-content: space-between;
|
|
4257
|
+
align-items: center;
|
|
4258
|
+
padding: 8px 18px;
|
|
4259
|
+
background: var(--panel-2);
|
|
4260
|
+
border-bottom: 0.5px solid var(--line);
|
|
4261
|
+
font-family: var(--mono);
|
|
4262
|
+
font-size: 9.5px;
|
|
4263
|
+
letter-spacing: 0.18em;
|
|
4264
|
+
text-transform: uppercase;
|
|
4265
|
+
color: var(--text-faint);
|
|
4266
|
+
}
|
|
4267
|
+
.ap-persona-overlay-classification .dot { color: var(--lime); margin-right: 4px; }
|
|
4268
|
+
.ap-persona-overlay-classification .right { color: var(--text-soft); }
|
|
4269
|
+
.ap-persona-overlay-head {
|
|
4270
|
+
display: flex;
|
|
4271
|
+
justify-content: space-between;
|
|
4272
|
+
align-items: center;
|
|
4273
|
+
gap: 14px;
|
|
4274
|
+
padding: 14px 22px;
|
|
4275
|
+
border-bottom: 0.5px solid var(--line);
|
|
4276
|
+
}
|
|
4277
|
+
.ap-persona-overlay-title {
|
|
4278
|
+
font-family: var(--font-human);
|
|
4279
|
+
font-size: 16px;
|
|
4280
|
+
font-weight: 700;
|
|
4281
|
+
color: var(--text);
|
|
4282
|
+
letter-spacing: -0.005em;
|
|
4283
|
+
}
|
|
4284
|
+
.ap-persona-overlay-actions {
|
|
4285
|
+
display: flex;
|
|
4286
|
+
align-items: center;
|
|
4287
|
+
gap: 8px;
|
|
4288
|
+
}
|
|
4289
|
+
.ap-persona-overlay-dl {
|
|
4290
|
+
font-family: var(--mono);
|
|
4291
|
+
font-size: 10px;
|
|
4292
|
+
letter-spacing: 0.12em;
|
|
4293
|
+
text-transform: uppercase;
|
|
4294
|
+
color: var(--lime);
|
|
4295
|
+
text-decoration: none;
|
|
4296
|
+
padding: 6px 12px;
|
|
4297
|
+
border: 0.5px solid var(--lime-dim, var(--line));
|
|
4298
|
+
background: var(--panel-2);
|
|
4299
|
+
transition: background 0.12s, color 0.12s;
|
|
4300
|
+
}
|
|
4301
|
+
.ap-persona-overlay-dl:hover {
|
|
4302
|
+
background: var(--lime);
|
|
4303
|
+
color: var(--bg, #0c0c0c);
|
|
4304
|
+
}
|
|
4305
|
+
.ap-persona-overlay-close {
|
|
4306
|
+
width: 30px;
|
|
4307
|
+
height: 30px;
|
|
4308
|
+
background: transparent;
|
|
4309
|
+
border: 0.5px solid var(--line);
|
|
4310
|
+
color: var(--text-soft);
|
|
4311
|
+
font-size: 14px;
|
|
4312
|
+
cursor: pointer;
|
|
4313
|
+
transition: color 0.12s, border-color 0.12s;
|
|
4314
|
+
}
|
|
4315
|
+
.ap-persona-overlay-close:hover { color: var(--text); border-color: var(--line-strong, var(--line)); }
|
|
4316
|
+
.ap-persona-overlay-body {
|
|
4317
|
+
flex: 1;
|
|
4318
|
+
min-height: 0;
|
|
4319
|
+
overflow-y: auto;
|
|
4320
|
+
padding: 22px 28px 28px;
|
|
4321
|
+
}
|
|
4322
|
+
.ap-persona-overlay-loading,
|
|
4323
|
+
.ap-persona-overlay-error {
|
|
4324
|
+
font-family: var(--mono);
|
|
4325
|
+
font-size: 11px;
|
|
4326
|
+
letter-spacing: 0.06em;
|
|
4327
|
+
color: var(--text-faint);
|
|
4328
|
+
text-align: center;
|
|
4329
|
+
padding: 60px 0;
|
|
4330
|
+
}
|
|
4331
|
+
.ap-persona-overlay-error { color: var(--ink, #c97373); }
|
|
4332
|
+
.ap-persona-overlay-md {
|
|
4333
|
+
font-family: var(--font-human);
|
|
4334
|
+
font-size: 13.5px;
|
|
4335
|
+
line-height: 1.65;
|
|
4336
|
+
color: var(--text);
|
|
4337
|
+
}
|
|
4338
|
+
.ap-persona-overlay-md h2 {
|
|
4339
|
+
font-family: var(--font-human);
|
|
4340
|
+
font-size: 16px;
|
|
4341
|
+
font-weight: 700;
|
|
4342
|
+
color: var(--text);
|
|
4343
|
+
margin: 24px 0 8px;
|
|
4344
|
+
padding-bottom: 6px;
|
|
4345
|
+
border-bottom: 0.5px solid var(--line);
|
|
4346
|
+
}
|
|
4347
|
+
.ap-persona-overlay-md h3 {
|
|
4348
|
+
font-family: var(--font-human);
|
|
4349
|
+
font-size: 13px;
|
|
4350
|
+
font-weight: 700;
|
|
4351
|
+
color: var(--text);
|
|
4352
|
+
margin: 18px 0 6px;
|
|
4353
|
+
}
|
|
4354
|
+
.ap-persona-overlay-md h4 {
|
|
4355
|
+
font-family: var(--mono);
|
|
4356
|
+
font-size: 10.5px;
|
|
4357
|
+
letter-spacing: 0.14em;
|
|
4358
|
+
text-transform: uppercase;
|
|
4359
|
+
color: var(--lime);
|
|
4360
|
+
margin: 14px 0 4px;
|
|
4361
|
+
}
|
|
4362
|
+
.ap-persona-overlay-md p { margin: 8px 0; }
|
|
4363
|
+
.ap-persona-overlay-md ul, .ap-persona-overlay-md ol {
|
|
4364
|
+
margin: 8px 0 8px 22px;
|
|
4365
|
+
padding: 0;
|
|
4366
|
+
}
|
|
4367
|
+
.ap-persona-overlay-md li { margin: 3px 0; }
|
|
4368
|
+
.ap-persona-overlay-md blockquote {
|
|
4369
|
+
margin: 10px 0;
|
|
4370
|
+
padding: 6px 12px;
|
|
4371
|
+
border-top: 0.5px solid var(--line);
|
|
4372
|
+
border-bottom: 0.5px solid var(--line);
|
|
4373
|
+
font-style: italic;
|
|
4374
|
+
color: var(--text-soft);
|
|
4375
|
+
}
|
|
4376
|
+
.ap-persona-overlay-md code {
|
|
4377
|
+
font-family: var(--mono);
|
|
4378
|
+
font-size: 12px;
|
|
4379
|
+
background: var(--panel-2);
|
|
4380
|
+
padding: 1px 5px;
|
|
4381
|
+
color: var(--text);
|
|
4382
|
+
}
|
|
4383
|
+
.ap-persona-overlay-md pre {
|
|
4384
|
+
background: var(--panel-2);
|
|
4385
|
+
padding: 10px 12px;
|
|
4386
|
+
overflow-x: auto;
|
|
4387
|
+
margin: 10px 0;
|
|
4388
|
+
border: 0.5px solid var(--line);
|
|
4389
|
+
}
|
|
4390
|
+
.ap-persona-overlay-md pre code { background: transparent; padding: 0; font-size: 11.5px; }
|
|
4391
|
+
.ap-persona-overlay-md strong { color: var(--text); font-weight: 700; }
|
|
4392
|
+
.ap-persona-overlay-md em { color: var(--lime); font-style: italic; }
|
|
4393
|
+
body.ap-persona-overlay-open { overflow: hidden; }
|
package/public/agent-profile.js
CHANGED
|
@@ -1079,6 +1079,100 @@
|
|
|
1079
1079
|
function renderRulesBlock(slug) {
|
|
1080
1080
|
return `<div class="ap-rules-block" data-ap-rules-block data-slug="${escape(slug)}">${renderRulesInner(slug)}</div>`;
|
|
1081
1081
|
}
|
|
1082
|
+
|
|
1083
|
+
/** Persona dossier card · only mounted for Full-mode agents (those
|
|
1084
|
+
* with `personaSpec` populated by the deep-build pipeline). The
|
|
1085
|
+
* card reads as a gamified character-sheet · mono kicker, big
|
|
1086
|
+
* divergence stat, secondary stat grid, ▸ CTA. Clicking opens an
|
|
1087
|
+
* overlay that previews the persona.md content with a Download
|
|
1088
|
+
* affordance. Hidden entirely for Signal-mode agents and seeded
|
|
1089
|
+
* directors so the panel doesn't render an empty section.
|
|
1090
|
+
*
|
|
1091
|
+
* Stats source from `live.personaSpec` (resolved from
|
|
1092
|
+
* window.app.agentsById at render time · the `p` object passed in
|
|
1093
|
+
* doesn't carry the spec, so we reach across). */
|
|
1094
|
+
function renderPersonaDossierSection(slug, p) {
|
|
1095
|
+
const live = window.app && window.app.agentsById ? window.app.agentsById[slug] : null;
|
|
1096
|
+
const spec = live && live.personaSpec ? live.personaSpec : null;
|
|
1097
|
+
if (!spec) return "";
|
|
1098
|
+
// Stats. Differentiation score is the headline — null when the
|
|
1099
|
+
// build skipped the eval pass (rare). The rest are counts of the
|
|
1100
|
+
// structured artifacts so the user can eyeball depth at a glance.
|
|
1101
|
+
const score = typeof spec.differentiationScore === "number" ? spec.differentiationScore : null;
|
|
1102
|
+
const scorePct = score == null ? "—" : `${Math.round(score * 100)}%`;
|
|
1103
|
+
const knowledge = spec.knowledge || {};
|
|
1104
|
+
const sourceCount = (knowledge.keyThinkers || []).length
|
|
1105
|
+
+ (knowledge.foundationalWorks || []).length
|
|
1106
|
+
+ (knowledge.recentDevelopments || []).length
|
|
1107
|
+
+ (knowledge.contestedClaims || []).length;
|
|
1108
|
+
const searchCount = (knowledge.searchQueries || []).length;
|
|
1109
|
+
const fewShotCount = (spec.fewShot || []).length;
|
|
1110
|
+
const rulesCount = (spec.rules || []).length;
|
|
1111
|
+
const checklistCount = (spec.reflectionChecklist || []).length;
|
|
1112
|
+
const evalCount = (spec.evalSet || []).length;
|
|
1113
|
+
const builtIso = spec.generatedAt || "";
|
|
1114
|
+
const builtLabel = builtIso ? new Date(builtIso).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" }) : "";
|
|
1115
|
+
return `
|
|
1116
|
+
<section class="ap-block ap-persona-block">
|
|
1117
|
+
<header class="ap-block-h">
|
|
1118
|
+
<span class="ap-block-h-title">Persona dossier</span>
|
|
1119
|
+
<span class="ap-block-h-tag">Full-mode build</span>
|
|
1120
|
+
</header>
|
|
1121
|
+
<button type="button" class="ap-persona-card" data-ap-persona-open data-slug="${escape(slug)}" aria-label="Open persona dossier">
|
|
1122
|
+
<div class="ap-persona-card-head">
|
|
1123
|
+
<div class="ap-persona-card-glyph" aria-hidden="true">
|
|
1124
|
+
<svg viewBox="0 0 32 32" width="28" height="28" fill="none" stroke="currentColor" stroke-width="1.4">
|
|
1125
|
+
<circle cx="16" cy="16" r="12" />
|
|
1126
|
+
<circle cx="16" cy="16" r="5" />
|
|
1127
|
+
<line x1="16" y1="2" x2="16" y2="8" />
|
|
1128
|
+
<line x1="16" y1="24" x2="16" y2="30" />
|
|
1129
|
+
<line x1="2" y1="16" x2="8" y2="16" />
|
|
1130
|
+
<line x1="24" y1="16" x2="30" y2="16" />
|
|
1131
|
+
</svg>
|
|
1132
|
+
</div>
|
|
1133
|
+
<div class="ap-persona-card-id">
|
|
1134
|
+
<div class="ap-persona-card-kicker">— PERSONA · 7-PHASE DOSSIER</div>
|
|
1135
|
+
<div class="ap-persona-card-title">${escape(p && p.name ? p.name : "Director")}</div>
|
|
1136
|
+
${builtLabel ? `<div class="ap-persona-card-meta">Built · ${escape(builtLabel)}</div>` : ""}
|
|
1137
|
+
</div>
|
|
1138
|
+
<div class="ap-persona-card-score" title="Differentiation vs generic-AI baseline">
|
|
1139
|
+
<div class="ap-persona-card-score-v">${escape(scorePct)}</div>
|
|
1140
|
+
<div class="ap-persona-card-score-l">DIVERGENCE</div>
|
|
1141
|
+
</div>
|
|
1142
|
+
</div>
|
|
1143
|
+
<div class="ap-persona-card-grid">
|
|
1144
|
+
<div class="ap-persona-stat">
|
|
1145
|
+
<div class="ap-persona-stat-v">${sourceCount}</div>
|
|
1146
|
+
<div class="ap-persona-stat-l">SOURCES</div>
|
|
1147
|
+
</div>
|
|
1148
|
+
<div class="ap-persona-stat">
|
|
1149
|
+
<div class="ap-persona-stat-v">${searchCount}</div>
|
|
1150
|
+
<div class="ap-persona-stat-l">SEARCHES</div>
|
|
1151
|
+
</div>
|
|
1152
|
+
<div class="ap-persona-stat">
|
|
1153
|
+
<div class="ap-persona-stat-v">${fewShotCount}</div>
|
|
1154
|
+
<div class="ap-persona-stat-l">VOICE EX.</div>
|
|
1155
|
+
</div>
|
|
1156
|
+
<div class="ap-persona-stat">
|
|
1157
|
+
<div class="ap-persona-stat-v">${rulesCount}</div>
|
|
1158
|
+
<div class="ap-persona-stat-l">RULES</div>
|
|
1159
|
+
</div>
|
|
1160
|
+
<div class="ap-persona-stat">
|
|
1161
|
+
<div class="ap-persona-stat-v">${checklistCount}</div>
|
|
1162
|
+
<div class="ap-persona-stat-l">CHECKS</div>
|
|
1163
|
+
</div>
|
|
1164
|
+
<div class="ap-persona-stat">
|
|
1165
|
+
<div class="ap-persona-stat-v">${evalCount}</div>
|
|
1166
|
+
<div class="ap-persona-stat-l">EVALS</div>
|
|
1167
|
+
</div>
|
|
1168
|
+
</div>
|
|
1169
|
+
<div class="ap-persona-card-cta">
|
|
1170
|
+
<span class="ap-persona-card-cta-label">▸ OPEN DOSSIER</span>
|
|
1171
|
+
<span class="ap-persona-card-cta-hint">preview · download .md</span>
|
|
1172
|
+
</div>
|
|
1173
|
+
</button>
|
|
1174
|
+
</section>`;
|
|
1175
|
+
}
|
|
1082
1176
|
function renderRulesInner(slug) {
|
|
1083
1177
|
const rules = rulesForAgent(slug);
|
|
1084
1178
|
const list = rules.length === 0
|
|
@@ -1772,6 +1866,79 @@
|
|
|
1772
1866
|
`;
|
|
1773
1867
|
}
|
|
1774
1868
|
|
|
1869
|
+
/* ─── Persona dossier overlay ──────────────────────────
|
|
1870
|
+
Full-screen modal that previews the persona.md content. Opens
|
|
1871
|
+
from the dossier card in the main column. Fetches the route
|
|
1872
|
+
once per open (no caching · the file is small and downloads
|
|
1873
|
+
are cheap), renders via the in-file renderMarkdown helper, and
|
|
1874
|
+
surfaces a Download button that hits the same endpoint with
|
|
1875
|
+
the browser's native download path. Closed on backdrop click
|
|
1876
|
+
or Escape. */
|
|
1877
|
+
let _personaOverlayEsc = null;
|
|
1878
|
+
function openPersonaOverlay(slug, agentName) {
|
|
1879
|
+
closePersonaOverlay();
|
|
1880
|
+
const overlay = document.createElement("div");
|
|
1881
|
+
overlay.id = "ap-persona-overlay";
|
|
1882
|
+
overlay.className = "ap-persona-overlay";
|
|
1883
|
+
overlay.innerHTML = `
|
|
1884
|
+
<div class="ap-persona-overlay-backdrop" data-ap-persona-close></div>
|
|
1885
|
+
<div class="ap-persona-overlay-modal" role="dialog" aria-modal="true" aria-label="Persona dossier">
|
|
1886
|
+
<div class="ap-persona-overlay-classification">
|
|
1887
|
+
<span><span class="dot">●</span> CLASSIFIED · DIRECTOR DOSSIER</span>
|
|
1888
|
+
<span class="right">${escape(agentName || "")}</span>
|
|
1889
|
+
</div>
|
|
1890
|
+
<div class="ap-persona-overlay-head">
|
|
1891
|
+
<div class="ap-persona-overlay-title">Persona dossier</div>
|
|
1892
|
+
<div class="ap-persona-overlay-actions">
|
|
1893
|
+
<a class="ap-persona-overlay-dl" href="/api/agents/${encodeURIComponent(slug)}/persona.md" download>
|
|
1894
|
+
<span aria-hidden="true">↓</span> Download .md
|
|
1895
|
+
</a>
|
|
1896
|
+
<button type="button" class="ap-persona-overlay-close" data-ap-persona-close aria-label="Close">✕</button>
|
|
1897
|
+
</div>
|
|
1898
|
+
</div>
|
|
1899
|
+
<div class="ap-persona-overlay-body" data-ap-persona-body>
|
|
1900
|
+
<div class="ap-persona-overlay-loading">Decrypting dossier…</div>
|
|
1901
|
+
</div>
|
|
1902
|
+
</div>
|
|
1903
|
+
`;
|
|
1904
|
+
document.body.appendChild(overlay);
|
|
1905
|
+
document.body.classList.add("ap-persona-overlay-open");
|
|
1906
|
+
_personaOverlayEsc = (ev) => {
|
|
1907
|
+
if (ev.key === "Escape") {
|
|
1908
|
+
ev.stopImmediatePropagation();
|
|
1909
|
+
closePersonaOverlay();
|
|
1910
|
+
}
|
|
1911
|
+
};
|
|
1912
|
+
document.addEventListener("keydown", _personaOverlayEsc, true);
|
|
1913
|
+
// Fetch + render. Same-origin so credentials default. The
|
|
1914
|
+
// fetch path returns text/markdown; we feed it straight into
|
|
1915
|
+
// the existing renderMarkdown helper.
|
|
1916
|
+
fetch(`/api/agents/${encodeURIComponent(slug)}/persona.md`, { credentials: "same-origin" })
|
|
1917
|
+
.then((r) => {
|
|
1918
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
1919
|
+
return r.text();
|
|
1920
|
+
})
|
|
1921
|
+
.then((md) => {
|
|
1922
|
+
const body = overlay.querySelector("[data-ap-persona-body]");
|
|
1923
|
+
if (!body) return;
|
|
1924
|
+
body.innerHTML = `<div class="ap-persona-overlay-md">${renderMarkdown(md)}</div>`;
|
|
1925
|
+
})
|
|
1926
|
+
.catch((err) => {
|
|
1927
|
+
const body = overlay.querySelector("[data-ap-persona-body]");
|
|
1928
|
+
if (!body) return;
|
|
1929
|
+
body.innerHTML = `<div class="ap-persona-overlay-error">Could not load dossier · ${escape(String(err && err.message ? err.message : err))}</div>`;
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
function closePersonaOverlay() {
|
|
1933
|
+
const el = document.getElementById("ap-persona-overlay");
|
|
1934
|
+
if (el) el.remove();
|
|
1935
|
+
document.body.classList.remove("ap-persona-overlay-open");
|
|
1936
|
+
if (_personaOverlayEsc) {
|
|
1937
|
+
document.removeEventListener("keydown", _personaOverlayEsc, true);
|
|
1938
|
+
_personaOverlayEsc = null;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1775
1942
|
/* ─── Profile · ⋯ menu (top-right of the cover) ─────
|
|
1776
1943
|
Small popover anchored to the menu button with one or more
|
|
1777
1944
|
actions. v1 ships a single "regenerate 8-bit avatar" item. */
|
|
@@ -1807,6 +1974,20 @@
|
|
|
1807
1974
|
<span>Regenerate 8-bit avatar</span>
|
|
1808
1975
|
</button>`);
|
|
1809
1976
|
}
|
|
1977
|
+
// Persona MD download · only present for Full-mode agents (those
|
|
1978
|
+
// built via the deep persona-builder pipeline). Their `personaSpec`
|
|
1979
|
+
// field carries the 7-phase artifact; the route renders it as
|
|
1980
|
+
// Markdown. Hidden for Signal-mode agents and seeded directors —
|
|
1981
|
+
// they have no spec to export.
|
|
1982
|
+
const hasPersonaSpec = !!(live && live.personaSpec);
|
|
1983
|
+
if (hasPersonaSpec) {
|
|
1984
|
+
parts.push(`<div class="ap-id-menu-divider" aria-hidden="true"></div>`);
|
|
1985
|
+
parts.push(`
|
|
1986
|
+
<a class="ap-id-menu-item" href="/api/agents/${encodeURIComponent(slug)}/persona.md" target="_blank" rel="noopener" data-ap-menu-action="persona-md">
|
|
1987
|
+
<span class="ap-id-menu-mark">↓</span>
|
|
1988
|
+
<span>Download persona.md</span>
|
|
1989
|
+
</a>`);
|
|
1990
|
+
}
|
|
1810
1991
|
if (isCustom) {
|
|
1811
1992
|
parts.push(`<div class="ap-id-menu-divider" aria-hidden="true"></div>`);
|
|
1812
1993
|
parts.push(`
|
|
@@ -2408,7 +2589,6 @@
|
|
|
2408
2589
|
ensureVoiceOptions();
|
|
2409
2590
|
const v = voiceForAgent(slug);
|
|
2410
2591
|
const label = v ? `${v.provider} · ${v.voiceId}` : uiT("ap_voice_browser_default");
|
|
2411
|
-
const deck = v ? v.model : uiT("ap_voice_engine_browser");
|
|
2412
2592
|
const speed = v?.speed ?? 1;
|
|
2413
2593
|
const pitch = v?.pitch ?? 0;
|
|
2414
2594
|
const emotion = v?.emotion || "";
|
|
@@ -2424,7 +2604,6 @@
|
|
|
2424
2604
|
<button type="button" class="ap-model-trigger" data-ap-voice-trigger>
|
|
2425
2605
|
<span class="ap-model-trigger-text">
|
|
2426
2606
|
<span class="ap-model-trigger-name" data-ap-voice-name>${escape(label)}</span>
|
|
2427
|
-
<span class="ap-model-trigger-provider" data-ap-voice-provider>${escape(deck)}</span>
|
|
2428
2607
|
</span>
|
|
2429
2608
|
<span class="ap-model-trigger-caret">▾</span>
|
|
2430
2609
|
</button>
|
|
@@ -2709,6 +2888,8 @@
|
|
|
2709
2888
|
</div>
|
|
2710
2889
|
</section>
|
|
2711
2890
|
|
|
2891
|
+
${renderPersonaDossierSection(slug, p)}
|
|
2892
|
+
|
|
2712
2893
|
<section class="ap-block">
|
|
2713
2894
|
<header class="ap-block-h">
|
|
2714
2895
|
<span class="ap-block-h-title">${escape(uiT("ap_instruction"))}</span>
|
|
@@ -3158,6 +3339,34 @@
|
|
|
3158
3339
|
return;
|
|
3159
3340
|
}
|
|
3160
3341
|
|
|
3342
|
+
// Persona dossier card · open the preview overlay. Card is a
|
|
3343
|
+
// <button>, so the click lands on the button or any of its
|
|
3344
|
+
// children — closest() catches both. The overlay reads the
|
|
3345
|
+
// slug from the data attribute and fetches /persona.md.
|
|
3346
|
+
const personaOpen = e.target.closest("[data-ap-persona-open]");
|
|
3347
|
+
if (personaOpen) {
|
|
3348
|
+
e.preventDefault();
|
|
3349
|
+
e.stopPropagation();
|
|
3350
|
+
const slug = personaOpen.getAttribute("data-slug");
|
|
3351
|
+
if (!slug) return;
|
|
3352
|
+
const live = window.app && window.app.agentsById ? window.app.agentsById[slug] : null;
|
|
3353
|
+
const agentName = live && live.name ? live.name : "";
|
|
3354
|
+
openPersonaOverlay(slug, agentName);
|
|
3355
|
+
return;
|
|
3356
|
+
}
|
|
3357
|
+
// Persona dossier overlay · backdrop / close-button click. The
|
|
3358
|
+
// download anchor inside the overlay is NOT marked with the
|
|
3359
|
+
// close attr, so it doesn't fire here — its native href
|
|
3360
|
+
// navigation handles the download and the user can dismiss
|
|
3361
|
+
// the overlay manually if they wish.
|
|
3362
|
+
const personaClose = e.target.closest("[data-ap-persona-close]");
|
|
3363
|
+
if (personaClose) {
|
|
3364
|
+
e.preventDefault();
|
|
3365
|
+
e.stopPropagation();
|
|
3366
|
+
closePersonaOverlay();
|
|
3367
|
+
return;
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3161
3370
|
// ⋯ menu · open the popover (anchored to the button).
|
|
3162
3371
|
const idMenuBtn = e.target.closest("[data-ap-id-menu]");
|
|
3163
3372
|
if (idMenuBtn) {
|
|
@@ -3173,10 +3382,20 @@
|
|
|
3173
3382
|
// ⋯ menu · action click.
|
|
3174
3383
|
const menuAction = e.target.closest("[data-ap-menu-action]");
|
|
3175
3384
|
if (menuAction) {
|
|
3176
|
-
e.preventDefault();
|
|
3177
3385
|
const action = menuAction.getAttribute("data-ap-menu-action");
|
|
3178
3386
|
const pop = document.getElementById("ap-id-menu-pop");
|
|
3179
3387
|
const slug = pop?.dataset.slug;
|
|
3388
|
+
// persona-md is rendered as an <a href=…> · let the browser
|
|
3389
|
+
// navigate natively (the route returns the file with a
|
|
3390
|
+
// download Content-Disposition). preventDefault here would
|
|
3391
|
+
// kill the download. For the button-based actions below we
|
|
3392
|
+
// DO want preventDefault (otherwise the button-as-form-
|
|
3393
|
+
// submitter behaviour and click bubbling can both fire).
|
|
3394
|
+
if (action === "persona-md") {
|
|
3395
|
+
closeProfileIdMenu();
|
|
3396
|
+
return;
|
|
3397
|
+
}
|
|
3398
|
+
e.preventDefault();
|
|
3180
3399
|
closeProfileIdMenu();
|
|
3181
3400
|
if (action === "regen-avatar" && slug) regenerateProfileAvatar(slug);
|
|
3182
3401
|
if (action === "delete" && slug && window.app && typeof window.app.deleteAgent === "function") {
|