docs(records): Jason's walkthrough rulings, Sage moved to SetSpark, CHAT-02 go
Jason ruled on seven open items (20:27Z-20:45Z): seat Gitea tokens read in place, one Discord restart after 6b with row 25 live, row 8 limited to the dev seats, DYOR in dyor-stack-v4 with Sage moved to SetSpark, skills/aws-* excluded locally, no second WebUI return defect, and go on CHAT-02 only. Sage persona files name SetSpark as its business work. Darkwing's SOUL drops harness names that were wrong for T3. DEFERRED adds the slash-prefix paste hazard and the board Host/Origin gap, and moves the ledger T3 item to Done. Dewey's approved CHAT-02 brief (636b0fac) and Filbert's review are recorded. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"generatedAt": "2026-09-26T20:15:18.304Z",
|
||||
"row": {
|
||||
"agent": "researcher",
|
||||
"project": "mosaic-stack",
|
||||
"state": "idle",
|
||||
"alive": true,
|
||||
"sessionFile": "/mnt/storage/src/mosaic-stack/.pi/state/researcher/sessions/2026-09-13T19-52-28-745Z_01a09c54-0b48-7154-addd-8fdce875aa4a.jsonl",
|
||||
"lastActivity": "2026-09-26T20:11:06.173Z",
|
||||
"ageSeconds": 251,
|
||||
"lastAssistantText": "pong",
|
||||
"waitingOnYou": false,
|
||||
"tmux": {
|
||||
"socket": null,
|
||||
"session": "researcher"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{"file":".pi/state/darkwing/sessions/2026-09-12T16-06-52-940Z_01a0965f-250c-70de-88f4-ac12e3669367.jsonl","sent":"2026-09-12T16:23:39.405Z","head":"[dragon-lin:control-board -> dragon-lin:darkwing] pizza?","firstStopAnswer":"2026-09-12T16:23:54.910Z","answerChars":74,"nextUser":"2026-09-12T16:38:39.266Z"}
|
||||
{"file":".pi/state/darkwing/sessions/2026-09-12T16-06-52-940Z_01a0965f-250c-70de-88f4-ac12e3669367.jsonl","sent":"2026-09-12T16:38:39.266Z","head":"[dragon-lin:control-board -> dragon-lin:darkwing] tacos? (control-board: answer ","firstStopAnswer":"2026-09-12T16:38:42.476Z","answerChars":13,"nextUser":null}
|
||||
{"file":".pi/state/darkwing/sessions/2026-09-12T16-44-19-503Z_01a09681-6cae-7141-89d6-723da6a23794.jsonl","sent":"2026-09-12T16:45:06.894Z","head":"[dragon-lin:control-board -> dragon-lin:darkwing] Read docs/plans/CURRENT.md and","firstStopAnswer":"2026-09-12T16:55:26.610Z","answerChars":161,"nextUser":"2026-09-12T17:02:40.313Z"}
|
||||
{"file":".pi/state/darkwing/sessions/2026-09-12T16-44-19-503Z_01a09681-6cae-7141-89d6-723da6a23794.jsonl","sent":"2026-09-13T00:17:17.246Z","head":"[dragon-lin:control-board -> dragon-lin:darkwing] Gate D is written in CURRENT.m","firstStopAnswer":"2026-09-13T00:19:04.273Z","answerChars":170,"nextUser":"2026-09-13T00:17:56.026Z"}
|
||||
{"file":".pi/state/darkwing/sessions/2026-09-12T16-44-19-503Z_01a09681-6cae-7141-89d6-723da6a23794.jsonl","sent":"2026-09-13T01:20:00.056Z","head":"[dragon-lin:control-board -> dragon-lin:darkwing] I do NOT want to clobber the w","firstStopAnswer":"2026-09-13T01:20:14.688Z","answerChars":588,"nextUser":"2026-09-13T01:20:40.591Z"}
|
||||
{"file":".pi/state/darkwing/sessions/2026-09-12T16-44-19-503Z_01a09681-6cae-7141-89d6-723da6a23794.jsonl","sent":"2026-09-13T01:28:29.259Z","head":"[dragon-lin:control-board -> dragon-lin:darkwing] I just relaunched the rocko se","firstStopAnswer":"2026-09-13T01:40:52.276Z","answerChars":299,"nextUser":"2026-09-13T01:28:43.425Z"}
|
||||
{"file":".pi/state/darkwing/sessions/2026-09-12T16-44-19-503Z_01a09681-6cae-7141-89d6-723da6a23794.jsonl","sent":"2026-09-13T02:28:05.633Z","head":"[dragon-lin:control-board -> dragon-lin:darkwing] I stepped away for a bit. What","firstStopAnswer":"2026-09-13T02:28:20.119Z","answerChars":720,"nextUser":"2026-09-13T02:30:23.812Z"}
|
||||
{"file":".pi/state/darkwing/sessions/2026-09-12T16-44-19-503Z_01a09681-6cae-7141-89d6-723da6a23794.jsonl","sent":"2026-09-13T02:30:23.812Z","head":"[dragon-lin:control-board -> dragon-lin:darkwing] approved (control-board: answe","firstStopAnswer":"2026-09-13T02:47:22.844Z","answerChars":195,"nextUser":"2026-09-13T02:32:08.207Z"}
|
||||
{"file":".pi/state/darkwing/sessions/2026-09-12T16-44-19-503Z_01a09681-6cae-7141-89d6-723da6a23794.jsonl","sent":"2026-09-13T04:10:18.210Z","head":"[dragon-lin:control-board -> dragon-lin:darkwing] Status? (control-board: answer","firstStopAnswer":"2026-09-13T04:10:24.562Z","answerChars":310,"nextUser":"2026-09-13T04:10:57.319Z"}
|
||||
{"file":".pi/state/darkwing/sessions/2026-09-12T16-44-19-503Z_01a09681-6cae-7141-89d6-723da6a23794.jsonl","sent":"2026-09-13T04:10:57.319Z","head":"[dragon-lin:control-board -> dragon-lin:darkwing] Is this actively moving? Which","firstStopAnswer":"2026-09-13T04:11:03.773Z","answerChars":262,"nextUser":"2026-09-13T04:11:40.327Z"}
|
||||
{"file":".pi/state/darkwing/sessions/2026-09-12T16-44-19-503Z_01a09681-6cae-7141-89d6-723da6a23794.jsonl","sent":"2026-09-13T04:11:40.327Z","head":"[dragon-lin:control-board -> dragon-lin:darkwing] Keep moving. DO NOT STOP FOR R","firstStopAnswer":"2026-09-13T04:26:18.709Z","answerChars":277,"nextUser":"2026-09-13T04:25:22.317Z"}
|
||||
{"file":".pi/state/darkwing/sessions/2026-09-12T16-44-19-503Z_01a09681-6cae-7141-89d6-723da6a23794.jsonl","sent":"2026-09-13T04:50:50.715Z","head":"[dragon-lin:control-board -> dragon-lin:darkwing] Need anything from me? (contro","firstStopAnswer":"2026-09-13T04:51:01.486Z","answerChars":223,"nextUser":"2026-09-13T04:53:09.729Z"}
|
||||
{"file":".pi/state/darkwing/sessions/2026-09-12T16-44-19-503Z_01a09681-6cae-7141-89d6-723da6a23794.jsonl","sent":"2026-09-13T19:51:30.379Z","head":"[dragon-lin:control-board -> dragon-lin:darkwing] What is needed from me? (contr","firstStopAnswer":"2026-09-13T19:51:36.688Z","answerChars":276,"nextUser":"2026-09-13T19:51:52.410Z"}
|
||||
{"file":".pi/state/darkwing/sessions/2026-09-12T16-44-19-503Z_01a09681-6cae-7141-89d6-723da6a23794.jsonl","sent":"2026-09-13T19:51:52.410Z","head":"[dragon-lin:control-board -> dragon-lin:darkwing] proceed (control-board: answer","firstStopAnswer":"2026-09-13T19:57:38.104Z","answerChars":323,"nextUser":"2026-09-13T20:01:37.928Z"}
|
||||
{"file":".pi/state/dewey/sessions/2026-09-13T00-10-59-217Z_01a0981a-5b11-7235-873b-7babccdc1cab.jsonl","sent":"2026-09-13T00:17:04.404Z","head":"[dragon-lin:control-board -> dragon-lin:dewey] Read docs/plans/CURRENT.md and ex","firstStopAnswer":"2026-09-13T00:32:56.857Z","answerChars":256,"nextUser":"2026-09-13T00:18:15.838Z"}
|
||||
{"file":".pi/state/dewey/sessions/2026-09-13T00-10-59-217Z_01a0981a-5b11-7235-873b-7babccdc1cab.jsonl","sent":"2026-09-13T00:49:59.771Z","head":"[dragon-lin:control-board -> dragon-lin:dewey] What are the Gate E criteria? (co","firstStopAnswer":"2026-09-13T00:50:07.519Z","answerChars":365,"nextUser":"2026-09-13T00:56:51.198Z"}
|
||||
{"file":".pi/state/dewey/sessions/2026-09-13T00-10-59-217Z_01a0981a-5b11-7235-873b-7babccdc1cab.jsonl","sent":"2026-09-13T01:03:12.462Z","head":"[dragon-lin:control-board -> dragon-lin:dewey] Q4: agreed Q5: yes Q6: agreed. We","firstStopAnswer":"2026-09-13T01:03:42.772Z","answerChars":1626,"nextUser":"2026-09-13T01:03:42.772Z"}
|
||||
{"file":".pi/state/dewey/sessions/2026-09-13T00-10-59-217Z_01a0981a-5b11-7235-873b-7babccdc1cab.jsonl","sent":"2026-09-13T01:06:15.824Z","head":"[dragon-lin:control-board -> dragon-lin:dewey] Q8: agreed Q9: agreed Q10: agreed","firstStopAnswer":"2026-09-13T01:07:02.468Z","answerChars":420,"nextUser":"2026-09-13T01:07:40.783Z"}
|
||||
{"file":".pi/state/dewey/sessions/2026-09-13T00-10-59-217Z_01a0981a-5b11-7235-873b-7babccdc1cab.jsonl","sent":"2026-09-13T01:10:23.469Z","head":"[dragon-lin:control-board -> dragon-lin:dewey] Q12: agreed Q13: agreed Q14: agre","firstStopAnswer":"2026-09-13T01:11:18.147Z","answerChars":1699,"nextUser":"2026-09-13T01:12:45.940Z"}
|
||||
{"file":".pi/state/dewey/sessions/2026-09-13T00-10-59-217Z_01a0981a-5b11-7235-873b-7babccdc1cab.jsonl","sent":"2026-09-13T01:12:45.940Z","head":"[dragon-lin:control-board -> dragon-lin:dewey] Q17: agreed Q18: agreed Q19: agre","firstStopAnswer":"2026-09-13T01:21:42.659Z","answerChars":1365,"nextUser":"2026-09-13T01:18:40.502Z"}
|
||||
{"file":".pi/state/dewey/sessions/2026-09-13T00-10-59-217Z_01a0981a-5b11-7235-873b-7babccdc1cab.jsonl","sent":"2026-09-13T01:22:57.512Z","head":"[dragon-lin:control-board -> dragon-lin:dewey] Q20: agreed Q21: yes Q22: agreed ","firstStopAnswer":"2026-09-13T01:24:20.818Z","answerChars":1418,"nextUser":"2026-09-13T01:24:05.743Z"}
|
||||
{"file":".pi/state/filbert/sessions/2026-09-12T16-26-47-117Z_01a09671-5dcd-720a-853d-b9dc5ff30872.jsonl","sent":"2026-09-12T16:28:52.615Z","head":"[dragon-lin:control-board -> dragon-lin:filbert] pizza?","firstStopAnswer":"2026-09-12T16:29:06.948Z","answerChars":74,"nextUser":null}
|
||||
{"file":".pi/state/filbert/sessions/2026-09-12T16-30-08-084Z_01a09674-6ed4-724b-a9c6-ed2e9a9f9b15.jsonl","sent":"2026-09-12T16:30:27.068Z","head":"[dragon-lin:control-board -> dragon-lin:filbert] pizza?","firstStopAnswer":"2026-09-12T16:30:28.611Z","answerChars":42,"nextUser":null}
|
||||
{"file":".pi/state/filbert/sessions/2026-09-12T16-32-47-764Z_01a09676-de94-71fe-b9fb-f2705b57f346.jsonl","sent":"2026-09-12T16:33:17.505Z","head":"[dragon-lin:control-board -> dragon-lin:filbert] pizza?","firstStopAnswer":null,"answerChars":null,"nextUser":null}
|
||||
{"file":".pi/state/filbert/sessions/2026-09-12T16-36-26-218Z_01a0967a-33e9-72f4-b511-9264d71b000e.jsonl","sent":"2026-09-12T16:36:36.722Z","head":"[dragon-lin:control-board -> dragon-lin:filbert] pizza? (control-board: answer i","firstStopAnswer":"2026-09-12T16:36:39.912Z","answerChars":15,"nextUser":null}
|
||||
{"file":".pi/state/filbert/sessions/2026-09-12T16-38-58-597Z_01a0967c-8724-7151-a812-ca70dc1b24cd.jsonl","sent":"2026-09-12T16:39:21.527Z","head":"[dragon-lin:control-board -> dragon-lin:filbert] chocolate? (control-board: answ","firstStopAnswer":"2026-09-12T16:39:57.717Z","answerChars":552,"nextUser":"2026-09-12T16:52:58.831Z"}
|
||||
{"file":".pi/state/researcher/sessions/2026-09-13T19-52-28-745Z_01a09c54-0b48-7154-addd-8fdce875aa4a.jsonl","sent":"2026-09-13T21:19:50.889Z","head":"[dragon-lin:control-board -> dragon-lin:researcher] Reply exactly BOARD_REPLY_OK","firstStopAnswer":"2026-09-13T21:19:57.102Z","answerChars":14,"nextUser":"2026-09-14T00:40:03.263Z"}
|
||||
{"file":".pi/state/researcher/sessions/2026-09-13T19-52-28-745Z_01a09c54-0b48-7154-addd-8fdce875aa4a.jsonl","sent":"2026-09-14T00:40:03.263Z","head":"[dragon-lin:control-board -> dragon-lin:researcher] Reply exactly: Input needed:","firstStopAnswer":"2026-09-14T00:40:10.107Z","answerChars":39,"nextUser":"2026-09-14T00:41:14.949Z"}
|
||||
{"file":".pi/state/researcher/sessions/2026-09-13T19-52-28-745Z_01a09c54-0b48-7154-addd-8fdce875aa4a.jsonl","sent":"2026-09-14T00:41:14.949Z","head":"[dragon-lin:control-board -> dragon-lin:researcher] Confirmed. Reply exactly TES","firstStopAnswer":"2026-09-14T00:41:20.185Z","answerChars":13,"nextUser":"2026-09-26T20:10:57.429Z"}
|
||||
{"file":".pi/state/researcher/sessions/2026-09-13T19-52-28-745Z_01a09c54-0b48-7154-addd-8fdce875aa4a.jsonl","sent":"2026-09-26T20:10:57.429Z","head":"/[dragon-lin:control-board -> dragon-lin:researcher] ping (control-board: answer","firstStopAnswer":"2026-09-26T20:11:06.173Z","answerChars":4,"nextUser":null}
|
||||
@@ -0,0 +1,17 @@
|
||||
# 09-13 Console replay (brief §1.1 item 4)
|
||||
|
||||
Source: `git archive ea00ec66` of `packages/webui`, `packages/control-board`,
|
||||
`packages/seat`, `tools/tmux` and `package.json`, extracted to a scratch
|
||||
directory, with `node_modules` symlinked from the checkout. All 45 extracted
|
||||
files were checked byte-identical to `ea00ec66` on 2026-09-26.
|
||||
|
||||
Test: `return-flow-0913.test.mjs` is the 42c08d52 `return-flow.test.mjs`
|
||||
with the Age, pending-notice and clear-once assertions removed, because ea00ec66 has
|
||||
neither feature. `removed-vs-42c08d52.diff` is the whole difference.
|
||||
|
||||
Run: `node --test --test-reporter=tap packages/webui/tests/return-flow-0913.test.mjs`
|
||||
from the scratch root. Output: `replay.tap` (1 pass, 20.7 s).
|
||||
|
||||
What it shows: at ea00ec66 the new answer arrives in the open inspector
|
||||
through the 10 s poll, once. The answer is short, so this replay does not
|
||||
exercise the board's 240-character clip.
|
||||
@@ -0,0 +1,16 @@
|
||||
40d39
|
||||
< assert.match(await b.evaluate('document.querySelector("table.sessions tbody td:last-child").textContent'), /^1m ago\d{4}-\d\d-\d\dT/);
|
||||
43d41
|
||||
< assert.match(await inspection(), /Last activity1m ago \(\d{4}-/);
|
||||
54,56d51
|
||||
< const pending = await inspection();
|
||||
< assert.match(pending, /Waiting for a reply/, 'the page says a reply is pending');
|
||||
< assert.doesNotMatch(pending, /Last assistant text/, 'the previous answer is not presented as the latest reply');
|
||||
68,74d62
|
||||
< // The notice clears once. Later work with no new send must not bring back the old sentAt (Filbert R1 item 2).
|
||||
< appendFileSync(file, message(at(4), { role: 'user', content: [{ type: 'text', text: 'typed in the seat terminal' }] }));
|
||||
< await b.evaluate('document.querySelector("#refresh").click()');
|
||||
< await wait('[...document.querySelectorAll("#inspection dt")].find(d => d.textContent === "State")?.nextElementSibling.textContent === "working"');
|
||||
< const later = await inspection();
|
||||
< assert.doesNotMatch(later, /Waiting for a reply|before your message/);
|
||||
< assert.match(later, /Last assistant text/);
|
||||
@@ -0,0 +1,16 @@
|
||||
TAP version 13
|
||||
# Subtest: reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh
|
||||
ok 1 - reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh
|
||||
---
|
||||
duration_ms: 20653.73802
|
||||
type: 'test'
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 1
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 20715.39878
|
||||
@@ -0,0 +1,64 @@
|
||||
// Jason's 2026-09-13 report (#1507 comment 26082): sending works, the return
|
||||
// flow does not. This replays his sequence against the real board scanner and
|
||||
// WebUI: send, then the seat appends user, toolCall, toolResult and a new final
|
||||
// answer. The answer must appear in the SAME open inspector through auto-refresh
|
||||
// alone, once, without the previous answer passing as the reply, and without
|
||||
// losing a draft typed meanwhile. Session-file polling only: the live adapters
|
||||
// and streaming belong to CHAT-03, not this test.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, appendFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { startServer as startBoard } from '../../control-board/src/serve.mjs';
|
||||
import { makeRegistration, writeRegistration } from '../../seat/src/seat.mjs';
|
||||
import { startServer } from '../src/serve.mjs';
|
||||
import { browser } from './browser.mjs';
|
||||
import { close } from './fixture.mjs';
|
||||
|
||||
const at = s => new Date(Date.now() + s * 1000).toISOString();
|
||||
const message = (timestamp, m) => JSON.stringify({ type: 'message', timestamp, message: m }) + '\n';
|
||||
const assistant = (timestamp, text) => message(timestamp, { role: 'assistant', stopReason: 'stop', content: [{ type: 'text', text }] });
|
||||
|
||||
test('reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh', { timeout: 90000 }, async () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'webui-return-'));
|
||||
let board, web, b;
|
||||
try {
|
||||
const sessionsDir = join(root, 'sessions'); mkdirSync(sessionsDir);
|
||||
const file = join(sessionsDir, 's.jsonl');
|
||||
writeFileSync(file, message(at(-120), { role: 'user', content: [{ type: 'text', text: 'Start' }] }) + assistant(at(-90), 'Input needed: OLD_ANSWER'));
|
||||
const seatsDir = join(root, 'seats'), captures = [];
|
||||
writeRegistration(seatsDir, makeRegistration({ resolved: { seat: 'fixture', project: 'repo', sessionsDir, seatDir: root, launchScript: join(root, 'unused.sh'), layout: 'repo', defaultWorkspace: root }, task: 'Fixed task', tmux: { session: 'fixture', socket: null }, pid: process.pid, now: () => new Date(Date.now() - 300000) }));
|
||||
board = await startBoard({ port: 0, specs: [{ agent: 'fixture', project: 'repo', sessionsDir, tmux: {} }], boardDir: join(root, 'board'), seatsDir, isAlive: () => true, isPidAlive: () => true,
|
||||
exec: (f, args) => { captures.push(args); return { status: 0, stdout: '', stderr: '' }; } });
|
||||
web = await startServer({ port: 0, board: `http://127.0.0.1:${board.address().port}` });
|
||||
b = await browser(); await b.viewport(1440, 1000);
|
||||
const wait = (expression, ms = 20000) => b.evaluate(`(async()=>{for(const end=Date.now()+${ms};Date.now()<end;){if(${expression})return true;await new Promise(r=>setTimeout(r,100))}throw Error('timeout: '+${JSON.stringify(expression)})})()`);
|
||||
const inspection = () => b.evaluate('document.querySelector("#inspection").textContent');
|
||||
await b.navigate(`http://127.0.0.1:${web.address().port}`); await wait('document.querySelector("table.sessions [data-open]")');
|
||||
// Relative Age (#1507): the board's ageSeconds, labelled as time since last activity, with the timestamp kept.
|
||||
await b.evaluate('document.querySelector("table.sessions [data-open]").click()');
|
||||
assert.match(await inspection(), /OLD_ANSWER/);
|
||||
|
||||
await b.evaluate('(() => { const t = document.querySelector("#reply"); t.value = "REPLY_FROM_JASON"; t.dispatchEvent(new Event("input", { bubbles: true })); document.querySelector(".reply-form").requestSubmit(); })()');
|
||||
await wait('document.querySelector(".receipt")?.textContent.includes("delivered")');
|
||||
assert.ok(captures.some(args => args.some(a => a.startsWith('REPLY_FROM_JASON\n'))), 'reply reached the transport');
|
||||
assert.equal(await b.evaluate('document.querySelector("#reply").value'), '', 'delivered draft cleared');
|
||||
|
||||
// The seat takes the message and starts a tool call. No answer yet.
|
||||
appendFileSync(file, message(at(0), { role: 'user', content: [{ type: 'text', text: 'REPLY_FROM_JASON' }] })
|
||||
+ message(at(1), { role: 'assistant', stopReason: 'toolUse', content: [{ type: 'toolCall', id: 'call-1', name: 'read', arguments: { path: 'x' } }] }));
|
||||
await wait('[...document.querySelectorAll("#inspection dt")].find(d => d.textContent === "State")?.nextElementSibling.textContent === "working"');
|
||||
// Typed while the seat works; auto-refresh must keep it and the caret.
|
||||
await b.evaluate('(() => { const t = document.querySelector("#reply"); t.focus(); t.value = "NEXT_DRAFT"; t.setSelectionRange(4, 4); t.dispatchEvent(new Event("input", { bubbles: true })); })()');
|
||||
|
||||
appendFileSync(file, message(at(2), { role: 'toolResult', toolCallId: 'call-1', toolName: 'read', content: [{ type: 'text', text: 'file body' }], isError: false })
|
||||
+ assistant(at(3), 'Input needed: NEW_ANSWER'));
|
||||
await wait('document.querySelector("#inspection").textContent.includes("NEW_ANSWER")');
|
||||
const answered = await inspection();
|
||||
assert.equal(answered.split('NEW_ANSWER').length, 2, 'answer shown once in the inspector');
|
||||
assert.doesNotMatch(answered, /OLD_ANSWER|Waiting for a reply/);
|
||||
assert.equal(await b.evaluate('document.querySelector("#inspector-title").textContent'), 'fixture', 'same conversation still open');
|
||||
assert.deepEqual(await b.evaluate('(() => { const t = document.querySelector("#reply"); return [t.value, t.selectionStart, document.activeElement === t]; })()'), ['NEXT_DRAFT', 4, true]);
|
||||
} finally { if (b) await b.close(); if (web) await close(web); if (board) await close(board); rmSync(root, { recursive: true, force: true }); }
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
/[dragon-lin:control-board -> dragon-lin:researcher] ping
|
||||
(control-board: answer in your own session as usual; the board reads your transcript. Do not agent-send to control-board.)
|
||||
|
||||
|
||||
Thinking...
|
||||
|
||||
pong
|
||||
@@ -0,0 +1,16 @@
|
||||
// Board-originated sends in pi session files: timestamps/roles only, no content beyond 80 chars of the send.
|
||||
import { readFileSync, readdirSync, statSync } from 'node:fs'; import { join } from 'node:path';
|
||||
const roots = process.argv.slice(2);
|
||||
const walk = d => readdirSync(d).flatMap(n => { const p = join(d, n); const s = statSync(p); return s.isDirectory() ? walk(p) : n.endsWith('.jsonl') ? [p] : []; });
|
||||
for (const root of roots) for (const f of walk(root)) {
|
||||
const E = readFileSync(f, 'utf8').split('\n').filter(Boolean).map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
||||
E.forEach((e, i) => {
|
||||
if (e.type !== 'message' || e.message.role !== 'user') return;
|
||||
const c = e.message.content; const t = typeof c === 'string' ? c : (c || []).filter(x => x.type === 'text').map(x => x.text).join('\n');
|
||||
if (!/control-board ->/.test(t)) return;
|
||||
const after = E.slice(i + 1).filter(x => x.type === 'message');
|
||||
const firstA = after.find(x => x.message.role === 'assistant' && x.message.stopReason === 'stop');
|
||||
const nextUser = after.find(x => x.message.role === 'user');
|
||||
console.log(JSON.stringify({ file: f.replace(process.env.HOME, '~'), sent: e.timestamp, head: t.slice(0, 80).replace(/\n/g, ' '), firstStopAnswer: firstA?.timestamp ?? null, answerChars: firstA ? firstA.message.content.filter(x => x.type === 'text').map(x => x.text).join('\n').length : null, nextUser: nextUser?.timestamp ?? null }));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user