@rubytech/create-maxy-code 0.1.314 → 0.1.316
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/__tests__/plugin-install.test.js +23 -1
- package/dist/index.js +47 -2
- package/dist/lib/plugin-install.js +16 -0
- package/package.json +1 -1
- package/payload/platform/lib/graph-search/dist/index.d.ts +18 -31
- package/payload/platform/lib/graph-search/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-search/dist/index.js +20 -36
- package/payload/platform/lib/graph-search/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-search/src/__tests__/fulltext-coverage.test.ts +55 -64
- package/payload/platform/lib/graph-search/src/index.ts +19 -37
- package/payload/platform/neo4j/schema.cypher +15 -41
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +12 -13
- package/payload/platform/plugins/docs/references/admin-session.md +1 -1
- package/payload/platform/plugins/docs/references/admin-ui.md +3 -3
- package/payload/platform/plugins/docs/references/internals.md +9 -10
- package/payload/platform/plugins/memory/mcp/dist/index.js +12 -15
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/references/schema-estate-agent.md +32 -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 +14 -6
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +1 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +11 -11
- 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 +96 -47
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +5 -6
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +6 -6
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/permission.d.ts +46 -0
- package/payload/platform/services/webchat-channel/dist/permission.d.ts.map +1 -0
- package/payload/platform/services/webchat-channel/dist/permission.js +56 -0
- package/payload/platform/services/webchat-channel/dist/permission.js.map +1 -0
- package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/server.js +47 -1
- package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
- package/payload/platform/templates/specialists/agents/data-manager.md +3 -1
- package/payload/platform/templates/specialists/agents/public-session-reviewer.md +2 -2
- package/payload/server/public/assets/{AccessGate-B_sL5-0I.js → AccessGate-bu988Xo-.js} +1 -1
- package/payload/server/public/assets/{AdminLoginScreens-BgicJfUu.js → AdminLoginScreens-BCYaOOuk.js} +1 -1
- package/payload/server/public/assets/AdminShell-DSjJ4Biu.js +1 -0
- package/payload/server/public/assets/{Checkbox-BJdwS4v2.js → Checkbox-B4Dbvjls.js} +1 -1
- package/payload/server/public/assets/{OperatorConversations-DPlZIELy.js → OperatorConversations-XsXP7-P-.js} +1 -1
- package/payload/server/public/assets/{admin-DAFDivnO.js → admin-m0797WGA.js} +1 -1
- package/payload/server/public/assets/{audio-attachment-mime-h53tpH_4.js → audio-attachment-mime-Jm4KJ4YC.js} +3 -3
- package/payload/server/public/assets/{brand-DqQPSWa7.js → brand-D8LClgr_.js} +1 -1
- package/payload/server/public/assets/{brand-bye0l_rp.css → brand-DvttVeGB.css} +1 -1
- package/payload/server/public/assets/{browser-CmFjVZYo.js → browser-CBjNMPsz.js} +1 -1
- package/payload/server/public/assets/chat-Sg-RGuV6.js +1 -0
- package/payload/server/public/assets/{data-C4v6AdQ-.js → data-CEqE4BDw.js} +1 -1
- package/payload/server/public/assets/{graph-BJyMQdRx.js → graph-BZNc3EXa.js} +3 -3
- package/payload/server/public/assets/{graph-labels-BTpv_U3g.js → graph-labels-Bj5yM9bI.js} +1 -1
- package/payload/server/public/assets/operator-Bph7mx87.js +1 -0
- package/payload/server/public/assets/page-CtWCeXdg.js +1 -0
- package/payload/server/public/assets/{public-IRKxc9hu.js → public-gxHCJCs3.js} +3 -3
- package/payload/server/public/assets/public-next-C3clISGQ.js +1 -0
- package/payload/server/public/assets/{useSelectionMode-CTJhWnNp.js → useSelectionMode-BMn70cch.js} +1 -1
- package/payload/server/public/browser.html +6 -6
- package/payload/server/public/chat.html +8 -8
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +8 -8
- package/payload/server/public/index.html +8 -8
- package/payload/server/public/operator.html +10 -10
- package/payload/server/public/public-next.html +9 -9
- package/payload/server/public/public.html +7 -7
- package/payload/server/server.js +101 -15
- package/payload/server/public/assets/AdminShell-C0gZ5JKc.js +0 -1
- package/payload/server/public/assets/chat-CSw2u48Q.js +0 -1
- package/payload/server/public/assets/operator-Bj88rdnI.js +0 -1
- package/payload/server/public/assets/page-hmlVCBRQ.js +0 -1
- package/payload/server/public/assets/public-next-DqQ4PZLk.js +0 -1
|
@@ -11,15 +11,13 @@
|
|
|
11
11
|
* The fulltext index `knowledge_fulltext` was renamed to `entity_search`
|
|
12
12
|
* and its label union expanded from 3 labels (KnowledgeDocument | Section |
|
|
13
13
|
* Chunk) to the full operator-meaningful label set (~40 labels) on every
|
|
14
|
-
* textual property the schema's writers assign. Task 416
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* `__tests__/fulltext-coverage.test.ts` parses both declarations and
|
|
22
|
-
* asserts the universal label union plus the compiledTruth field split.
|
|
14
|
+
* textual property the schema's writers assign. Task 416 split that single
|
|
15
|
+
* `entity_search` into an admin index plus a public twin; the public twin
|
|
16
|
+
* and its whole read path were retired once public agents became toolless,
|
|
17
|
+
* so the lib now targets the single `entity_search_admin` index via
|
|
18
|
+
* FULLTEXT_INDEX_ADMIN below. The doctrine test at
|
|
19
|
+
* `__tests__/fulltext-coverage.test.ts` parses that declaration and asserts
|
|
20
|
+
* the universal label union.
|
|
23
21
|
*
|
|
24
22
|
* QUERY --> EMBED --> VECTOR SEARCH (per index) --> ┐
|
|
25
23
|
* │ ├--> MERGE --> EXPAND --> RESULTS
|
|
@@ -83,36 +81,20 @@ function readFlags() {
|
|
|
83
81
|
export type { DedupLayer, RetrievalClass };
|
|
84
82
|
export { DEFAULT_LAYERS };
|
|
85
83
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* BM25 silently.
|
|
91
|
-
*
|
|
92
|
-
* `entity_search_admin` carries `n.compiledTruth`; `entity_search_public`
|
|
93
|
-
* carries `n.compiledTruthPublic`. Other indexed text fields are shared.
|
|
84
|
+
* Name of the fulltext index (Task 416 admin index; the public twin was
|
|
85
|
+
* retired with the public read path). Mirrors the `CREATE FULLTEXT INDEX`
|
|
86
|
+
* declaration in `platform/neo4j/schema.cypher`. The doctrine test asserts
|
|
87
|
+
* the name matches this constant — drift between schema and this constant
|
|
88
|
+
* breaks BM25 silently. `entity_search_admin` carries `n.compiledTruth`.
|
|
94
89
|
*/
|
|
95
90
|
export const FULLTEXT_INDEX_ADMIN = "entity_search_admin";
|
|
96
|
-
export const FULLTEXT_INDEX_PUBLIC = "entity_search_public";
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Pick the BM25 index for a read. Returns the public index iff the caller
|
|
100
|
-
* is in the verified public-only scope (`allowedScopes` is exactly
|
|
101
|
-
* `['public']` — the public-agent spawn shape). Anything else — including
|
|
102
|
-
* `undefined` (admin route default), `[]`, `['shared']`, `['public','shared']`
|
|
103
|
-
* — routes to the admin index, so ranking only reflects `compiledTruthPublic`
|
|
104
|
-
* for callers whose Cypher scope filter already restricts them to public
|
|
105
|
-
* rows. Mirrors `isPublicOnlyScope` semantics in memory-search.ts; that
|
|
106
|
-
* file imports this predicate via `isPublicOnlyScope` below.
|
|
107
|
-
*/
|
|
108
|
-
export function pickFulltextIndex(
|
|
109
|
-
allowedScopes: readonly string[] | undefined,
|
|
110
|
-
): string {
|
|
111
|
-
return isPublicOnlyScope(allowedScopes) ? FULLTEXT_INDEX_PUBLIC : FULLTEXT_INDEX_ADMIN;
|
|
112
|
-
}
|
|
113
91
|
|
|
114
|
-
/** Detect the public-agent read shape
|
|
115
|
-
*
|
|
92
|
+
/** Detect the public-agent read shape (`allowedScopes` exactly `['public']`).
|
|
93
|
+
* The BM25 index picker that used this predicate was retired with the public
|
|
94
|
+
* read path. Its one remaining consumer — the `projectPublicProperties` swap
|
|
95
|
+
* in the memory MCP's memory-search.ts — is now unreachable: every live read
|
|
96
|
+
* passes `allowedScopes: undefined`, so this returns false on every call. Kept
|
|
97
|
+
* as that projection's gate for when a public read surface is restored. */
|
|
116
98
|
export function isPublicOnlyScope(
|
|
117
99
|
allowedScopes: readonly string[] | undefined,
|
|
118
100
|
): boolean {
|
|
@@ -481,7 +463,7 @@ export async function bm25Only(
|
|
|
481
463
|
ORDER BY score DESC
|
|
482
464
|
LIMIT $limit`,
|
|
483
465
|
{
|
|
484
|
-
indexName:
|
|
466
|
+
indexName: FULLTEXT_INDEX_ADMIN,
|
|
485
467
|
query: escaped,
|
|
486
468
|
accountId,
|
|
487
469
|
limit: int(limit),
|
|
@@ -429,24 +429,20 @@ OPTIONS {
|
|
|
429
429
|
// `role` = discriminator on KnowledgeDocument projections
|
|
430
430
|
// ('identity'|'soul'|'knowledge'|'knowledge-summary') so BM25 hits surface
|
|
431
431
|
// which file backed the result. Distinct from Person.role (no shadow).
|
|
432
|
-
//
|
|
433
|
-
//
|
|
434
|
-
//
|
|
435
|
-
//
|
|
436
|
-
//
|
|
437
|
-
//
|
|
438
|
-
//
|
|
439
|
-
// `entity_search` carried both columns, which let admin notes silently
|
|
440
|
-
// shape the BM25 score of public-twin rows even though the memory-search
|
|
441
|
-
// projection swapped the field on the way out. Drop the pre-416 single
|
|
442
|
-
// index so existing deployments don't keep a populated dead third copy.
|
|
432
|
+
// Admin BM25 fulltext index. Task 416 split a single `entity_search` into an
|
|
433
|
+
// admin index plus a public twin (`entity_search_public`) so a public read
|
|
434
|
+
// ranked only against `n.compiledTruthPublic`. Task 654 retired the public twin
|
|
435
|
+
// and its whole read path once public agents became toolless; `entity_search_admin`
|
|
436
|
+
// is now the only fulltext index, carrying `n.compiledTruth`. Both the pre-416
|
|
437
|
+
// single index and the retired public twin keep a permanent `DROP ... IF EXISTS`
|
|
438
|
+
// below so existing deployments stop populating a dead index that consumes disk.
|
|
443
439
|
DROP INDEX entity_search IF EXISTS;
|
|
440
|
+
DROP INDEX entity_search_public IF EXISTS;
|
|
444
441
|
|
|
445
|
-
// Task 397 added :ConversationArchive to the label union. DROP + recreate
|
|
446
|
-
// existing installs pick up
|
|
447
|
-
// installs no-op the DROP via IF EXISTS.
|
|
442
|
+
// Task 397 added :ConversationArchive to the label union. DROP + recreate the
|
|
443
|
+
// admin index so existing installs pick up new labels without manual reindex;
|
|
444
|
+
// fresh installs no-op the DROP via IF EXISTS.
|
|
448
445
|
DROP INDEX entity_search_admin IF EXISTS;
|
|
449
|
-
DROP INDEX entity_search_public IF EXISTS;
|
|
450
446
|
|
|
451
447
|
CREATE FULLTEXT INDEX entity_search_admin IF NOT EXISTS
|
|
452
448
|
FOR (n:LocalBusiness|Service|PriceSpecification|OpeningHoursSpecification|Organization
|
|
@@ -470,33 +466,11 @@ ON EACH [n.name, n.firstName, n.lastName, n.givenName, n.familyName,
|
|
|
470
466
|
n.displayName, n.slug, n.role,
|
|
471
467
|
n.client, n.address, n.supplier, n.clientName,
|
|
472
468
|
n.compiledTruth];
|
|
473
|
-
|
|
474
|
-
CREATE FULLTEXT INDEX entity_search_public IF NOT EXISTS
|
|
475
|
-
FOR (n:LocalBusiness|Service|PriceSpecification|OpeningHoursSpecification|Organization
|
|
476
|
-
|Listing|Property|Viewing|Offer|PostalAddress
|
|
477
|
-
|Job|LineItem|Valuation|Milestone|QuoteDocument|VariationNote|InboundInvoice|CheckIn|WhatsAppGroup
|
|
478
|
-
|Person|UserProfile|Preference|AdminUser|AccessGrant
|
|
479
|
-
|KnowledgeDocument|ConversationArchive|Section|Chunk|DigitalDocument|CreativeWork|Question|FAQPage|DefinedTerm|Review|ImageObject
|
|
480
|
-
|Conversation|AdminConversation|PublicConversation|Message|UserMessage|AssistantMessage|ToolCall
|
|
481
|
-
|Task|Project|Event|Meeting|Attachment
|
|
482
|
-
|Workflow|WorkflowStep|WorkflowRun|StepResult
|
|
483
|
-
|EmailAccount
|
|
484
|
-
|Position|Credential|Agent|Idea|Concept
|
|
485
|
-
|VoiceProfile|VoiceEdit|SocialPost|AEOAudit|Page
|
|
486
|
-
|TimelineEvent|CompiledTruthRevision|Report|OrphanCandidate|CitationProposal)
|
|
487
|
-
ON EACH [n.name, n.firstName, n.lastName, n.givenName, n.familyName,
|
|
488
|
-
n.title, n.currentTitle, n.summary, n.body, n.content, n.text, n.description, n.headline, n.abstract,
|
|
489
|
-
n.email, n.note, n.label, n.value, n.message, n.preview, n.tagline,
|
|
490
|
-
n.subject, n.bodyPreview, n.fromName, n.fromAddress, n.agentAddress, n.screeningReason,
|
|
491
|
-
n.authority, n.contactValue, n.toolName,
|
|
492
|
-
n.displayName, n.slug, n.role,
|
|
493
|
-
n.compiledTruthPublic];
|
|
494
469
|
// `compiledTruth` (Task 306) is the authoritative admin-side summary on
|
|
495
|
-
// Person/Company/Concept/Project/LocalBusiness
|
|
496
|
-
// (Task 392) is
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
// is shaped only by the text the caller's scope is allowed to read.
|
|
470
|
+
// Person/Company/Concept/Project/LocalBusiness, indexed above. Its customer-facing
|
|
471
|
+
// twin `compiledTruthPublic` (Task 392) is still written, but is no longer
|
|
472
|
+
// fulltext-indexed: the public index it fed was retired with the public read
|
|
473
|
+
// path (Task 654), so no BM25 surface ranks against it.
|
|
500
474
|
|
|
501
475
|
// Project node — a standalone creative-output node distinct from
|
|
502
476
|
// :Section. Anchored via (:UserProfile)-[:CREATED]->(:Project), with optional
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:4c25ba31f010fd3042621f035682a6df66d7ec88011a11984ef197f056a61b60
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -2314,7 +2314,7 @@ either is a regression.
|
|
|
2314
2314
|
| `/session-rc-spawn` | POST `{ sessionId?, name? }`. Fire-and-forget `claude --remote-control [name] [--session-id <sid>]`. Present `sessionId` resumes; absent starts a fresh session (also used by the sidebar's "New session" button — it no longer opens claude.ai/code directly). Proxies to the manager's `/rc-spawn`, which waits up to **60 s** (raised from 12 s) for the spawned PTY to bind and returns `{ spawnedPid, sessionId, bridgeSessionId, slug, outcome, reason }`. The Sidebar navigates the opened tab to `claude.ai/code/session_<id>` on `outcome=bound`; on `timeout` or `spawn-failed` it shows an error modal (reason + sessionId) and **never** opens a bare claude.ai/code tab. The new process registers itself as its own Remote Control entry in claude.ai/code. | `POST /` |
|
|
2315
2315
|
| `/claude-sessions` | **Spawn surface only**. `POST /` is the Sidebar new-session-with-prompt path, cookie-auth only (the recorder loopback caller was removed; LinkedIn ingest moved to `/rc-spawn`). The former UI-facing handlers (SSE row feed, list, resume, stop, rename, archive, delete, `/:id/meta`, `/:id/input`, `/:id/log`) were removed — the maxy dashboard no longer manages or displays sessions. | `POST /` |
|
|
2316
2316
|
|
|
2317
|
-
**Admin session management moved entirely to claude's own interfaces** (claude.ai/code, claude desktop). A manager-owned per-account `claude rc --spawn same-dir` daemon registers the device as a Remote Control target there; the composer creates / resumes / stops / renames / archives / deletes sessions, with model + permission-mode applied at inception. The model lever is `account.json.adminModel` → `CLAUDE_CONFIG_DIR/settings.json "model"`, written by the daemon supervisor at boot; the reasoning-effort lever is `account.json.effort` → `settings.json "effortLevel"` (accepted values `low|medium|high|xhigh`; any other value, e.g. legacy `"auto"`, leaves the key unset so claude's own default governs), written by the same read-merge-write at boot. A third inception lever, permission mode, is added: `account.json.adminPermissionMode` → `settings.json "permissions.defaultMode"` (the 5 composer-writable modes `default|acceptEdits|plan|auto|bypassPermissions`; `dontAsk` is out of scope). Unlike model/effort (top-level keys), this lever **deep-merges** the nested `permissions.defaultMode`, owning only that key and preserving sibling `permissions` keys (allow/deny/…) — what the binary and the spawn lifeline actually read. All three levers are now changeable from the `/chat` composer (the model is a friendly-labelled checkmark dropdown via `MODEL_DISPLAY_NAMES`, effort a Faster↔Smarter slider, mode a 5-mode checkmark dropdown), each writing its `account.json` key then `POST /reapply-levers` so the next spawn honours it; the full write path is in `.docs/admin-webchat-native-channel.md`. Beyond the composer, the maxy admin UI keeps a single "New session" link (`https://claude.ai/code`, opens in a new tab) and no separate session list, viewer, controls, or model/mode picker. The daemon supervisor lives at [`platform/services/claude-session-manager/src/rc-daemon.ts`](../../../services/claude-session-manager/src/rc-daemon.ts). The `/session-defaults` route and `SpawnPreference` node were deleted with the picker. `/new-session-failure`, `/new-session-submit`, and `/claude-capabilities` are now orphaned (consumed only by the deleted NewSessionModal) — see [`.tasks/501`](../../../.tasks/) for their removal.
|
|
2317
|
+
**Admin session management moved entirely to claude's own interfaces** (claude.ai/code, claude desktop). A manager-owned per-account `claude rc --spawn same-dir` daemon registers the device as a Remote Control target there; the composer creates / resumes / stops / renames / archives / deletes sessions, with model + permission-mode applied at inception. The model lever is `account.json.adminModel` → `CLAUDE_CONFIG_DIR/settings.json "model"`, written by the daemon supervisor at boot; the reasoning-effort lever is `account.json.effort` → `settings.json "effortLevel"` (accepted values `low|medium|high|xhigh`; any other value, e.g. legacy `"auto"`, leaves the key unset so claude's own default governs), written by the same read-merge-write at boot. A third inception lever, permission mode, is added: `account.json.adminPermissionMode` → `settings.json "permissions.defaultMode"` (the 5 composer-writable modes `default|acceptEdits|plan|auto|bypassPermissions`; `dontAsk` is out of scope). Unlike model/effort (top-level keys), this lever **deep-merges** the nested `permissions.defaultMode`, owning only that key and preserving sibling `permissions` keys (allow/deny/…) — what the binary and the spawn lifeline actually read. All three levers are now changeable from the `/chat` composer (the model is a friendly-labelled checkmark dropdown via `MODEL_DISPLAY_NAMES`, effort a Faster↔Smarter slider, mode a 5-mode checkmark dropdown), each writing its `account.json` key then `POST /reapply-levers` so the next spawn honours it; the full write path is in `.docs/admin-webchat-native-channel.md`. On an existing chat each picker re-seats the live session and carries the operator's current model+mode+effort so picking one never resets the others. When the live session runs in `default` ("Ask permissions") mode, a tool the agent attempts that needs approval surfaces an Allow/Deny card in the composer (tool, description, argument preview); a click sends the verdict over Claude Code's channel permission relay and the tool runs or is refused — webchat is the only interactive ask surface (WhatsApp stays text-only; Telegram has no agent channel). Beyond the composer, the maxy admin UI keeps a single "New session" link (`https://claude.ai/code`, opens in a new tab) and no separate session list, viewer, controls, or model/mode picker. The daemon supervisor lives at [`platform/services/claude-session-manager/src/rc-daemon.ts`](../../../services/claude-session-manager/src/rc-daemon.ts). The `/session-defaults` route and `SpawnPreference` node were deleted with the picker. `/new-session-failure`, `/new-session-submit`, and `/claude-capabilities` are now orphaned (consumed only by the deleted NewSessionModal) — see [`.tasks/501`](../../../.tasks/) for their removal.
|
|
2318
2318
|
|
|
2319
2319
|
**Row title resolution.** Both the sidebar (`/sidebar-sessions`) and the manager's own row payload resolve a row's title in the same order: operator rename → Claude Code `ai-title` → first non-CLI user message → 8-char sessionId prefix. The operator-rename tier is the on-disk `<accountDir>/session-titles.json` (the manager's `UserTitleStore`), keyed by the CC sessionId — the sidebar reads that same file, so a write to the store lights up both surfaces with one write.
|
|
2320
2320
|
|
|
@@ -2567,8 +2567,8 @@ authoritative. This section names the surfaces and what backs each.
|
|
|
2567
2567
|
| `+ New session` button | Opens `NewSessionModal`, which POSTs `{channel, permissionMode, model, initialMessage}` to `/api/admin/claude-sessions`. | `claude-sessions.ts` |
|
|
2568
2568
|
| Mode trigger | Per-`(accountId, userId)` `SpawnPreference` for `permissionMode` and `model`; persists across reload, tab, device. | `session-defaults.ts` |
|
|
2569
2569
|
| Nav rows (Chat / People / Agents / Projects / Tasks / Artefacts) | People, Agents, Tasks open the artefact-pane Graph filtered to the matching label. Chat selects the active conversation. Artefacts swaps the list to editable documents. | `graph-search.ts`, `graph-subgraph.ts`, `sidebar-artefacts.ts` |
|
|
2570
|
-
| Sessions list (Active / Archived / All) | Live row store driven by SSE; manual reconcile button on the segmented control re-fetches the full id set. Every listed row carries two launch actions: the claude.ai/code resume (`ExternalLink`) and a chat-launch (`MessageSquare`) that opens `/chat?session=<full sessionId>` — the admin webchat pointed at that exact session; sending there resumes a stopped session with the webchat channel bound to that id (see `admin-webchat-native-channel.md`, "Session-targeted mode"). At the ≤720 px drawer breakpoint each row stacks into two lines — dot + title + id/stamp, then the action icons — with a divider between rows and actions at full opacity (the desktop hover-gated 0.5 opacity never resolves on touch). Below a 400 px list width each row's action icons collapse to a single `MoreHorizontal` ellipsis trigger; its popover menu lists the same actions as the inline icons but labels them with concise verbs (`Resume in claude.ai/code`, `Open in webchat`, `Stop`, `Archive` / `Unarchive`, `Rename`, `Delete`) rather than the inline icon buttons' verbose per-session aria-labels — the inline buttons keep those verbose labels because an icon-only control needs the full descriptive accessible name. **Re-seat:**
|
|
2571
|
-
| Channel reader nav rows (WhatsApp / Telegram) | One conditional nav row **per interactive channel that has ≥1 admin session**. Each row carries its brand glyph (WhatsApp `#25D366`, Telegram `#229ED9`); a channel with no admin session shows no row, and no install ever shows an always-empty "No conversations" row. The channel list loads **once on sidebar mount** (the per-channel counts must be known before the nav renders), so a failed load shows one muted, non-clickable line instead of channel rows. Clicking a channel row lists that channel's admin conversations; each conversation row shows a **display name** resolved by precedence `operatorName ?? whatsappName ?? senderId ?? title`: the bound `Person givenName familyName` when a `senderId`→`userId` binding exists, else the persisted WhatsApp display name (`pushName`, read from the latest `:Message:WhatsAppMessage.senderName`), else the raw `senderId`, else the agent title; the agent session title is the hover tooltip only. Under the name each row shows a **last-message-time breadcrumb** (`conv-timestamp`, same markup as the Sessions rows) formatted from the row's `lastMessageAt` (the last parseable turn's `ts`); a session with no parseable turn shows no time, never "Invalid Date". **Re-seat:** each channel conversation row
|
|
2570
|
+
| Sessions list (Active / Archived / All) | Live row store driven by SSE; manual reconcile button on the segmented control re-fetches the full id set. Every listed row carries two launch actions: the claude.ai/code resume (`ExternalLink`) and a chat-launch (`MessageSquare`) that opens `/chat?session=<full sessionId>` — the admin webchat pointed at that exact session; sending there resumes a stopped session with the webchat channel bound to that id (see `admin-webchat-native-channel.md`, "Session-targeted mode"). At the ≤720 px drawer breakpoint each row stacks into two lines — dot + title + id/stamp, then the action icons — with a divider between rows and actions at full opacity (the desktop hover-gated 0.5 opacity never resolves on touch). Below a 400 px list width each row's action icons collapse to a single `MoreHorizontal` ellipsis trigger; its popover menu lists the same actions as the inline icons but labels them with concise verbs (`Resume in claude.ai/code`, `Open in webchat`, `Stop`, `Archive` / `Unarchive`, `Rename`, `Delete`, `Re-seat`) rather than the inline icon buttons' verbose per-session aria-labels — the inline buttons keep those verbose labels because an icon-only control needs the full descriptive accessible name. **Re-seat:** Re-seat is a member action of the one `SessionRowActions` cluster, not a separate sibling control — wide it is the sliders icon inline with the other icons (sharing the cluster geometry and alignment); collapsed it is the `Re-seat` menu item, which expands the model/mode/effort form inline inside the overflow menu. The form (`SessionReseatControl`) forks the session via `/api/admin/session-reseat` and navigates to the fork (the same fork mechanism `/chat`'s pickers drive); its model picker defaults to the row's current model (a `model` field on each row, read from the JSONL tail), and mode and effort each carry a "Keep" option that omits the field. The overflow menu and the Re-seat form render through a `document.body` portal, fixed-positioned from the trigger's rect (right-aligned, flipped above the trigger when there is no room below), so neither is clipped by `.side-list`'s `overflow-y:auto`; Escape, a pointerdown outside the trigger/popover, and any scroll or resize dismiss them. | `/claude-sessions/events`, `/claude-sessions`, `/session-reseat` |
|
|
2571
|
+
| Channel reader nav rows (WhatsApp / Telegram) | One conditional nav row **per interactive channel that has ≥1 admin session**. Each row carries its brand glyph (WhatsApp `#25D366`, Telegram `#229ED9`); a channel with no admin session shows no row, and no install ever shows an always-empty "No conversations" row. The channel list loads **once on sidebar mount** (the per-channel counts must be known before the nav renders), so a failed load shows one muted, non-clickable line instead of channel rows. Clicking a channel row lists that channel's admin conversations; each conversation row shows a **display name** resolved by precedence `operatorName ?? whatsappName ?? senderId ?? title`: the bound `Person givenName familyName` when a `senderId`→`userId` binding exists, else the persisted WhatsApp display name (`pushName`, read from the latest `:Message:WhatsAppMessage.senderName`), else the raw `senderId`, else the agent title; the agent session title is the hover tooltip only. Under the name each row shows a **last-message-time breadcrumb** (`conv-timestamp`, same markup as the Sessions rows) formatted from the row's `lastMessageAt` (the last parseable turn's `ts`); a session with no parseable turn shows no time, never "Invalid Date". **Re-seat:** each channel conversation row carries a `SessionRowActions` cluster whose sole action is Re-seat (the row is a `conv-with-actions` shell whose open click moved onto an inner button so the cluster can sit beside it), so a WhatsApp/Telegram session's model/mode/effort is re-seatable from the dashboard exactly like a webchat row — wide it is the inline sliders icon, and below the 400 px width it folds into the `…` overflow menu identically to the Sessions rows; the channel reader row carries the same `model` field for the picker default. A click on a conversation **opens its transcript on the home surface**: on `/` it selects in place, and from any other route (`/graph`, `/data`, `/browser`) it navigates to `/?wa=<sessionId>&projectDir=<dir>`, which the root shell hydrates on mount (then strips the query so a later refresh does not re-pin a closed conversation). Server logs `[wa-reader] op=operator-name-fallback senderId=… source=whatsapp-pushname` when the pushName fallback fires and `[wa-reader] op=conversations count=<n> withTimestamp=<m>` per list build; the client logs `[admin-ui] wa-open route=… via=<in-place\|navigate> …` and `[admin-ui] wa-hydrate route=/ …`. | `/whatsapp-reader/conversations` |
|
|
2572
2572
|
| Channel transcript (`<Transcript>`) | Reads one session's JSONL over SSE (`/whatsapp-reader/stream`) and renders both sides plus tool calls — the turns claude.ai/code hides because channel inbound is stamped `isMeta`. Every turn shows its time-of-day (HH:MM from the turn's `ts`; a null/unparseable `ts` shows no time, never "Invalid Date"). `tool-call`/`tool-result` turns render as **collapsed cards** (chevron + label + time, default collapsed, expandable per card; one card's state never affects another) so the human↔agent text is not buried in JSON; the three conversation kinds always render in full. The thread **follows the tail**: it opens at the newest turn and pins to the bottom on each live append **only while the operator is already within 32 px of the bottom** — a scroll up suppresses the jump so reading history is never yanked. While the operator is scrolled up a **jump-to-bottom control** (`.wa-jump`, bottom-right of the viewport) appears; clicking it scrolls to the newest turn and re-arms follow-tail, and it hides again at the bottom. The thread scrolls inside its grid cell (header/sidebar/footer fixed). The stream sends a 20 s heartbeat so an idle Cloudflare tunnel does not idle-drop, tags each frame with a byte-offset `id:`, and resumes from the client's `Last-Event-ID` on reconnect (no duplicate backlog); the client clears the "Stream disconnected" banner on reopen and latches it only on a terminal CLOSED state. The thread also interleaves a collapsed `⚙ directive injected (N B)` row per turn, sorted by timestamp just before the turn it informed; expanding one fetches the exact stored `prompt-optimiser-directive` bytes for that turn. The entries are listed by `GET /whatsapp-reader/directives?sessionId=` and the bytes served by `GET /whatsapp-reader/directive?sessionId=&name=`, both admin-gated and account-scoped; a missing store degrades to no rows. | `/whatsapp-reader/stream`, `/whatsapp-reader/directives`, `/whatsapp-reader/directive` |
|
|
2573
2573
|
| Conversations row hover actions | Inline rename, archive, delete, JSONL view / download per row. The historical `.conversations-modal` CSS block exists in `globals.css` but is no longer mounted from any TSX — Sidebar.tsx now owns every per-row affordance directly. | `claude-sessions.ts` |
|
|
2574
2574
|
| Artefacts list | Lists every `:FileArtifact` under this account's tree (`relativePath STARTS WITH 'accounts/'`, all file types, excluding the `uploads/<id>/` subtree) plus this account's IDENTITY / SOUL / KNOWLEDGE / specialist templates. Click downloads the row's backing file (`downloadPath` → `GET /api/admin/files/download`) so the operator opens it in their local app; rows whose file is outside `DATA_ROOT` (bundled-fallback templates) show a "can't be downloaded" pill. The in-app artefact pane is dead pending removal. | `sidebar-artefacts.ts`, `files.ts` |
|
|
@@ -3241,16 +3241,15 @@ The admin agent runs via Claude Code CLI, which manages its own system prompt as
|
|
|
3241
3241
|
|
|
3242
3242
|
This is assembled as a `<previous-context>` block in the system prompt on each admin turn.
|
|
3243
3243
|
|
|
3244
|
-
###
|
|
3244
|
+
### Public-agent memory context (retired)
|
|
3245
3245
|
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
This subprocess model means each public agent query gets an isolated, short-lived memory server instance with the correct scope constraints baked into its environment.
|
|
3246
|
+
Public agents historically had their memory context injected by a server-side bridge
|
|
3247
|
+
(`fetchMemoryContext`) that spawned the memory MCP server with `ALLOWED_SCOPES=public,shared`
|
|
3248
|
+
and ran a `memory-search` on the agent's behalf. That bridge has been removed: public agents
|
|
3249
|
+
are now toolless (`buildPublicDeny` denies `mcp__memory__*`), so neither the agent nor a
|
|
3250
|
+
server-side proxy runs a public-scope memory read. The public-scope fulltext index that this
|
|
3251
|
+
path ranked against was retired alongside it. Restoring read-back for gated visitors is
|
|
3252
|
+
tracked separately — see the gated-public-agents doc.
|
|
3254
3253
|
|
|
3255
3254
|
---
|
|
3256
3255
|
|
|
@@ -95,7 +95,7 @@ The Hono route `POST /api/admin/claude-sessions` at [`platform/ui/server/routes/
|
|
|
95
95
|
|
|
96
96
|
The metadata pane surfaces two distinct identifier values. The three-id model was collapsed back to a single-identity contract: claude's bridge suffix and the JSONL basename UUID are two phases of one claude-side identity, not two operator-visible identifiers. The manager resolver accepts either phase on any route. The wire emits one canonical `sessionId` — whichever phase is current — and the pane shows one row.
|
|
97
97
|
|
|
98
|
-
**Re-seat mints a new id.** Model, mode, and effort are inception-only levers — claude reads them when a session is born and the `claude rc` daemon exposes no per-session switch — so changing one on an *existing* session forks it: `/api/admin/session-reseat` pre-mints a fresh `sessionId`, the manager `/rc-spawn` fork branch runs `--resume <old> --fork-session --session-id <new> --model <model>` (copying history into the new id), and the operator lands on the fork. The fork pins the chosen model and, when supplied, the chosen `permissionMode` (pushed as `--permission-mode`, one of the 5 composer-writable modes) and `effort` (`low|medium|high|xhigh`, merged into the per-spawn inline `--settings.effortLevel`); each is validated against its allowlist before reaching the argv (a present-but-disallowed value is a 400). Both `/chat`'s composer pickers and the dashboard's per-row Re-seat control drive this one fork, for webchat and WhatsApp/Telegram sessions alike. The full write path is in [`admin-webchat-native-channel.md`](../../../.docs/admin-webchat-native-channel.md).
|
|
98
|
+
**Re-seat mints a new id.** Model, mode, and effort are inception-only levers — claude reads them when a session is born and the `claude rc` daemon exposes no per-session switch — so changing one on an *existing* session forks it: `/api/admin/session-reseat` pre-mints a fresh `sessionId`, the manager `/rc-spawn` fork branch runs `--resume <old> --fork-session --session-id <new> --model <model>` (copying history into the new id), and the operator lands on the fork. The fork pins the chosen model and, when supplied, the chosen `permissionMode` (pushed as `--permission-mode`, one of the 5 composer-writable modes) and `effort` (`low|medium|high|xhigh`, merged into the per-spawn inline `--settings.effortLevel`); each is validated against its allowlist before reaching the argv (a present-but-disallowed value is a 400). Both `/chat`'s composer pickers and the dashboard's per-row Re-seat control drive this one fork, for webchat and WhatsApp/Telegram sessions alike. On `/chat`, every picker now carries the operator's *current* model, mode, and effort and overrides only the picked lever, so changing one never silently resets the other two (a non-writable current mode is omitted rather than rejected). When a re-seated webchat session runs in `default` ("Ask permissions") mode, a tool the agent attempts that needs approval surfaces an Allow/Deny prompt in `/chat` and blocks until the operator answers — over Claude Code's channel permission relay, the only interactive ask surface (WhatsApp stays text-only). The full write path is in [`admin-webchat-native-channel.md`](../../../.docs/admin-webchat-native-channel.md).
|
|
99
99
|
|
|
100
100
|
| Operator label | What it is | Manager wire field | Log key |
|
|
101
101
|
|---|---|---|---|
|
|
@@ -49,7 +49,7 @@ either is a regression.
|
|
|
49
49
|
| `/session-rc-spawn` | POST `{ sessionId?, name? }`. Fire-and-forget `claude --remote-control [name] [--session-id <sid>]`. Present `sessionId` resumes; absent starts a fresh session (also used by the sidebar's "New session" button — it no longer opens claude.ai/code directly). Proxies to the manager's `/rc-spawn`, which waits up to **60 s** (raised from 12 s) for the spawned PTY to bind and returns `{ spawnedPid, sessionId, bridgeSessionId, slug, outcome, reason }`. The Sidebar navigates the opened tab to `claude.ai/code/session_<id>` on `outcome=bound`; on `timeout` or `spawn-failed` it shows an error modal (reason + sessionId) and **never** opens a bare claude.ai/code tab. The new process registers itself as its own Remote Control entry in claude.ai/code. | `POST /` |
|
|
50
50
|
| `/claude-sessions` | **Spawn surface only**. `POST /` is the Sidebar new-session-with-prompt path, cookie-auth only (the recorder loopback caller was removed; LinkedIn ingest moved to `/rc-spawn`). The former UI-facing handlers (SSE row feed, list, resume, stop, rename, archive, delete, `/:id/meta`, `/:id/input`, `/:id/log`) were removed — the maxy dashboard no longer manages or displays sessions. | `POST /` |
|
|
51
51
|
|
|
52
|
-
**Admin session management moved entirely to claude's own interfaces** (claude.ai/code, claude desktop). A manager-owned per-account `claude rc --spawn same-dir` daemon registers the device as a Remote Control target there; the composer creates / resumes / stops / renames / archives / deletes sessions, with model + permission-mode applied at inception. The model lever is `account.json.adminModel` → `CLAUDE_CONFIG_DIR/settings.json "model"`, written by the daemon supervisor at boot; the reasoning-effort lever is `account.json.effort` → `settings.json "effortLevel"` (accepted values `low|medium|high|xhigh`; any other value, e.g. legacy `"auto"`, leaves the key unset so claude's own default governs), written by the same read-merge-write at boot. A third inception lever, permission mode, is added: `account.json.adminPermissionMode` → `settings.json "permissions.defaultMode"` (the 5 composer-writable modes `default|acceptEdits|plan|auto|bypassPermissions`; `dontAsk` is out of scope). Unlike model/effort (top-level keys), this lever **deep-merges** the nested `permissions.defaultMode`, owning only that key and preserving sibling `permissions` keys (allow/deny/…) — what the binary and the spawn lifeline actually read. All three levers are now changeable from the `/chat` composer (the model is a friendly-labelled checkmark dropdown via `MODEL_DISPLAY_NAMES`, effort a Faster↔Smarter slider, mode a 5-mode checkmark dropdown), each writing its `account.json` key then `POST /reapply-levers` so the next spawn honours it; the full write path is in `.docs/admin-webchat-native-channel.md`. Beyond the composer, the maxy admin UI keeps a single "New session" link (`https://claude.ai/code`, opens in a new tab) and no separate session list, viewer, controls, or model/mode picker. The daemon supervisor lives at [`platform/services/claude-session-manager/src/rc-daemon.ts`](../../../services/claude-session-manager/src/rc-daemon.ts). The `/session-defaults` route and `SpawnPreference` node were deleted with the picker. `/new-session-failure`, `/new-session-submit`, and `/claude-capabilities` are now orphaned (consumed only by the deleted NewSessionModal) — see [`.tasks/501`](../../../.tasks/) for their removal.
|
|
52
|
+
**Admin session management moved entirely to claude's own interfaces** (claude.ai/code, claude desktop). A manager-owned per-account `claude rc --spawn same-dir` daemon registers the device as a Remote Control target there; the composer creates / resumes / stops / renames / archives / deletes sessions, with model + permission-mode applied at inception. The model lever is `account.json.adminModel` → `CLAUDE_CONFIG_DIR/settings.json "model"`, written by the daemon supervisor at boot; the reasoning-effort lever is `account.json.effort` → `settings.json "effortLevel"` (accepted values `low|medium|high|xhigh`; any other value, e.g. legacy `"auto"`, leaves the key unset so claude's own default governs), written by the same read-merge-write at boot. A third inception lever, permission mode, is added: `account.json.adminPermissionMode` → `settings.json "permissions.defaultMode"` (the 5 composer-writable modes `default|acceptEdits|plan|auto|bypassPermissions`; `dontAsk` is out of scope). Unlike model/effort (top-level keys), this lever **deep-merges** the nested `permissions.defaultMode`, owning only that key and preserving sibling `permissions` keys (allow/deny/…) — what the binary and the spawn lifeline actually read. All three levers are now changeable from the `/chat` composer (the model is a friendly-labelled checkmark dropdown via `MODEL_DISPLAY_NAMES`, effort a Faster↔Smarter slider, mode a 5-mode checkmark dropdown), each writing its `account.json` key then `POST /reapply-levers` so the next spawn honours it; the full write path is in `.docs/admin-webchat-native-channel.md`. On an existing chat each picker re-seats the live session and carries the operator's current model+mode+effort so picking one never resets the others. When the live session runs in `default` ("Ask permissions") mode, a tool the agent attempts that needs approval surfaces an Allow/Deny card in the composer (tool, description, argument preview); a click sends the verdict over Claude Code's channel permission relay and the tool runs or is refused — webchat is the only interactive ask surface (WhatsApp stays text-only; Telegram has no agent channel). Beyond the composer, the maxy admin UI keeps a single "New session" link (`https://claude.ai/code`, opens in a new tab) and no separate session list, viewer, controls, or model/mode picker. The daemon supervisor lives at [`platform/services/claude-session-manager/src/rc-daemon.ts`](../../../services/claude-session-manager/src/rc-daemon.ts). The `/session-defaults` route and `SpawnPreference` node were deleted with the picker. `/new-session-failure`, `/new-session-submit`, and `/claude-capabilities` are now orphaned (consumed only by the deleted NewSessionModal) — see [`.tasks/501`](../../../.tasks/) for their removal.
|
|
53
53
|
|
|
54
54
|
**Row title resolution.** Both the sidebar (`/sidebar-sessions`) and the manager's own row payload resolve a row's title in the same order: operator rename → Claude Code `ai-title` → first non-CLI user message → 8-char sessionId prefix. The operator-rename tier is the on-disk `<accountDir>/session-titles.json` (the manager's `UserTitleStore`), keyed by the CC sessionId — the sidebar reads that same file, so a write to the store lights up both surfaces with one write.
|
|
55
55
|
|
|
@@ -302,8 +302,8 @@ authoritative. This section names the surfaces and what backs each.
|
|
|
302
302
|
| `+ New session` button | Opens `NewSessionModal`, which POSTs `{channel, permissionMode, model, initialMessage}` to `/api/admin/claude-sessions`. | `claude-sessions.ts` |
|
|
303
303
|
| Mode trigger | Per-`(accountId, userId)` `SpawnPreference` for `permissionMode` and `model`; persists across reload, tab, device. | `session-defaults.ts` |
|
|
304
304
|
| Nav rows (Chat / People / Agents / Projects / Tasks / Artefacts) | People, Agents, Tasks open the artefact-pane Graph filtered to the matching label. Chat selects the active conversation. Artefacts swaps the list to editable documents. | `graph-search.ts`, `graph-subgraph.ts`, `sidebar-artefacts.ts` |
|
|
305
|
-
| Sessions list (Active / Archived / All) | Live row store driven by SSE; manual reconcile button on the segmented control re-fetches the full id set. Every listed row carries two launch actions: the claude.ai/code resume (`ExternalLink`) and a chat-launch (`MessageSquare`) that opens `/chat?session=<full sessionId>` — the admin webchat pointed at that exact session; sending there resumes a stopped session with the webchat channel bound to that id (see `admin-webchat-native-channel.md`, "Session-targeted mode"). At the ≤720 px drawer breakpoint each row stacks into two lines — dot + title + id/stamp, then the action icons — with a divider between rows and actions at full opacity (the desktop hover-gated 0.5 opacity never resolves on touch). Below a 400 px list width each row's action icons collapse to a single `MoreHorizontal` ellipsis trigger; its popover menu lists the same actions as the inline icons but labels them with concise verbs (`Resume in claude.ai/code`, `Open in webchat`, `Stop`, `Archive` / `Unarchive`, `Rename`, `Delete`) rather than the inline icon buttons' verbose per-session aria-labels — the inline buttons keep those verbose labels because an icon-only control needs the full descriptive accessible name. **Re-seat:**
|
|
306
|
-
| Channel reader nav rows (WhatsApp / Telegram) | One conditional nav row **per interactive channel that has ≥1 admin session**. Each row carries its brand glyph (WhatsApp `#25D366`, Telegram `#229ED9`); a channel with no admin session shows no row, and no install ever shows an always-empty "No conversations" row. The channel list loads **once on sidebar mount** (the per-channel counts must be known before the nav renders), so a failed load shows one muted, non-clickable line instead of channel rows. Clicking a channel row lists that channel's admin conversations; each conversation row shows a **display name** resolved by precedence `operatorName ?? whatsappName ?? senderId ?? title`: the bound `Person givenName familyName` when a `senderId`→`userId` binding exists, else the persisted WhatsApp display name (`pushName`, read from the latest `:Message:WhatsAppMessage.senderName`), else the raw `senderId`, else the agent title; the agent session title is the hover tooltip only. Under the name each row shows a **last-message-time breadcrumb** (`conv-timestamp`, same markup as the Sessions rows) formatted from the row's `lastMessageAt` (the last parseable turn's `ts`); a session with no parseable turn shows no time, never "Invalid Date". **Re-seat:** each channel conversation row
|
|
305
|
+
| Sessions list (Active / Archived / All) | Live row store driven by SSE; manual reconcile button on the segmented control re-fetches the full id set. Every listed row carries two launch actions: the claude.ai/code resume (`ExternalLink`) and a chat-launch (`MessageSquare`) that opens `/chat?session=<full sessionId>` — the admin webchat pointed at that exact session; sending there resumes a stopped session with the webchat channel bound to that id (see `admin-webchat-native-channel.md`, "Session-targeted mode"). At the ≤720 px drawer breakpoint each row stacks into two lines — dot + title + id/stamp, then the action icons — with a divider between rows and actions at full opacity (the desktop hover-gated 0.5 opacity never resolves on touch). Below a 400 px list width each row's action icons collapse to a single `MoreHorizontal` ellipsis trigger; its popover menu lists the same actions as the inline icons but labels them with concise verbs (`Resume in claude.ai/code`, `Open in webchat`, `Stop`, `Archive` / `Unarchive`, `Rename`, `Delete`, `Re-seat`) rather than the inline icon buttons' verbose per-session aria-labels — the inline buttons keep those verbose labels because an icon-only control needs the full descriptive accessible name. **Re-seat:** Re-seat is a member action of the one `SessionRowActions` cluster, not a separate sibling control — wide it is the sliders icon inline with the other icons (sharing the cluster geometry and alignment); collapsed it is the `Re-seat` menu item, which expands the model/mode/effort form inline inside the overflow menu. The form (`SessionReseatControl`) forks the session via `/api/admin/session-reseat` and navigates to the fork (the same fork mechanism `/chat`'s pickers drive); its model picker defaults to the row's current model (a `model` field on each row, read from the JSONL tail), and mode and effort each carry a "Keep" option that omits the field. The overflow menu and the Re-seat form render through a `document.body` portal, fixed-positioned from the trigger's rect (right-aligned, flipped above the trigger when there is no room below), so neither is clipped by `.side-list`'s `overflow-y:auto`; Escape, a pointerdown outside the trigger/popover, and any scroll or resize dismiss them. | `/claude-sessions/events`, `/claude-sessions`, `/session-reseat` |
|
|
306
|
+
| Channel reader nav rows (WhatsApp / Telegram) | One conditional nav row **per interactive channel that has ≥1 admin session**. Each row carries its brand glyph (WhatsApp `#25D366`, Telegram `#229ED9`); a channel with no admin session shows no row, and no install ever shows an always-empty "No conversations" row. The channel list loads **once on sidebar mount** (the per-channel counts must be known before the nav renders), so a failed load shows one muted, non-clickable line instead of channel rows. Clicking a channel row lists that channel's admin conversations; each conversation row shows a **display name** resolved by precedence `operatorName ?? whatsappName ?? senderId ?? title`: the bound `Person givenName familyName` when a `senderId`→`userId` binding exists, else the persisted WhatsApp display name (`pushName`, read from the latest `:Message:WhatsAppMessage.senderName`), else the raw `senderId`, else the agent title; the agent session title is the hover tooltip only. Under the name each row shows a **last-message-time breadcrumb** (`conv-timestamp`, same markup as the Sessions rows) formatted from the row's `lastMessageAt` (the last parseable turn's `ts`); a session with no parseable turn shows no time, never "Invalid Date". **Re-seat:** each channel conversation row carries a `SessionRowActions` cluster whose sole action is Re-seat (the row is a `conv-with-actions` shell whose open click moved onto an inner button so the cluster can sit beside it), so a WhatsApp/Telegram session's model/mode/effort is re-seatable from the dashboard exactly like a webchat row — wide it is the inline sliders icon, and below the 400 px width it folds into the `…` overflow menu identically to the Sessions rows; the channel reader row carries the same `model` field for the picker default. A click on a conversation **opens its transcript on the home surface**: on `/` it selects in place, and from any other route (`/graph`, `/data`, `/browser`) it navigates to `/?wa=<sessionId>&projectDir=<dir>`, which the root shell hydrates on mount (then strips the query so a later refresh does not re-pin a closed conversation). Server logs `[wa-reader] op=operator-name-fallback senderId=… source=whatsapp-pushname` when the pushName fallback fires and `[wa-reader] op=conversations count=<n> withTimestamp=<m>` per list build; the client logs `[admin-ui] wa-open route=… via=<in-place\|navigate> …` and `[admin-ui] wa-hydrate route=/ …`. | `/whatsapp-reader/conversations` |
|
|
307
307
|
| Channel transcript (`<Transcript>`) | Reads one session's JSONL over SSE (`/whatsapp-reader/stream`) and renders both sides plus tool calls — the turns claude.ai/code hides because channel inbound is stamped `isMeta`. Every turn shows its time-of-day (HH:MM from the turn's `ts`; a null/unparseable `ts` shows no time, never "Invalid Date"). `tool-call`/`tool-result` turns render as **collapsed cards** (chevron + label + time, default collapsed, expandable per card; one card's state never affects another) so the human↔agent text is not buried in JSON; the three conversation kinds always render in full. The thread **follows the tail**: it opens at the newest turn and pins to the bottom on each live append **only while the operator is already within 32 px of the bottom** — a scroll up suppresses the jump so reading history is never yanked. While the operator is scrolled up a **jump-to-bottom control** (`.wa-jump`, bottom-right of the viewport) appears; clicking it scrolls to the newest turn and re-arms follow-tail, and it hides again at the bottom. The thread scrolls inside its grid cell (header/sidebar/footer fixed). The stream sends a 20 s heartbeat so an idle Cloudflare tunnel does not idle-drop, tags each frame with a byte-offset `id:`, and resumes from the client's `Last-Event-ID` on reconnect (no duplicate backlog); the client clears the "Stream disconnected" banner on reopen and latches it only on a terminal CLOSED state. The thread also interleaves a collapsed `⚙ directive injected (N B)` row per turn, sorted by timestamp just before the turn it informed; expanding one fetches the exact stored `prompt-optimiser-directive` bytes for that turn. The entries are listed by `GET /whatsapp-reader/directives?sessionId=` and the bytes served by `GET /whatsapp-reader/directive?sessionId=&name=`, both admin-gated and account-scoped; a missing store degrades to no rows. | `/whatsapp-reader/stream`, `/whatsapp-reader/directives`, `/whatsapp-reader/directive` |
|
|
308
308
|
| Conversations row hover actions | Inline rename, archive, delete, JSONL view / download per row. The historical `.conversations-modal` CSS block exists in `globals.css` but is no longer mounted from any TSX — Sidebar.tsx now owns every per-row affordance directly. | `claude-sessions.ts` |
|
|
309
309
|
| Artefacts list | Lists every `:FileArtifact` under this account's tree (`relativePath STARTS WITH 'accounts/'`, all file types, excluding the `uploads/<id>/` subtree) plus this account's IDENTITY / SOUL / KNOWLEDGE / specialist templates. Click downloads the row's backing file (`downloadPath` → `GET /api/admin/files/download`) so the operator opens it in their local app; rows whose file is outside `DATA_ROOT` (bundled-fallback templates) show a "can't be downloaded" pill. The in-app artefact pane is dead pending removal. | `sidebar-artefacts.ts`, `files.ts` |
|
|
@@ -373,16 +373,15 @@ The admin agent runs via Claude Code CLI, which manages its own system prompt as
|
|
|
373
373
|
|
|
374
374
|
This is assembled as a `<previous-context>` block in the system prompt on each admin turn.
|
|
375
375
|
|
|
376
|
-
###
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
This subprocess model means each public agent query gets an isolated, short-lived memory server instance with the correct scope constraints baked into its environment.
|
|
376
|
+
### Public-agent memory context (retired)
|
|
377
|
+
|
|
378
|
+
Public agents historically had their memory context injected by a server-side bridge
|
|
379
|
+
(`fetchMemoryContext`) that spawned the memory MCP server with `ALLOWED_SCOPES=public,shared`
|
|
380
|
+
and ran a `memory-search` on the agent's behalf. That bridge has been removed: public agents
|
|
381
|
+
are now toolless (`buildPublicDeny` denies `mcp__memory__*`), so neither the agent nor a
|
|
382
|
+
server-side proxy runs a public-scope memory read. The public-scope fulltext index that this
|
|
383
|
+
path ranked against was retired alongside it. Restoring read-back for gated visitors is
|
|
384
|
+
tracked separately — see the gated-public-agents doc.
|
|
386
385
|
|
|
387
386
|
---
|
|
388
387
|
|
|
@@ -140,18 +140,15 @@ const validator = {
|
|
|
140
140
|
liveSource: liveSchemaRuntime.source,
|
|
141
141
|
};
|
|
142
142
|
const userId = process.env.USER_ID; // Optional — present for admin sessions with users.json auth
|
|
143
|
-
// Scope filtering
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
// so the agent sees its visitor's user-scoped nodes alongside public-scope
|
|
153
|
-
// rows. When absent (admin path + open-mode public), behaviour is
|
|
154
|
-
// unchanged.
|
|
143
|
+
// Scope filtering via `ALLOWED_SCOPES` was retired with the public read path
|
|
144
|
+
// (Task 654): no spawn stamps it any more, so every memory read runs unscoped
|
|
145
|
+
// (admin behaviour). `allowedScopes` is passed as `undefined` below.
|
|
146
|
+
// Task 485 — per-visitor slice ringfence. When `ACCESS_SLICE_TOKEN` is set,
|
|
147
|
+
// every memory-search WHERE composes `(n.sliceToken = $sliceToken OR n.scope
|
|
148
|
+
// IS NULL)` so the agent sees its visitor's user-scoped nodes alongside
|
|
149
|
+
// scope-less rows. The read machinery is retained, but currently inert: no
|
|
150
|
+
// spawn stamps `ACCESS_SLICE_TOKEN` since the public read path was retired
|
|
151
|
+
// (Task 654), so `accessSliceToken` is always undefined and reads run admin-wide.
|
|
155
152
|
const accessSliceToken = typeof process.env.ACCESS_SLICE_TOKEN === "string" && process.env.ACCESS_SLICE_TOKEN.length > 0
|
|
156
153
|
? process.env.ACCESS_SLICE_TOKEN
|
|
157
154
|
: undefined;
|
|
@@ -259,7 +256,7 @@ eagerTool(server, "memory-search", "Search the knowledge graph using semantic ve
|
|
|
259
256
|
accountId,
|
|
260
257
|
limit,
|
|
261
258
|
expandHops,
|
|
262
|
-
allowedScopes,
|
|
259
|
+
allowedScopes: undefined,
|
|
263
260
|
sliceToken: accessSliceToken,
|
|
264
261
|
keywords,
|
|
265
262
|
keywordMatch,
|
|
@@ -368,7 +365,7 @@ eagerTool(server, "memory-lookup-by-name", "Deterministic by-name entity lookup.
|
|
|
368
365
|
name,
|
|
369
366
|
accountId,
|
|
370
367
|
labels,
|
|
371
|
-
allowedScopes,
|
|
368
|
+
allowedScopes: undefined,
|
|
372
369
|
sliceToken: accessSliceToken,
|
|
373
370
|
agentSlug: effectiveAgentSlug,
|
|
374
371
|
limit,
|
|
@@ -376,7 +373,7 @@ eagerTool(server, "memory-lookup-by-name", "Deterministic by-name entity lookup.
|
|
|
376
373
|
process.stderr.write(`[memory-lookup-by-name] name=${JSON.stringify(name)} ` +
|
|
377
374
|
`labels=${labels && labels.length > 0 ? labels.join("|") : "any"} ` +
|
|
378
375
|
`resultCount=${hits.length} ` +
|
|
379
|
-
`scope=${accessSliceToken ? accessSliceToken.slice(0, 8) :
|
|
376
|
+
`scope=${accessSliceToken ? accessSliceToken.slice(0, 8) : "admin"}\n`);
|
|
380
377
|
if (hits.length === 0) {
|
|
381
378
|
return {
|
|
382
379
|
content: [
|