@bongos/core 1.19.819 → 1.19.820
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/.bongos-core.json +60 -25
- package/clients/bongos-client/index.d.ts +1 -1
- package/docs/api/openapi.json +4 -2
- package/docs/copy-inventory.md +23 -18
- package/docs/copy-registry.json +76 -31
- package/docs/module-api-changelog.md +3 -1
- package/migrations/core_246_session_type.sql +49 -0
- package/modules/hall-ui/public/sessions.css +19 -0
- package/modules/hall-ui/public/sessions.html +16 -1
- package/modules/hall-ui/public/sessions.js +135 -6
- package/modules/sessions/db.js +92 -6
- package/modules/sessions/routes/sessions.js +18 -1
- package/modules/sessions/session-type.js +132 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/backfill-session-types.js +87 -0
- package/scripts/gds/fitness.js +12 -1
- package/scripts/gds/home-isolation-guard.js +126 -0
- package/scripts/gds/ship-merge.js +5 -1
- package/scripts/gds/ship-regen.js +29 -0
- package/src/module-api.js +1 -1
- package/tests/cli_package.mjs +18 -2
- package/tests/hall_roster_world.mjs +10 -5
- package/tests/home_isolation_guard.mjs +95 -0
- package/tests/session_type.mjs +215 -0
- package/tests/ship_regen_copy_inventory.mjs +69 -0
package/docs/copy-registry.json
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"generator": "scripts/gds/copy-inventory.js",
|
|
4
4
|
"goal": 1000074,
|
|
5
5
|
"counts": {
|
|
6
|
-
"strings":
|
|
7
|
-
"unique_ids":
|
|
8
|
-
"certain":
|
|
6
|
+
"strings": 1868,
|
|
7
|
+
"unique_ids": 1622,
|
|
8
|
+
"certain": 1868,
|
|
9
9
|
"heuristic": 0,
|
|
10
10
|
"duplicate_ids": 170
|
|
11
11
|
},
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"builders-hall": {
|
|
14
14
|
"at": "modules/hall-ui/public",
|
|
15
15
|
"files": 86,
|
|
16
|
-
"strings":
|
|
16
|
+
"strings": 1378
|
|
17
17
|
},
|
|
18
18
|
"status-dashboard": {
|
|
19
19
|
"at": "modules/status-ui/public",
|
|
@@ -730,8 +730,8 @@
|
|
|
730
730
|
"text": "Could not load sessions.",
|
|
731
731
|
"occurrences": 2,
|
|
732
732
|
"at": [
|
|
733
|
-
"modules/hall-ui/public/sessions.js:
|
|
734
|
-
"modules/hall-ui/public/sessions.js:
|
|
733
|
+
"modules/hall-ui/public/sessions.js:399",
|
|
734
|
+
"modules/hall-ui/public/sessions.js:437"
|
|
735
735
|
]
|
|
736
736
|
},
|
|
737
737
|
{
|
|
@@ -1480,8 +1480,8 @@
|
|
|
1480
1480
|
"text": "The session log could not be loaded. Try again.",
|
|
1481
1481
|
"occurrences": 2,
|
|
1482
1482
|
"at": [
|
|
1483
|
-
"modules/hall-ui/public/sessions.js:
|
|
1484
|
-
"modules/hall-ui/public/sessions.js:
|
|
1483
|
+
"modules/hall-ui/public/sessions.js:398",
|
|
1484
|
+
"modules/hall-ui/public/sessions.js:450"
|
|
1485
1485
|
]
|
|
1486
1486
|
},
|
|
1487
1487
|
{
|
|
@@ -2491,6 +2491,15 @@
|
|
|
2491
2491
|
"origin": "html-text",
|
|
2492
2492
|
"confidence": "certain"
|
|
2493
2493
|
},
|
|
2494
|
+
{
|
|
2495
|
+
"id": "e08d410571e9",
|
|
2496
|
+
"surface": "builders-hall",
|
|
2497
|
+
"text": "All kinds",
|
|
2498
|
+
"file": "modules/hall-ui/public/sessions.js",
|
|
2499
|
+
"line": 286,
|
|
2500
|
+
"origin": "js-markup",
|
|
2501
|
+
"confidence": "certain"
|
|
2502
|
+
},
|
|
2494
2503
|
{
|
|
2495
2504
|
"id": "9c34bccb06ef",
|
|
2496
2505
|
"surface": "builders-hall",
|
|
@@ -3954,7 +3963,7 @@
|
|
|
3954
3963
|
"surface": "builders-hall",
|
|
3955
3964
|
"text": "Could not load sessions.",
|
|
3956
3965
|
"file": "modules/hall-ui/public/sessions.js",
|
|
3957
|
-
"line":
|
|
3966
|
+
"line": 399,
|
|
3958
3967
|
"origin": "js-notice",
|
|
3959
3968
|
"confidence": "certain"
|
|
3960
3969
|
},
|
|
@@ -3963,7 +3972,7 @@
|
|
|
3963
3972
|
"surface": "builders-hall",
|
|
3964
3973
|
"text": "Could not load sessions.",
|
|
3965
3974
|
"file": "modules/hall-ui/public/sessions.js",
|
|
3966
|
-
"line":
|
|
3975
|
+
"line": 437,
|
|
3967
3976
|
"origin": "js-notice",
|
|
3968
3977
|
"confidence": "certain"
|
|
3969
3978
|
},
|
|
@@ -4395,7 +4404,7 @@
|
|
|
4395
4404
|
"surface": "builders-hall",
|
|
4396
4405
|
"text": "Could not verify your access. Try again.",
|
|
4397
4406
|
"file": "modules/hall-ui/public/sessions.js",
|
|
4398
|
-
"line":
|
|
4407
|
+
"line": 436,
|
|
4399
4408
|
"origin": "js-text-assign",
|
|
4400
4409
|
"confidence": "certain"
|
|
4401
4410
|
},
|
|
@@ -5119,6 +5128,15 @@
|
|
|
5119
5128
|
"origin": "js-text-assign",
|
|
5120
5129
|
"confidence": "certain"
|
|
5121
5130
|
},
|
|
5131
|
+
{
|
|
5132
|
+
"id": "e8dd9a8f3a7e",
|
|
5133
|
+
"surface": "builders-hall",
|
|
5134
|
+
"text": "Filter sessions by kind",
|
|
5135
|
+
"file": "modules/hall-ui/public/sessions.html",
|
|
5136
|
+
"line": 67,
|
|
5137
|
+
"origin": "html-attr:aria-label",
|
|
5138
|
+
"confidence": "certain"
|
|
5139
|
+
},
|
|
5122
5140
|
{
|
|
5123
5141
|
"id": "1def0e7c716e",
|
|
5124
5142
|
"surface": "builders-hall",
|
|
@@ -5700,7 +5718,7 @@
|
|
|
5700
5718
|
"surface": "builders-hall",
|
|
5701
5719
|
"text": "How the session read to this builder, scored against their own interaction profile. Advisory — it gates nothing.",
|
|
5702
5720
|
"file": "modules/hall-ui/public/sessions.html",
|
|
5703
|
-
"line":
|
|
5721
|
+
"line": 78,
|
|
5704
5722
|
"origin": "html-attr:title",
|
|
5705
5723
|
"confidence": "certain"
|
|
5706
5724
|
},
|
|
@@ -6735,7 +6753,7 @@
|
|
|
6735
6753
|
"surface": "builders-hall",
|
|
6736
6754
|
"text": "Most recent first. Each load of this page is itself recorded in the audit log.",
|
|
6737
6755
|
"file": "modules/hall-ui/public/sessions.js",
|
|
6738
|
-
"line":
|
|
6756
|
+
"line": 340,
|
|
6739
6757
|
"origin": "js-text-assign",
|
|
6740
6758
|
"confidence": "certain"
|
|
6741
6759
|
},
|
|
@@ -7104,7 +7122,7 @@
|
|
|
7104
7122
|
"surface": "builders-hall",
|
|
7105
7123
|
"text": "No sessions recorded yet. They land here as builders ship and end sessions.",
|
|
7106
7124
|
"file": "modules/hall-ui/public/sessions.js",
|
|
7107
|
-
"line":
|
|
7125
|
+
"line": 146,
|
|
7108
7126
|
"origin": "js-markup",
|
|
7109
7127
|
"confidence": "certain"
|
|
7110
7128
|
},
|
|
@@ -7162,6 +7180,15 @@
|
|
|
7162
7180
|
"origin": "js-markup",
|
|
7163
7181
|
"confidence": "certain"
|
|
7164
7182
|
},
|
|
7183
|
+
{
|
|
7184
|
+
"id": "086665af6208",
|
|
7185
|
+
"surface": "builders-hall",
|
|
7186
|
+
"text": "No {…} sessions recorded. Choose another kind to widen the ledger.",
|
|
7187
|
+
"file": "modules/hall-ui/public/sessions.js",
|
|
7188
|
+
"line": 145,
|
|
7189
|
+
"origin": "js-markup",
|
|
7190
|
+
"confidence": "certain"
|
|
7191
|
+
},
|
|
7165
7192
|
{
|
|
7166
7193
|
"id": "1967d2176f2e",
|
|
7167
7194
|
"surface": "builders-hall",
|
|
@@ -7230,7 +7257,7 @@
|
|
|
7230
7257
|
"surface": "builders-hall",
|
|
7231
7258
|
"text": "Not scored — this builder’s own profile waived every signal, or the session predates this measure.",
|
|
7232
7259
|
"file": "modules/hall-ui/public/sessions.js",
|
|
7233
|
-
"line":
|
|
7260
|
+
"line": 100,
|
|
7234
7261
|
"origin": "js-markup",
|
|
7235
7262
|
"confidence": "certain"
|
|
7236
7263
|
},
|
|
@@ -7261,6 +7288,15 @@
|
|
|
7261
7288
|
"origin": "js-markup",
|
|
7262
7289
|
"confidence": "certain"
|
|
7263
7290
|
},
|
|
7291
|
+
{
|
|
7292
|
+
"id": "20bb29015a84",
|
|
7293
|
+
"surface": "builders-hall",
|
|
7294
|
+
"text": "Not yet classified.",
|
|
7295
|
+
"file": "modules/hall-ui/public/sessions.js",
|
|
7296
|
+
"line": 72,
|
|
7297
|
+
"origin": "js-markup",
|
|
7298
|
+
"confidence": "certain"
|
|
7299
|
+
},
|
|
7264
7300
|
{
|
|
7265
7301
|
"id": "571b9b22dbda",
|
|
7266
7302
|
"surface": "builders-hall",
|
|
@@ -7869,7 +7905,7 @@
|
|
|
7869
7905
|
"surface": "builders-hall",
|
|
7870
7906
|
"text": "Plain speech",
|
|
7871
7907
|
"file": "modules/hall-ui/public/sessions.html",
|
|
7872
|
-
"line":
|
|
7908
|
+
"line": 78,
|
|
7873
7909
|
"origin": "html-text",
|
|
7874
7910
|
"confidence": "certain"
|
|
7875
7911
|
},
|
|
@@ -10209,7 +10245,7 @@
|
|
|
10209
10245
|
"surface": "builders-hall",
|
|
10210
10246
|
"text": "The session log could not be loaded. Try again.",
|
|
10211
10247
|
"file": "modules/hall-ui/public/sessions.js",
|
|
10212
|
-
"line":
|
|
10248
|
+
"line": 398,
|
|
10213
10249
|
"origin": "js-text-assign",
|
|
10214
10250
|
"confidence": "certain"
|
|
10215
10251
|
},
|
|
@@ -10218,7 +10254,7 @@
|
|
|
10218
10254
|
"surface": "builders-hall",
|
|
10219
10255
|
"text": "The session log could not be loaded. Try again.",
|
|
10220
10256
|
"file": "modules/hall-ui/public/sessions.js",
|
|
10221
|
-
"line":
|
|
10257
|
+
"line": 450,
|
|
10222
10258
|
"origin": "js-text-assign",
|
|
10223
10259
|
"confidence": "certain"
|
|
10224
10260
|
},
|
|
@@ -10731,7 +10767,7 @@
|
|
|
10731
10767
|
"surface": "builders-hall",
|
|
10732
10768
|
"text": "Tool errors",
|
|
10733
10769
|
"file": "modules/hall-ui/public/sessions.html",
|
|
10734
|
-
"line":
|
|
10770
|
+
"line": 76,
|
|
10735
10771
|
"origin": "html-text",
|
|
10736
10772
|
"confidence": "certain"
|
|
10737
10773
|
},
|
|
@@ -11068,6 +11104,15 @@
|
|
|
11068
11104
|
"origin": "html-attr:placeholder",
|
|
11069
11105
|
"confidence": "certain"
|
|
11070
11106
|
},
|
|
11107
|
+
{
|
|
11108
|
+
"id": "e179870922a3",
|
|
11109
|
+
"surface": "builders-hall",
|
|
11110
|
+
"text": "What the session did, derived from its own transcript: whether it wrote files, used tools, and how many prompts a person actually sent.",
|
|
11111
|
+
"file": "modules/hall-ui/public/sessions.html",
|
|
11112
|
+
"line": 74,
|
|
11113
|
+
"origin": "html-attr:title",
|
|
11114
|
+
"confidence": "certain"
|
|
11115
|
+
},
|
|
11071
11116
|
{
|
|
11072
11117
|
"id": "5f2b989bbf6a",
|
|
11073
11118
|
"surface": "builders-hall",
|
|
@@ -13395,7 +13440,7 @@
|
|
|
13395
13440
|
"surface": "builders-hall",
|
|
13396
13441
|
"text": "{…} and up",
|
|
13397
13442
|
"file": "modules/hall-ui/public/sessions.js",
|
|
13398
|
-
"line":
|
|
13443
|
+
"line": 187,
|
|
13399
13444
|
"origin": "js-text-assign",
|
|
13400
13445
|
"confidence": "certain"
|
|
13401
13446
|
},
|
|
@@ -13404,7 +13449,7 @@
|
|
|
13404
13449
|
"surface": "builders-hall",
|
|
13405
13450
|
"text": "{…} and up.",
|
|
13406
13451
|
"file": "modules/hall-ui/public/sessions.js",
|
|
13407
|
-
"line":
|
|
13452
|
+
"line": 185,
|
|
13408
13453
|
"origin": "js-text-assign",
|
|
13409
13454
|
"confidence": "certain"
|
|
13410
13455
|
},
|
|
@@ -13579,15 +13624,6 @@
|
|
|
13579
13624
|
"origin": "js-markup",
|
|
13580
13625
|
"confidence": "certain"
|
|
13581
13626
|
},
|
|
13582
|
-
{
|
|
13583
|
-
"id": "3d76dce19c4a",
|
|
13584
|
-
"surface": "builders-hall",
|
|
13585
|
-
"text": "{…} session{…} across {…} builder{…}.",
|
|
13586
|
-
"file": "modules/hall-ui/public/sessions.js",
|
|
13587
|
-
"line": 226,
|
|
13588
|
-
"origin": "js-text-assign",
|
|
13589
|
-
"confidence": "certain"
|
|
13590
|
-
},
|
|
13591
13627
|
{
|
|
13592
13628
|
"id": "e08fa22622f0",
|
|
13593
13629
|
"surface": "builders-hall",
|
|
@@ -13629,7 +13665,7 @@
|
|
|
13629
13665
|
"surface": "builders-hall",
|
|
13630
13666
|
"text": "{…} tokens",
|
|
13631
13667
|
"file": "modules/hall-ui/public/sessions.js",
|
|
13632
|
-
"line":
|
|
13668
|
+
"line": 170,
|
|
13633
13669
|
"origin": "js-markup",
|
|
13634
13670
|
"confidence": "certain"
|
|
13635
13671
|
},
|
|
@@ -13840,6 +13876,15 @@
|
|
|
13840
13876
|
"origin": "js-markup",
|
|
13841
13877
|
"confidence": "certain"
|
|
13842
13878
|
},
|
|
13879
|
+
{
|
|
13880
|
+
"id": "97e27ab169ae",
|
|
13881
|
+
"surface": "builders-hall",
|
|
13882
|
+
"text": "{…}{…} session{…} across {…} builder{…}.",
|
|
13883
|
+
"file": "modules/hall-ui/public/sessions.js",
|
|
13884
|
+
"line": 338,
|
|
13885
|
+
"origin": "js-text-assign",
|
|
13886
|
+
"confidence": "certain"
|
|
13887
|
+
},
|
|
13843
13888
|
{
|
|
13844
13889
|
"id": "3d74668f69e5",
|
|
13845
13890
|
"surface": "builders-hall",
|
|
@@ -2111,6 +2111,8 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2111
2111
|
1.19.818 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2112
2112
|
landed since 1.19.817 with no explicit bump. run 35201213516. (task 1002620)
|
|
2113
2113
|
1.19.819 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2114
|
-
landed since 1.19.818 with no explicit bump. run
|
|
2114
|
+
landed since 1.19.818 with no explicit bump. run 35206448426. (task 1002620)
|
|
2115
|
+
1.19.820 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2116
|
+
landed since 1.19.819 with no explicit bump. run 35209838815. (task 1002620)
|
|
2115
2117
|
---------------------------------------------------------------------------
|
|
2116
2118
|
```
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
-- core_246_session_type.sql — WHAT EACH SESSION WAS (task 1003975, idea 1001033).
|
|
2
|
+
--
|
|
3
|
+
-- The session ledger could say whether a session shipped a task and nothing else,
|
|
4
|
+
-- so the 54% of the corpus with an empty task_ids read as an undifferentiated
|
|
5
|
+
-- column of dashes. This column holds the verdict of modules/sessions/session-type.js
|
|
6
|
+
-- — a pure, deterministic read of the digest the session already uploaded (its
|
|
7
|
+
-- prompt count and which tools it called), computed once at upload rather than on
|
|
8
|
+
-- every query, because the write-tool signal lives in the `detail` jsonb and
|
|
9
|
+
-- scanning it per request would put a jsonb walk on the ledger's hot path.
|
|
10
|
+
--
|
|
11
|
+
-- WHY IT IS HERE AND NOT IN modules/sessions/migrations/. The sessions module's
|
|
12
|
+
-- own CLAUDE.md says a new session migration belongs in the module — but that
|
|
13
|
+
-- holds only for a table the module OWNS. `session_records` predates the module
|
|
14
|
+
-- system and lives in core, and ADR 0083 §Decision #5 (enforced by fitness.js,
|
|
15
|
+
-- "module migrations additive + namespaced") lets a module migration touch only
|
|
16
|
+
-- its own `sessions_`-prefixed tables. A module migration adding this column was
|
|
17
|
+
-- written first and the gate refused it, correctly. The rule wins; the module doc
|
|
18
|
+
-- has been narrowed to say so.
|
|
19
|
+
--
|
|
20
|
+
-- NULLABLE ON PURPOSE, and there is no DEFAULT. Three reasons:
|
|
21
|
+
-- * Every row that predates this migration is legitimately unclassified until
|
|
22
|
+
-- the backfill (scripts/gds/backfill-session-types.js) runs. A DEFAULT would
|
|
23
|
+
-- assert a type for 4,595 rows nobody measured.
|
|
24
|
+
-- * The upload path fills it, but an older CLI posting the current body shape
|
|
25
|
+
-- must keep working — the column is derived server-side from what the body
|
|
26
|
+
-- already carries, never sent by the client, so no pinned core is broken.
|
|
27
|
+
-- * NULL is distinguishable from 'unknown'. NULL means "not yet classified";
|
|
28
|
+
-- 'unknown' means "classified, and the digest did not decide". Collapsing the
|
|
29
|
+
-- two would make the backfill's own progress unreadable.
|
|
30
|
+
--
|
|
31
|
+
-- NO CHECK CONSTRAINT against the type list. The closed set lives in
|
|
32
|
+
-- session-type.js, which the route filter and the ledger both validate against;
|
|
33
|
+
-- duplicating it here would give the vocabulary a second home that a deploy can
|
|
34
|
+
-- leave behind. The values are written by one function, never by a client.
|
|
35
|
+
|
|
36
|
+
ALTER TABLE session_records
|
|
37
|
+
ADD COLUMN IF NOT EXISTS session_type text;
|
|
38
|
+
|
|
39
|
+
COMMENT ON COLUMN session_records.session_type IS
|
|
40
|
+
'Derived shape of the session (build / automated / exploration / conversation / unknown) '
|
|
41
|
+
'from modules/sessions/session-type.js. NULL = not yet classified, which is not the same '
|
|
42
|
+
'as ''unknown''. Never client-supplied.';
|
|
43
|
+
|
|
44
|
+
-- Partial index: the ledger filters BY a type, never FOR the unclassified, so the
|
|
45
|
+
-- NULL rows are dead weight in the index. Drops with the backlog as the backfill
|
|
46
|
+
-- runs, which is the opposite of what a full index would do.
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_session_records_type
|
|
48
|
+
ON session_records (session_type, uploaded_at DESC)
|
|
49
|
+
WHERE session_type IS NOT NULL;
|
|
@@ -17,3 +17,22 @@
|
|
|
17
17
|
/* The task ids: chips that are doors (the family's a.fact-chip hover). */
|
|
18
18
|
.ledger-tasks { display: inline-flex; flex-wrap: wrap; gap: 6px; }
|
|
19
19
|
.ledger-tasks .fact-chip { text-decoration: none; }
|
|
20
|
+
/* task 1003975 — the kind filter row. Right-aligned above the ledger so it sits
|
|
21
|
+
over the column it acts on without stealing the lede's line; it reuses the
|
|
22
|
+
pager's .pager__perpage label+select so the page has ONE control grammar. */
|
|
23
|
+
.ledger-filters { display: flex; justify-content: flex-end; margin: 0 0 var(--sp-sm); }
|
|
24
|
+
/* The control itself: the same label+select proportions as the pager's per-page
|
|
25
|
+
picker, so the page reads as one grammar — but its OWN class, because the
|
|
26
|
+
hall_audit guard treats any .pager* markup outside the kit as a second pager,
|
|
27
|
+
and borrowing that family is exactly how one starts. */
|
|
28
|
+
.ledger-filter {
|
|
29
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
30
|
+
font-size: 12px; color: var(--ink-faint);
|
|
31
|
+
}
|
|
32
|
+
.ledger-filter select { height: 30px; padding: 2px 8px; font-size: 13px; }
|
|
33
|
+
/* The kind cell: a quiet fact, not a chip. The ledger already spends its colour
|
|
34
|
+
on the two warnings (tool errors, plain speech), and a third coloured token
|
|
35
|
+
per row would make the warnings stop reading as warnings. Deliberately NO
|
|
36
|
+
white-space: nowrap — task 1003470 removed the blanket nowrap that broke this
|
|
37
|
+
table on a phone, and "Automated run" is short enough to wrap harmlessly. */
|
|
38
|
+
.ledger-kind { color: var(--ink-soft); }
|
|
@@ -54,10 +54,25 @@
|
|
|
54
54
|
<section class="scroll" id="sessions-scroll" aria-labelledby="sessions-h" hidden>
|
|
55
55
|
<h2 id="sessions-h" class="scroll__h">Recorded sessions</h2>
|
|
56
56
|
<p class="scroll__lede">Most recent first. Each row is one recorded work session; every id on it opens its record.</p>
|
|
57
|
+
<!-- task 1003975: the ledger's first filter. It LOOKS like the pager's
|
|
58
|
+
per-page control on purpose (one control grammar per page) but wears
|
|
59
|
+
its own class — the hall_audit guard reads `class="pager…"` as a
|
|
60
|
+
hand-rolled pager, and it is right to: borrowing that family is how a
|
|
61
|
+
second pager implementation starts. Its options are filled from the
|
|
62
|
+
server's vocabulary in sessions.js, never hardcoded here, where they
|
|
63
|
+
would drift from what the route accepts. -->
|
|
64
|
+
<div class="ledger-filters">
|
|
65
|
+
<label class="ledger-filter">
|
|
66
|
+
<span>Kind</span>
|
|
67
|
+
<select id="session-type-filter" aria-label="Filter sessions by kind"></select>
|
|
68
|
+
</label>
|
|
69
|
+
</div>
|
|
57
70
|
<table class="ledger" id="sessions-table">
|
|
58
71
|
<thead>
|
|
59
72
|
<tr>
|
|
60
|
-
<th>Session</th><th>Builder</th
|
|
73
|
+
<th>Session</th><th>Builder</th>
|
|
74
|
+
<th title="What the session did, derived from its own transcript: whether it wrote files, used tools, and how many prompts a person actually sent.">Kind</th>
|
|
75
|
+
<th>Tasks</th>
|
|
61
76
|
<th class="ledger__num">Earned</th><th class="ledger__num">Turns</th><th class="ledger__num">Tokens</th><th class="ledger__num">Tool errors</th>
|
|
62
77
|
<th class="ledger__num">Duration</th>
|
|
63
78
|
<th class="ledger__num" title="How the session read to this builder, scored against their own interaction profile. Advisory — it gates nothing.">Plain speech</th>
|
|
@@ -42,6 +42,39 @@
|
|
|
42
42
|
|
|
43
43
|
const shortSid = (sid) => String(sid || '').slice(0, 8) || '—';
|
|
44
44
|
|
|
45
|
+
// task 1003975 — what each session type is CALLED, and what it means on hover.
|
|
46
|
+
// The keys come from the server (`session_types` on the search response); this
|
|
47
|
+
// map only supplies the words. A type the server adds before this map learns it
|
|
48
|
+
// still renders — as its raw key — because a missing label must never make a
|
|
49
|
+
// real row look blank.
|
|
50
|
+
const KIND_LABEL = {
|
|
51
|
+
build: 'Build',
|
|
52
|
+
automated: 'Automated run',
|
|
53
|
+
exploration: 'Exploring',
|
|
54
|
+
conversation: 'Conversation',
|
|
55
|
+
unknown: 'Unclear',
|
|
56
|
+
};
|
|
57
|
+
const KIND_TITLE = {
|
|
58
|
+
build: 'Files were written or edited in this session.',
|
|
59
|
+
automated: 'One prompt and no edits — a grader, a subagent or a scripted run, not someone working.',
|
|
60
|
+
exploration: 'Someone steered it through several prompts, reading and running things without editing.',
|
|
61
|
+
conversation: 'Several prompts and no tools at all — a question or a discussion.',
|
|
62
|
+
unknown: 'The recorded transcript does not say — usually a session that predates this measure.',
|
|
63
|
+
};
|
|
64
|
+
const kindLabel = (t) => KIND_LABEL[t] || String(t);
|
|
65
|
+
|
|
66
|
+
// Emits the whole <td>. A row with no type yet (the backfill has not reached it)
|
|
67
|
+
// is a dash, NOT 'Unclear' — "not classified" and "classified as undecidable"
|
|
68
|
+
// are different facts and the ledger should not merge them.
|
|
69
|
+
function kindCell(s) {
|
|
70
|
+
const t = s.session_type;
|
|
71
|
+
if (!t) {
|
|
72
|
+
return '<td class="ledger-kind" title="Not yet classified.">—</td>';
|
|
73
|
+
}
|
|
74
|
+
const title = KIND_TITLE[t] || '';
|
|
75
|
+
return `<td class="ledger-kind" title="${escapeHtml(title)}">${escapeHtml(kindLabel(t))}</td>`;
|
|
76
|
+
}
|
|
77
|
+
|
|
45
78
|
// task 1002516 — the transcript accessibility dimension (criterion
|
|
46
79
|
// `accessibility-fluency-profile`, clause 4). Headed "Plain speech" rather than
|
|
47
80
|
// "Accessibility" because this page already lives under a shell whose only
|
|
@@ -105,8 +138,12 @@
|
|
|
105
138
|
function renderTable(sessions) {
|
|
106
139
|
const body = $('#sessions-body');
|
|
107
140
|
if (!sessions.length) {
|
|
108
|
-
|
|
109
|
-
|
|
141
|
+
// The empty line has to answer the question the reader is actually asking,
|
|
142
|
+
// and with a filter on that question is "did I filter everything away?",
|
|
143
|
+
// not "why has nobody ever worked here".
|
|
144
|
+
body.innerHTML = state.sessionType
|
|
145
|
+
? `<tr><td colspan="11" class="ledger__empty">No ${escapeHtml(kindLabel(state.sessionType).toLowerCase())} sessions recorded. Choose another kind to widen the ledger.</td></tr>`
|
|
146
|
+
: '<tr><td colspan="11" class="ledger__empty">No sessions recorded yet. They land here as builders ship and end sessions.</td></tr>';
|
|
110
147
|
return;
|
|
111
148
|
}
|
|
112
149
|
body.innerHTML = sessions
|
|
@@ -126,6 +163,7 @@
|
|
|
126
163
|
`<td>${whoHtml}` +
|
|
127
164
|
(s.rank ? ` <span class="ledger-rank">${escapeHtml(rankLabel(s.rank))}</span>` : '') +
|
|
128
165
|
'</td>' +
|
|
166
|
+
kindCell(s) +
|
|
129
167
|
`<td>${tasks}</td>` +
|
|
130
168
|
`<td class="ledger__num">${fmtInt(s.drachmae_earned)}</td>` +
|
|
131
169
|
`<td class="ledger__num">${fmtInt(s.assistant_turns)}</td>` +
|
|
@@ -186,7 +224,24 @@
|
|
|
186
224
|
// pager first and calls back second, so a failed fetch has to put it back to these —
|
|
187
225
|
// otherwise the bar goes on claiming a page whose rows never arrived. Keeping both
|
|
188
226
|
// (not just the page) means a failed per-page change rolls back cleanly too.
|
|
189
|
-
|
|
227
|
+
// `sessionType` is the live filter (null = every kind). It sits in the same
|
|
228
|
+
// roll-back state as page/perPage because a failed fetch has to put ALL THREE
|
|
229
|
+
// back — a bar claiming page 1 of "Exploring" whose rows never arrived is the
|
|
230
|
+
// same lie whichever control moved last.
|
|
231
|
+
const state = { total: 0, shown: 0, page: 1, perPage: pager?.perPage ?? 0, sessionType: null };
|
|
232
|
+
// Remembered per viewer, like the pager's per-page choice: a monitoring page is
|
|
233
|
+
// returned to, and re-picking the same lens every visit is a tax. Wrapped
|
|
234
|
+
// because storage throws in a private window, where the page must still work.
|
|
235
|
+
const TYPE_KEY = 'otb.sessions.kind';
|
|
236
|
+
function readStoredType() {
|
|
237
|
+
try { return window.localStorage.getItem(TYPE_KEY) || null; } catch { return null; }
|
|
238
|
+
}
|
|
239
|
+
function storeType(t) {
|
|
240
|
+
try {
|
|
241
|
+
if (t) window.localStorage.setItem(TYPE_KEY, t);
|
|
242
|
+
else window.localStorage.removeItem(TYPE_KEY);
|
|
243
|
+
} catch { /* no-op: the filter still works for this visit */ }
|
|
244
|
+
}
|
|
190
245
|
let loading = false;
|
|
191
246
|
// Two clicks can be in flight at once; only the newest may paint (the profile
|
|
192
247
|
// activity-log idiom, task 1003281).
|
|
@@ -201,6 +256,11 @@
|
|
|
201
256
|
const clamped = kit.renderServerPage(pager, state.total, state.shown);
|
|
202
257
|
const perPage = $(`#${PAGER_PREFIX}-per-page`);
|
|
203
258
|
if (perPage) perPage.disabled = loading;
|
|
259
|
+
// The kind filter is disabled for the same reason the pager is: a second
|
|
260
|
+
// change mid-fetch would put a third request in flight behind two that are
|
|
261
|
+
// already racing to paint.
|
|
262
|
+
const kind = $('#session-type-filter');
|
|
263
|
+
if (kind) kind.disabled = loading;
|
|
204
264
|
if (loading) {
|
|
205
265
|
for (const id of ['prev', 'next']) {
|
|
206
266
|
const el = $(`#${PAGER_PREFIX}-${id}`);
|
|
@@ -210,6 +270,52 @@
|
|
|
210
270
|
return clamped;
|
|
211
271
|
}
|
|
212
272
|
|
|
273
|
+
// ---- the kind filter -----------------------------------------------------
|
|
274
|
+
|
|
275
|
+
// Fill the control from the vocabulary the SERVER sent, once. Re-stamping it on
|
|
276
|
+
// every paint would reset the open dropdown under a reader's cursor, so it
|
|
277
|
+
// returns early when the options already match what arrived.
|
|
278
|
+
let typeOptionsFrom = null;
|
|
279
|
+
function syncTypeOptions(types) {
|
|
280
|
+
const el = $('#session-type-filter');
|
|
281
|
+
if (!el || !Array.isArray(types) || types.length === 0) return;
|
|
282
|
+
const key = types.join(',');
|
|
283
|
+
if (typeOptionsFrom === key) return;
|
|
284
|
+
typeOptionsFrom = key;
|
|
285
|
+
el.innerHTML =
|
|
286
|
+
'<option value="">All kinds</option>' +
|
|
287
|
+
types.map((t) => `<option value="${escapeHtml(t)}">${escapeHtml(kindLabel(t))}</option>`).join('');
|
|
288
|
+
// A remembered kind is only honoured once the server has confirmed it is
|
|
289
|
+
// still a kind — otherwise the control would show a value no query accepts.
|
|
290
|
+
if (state.sessionType && !types.includes(state.sessionType)) {
|
|
291
|
+
setTypeFilter(null, { reload: false });
|
|
292
|
+
}
|
|
293
|
+
el.value = state.sessionType || '';
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Move the filter. `reload:false` is the internal correction (a stale stored
|
|
297
|
+
// value being dropped) — it must not fire a fetch from inside one.
|
|
298
|
+
function setTypeFilter(type, { reload = true } = {}) {
|
|
299
|
+
state.sessionType = type || null;
|
|
300
|
+
storeType(state.sessionType);
|
|
301
|
+
const el = $('#session-type-filter');
|
|
302
|
+
if (el) el.value = state.sessionType || '';
|
|
303
|
+
if (!reload) return;
|
|
304
|
+
// A narrower corpus makes the open page meaningless — page 7 of Build is not
|
|
305
|
+
// page 7 of everything — so every change starts at the first page.
|
|
306
|
+
pager.page = 1;
|
|
307
|
+
state.page = 1;
|
|
308
|
+
fetchPage().then((ok) => {
|
|
309
|
+
if (ok) window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function wireTypeFilter() {
|
|
314
|
+
const el = $('#session-type-filter');
|
|
315
|
+
if (!el) return;
|
|
316
|
+
el.addEventListener('change', () => setTypeFilter(el.value || null));
|
|
317
|
+
}
|
|
318
|
+
|
|
213
319
|
// Paint one page. Returns true when the pager clamped — the corpus shrank under an
|
|
214
320
|
// open page, so the caller must re-ask rather than leave a range that lies.
|
|
215
321
|
function showData(data) {
|
|
@@ -222,9 +328,15 @@
|
|
|
222
328
|
$('#sessions-scroll').hidden = false;
|
|
223
329
|
renderStats(totals);
|
|
224
330
|
renderTable(sessions);
|
|
331
|
+
syncTypeOptions(data.session_types);
|
|
225
332
|
const clamped = renderPager();
|
|
333
|
+
// With a kind chosen, the count IS the filtered count (the tally shares the
|
|
334
|
+
// filter clause with the page query), so the line has to say which — a
|
|
335
|
+
// headline reading "57 sessions" under a control set to Build would otherwise
|
|
336
|
+
// look like the whole corpus had shrunk.
|
|
337
|
+
const scope = state.sessionType ? ` ${kindLabel(state.sessionType).toLowerCase()}` : '';
|
|
226
338
|
$('#ledger-sub').textContent =
|
|
227
|
-
`${fmtInt(totals.total)} session${num(totals.total) === 1 ? '' : 's'} across ${fmtInt(totals.builders)} builder${num(totals.builders) === 1 ? '' : 's'}.`;
|
|
339
|
+
`${fmtInt(totals.total)}${scope} session${num(totals.total) === 1 ? '' : 's'} across ${fmtInt(totals.builders)} builder${num(totals.builders) === 1 ? '' : 's'}.`;
|
|
228
340
|
$('#ledger-foot').textContent =
|
|
229
341
|
'Most recent first. Each load of this page is itself recorded in the audit log.';
|
|
230
342
|
return clamped;
|
|
@@ -238,14 +350,17 @@
|
|
|
238
350
|
// then calls back — so a failure restores `state.page`/`state.perPage`, the last
|
|
239
351
|
// values that painted. That roll-back is what the old hand-roll did with
|
|
240
352
|
// `state.offset`, and it is the one piece of it worth carrying over.
|
|
241
|
-
async function fetchPage({ reclamps = 0 } = {}) {
|
|
353
|
+
async function fetchPage({ reclamps = 0, droppedFilter = false } = {}) {
|
|
242
354
|
const seq = ++inFlight;
|
|
243
355
|
loading = true;
|
|
244
356
|
renderPager();
|
|
245
357
|
try {
|
|
358
|
+
const typeParam = state.sessionType
|
|
359
|
+
? `&session_type=${encodeURIComponent(state.sessionType)}`
|
|
360
|
+
: '';
|
|
246
361
|
const res = await api.request(
|
|
247
362
|
'GET',
|
|
248
|
-
`${API}/sessions/search?order=recent&limit=${pager.perPage}&offset=${kit.pageOffset(pager)}`
|
|
363
|
+
`${API}/sessions/search?order=recent&limit=${pager.perPage}&offset=${kit.pageOffset(pager)}${typeParam}`
|
|
249
364
|
);
|
|
250
365
|
// A newer click already went out; painting this answer would show a page nobody
|
|
251
366
|
// is on any more.
|
|
@@ -255,6 +370,15 @@
|
|
|
255
370
|
showSealed('You do not have access to the session log.');
|
|
256
371
|
return false;
|
|
257
372
|
}
|
|
373
|
+
// A remembered kind the server no longer accepts (a type retired between
|
|
374
|
+
// visits) comes back as a named 400. Drop the stale lens and re-ask once,
|
|
375
|
+
// rather than showing a load failure the reader cannot act on — their only
|
|
376
|
+
// clue would be a control still displaying the dead value.
|
|
377
|
+
if (res.status === 400 && state.sessionType && !droppedFilter) {
|
|
378
|
+
setTypeFilter(null, { reload: false });
|
|
379
|
+
loading = false;
|
|
380
|
+
return fetchPage({ reclamps, droppedFilter: true });
|
|
381
|
+
}
|
|
258
382
|
if (!res.ok) throw new Error(`search → ${res.status}`);
|
|
259
383
|
const clamped = showData(res.data);
|
|
260
384
|
// The corpus shrank under this page (sessions pruned between loads):
|
|
@@ -329,6 +453,11 @@
|
|
|
329
453
|
|
|
330
454
|
// 2. Fetch the first page (Archon-gated + audited server-side).
|
|
331
455
|
mountPager();
|
|
456
|
+
// The remembered kind is applied BEFORE the first fetch so a returning reader
|
|
457
|
+
// lands on their own lens rather than seeing the whole corpus paint and then
|
|
458
|
+
// snap to a narrower one.
|
|
459
|
+
state.sessionType = readStoredType();
|
|
460
|
+
wireTypeFilter();
|
|
332
461
|
await fetchPage();
|
|
333
462
|
}
|
|
334
463
|
|