@rubytech/create-maxy-code 0.1.58 → 0.1.59
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/package.json +1 -1
- package/payload/platform/plugins/admin/hooks/__tests__/turn-completed-graph-write.test.sh +30 -19
- package/payload/platform/plugins/admin/hooks/turn-completed-graph-write.sh +35 -90
- package/payload/platform/plugins/docs/references/admin-session.md +17 -16
- package/payload/platform/plugins/docs/references/platform.md +3 -3
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
- package/payload/platform/plugins/preval/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/preval/PLUGIN.md +39 -0
- package/payload/platform/plugins/preval/skills/property-preval/SKILL.md +78 -0
- package/payload/platform/plugins/preval/skills/property-preval/references/render.py +265 -0
- package/payload/platform/plugins/preval/skills/property-preval/references/template-inputs.schema.json +143 -0
- package/payload/platform/plugins/preval/skills/property-preval/references/template.html +253 -0
- package/payload/platform/plugins/property-data/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/property-data/PLUGIN.md +46 -1
- package/payload/platform/plugins/property-data/mcp/dist/index.js +81 -1
- package/payload/platform/plugins/property-data/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/property-data/mcp/dist/tools/endpoints.d.ts +10 -0
- package/payload/platform/plugins/property-data/mcp/dist/tools/endpoints.d.ts.map +1 -1
- package/payload/platform/plugins/property-data/mcp/dist/tools/endpoints.js +13 -0
- package/payload/platform/plugins/property-data/mcp/dist/tools/endpoints.js.map +1 -1
- package/payload/platform/plugins/property-data/mcp/src/__tests__/endpoints.test.ts +252 -0
- package/payload/platform/plugins/property-data/mcp/src/__tests__/schema-parity.test.ts +16 -2
- package/payload/platform/plugins/property-data/mcp/src/index.ts +155 -0
- package/payload/platform/plugins/property-data/mcp/src/tools/endpoints.ts +20 -0
- package/payload/platform/plugins/property-data/skills/property-data/SKILL.md +19 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +25 -260
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.d.ts +3 -7
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.js +6 -32
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +6 -24
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +18 -91
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/reaper.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/reaper.js +0 -11
- package/payload/platform/services/claude-session-manager/dist/reaper.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-store.d.ts +0 -21
- package/payload/platform/services/claude-session-manager/dist/session-store.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-store.js.map +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
- package/payload/platform/templates/agents/admin/SOUL.md +1 -1
- package/payload/premium-plugins/real-agent/BUNDLE.md +5 -3
- package/payload/premium-plugins/real-agent/agents/negotiator.md +1 -1
- package/payload/premium-plugins/real-agent/agents/valuer.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/premium-plugins/real-agent/plugins/preval/.claude-plugin/plugin.json +8 -0
- package/payload/premium-plugins/real-agent/plugins/preval/PLUGIN.md +39 -0
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/SKILL.md +78 -0
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/render.py +265 -0
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/template-inputs.schema.json +143 -0
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/template.html +253 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/.claude-plugin/plugin.json +1 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/PLUGIN.md +46 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/index.js +81 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/index.js.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/endpoints.d.ts +10 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/endpoints.d.ts.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/endpoints.js +13 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/endpoints.js.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/__tests__/endpoints.test.ts +252 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/__tests__/schema-parity.test.ts +16 -2
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/index.ts +155 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/tools/endpoints.ts +20 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/skills/property-data/SKILL.md +19 -0
- package/payload/server/public/assets/{admin-2ZJmzJ_g.js → admin-nqCBFiSl.js} +1 -1
- package/payload/server/public/assets/{data-DlmoKxmN.js → data-BGUAGVkV.js} +1 -1
- package/payload/server/public/assets/{graph-BVvcFSEQ.js → graph-MvYxZOBF.js} +1 -1
- package/payload/server/public/assets/{graph-labels-cCvgu38Y.js → graph-labels-D865qb3K.js} +1 -1
- package/payload/server/public/assets/{page-BnAxPkmg.js → page--hOVRrgN.js} +1 -1
- package/payload/server/public/assets/{page-BnaN3u86.js → page-C2b1nlOc.js} +1 -1
- package/payload/server/public/data.html +3 -3
- package/payload/server/public/graph.html +3 -3
- package/payload/server/public/index.html +4 -4
- package/payload/server/server.js +19 -85
|
@@ -8,14 +8,13 @@
|
|
|
8
8
|
// POST /resume { senderId, role, channel, sessionId } -> Session
|
|
9
9
|
// POST /:id/stop -> 204 (kill PTY; idempotent on already-dead row)
|
|
10
10
|
// 404 if no row matches any of the 4 keys
|
|
11
|
-
// DELETE /:id -> 204 (rmSync JSONL +
|
|
11
|
+
// DELETE /:id -> 204 (rmSync JSONL + subdir)
|
|
12
12
|
// 409 if PTY still alive
|
|
13
13
|
// 404 if no transcript on disk
|
|
14
14
|
// POST /:id/input { text }
|
|
15
15
|
// GET /:id/log -> { jsonlPath, sizeBytes, exists }
|
|
16
16
|
// GET /:id/log?follow=1 -> streamed file content (current + new)
|
|
17
17
|
// GET /:id/meta -> SessionMeta payload
|
|
18
|
-
// PATCH /:id { displayName } -> 204 (writes sidecar)
|
|
19
18
|
// POST /:id/archive { mode }
|
|
20
19
|
//
|
|
21
20
|
// Any of the four ids — intrinsic sessionId, bridgeSessionId, bridgeSuffix,
|
|
@@ -24,10 +23,9 @@
|
|
|
24
23
|
// manager does not know.
|
|
25
24
|
import { Hono } from 'hono';
|
|
26
25
|
import { stream } from 'hono/streaming';
|
|
27
|
-
import { existsSync, statSync, createReadStream, watchFile, unwatchFile, rmSync,
|
|
28
|
-
import { createHash } from 'node:crypto';
|
|
26
|
+
import { existsSync, statSync, createReadStream, watchFile, unwatchFile, rmSync, mkdirSync, renameSync, readFileSync } from 'node:fs';
|
|
29
27
|
import { spawnClaudeSession, stopSession, PERMISSION_MODES } from './pty-spawner.js';
|
|
30
|
-
import { readJsonlSession
|
|
28
|
+
import { readJsonlSession } from './jsonl-enumerator.js';
|
|
31
29
|
import { claudeStateRoot, projectSlugForCwd } from './jsonl-path.js';
|
|
32
30
|
import { basename, join } from 'node:path';
|
|
33
31
|
const ROLES = ['admin', 'public'];
|
|
@@ -116,7 +114,6 @@ function parseSpecialistDomains(raw) {
|
|
|
116
114
|
// bridgeSuffix, or pid (numeric). All resolve to a sessionId via watcher.
|
|
117
115
|
const SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
118
116
|
const LIST_TAIL_BYTES = 64 * 1024;
|
|
119
|
-
const DISPLAY_NAME_MAX = 256;
|
|
120
117
|
function timed(logger, method, path, status, ms) {
|
|
121
118
|
logger(`api method=${method} path=${path} outcome=${status} ms=${ms}`);
|
|
122
119
|
}
|
|
@@ -140,98 +137,18 @@ function resolveRow(watcher, id) {
|
|
|
140
137
|
}
|
|
141
138
|
return undefined;
|
|
142
139
|
}
|
|
143
|
-
//
|
|
144
|
-
// The
|
|
145
|
-
// is
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// against itself and lets the normal archive path run. Recorder
|
|
151
|
-
// detection keys on `originatingSessionId` presence, the one field
|
|
152
|
-
// that is set only for recorder spawns.
|
|
140
|
+
// Task 147 — auto-archive for the database-operator specialist on
|
|
141
|
+
// `end_turn`. The recorder is a Sidebar-equivalent admin spawn whose only
|
|
142
|
+
// override is the specialist value. Once its JSONL contains an assistant
|
|
143
|
+
// message with `stop_reason === "end_turn"`, stop the PTY and let the
|
|
144
|
+
// normal archive path run. No tool-call tally, no recorder-only log
|
|
145
|
+
// surfaces — the Sidebar's `pty-spawned … jsonl … end_turn …
|
|
146
|
+
// auto-archive` lines cover the lifecycle.
|
|
153
147
|
const END_TURN_SIGNATURE = '"stop_reason":"end_turn"';
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
let serialized;
|
|
158
|
-
try {
|
|
159
|
-
serialized = JSON.stringify(toolArgs ?? null);
|
|
160
|
-
}
|
|
161
|
-
catch {
|
|
162
|
-
serialized = '<unserializable>';
|
|
163
|
-
}
|
|
164
|
-
return createHash('sha256').update(serialized).digest('hex').slice(0, 8);
|
|
165
|
-
}
|
|
166
|
-
/** Sanitize a record's error string for the log line. Strip newlines,
|
|
167
|
-
* cap at 200 chars. */
|
|
168
|
-
function sanitizeErr(value) {
|
|
169
|
-
if (typeof value !== 'string')
|
|
170
|
-
return String(value ?? 'unknown');
|
|
171
|
-
return value.replace(/[\r\n\t]+/g, ' ').slice(0, 200);
|
|
172
|
-
}
|
|
173
|
-
/** Per-recorder JSONL observer (Task 129). Parses newly-appended bytes
|
|
174
|
-
* on each modify event and emits one `[turn-recorder] tool-call …` line
|
|
175
|
-
* per JSONL record carrying a `tool_use` content block, and one
|
|
176
|
-
* `[turn-recorder] tool-result …` line per record carrying a
|
|
177
|
-
* `tool_result` content block. Maintains the session's `recorderTally`
|
|
178
|
-
* as side-effect so `attachRecorderAutoArchive` can read final counts
|
|
179
|
-
* at `write-complete` time.
|
|
180
|
-
*
|
|
181
|
-
* Position tracking lives on the StoredSession (`recorderJsonlPosition`)
|
|
182
|
-
* so re-entry on a modify storm cannot double-emit the same record. */
|
|
183
|
-
function readNewRecords(jsonlPath, fromPos) {
|
|
184
|
-
if (!existsSync(jsonlPath))
|
|
185
|
-
return { records: [], newPos: fromPos };
|
|
186
|
-
const size = statSync(jsonlPath).size;
|
|
187
|
-
if (size <= fromPos)
|
|
188
|
-
return { records: [], newPos: fromPos };
|
|
189
|
-
let buf;
|
|
190
|
-
try {
|
|
191
|
-
const fd = openSync(jsonlPath, 'r');
|
|
192
|
-
try {
|
|
193
|
-
buf = Buffer.alloc(size - fromPos);
|
|
194
|
-
readSync(fd, buf, 0, buf.length, fromPos);
|
|
195
|
-
}
|
|
196
|
-
finally {
|
|
197
|
-
closeSync(fd);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
catch {
|
|
201
|
-
return { records: [], newPos: fromPos };
|
|
202
|
-
}
|
|
203
|
-
const text = buf.toString('utf8');
|
|
204
|
-
// If the last line lacks a newline it is a partial append; rewind so
|
|
205
|
-
// we re-read it on the next modify.
|
|
206
|
-
const lastNl = text.lastIndexOf('\n');
|
|
207
|
-
const completeText = lastNl === -1 ? '' : text.slice(0, lastNl + 1);
|
|
208
|
-
const advance = Buffer.byteLength(completeText, 'utf8');
|
|
209
|
-
const lines = completeText.split('\n').filter((l) => l.length > 0);
|
|
210
|
-
const records = [];
|
|
211
|
-
for (const line of lines) {
|
|
212
|
-
try {
|
|
213
|
-
records.push(JSON.parse(line));
|
|
214
|
-
}
|
|
215
|
-
catch {
|
|
216
|
-
// skip malformed jsonl line — recorder may truncate
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return { records, newPos: fromPos + advance };
|
|
220
|
-
}
|
|
221
|
-
function attachRecorderAutoArchive(deps, session) {
|
|
222
|
-
// Task 131 — recorder discriminator switched from senderId to
|
|
223
|
-
// originatingSessionId. The recorder's senderId is now the operator's
|
|
224
|
-
// accountId (Sidebar-shape parity); originatingSessionId is the only
|
|
225
|
-
// structural marker that this session is a recorder.
|
|
226
|
-
if (!session.originatingSessionId)
|
|
227
|
-
return;
|
|
228
|
-
if (!session.recorderTally) {
|
|
229
|
-
deps.logger(`[turn-recorder] auto-archive-attach-skipped recorderSessionId=${session.sessionId} reason=missing-recorder-tally`);
|
|
148
|
+
const AUTO_ARCHIVE_SPECIALIST = 'database-operator';
|
|
149
|
+
function attachSpecialistEndTurnAutoArchive(deps, session) {
|
|
150
|
+
if (session.specialist !== AUTO_ARCHIVE_SPECIALIST)
|
|
230
151
|
return;
|
|
231
|
-
}
|
|
232
|
-
const op = session.originatingSessionId;
|
|
233
|
-
const rec = session.sessionId;
|
|
234
|
-
const tally = session.recorderTally;
|
|
235
152
|
let archived = false;
|
|
236
153
|
function endTurnSeen(jsonlPath) {
|
|
237
154
|
try {
|
|
@@ -242,72 +159,7 @@ function attachRecorderAutoArchive(deps, session) {
|
|
|
242
159
|
return false;
|
|
243
160
|
}
|
|
244
161
|
}
|
|
245
|
-
// Drain newly-appended JSONL records, emit one line per tool_use /
|
|
246
|
-
// tool_result content block, and update the running tally. The record
|
|
247
|
-
// shape follows Claude Code's transcript: top-level `type`, a `message`
|
|
248
|
-
// object with `content[]`, each block carrying `type: 'tool_use' |
|
|
249
|
-
// 'tool_result'`. Tool results carry `is_error: boolean` on the block;
|
|
250
|
-
// when absent we treat the call as `ok`.
|
|
251
|
-
function drainJsonl(jsonlPath) {
|
|
252
|
-
const startedAtMs = new Map();
|
|
253
|
-
const { records, newPos } = readNewRecords(jsonlPath, session.recorderJsonlPosition ?? 0);
|
|
254
|
-
session.recorderJsonlPosition = newPos;
|
|
255
|
-
for (const rec0 of records) {
|
|
256
|
-
if (!rec0 || typeof rec0 !== 'object')
|
|
257
|
-
continue;
|
|
258
|
-
const top = rec0;
|
|
259
|
-
const ts = typeof top.timestamp === 'string' ? Date.parse(top.timestamp) : Date.now();
|
|
260
|
-
const message = top.message;
|
|
261
|
-
if (!message || typeof message !== 'object')
|
|
262
|
-
continue;
|
|
263
|
-
const content = message.content;
|
|
264
|
-
if (!Array.isArray(content))
|
|
265
|
-
continue;
|
|
266
|
-
for (const block of content) {
|
|
267
|
-
if (!block || typeof block !== 'object')
|
|
268
|
-
continue;
|
|
269
|
-
const b = block;
|
|
270
|
-
if (b.type === 'tool_use') {
|
|
271
|
-
tally.calls += 1;
|
|
272
|
-
const name = typeof b.name === 'string' ? b.name : 'unknown';
|
|
273
|
-
const id = typeof b.id === 'string' ? b.id : '';
|
|
274
|
-
if (id)
|
|
275
|
-
startedAtMs.set(id, ts);
|
|
276
|
-
deps.logger(`[turn-recorder] tool-call sessionId=${op} recorderSessionId=${rec} tool=${name} argHash=${argHash(b.input)}`);
|
|
277
|
-
}
|
|
278
|
-
else if (b.type === 'tool_result') {
|
|
279
|
-
const isError = b.is_error === true;
|
|
280
|
-
if (isError)
|
|
281
|
-
tally.errors += 1;
|
|
282
|
-
else
|
|
283
|
-
tally.oks += 1;
|
|
284
|
-
const useId = typeof b.tool_use_id === 'string' ? b.tool_use_id : '';
|
|
285
|
-
const startMs = useId ? startedAtMs.get(useId) : undefined;
|
|
286
|
-
const elapsedMs = startMs ? Math.max(0, ts - startMs) : 0;
|
|
287
|
-
// Tool name is not on the result block; the call line that
|
|
288
|
-
// preceded it already named the tool — leave `tool=unknown`
|
|
289
|
-
// here rather than guess. Operators correlate via the
|
|
290
|
-
// adjacent argHash + tool_use_id ordering.
|
|
291
|
-
let line = `[turn-recorder] tool-result sessionId=${op} recorderSessionId=${rec} tool=unknown outcome=${isError ? 'error' : 'ok'} ms=${elapsedMs}`;
|
|
292
|
-
if (isError) {
|
|
293
|
-
const errText = typeof b.content === 'string'
|
|
294
|
-
? b.content
|
|
295
|
-
: Array.isArray(b.content)
|
|
296
|
-
? b.content
|
|
297
|
-
.map((c) => c && typeof c === 'object' && 'text' in c
|
|
298
|
-
? String(c.text ?? '')
|
|
299
|
-
: '')
|
|
300
|
-
.join(' ')
|
|
301
|
-
: '';
|
|
302
|
-
line += ` err=${JSON.stringify(sanitizeErr(errText))}`;
|
|
303
|
-
}
|
|
304
|
-
deps.logger(line);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
162
|
function tryArchive(jsonlPath) {
|
|
310
|
-
drainJsonl(jsonlPath);
|
|
311
163
|
if (archived)
|
|
312
164
|
return;
|
|
313
165
|
if (!existsSync(jsonlPath))
|
|
@@ -315,22 +167,7 @@ function attachRecorderAutoArchive(deps, session) {
|
|
|
315
167
|
if (!endTurnSeen(jsonlPath))
|
|
316
168
|
return;
|
|
317
169
|
archived = true;
|
|
318
|
-
|
|
319
|
-
// end-turn path. Read the running tally; if zero successful tool
|
|
320
|
-
// calls also emit `write-empty` (§65). On `tally.oks === 0` the
|
|
321
|
-
// `write-empty` reason is the cheapest derivable one. The richer
|
|
322
|
-
// reasons (`tool-surface-missing`, `no-extractable-entities`) are
|
|
323
|
-
// emitted earlier on dedicated paths; this is the catch-all.
|
|
324
|
-
deps.logger(`[turn-recorder] write-complete sessionId=${op} recorderSessionId=${rec} toolCalls=${tally.calls} toolOks=${tally.oks} toolErrors=${tally.errors}`);
|
|
325
|
-
if (tally.oks === 0) {
|
|
326
|
-
const reason = tally.calls === 0
|
|
327
|
-
? 'no-extractable-entities'
|
|
328
|
-
: tally.errors > 0 && tally.errors === tally.calls
|
|
329
|
-
? 'tool-errors-only'
|
|
330
|
-
: 'no-extractable-entities';
|
|
331
|
-
deps.logger(`[turn-recorder] write-empty sessionId=${op} recorderSessionId=${rec} reason=${reason}`);
|
|
332
|
-
}
|
|
333
|
-
deps.logger(`[turn-recorder] auto-archive sessionId=${op} recorderSessionId=${rec} reason=end-turn archivedAt=${new Date().toISOString()}`);
|
|
170
|
+
deps.logger(`auto-archive sessionId=${session.sessionId} specialist=${AUTO_ARCHIVE_SPECIALIST} reason=end-turn archivedAt=${new Date().toISOString()}`);
|
|
334
171
|
void stopSession({ store: deps.store, killGraceMs: deps.killGraceMs, logger: deps.logger }, session.sessionId);
|
|
335
172
|
}
|
|
336
173
|
if (session.jsonlPath)
|
|
@@ -384,18 +221,15 @@ export function buildHttpApp(deps) {
|
|
|
384
221
|
deps.logger(`[fs-watcher] live-row-jsonl-attached sessionId=${event.sessionId} path=${event.row.jsonlPath}`);
|
|
385
222
|
attachPublicAudit(deps, live);
|
|
386
223
|
});
|
|
387
|
-
function liveDisplayName(s
|
|
388
|
-
if (sidecarName && sidecarName.trim())
|
|
389
|
-
return sidecarName.trim();
|
|
224
|
+
function liveDisplayName(s) {
|
|
390
225
|
if (s.jsonlPath && existsSync(s.jsonlPath)) {
|
|
391
226
|
const enriched = readJsonlSession(deps.spawnCwd, s.sessionId, LIST_TAIL_BYTES, deps.logger);
|
|
392
227
|
if (enriched?.aiTitle?.trim())
|
|
393
228
|
return enriched.aiTitle.trim();
|
|
394
229
|
}
|
|
395
|
-
return
|
|
230
|
+
return null;
|
|
396
231
|
}
|
|
397
232
|
function toLivePayload(s) {
|
|
398
|
-
const sidecar = s.jsonlPath ? readSidecar(s.jsonlPath) : null;
|
|
399
233
|
const hasJsonl = s.jsonlPath !== null && existsSync(s.jsonlPath);
|
|
400
234
|
return {
|
|
401
235
|
sessionId: s.sessionId,
|
|
@@ -407,7 +241,7 @@ export function buildHttpApp(deps) {
|
|
|
407
241
|
channel: s.channel,
|
|
408
242
|
jsonlPath: s.jsonlPath,
|
|
409
243
|
status: 'alive',
|
|
410
|
-
displayName: liveDisplayName(s
|
|
244
|
+
displayName: liveDisplayName(s),
|
|
411
245
|
lastMessageAt: null,
|
|
412
246
|
turnCount: 0,
|
|
413
247
|
capped: false,
|
|
@@ -417,7 +251,7 @@ export function buildHttpApp(deps) {
|
|
|
417
251
|
};
|
|
418
252
|
}
|
|
419
253
|
function toArchivedPayload(row, senderId) {
|
|
420
|
-
let displayName =
|
|
254
|
+
let displayName = null;
|
|
421
255
|
let lastMessageAt = null;
|
|
422
256
|
let turnCount = 0;
|
|
423
257
|
let capped = false;
|
|
@@ -471,8 +305,8 @@ export function buildHttpApp(deps) {
|
|
|
471
305
|
const specialist = typeof body.specialist === 'string' && /^[A-Za-z0-9_-]{1,64}$/.test(body.specialist)
|
|
472
306
|
? body.specialist
|
|
473
307
|
: undefined;
|
|
474
|
-
const
|
|
475
|
-
? body.
|
|
308
|
+
const model = typeof body.model === 'string' && /^[A-Za-z0-9._-]{1,64}$/.test(body.model)
|
|
309
|
+
? body.model
|
|
476
310
|
: undefined;
|
|
477
311
|
const aboutOwnerShape = body.aboutOwner == null ? 'absent' : (typeof body.aboutOwner === 'object' ? 'object' : typeof body.aboutOwner);
|
|
478
312
|
const dormantPluginsShape = Array.isArray(body.dormantPlugins)
|
|
@@ -512,7 +346,7 @@ export function buildHttpApp(deps) {
|
|
|
512
346
|
: body.tunnelUrl === null
|
|
513
347
|
? null
|
|
514
348
|
: undefined;
|
|
515
|
-
const result = await spawnClaudeSession({ ...deps, store: deps.store, watcher: deps.watcher, onSessionReady: (s) => { attachPublicAudit(deps, s);
|
|
349
|
+
const result = await spawnClaudeSession({ ...deps, store: deps.store, watcher: deps.watcher, onSessionReady: (s) => { attachPublicAudit(deps, s); attachSpecialistEndTurnAutoArchive(deps, s); }, tunnelUrlOverride }, {
|
|
516
350
|
senderId,
|
|
517
351
|
role: role,
|
|
518
352
|
channel: channel,
|
|
@@ -525,7 +359,7 @@ export function buildHttpApp(deps) {
|
|
|
525
359
|
specialistDomains,
|
|
526
360
|
hidden,
|
|
527
361
|
specialist,
|
|
528
|
-
|
|
362
|
+
model,
|
|
529
363
|
});
|
|
530
364
|
if (!result.ok) {
|
|
531
365
|
if ('rejected' in result) {
|
|
@@ -535,18 +369,6 @@ export function buildHttpApp(deps) {
|
|
|
535
369
|
}
|
|
536
370
|
return c.json({ error: 'operator-slots-reserved', total: result.rejected.total, reserve: result.rejected.reserve, totalCap: result.rejected.totalCap }, 503);
|
|
537
371
|
}
|
|
538
|
-
// Task 129 — recorder preflight failures return distinct HTTP codes
|
|
539
|
-
// so the loopback caller can route the error to the correct named
|
|
540
|
-
// log line. The spawn-failure log was already emitted inside the
|
|
541
|
-
// preflight before returning.
|
|
542
|
-
if (result.reason === 'missing-originating-session-id') {
|
|
543
|
-
timed(deps.logger, 'POST', '/spawn', 400, Date.now() - start);
|
|
544
|
-
return c.json({ error: 'missing-originating-session-id' }, 400);
|
|
545
|
-
}
|
|
546
|
-
if (result.reason === 'tool-surface-missing') {
|
|
547
|
-
timed(deps.logger, 'POST', '/spawn', 503, Date.now() - start);
|
|
548
|
-
return c.json({ error: 'tool-surface-missing', detail: result.stderrTail }, 503);
|
|
549
|
-
}
|
|
550
372
|
timed(deps.logger, 'POST', '/spawn', 500, Date.now() - start);
|
|
551
373
|
return c.json({ error: 'spawn-failed', reason: result.reason, stderrTail: result.stderrTail }, 500);
|
|
552
374
|
}
|
|
@@ -568,8 +390,8 @@ export function buildHttpApp(deps) {
|
|
|
568
390
|
// watcher's `idle | busy | gone`. `agentLabel` is the resolution
|
|
569
391
|
// `agent ?? cachedAgentSetting`; the UI maps null → "admin",
|
|
570
392
|
// non-null → the specialist name. `firstPrompt` is the resolved
|
|
571
|
-
// displayName (
|
|
572
|
-
// computed once on the server, not by every browser tab.
|
|
393
|
+
// displayName (tail.aiTitle verbatim, else null — Task 146) so the row
|
|
394
|
+
// title is computed once on the server, not by every browser tab.
|
|
573
395
|
app.get('/events', (c) => {
|
|
574
396
|
const start = Date.now();
|
|
575
397
|
const clientIp = c.req.header('x-forwarded-for') ?? 'loopback';
|
|
@@ -583,10 +405,6 @@ export function buildHttpApp(deps) {
|
|
|
583
405
|
if (enriched)
|
|
584
406
|
firstPrompt = enriched.displayName;
|
|
585
407
|
}
|
|
586
|
-
if (!firstPrompt) {
|
|
587
|
-
const sidecar = row.jsonlPath ? readSidecar(row.jsonlPath) : null;
|
|
588
|
-
firstPrompt = sidecar?.displayName ?? row.sessionId.slice(0, 8);
|
|
589
|
-
}
|
|
590
408
|
return {
|
|
591
409
|
sessionId: row.sessionId,
|
|
592
410
|
live: row.state === 'live',
|
|
@@ -757,7 +575,7 @@ export function buildHttpApp(deps) {
|
|
|
757
575
|
: body.tunnelUrl === null
|
|
758
576
|
? null
|
|
759
577
|
: undefined;
|
|
760
|
-
const result = await spawnClaudeSession({ ...deps, store: deps.store, watcher: deps.watcher, onSessionReady: (s) => { attachPublicAudit(deps, s);
|
|
578
|
+
const result = await spawnClaudeSession({ ...deps, store: deps.store, watcher: deps.watcher, onSessionReady: (s) => { attachPublicAudit(deps, s); attachSpecialistEndTurnAutoArchive(deps, s); }, tunnelUrlOverride }, {
|
|
761
579
|
senderId,
|
|
762
580
|
role: role,
|
|
763
581
|
channel: channel,
|
|
@@ -822,51 +640,6 @@ export function buildHttpApp(deps) {
|
|
|
822
640
|
timed(deps.logger, 'GET', `/${id}/meta`, 200, Date.now() - start);
|
|
823
641
|
return c.json(payload, 200);
|
|
824
642
|
});
|
|
825
|
-
app.patch('/:sessionId', async (c) => {
|
|
826
|
-
const start = Date.now();
|
|
827
|
-
const id = c.req.param('sessionId');
|
|
828
|
-
if (!SESSION_ID_PATTERN.test(id)) {
|
|
829
|
-
timed(deps.logger, 'PATCH', `/${id}`, 400, Date.now() - start);
|
|
830
|
-
return c.json({ error: 'invalid-session-id' }, 400);
|
|
831
|
-
}
|
|
832
|
-
let body;
|
|
833
|
-
try {
|
|
834
|
-
body = await c.req.json();
|
|
835
|
-
}
|
|
836
|
-
catch {
|
|
837
|
-
timed(deps.logger, 'PATCH', `/${id}`, 400, Date.now() - start);
|
|
838
|
-
return c.json({ error: 'body-not-json' }, 400);
|
|
839
|
-
}
|
|
840
|
-
if (typeof body.displayName !== 'string' || body.displayName.length === 0 || body.displayName.length > DISPLAY_NAME_MAX) {
|
|
841
|
-
timed(deps.logger, 'PATCH', `/${id}`, 400, Date.now() - start);
|
|
842
|
-
return c.json({ error: 'invalid-displayName', detail: { maxLen: DISPLAY_NAME_MAX } }, 400);
|
|
843
|
-
}
|
|
844
|
-
const row = resolveRow(deps.watcher, id);
|
|
845
|
-
if (!row || !row.jsonlPath) {
|
|
846
|
-
timed(deps.logger, 'PATCH', `/${id}`, 404, Date.now() - start);
|
|
847
|
-
return c.json({ error: 'session-not-found' }, 404);
|
|
848
|
-
}
|
|
849
|
-
const sidecarPath = sidecarPathFor(row.jsonlPath);
|
|
850
|
-
const prior = readSidecar(row.jsonlPath)?.displayName ?? null;
|
|
851
|
-
try {
|
|
852
|
-
writeFileSync(sidecarPath, JSON.stringify({ displayName: body.displayName }, null, 2) + '\n', 'utf8');
|
|
853
|
-
}
|
|
854
|
-
catch (err) {
|
|
855
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
856
|
-
deps.logger(`rename-failed sessionId=${row.sessionId.slice(0, 8)} message=${msg}`);
|
|
857
|
-
timed(deps.logger, 'PATCH', `/${id}`, 500, Date.now() - start);
|
|
858
|
-
return c.json({ error: 'sidecar-write-failed', detail: msg }, 500);
|
|
859
|
-
}
|
|
860
|
-
deps.logger(`rename sessionId=${row.sessionId.slice(0, 8)} from=${prior ?? ''} to=${body.displayName}`);
|
|
861
|
-
if (prior === null) {
|
|
862
|
-
// First write — sidebar's auto-stamp from `/spawn`. Distinct log so
|
|
863
|
-
// an operator can grep this to confirm the brand-stamped label path
|
|
864
|
-
// ran, separately from manual renames via the modal. Task 120.
|
|
865
|
-
deps.logger(`sidecar-displayname-stamped sessionId=${row.sessionId.slice(0, 8)} displayName=${body.displayName}`);
|
|
866
|
-
}
|
|
867
|
-
timed(deps.logger, 'PATCH', `/${id}`, 200, Date.now() - start);
|
|
868
|
-
return c.json({ ok: true, displayName: body.displayName }, 200);
|
|
869
|
-
});
|
|
870
643
|
app.post('/:sessionId/archive', async (c) => {
|
|
871
644
|
const start = Date.now();
|
|
872
645
|
const id = c.req.param('sessionId');
|
|
@@ -901,11 +674,8 @@ export function buildHttpApp(deps) {
|
|
|
901
674
|
const slugDir = join(claudeStateRoot(), 'projects', slug);
|
|
902
675
|
const archiveDir = join(slugDir, 'archive');
|
|
903
676
|
const fileName = `${row.sessionId}.jsonl`;
|
|
904
|
-
const sidecarName = `${row.sessionId}.meta.json`;
|
|
905
677
|
const fromJsonl = mode === 'archive' ? join(slugDir, fileName) : join(archiveDir, fileName);
|
|
906
678
|
const toJsonl = mode === 'archive' ? join(archiveDir, fileName) : join(slugDir, fileName);
|
|
907
|
-
const fromSidecar = mode === 'archive' ? join(slugDir, sidecarName) : join(archiveDir, sidecarName);
|
|
908
|
-
const toSidecar = mode === 'archive' ? join(archiveDir, sidecarName) : join(slugDir, sidecarName);
|
|
909
679
|
if (!existsSync(fromJsonl)) {
|
|
910
680
|
timed(deps.logger, 'POST', `/${id}/archive`, 404, Date.now() - start);
|
|
911
681
|
return c.json({ error: 'session-not-found' }, 404);
|
|
@@ -914,8 +684,6 @@ export function buildHttpApp(deps) {
|
|
|
914
684
|
if (mode === 'archive')
|
|
915
685
|
mkdirSync(archiveDir, { recursive: true });
|
|
916
686
|
renameSync(fromJsonl, toJsonl);
|
|
917
|
-
if (existsSync(fromSidecar))
|
|
918
|
-
renameSync(fromSidecar, toSidecar);
|
|
919
687
|
}
|
|
920
688
|
catch (err) {
|
|
921
689
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -964,7 +732,6 @@ export function buildHttpApp(deps) {
|
|
|
964
732
|
timed(deps.logger, 'DELETE', `/${id}`, 404, Date.now() - start);
|
|
965
733
|
return c.json({ error: 'session-not-found' }, 404);
|
|
966
734
|
}
|
|
967
|
-
const sidecarPath = sidecarPathFor(row.jsonlPath);
|
|
968
735
|
const subdir = join(claudeStateRoot(), 'projects', projectSlugForCwd(deps.spawnCwd), sessionId);
|
|
969
736
|
let bytes = 0;
|
|
970
737
|
try {
|
|
@@ -976,8 +743,6 @@ export function buildHttpApp(deps) {
|
|
|
976
743
|
try {
|
|
977
744
|
if (existsSync(row.jsonlPath))
|
|
978
745
|
rmSync(row.jsonlPath);
|
|
979
|
-
if (existsSync(sidecarPath))
|
|
980
|
-
rmSync(sidecarPath);
|
|
981
746
|
if (existsSync(subdir))
|
|
982
747
|
rmSync(subdir, { recursive: true, force: true });
|
|
983
748
|
}
|