@adia-ai/web-modules 0.8.24 → 0.8.26
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/CHANGELOG.md +27 -0
- package/agent-admin/admin-roster/admin-roster.a2ui.json +171 -0
- package/agent-admin/admin-roster/admin-roster.css +112 -0
- package/agent-admin/admin-roster/admin-roster.d.ts +44 -0
- package/agent-admin/admin-roster/admin-roster.js +202 -0
- package/agent-admin/admin-roster/admin-roster.yaml +167 -0
- package/agent-admin/admin-settings/admin-settings.a2ui.json +156 -0
- package/agent-admin/admin-settings/admin-settings.css +114 -0
- package/agent-admin/admin-settings/admin-settings.d.ts +30 -0
- package/agent-admin/admin-settings/admin-settings.js +219 -0
- package/agent-admin/admin-settings/admin-settings.yaml +153 -0
- package/agent-admin/agent-admin/agent-admin.a2ui.json +135 -0
- package/agent-admin/agent-admin/agent-admin.css +49 -0
- package/agent-admin/agent-admin/agent-admin.d.ts +45 -0
- package/agent-admin/agent-admin/agent-admin.js +257 -0
- package/agent-admin/agent-admin/agent-admin.yaml +162 -0
- package/agent-admin/demo-seeds.js +95 -0
- package/agent-admin/index.js +13 -0
- package/agent-admin/live-persona.js +53 -0
- package/chat/chat-surfaces/chat-surfaces.d.ts +56 -0
- package/chat/chat-surfaces/chat-surfaces.js +242 -0
- package/chat/index.js +1 -0
- package/dist/everything.min.js +181 -110
- package/index.js +1 -0
- package/package.json +26 -5
- package/theme/theme-panel/theme-panel.js +71 -31
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Edit this file; run `npm run build:components` to regenerate a2ui.json.
|
|
2
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
3
|
+
name: UIAdminSettings
|
|
4
|
+
tag: admin-settings-ui
|
|
5
|
+
status: stable
|
|
6
|
+
component: AdminSettings
|
|
7
|
+
category: container
|
|
8
|
+
version: 1
|
|
9
|
+
description: |
|
|
10
|
+
L4 settings pane over ONE bound per-persona store (CHAT-HARNESS.md
|
|
11
|
+
§Components row L4 + §Interfaces 5, gh#600 / WCH-5). Sections group by
|
|
12
|
+
`@adia-ai/persona`'s `KNOWN_KINDS` — DISPLAY grouping only; this
|
|
13
|
+
composite never switches on `entry.kind` for logic (law 7: Entry is one
|
|
14
|
+
generic primitive). Each Entry renders through the same row: label +
|
|
15
|
+
description, an enabled toggle, and a collapsible content textarea
|
|
16
|
+
(authoring). Both write through `store.set(...)` immediately — the
|
|
17
|
+
store is the state, persisted per persona.
|
|
18
|
+
|
|
19
|
+
`persona` and `store` are plain JS properties (no attribute path);
|
|
20
|
+
`store` is an opaque `SettingsStore` object from
|
|
21
|
+
`@adia-ai/persona`'s `personaStore`.
|
|
22
|
+
|
|
23
|
+
Lives in `packages/web-modules/agent-admin/` alongside
|
|
24
|
+
`<admin-roster-ui>` and `<agent-admin-ui>`.
|
|
25
|
+
|
|
26
|
+
composes:
|
|
27
|
+
- switch-ui
|
|
28
|
+
- textarea-ui
|
|
29
|
+
- text-ui
|
|
30
|
+
- icon-ui
|
|
31
|
+
|
|
32
|
+
props:
|
|
33
|
+
persona:
|
|
34
|
+
description: |
|
|
35
|
+
The `Persona` (from `@adia-ai/persona`) whose entries this pane
|
|
36
|
+
renders, grouped by `KNOWN_KINDS`. JS property only — no attribute.
|
|
37
|
+
type: object
|
|
38
|
+
required: true
|
|
39
|
+
dynamic: true
|
|
40
|
+
store:
|
|
41
|
+
description: |
|
|
42
|
+
The `SettingsStore` bound to `persona` (typically
|
|
43
|
+
`personaStore(persona)`). Toggle + content edits write through it
|
|
44
|
+
immediately; external `store.set()` calls (e.g. a live-apply probe)
|
|
45
|
+
re-render this pane via `store.subscribe`. JS property only.
|
|
46
|
+
type: object
|
|
47
|
+
required: true
|
|
48
|
+
dynamic: true
|
|
49
|
+
|
|
50
|
+
events:
|
|
51
|
+
entry-change:
|
|
52
|
+
description: An entry's `enabled` or `content` field was written through the store.
|
|
53
|
+
detail:
|
|
54
|
+
entryId:
|
|
55
|
+
type: string
|
|
56
|
+
field:
|
|
57
|
+
type: string
|
|
58
|
+
description: "'enabled' or 'content'."
|
|
59
|
+
value:
|
|
60
|
+
type: string
|
|
61
|
+
description: The new value (boolean for enabled, string for content).
|
|
62
|
+
|
|
63
|
+
slots: {}
|
|
64
|
+
|
|
65
|
+
states:
|
|
66
|
+
- name: idle
|
|
67
|
+
description: Default; renders `persona`'s entries grouped by kind.
|
|
68
|
+
- name: empty
|
|
69
|
+
description: No `persona` set.
|
|
70
|
+
|
|
71
|
+
traits: []
|
|
72
|
+
|
|
73
|
+
tokens:
|
|
74
|
+
--admin-settings-bg:
|
|
75
|
+
description: Panel background.
|
|
76
|
+
default: var(--a-bg)
|
|
77
|
+
--admin-settings-fg:
|
|
78
|
+
description: Primary foreground.
|
|
79
|
+
default: var(--a-fg)
|
|
80
|
+
--admin-settings-fg-muted:
|
|
81
|
+
description: Secondary foreground (description, empty state).
|
|
82
|
+
default: var(--a-fg-muted)
|
|
83
|
+
--admin-settings-px:
|
|
84
|
+
description: Horizontal padding.
|
|
85
|
+
default: var(--a-space-4)
|
|
86
|
+
--admin-settings-py:
|
|
87
|
+
description: Vertical padding.
|
|
88
|
+
default: var(--a-space-4)
|
|
89
|
+
--admin-settings-gap:
|
|
90
|
+
description: Vertical gap between header and sections.
|
|
91
|
+
default: var(--a-space-5)
|
|
92
|
+
--admin-settings-section-gap:
|
|
93
|
+
description: Gap between a section's heading and its entries.
|
|
94
|
+
default: var(--a-space-2)
|
|
95
|
+
--admin-settings-entry-gap:
|
|
96
|
+
description: Gap between sibling entries in a section.
|
|
97
|
+
default: var(--a-space-1)
|
|
98
|
+
--admin-settings-border:
|
|
99
|
+
description: Entry card border.
|
|
100
|
+
default: var(--a-border-subtle)
|
|
101
|
+
--admin-settings-radius:
|
|
102
|
+
description: Entry card border radius.
|
|
103
|
+
default: var(--a-radius-md)
|
|
104
|
+
|
|
105
|
+
a2ui:
|
|
106
|
+
rules:
|
|
107
|
+
- rule: |
|
|
108
|
+
Use <admin-settings-ui> for the canonical per-persona settings
|
|
109
|
+
pane. Set both `persona` and `store` as JS properties — neither
|
|
110
|
+
has an attribute form.
|
|
111
|
+
reason: One composite replaces a hand-rolled per-kind settings UI.
|
|
112
|
+
- rule: |
|
|
113
|
+
Never author per-kind branches around this composite (a
|
|
114
|
+
prompt-section-specific control, a skill-specific control, …) —
|
|
115
|
+
`KNOWN_KINDS` is display grouping only; a seventh kind must render
|
|
116
|
+
through the SAME entry row with zero new code.
|
|
117
|
+
reason: Law 7 — Entry is one generic primitive across all kinds.
|
|
118
|
+
|
|
119
|
+
anti_patterns:
|
|
120
|
+
- wrong: |
|
|
121
|
+
{"component": "AdminSettings"}
|
|
122
|
+
why: Renders "No persona selected." — `persona` and `store` must be set as JS properties before use.
|
|
123
|
+
fix: |
|
|
124
|
+
const el = document.querySelector('admin-settings-ui');
|
|
125
|
+
el.persona = myPersona;
|
|
126
|
+
el.store = personaStore(myPersona);
|
|
127
|
+
|
|
128
|
+
examples:
|
|
129
|
+
- name: bound-to-a-persona
|
|
130
|
+
description: Settings pane bound to a persona + its store (JS-driven; not expressible as static a2ui JSON since `store` is an opaque object).
|
|
131
|
+
a2ui: |
|
|
132
|
+
[{"id": "settings", "component": "AdminSettings"}]
|
|
133
|
+
|
|
134
|
+
keywords:
|
|
135
|
+
- admin
|
|
136
|
+
- settings
|
|
137
|
+
- persona
|
|
138
|
+
- entry
|
|
139
|
+
- store
|
|
140
|
+
- agent
|
|
141
|
+
|
|
142
|
+
synonyms:
|
|
143
|
+
settings:
|
|
144
|
+
- config
|
|
145
|
+
- preferences
|
|
146
|
+
- entries
|
|
147
|
+
persona:
|
|
148
|
+
- agent
|
|
149
|
+
- profile
|
|
150
|
+
|
|
151
|
+
related:
|
|
152
|
+
- AdminRoster
|
|
153
|
+
- AgentAdmin
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://adiaui.dev/a2ui/v0_9/components/AgentAdmin.json",
|
|
4
|
+
"title": "AgentAdmin",
|
|
5
|
+
"description": "L4 admin composite (CHAT-HARNESS.md §Components row L4 + §Interfaces 5,\ngh#600 / WCH-5) — persona roster + settings pane + a chat pane, wired to\nthe store-swap law: a genuine persona switch (a new `SettingsStore`\nobject) resets the conversation, disposes open generative-UI surfaces,\nand mints a fresh session; re-selecting the already-active persona\n(same memoized store reference) resets nothing.\n\nBehavior-only orchestrator (mirrors `<chat-shell>`) — stamps no HTML of\nits own. Bespoke light-DOM children: `<admin-roster-ui>`,\n`<admin-settings-ui>`, `<chat-shell>` in EXTERNAL mode (no `proxy-url` —\nthis composite drives it via `wireAgentEvents`).\n\nTransport-blind (CHAT-HARNESS law 1): the `runTurn` JS property is\n`(session, text) → AsyncIterable<AgentEvent>`; packaged default\n`undefined` falls back to a deterministic, keyless stub that compiles\nthe active persona FRESH every turn (`readLivePersona` +\n`compilePersona`) and visibly cites what it read — the live-apply\nprobe: edit an entry's content in `<admin-settings-ui>`, the next\nturn's stub output cites the new text immediately, no propagation\nchannel.\n\nLives in `packages/web-modules/agent-admin/` alongside\n`<admin-roster-ui>` and `<admin-settings-ui>`.\n",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"allOf": [
|
|
8
|
+
{
|
|
9
|
+
"$ref": "common_types.json#/$defs/ComponentCommon"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"$ref": "common_types.json#/$defs/CatalogComponentCommon"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"active-id": {
|
|
17
|
+
"description": "The currently-active persona's id. Reflects; setting it programmatically\n(or via `<admin-roster-ui>`'s `persona-change`) runs `setActivePersona`.\n",
|
|
18
|
+
"$ref": "common_types.json#/$defs/DynamicString"
|
|
19
|
+
},
|
|
20
|
+
"component": {
|
|
21
|
+
"const": "AgentAdmin"
|
|
22
|
+
},
|
|
23
|
+
"personas": {
|
|
24
|
+
"description": "Full array of `Persona` objects (from `@adia-ai/persona`) this admin\nsurface manages. JSON-attribute or JS-property; the composite derives\n`<admin-roster-ui>`'s summary list from it automatically.\n",
|
|
25
|
+
"$ref": "common_types.json#/$defs/DynamicStringList"
|
|
26
|
+
},
|
|
27
|
+
"renderer": {
|
|
28
|
+
"description": "Optional generative-UI renderer (WCH-4's surface-host registry seam)\n— JS property only, host-injected (private `@genui/renderer` packages\nnever ship inside this composite). Omitted, surface events are safely\ndropped.\n",
|
|
29
|
+
"type": "object"
|
|
30
|
+
},
|
|
31
|
+
"runTurn": {
|
|
32
|
+
"description": "Optional transport override — `(session, text) => AsyncIterable<AgentEvent>`.\nJS property only. Omitted, the composite's own deterministic stub\ndrives every turn (see description).\n",
|
|
33
|
+
"type": "object"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"required": [
|
|
37
|
+
"component",
|
|
38
|
+
"personas"
|
|
39
|
+
],
|
|
40
|
+
"unevaluatedProperties": false,
|
|
41
|
+
"x-adiaui": {
|
|
42
|
+
"anti_patterns": [
|
|
43
|
+
{
|
|
44
|
+
"fix": "<agent-admin-ui id=\"admin\">\n <admin-roster-ui></admin-roster-ui>\n <admin-settings-ui></admin-settings-ui>\n <chat-shell><chat-header></chat-header><chat-thread></chat-thread><chat-composer></chat-composer></chat-shell>\n</agent-admin-ui>\n",
|
|
45
|
+
"why": "Renders nothing without its bespoke children — `<agent-admin-ui>` needs `<admin-roster-ui>` + `<admin-settings-ui>` + `<chat-shell>` authored inside it.",
|
|
46
|
+
"wrong": "{\"component\": \"AgentAdmin\", \"personas\": [{\"id\": \"a\", \"label\": \"A\", \"seedVersion\": 1, \"entries\": []}]}\n"
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"category": "container",
|
|
50
|
+
"composes": [
|
|
51
|
+
"admin-roster-ui",
|
|
52
|
+
"admin-settings-ui",
|
|
53
|
+
"chat-shell"
|
|
54
|
+
],
|
|
55
|
+
"events": {
|
|
56
|
+
"persona-activated": {
|
|
57
|
+
"description": "A persona finished activating (fired for both a genuine swap and a same-store reconnect).",
|
|
58
|
+
"detail": {
|
|
59
|
+
"personaId": {
|
|
60
|
+
"type": "string"
|
|
61
|
+
},
|
|
62
|
+
"reset": {
|
|
63
|
+
"description": "True for a genuine store swap (conversation/surfaces/session reset); false for a same-store reconnect.",
|
|
64
|
+
"type": "boolean"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"persona-imported": {
|
|
69
|
+
"description": "An imported persona (from `<admin-roster-ui>`'s `persona-import`) was appended and activated.",
|
|
70
|
+
"detail": {
|
|
71
|
+
"persona": {
|
|
72
|
+
"type": "object"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"examples": [
|
|
78
|
+
{
|
|
79
|
+
"description": "Full agent-admin composition (JS-driven; `personas` with full Entry arrays isn't practically hand-authored as static a2ui JSON).",
|
|
80
|
+
"a2ui": "[{\"id\": \"admin\", \"component\": \"AgentAdmin\", \"children\": [\n {\"id\": \"roster\", \"component\": \"AdminRoster\"},\n {\"id\": \"settings\", \"component\": \"AdminSettings\"},\n {\"id\": \"chat\", \"component\": \"ChatShell\"}\n]}]\n",
|
|
81
|
+
"name": "composed-shell"
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
"keywords": [
|
|
85
|
+
"admin",
|
|
86
|
+
"agent",
|
|
87
|
+
"persona",
|
|
88
|
+
"roster",
|
|
89
|
+
"settings",
|
|
90
|
+
"chat",
|
|
91
|
+
"store-swap"
|
|
92
|
+
],
|
|
93
|
+
"name": "UIAgentAdmin",
|
|
94
|
+
"related": [
|
|
95
|
+
"AdminRoster",
|
|
96
|
+
"AdminSettings",
|
|
97
|
+
"ChatShell"
|
|
98
|
+
],
|
|
99
|
+
"slots": {},
|
|
100
|
+
"states": [
|
|
101
|
+
{
|
|
102
|
+
"description": "Default; one persona active, chat pane external-driven.",
|
|
103
|
+
"name": "idle"
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"status": "stable",
|
|
107
|
+
"synonyms": {
|
|
108
|
+
"admin": [
|
|
109
|
+
"agent-admin",
|
|
110
|
+
"persona-admin"
|
|
111
|
+
],
|
|
112
|
+
"persona": [
|
|
113
|
+
"agent",
|
|
114
|
+
"profile"
|
|
115
|
+
]
|
|
116
|
+
},
|
|
117
|
+
"tag": "agent-admin-ui",
|
|
118
|
+
"tokens": {
|
|
119
|
+
"--agent-admin-border": {
|
|
120
|
+
"description": "Column divider border.",
|
|
121
|
+
"default": "var(--a-border-subtle)"
|
|
122
|
+
},
|
|
123
|
+
"--agent-admin-roster-w": {
|
|
124
|
+
"description": "Roster column width.",
|
|
125
|
+
"default": "var(--a-space-12)"
|
|
126
|
+
},
|
|
127
|
+
"--agent-admin-settings-w": {
|
|
128
|
+
"description": "Settings column width.",
|
|
129
|
+
"default": "calc(var(--a-space-12) * 1.6)"
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"traits": [],
|
|
133
|
+
"version": 1
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
2
|
+
agent-admin-ui — Module CSS
|
|
3
|
+
|
|
4
|
+
Behavior-only orchestrator (mirrors chat-shell.css) — this file is
|
|
5
|
+
LAYOUT ONLY, reading bespoke light-DOM child tags directly
|
|
6
|
+
(<admin-roster-ui>, <admin-settings-ui>, <chat-shell>); each child
|
|
7
|
+
carries its own chrome via its own scoped stylesheet.
|
|
8
|
+
═══════════════════════════════════════════════════════════════ */
|
|
9
|
+
|
|
10
|
+
@scope (agent-admin-ui) {
|
|
11
|
+
|
|
12
|
+
:where(:scope) {
|
|
13
|
+
--agent-admin-roster-w: var(--a-space-12);
|
|
14
|
+
--agent-admin-settings-w: calc(var(--a-space-12) * 1.6);
|
|
15
|
+
--agent-admin-border: var(--a-border-subtle);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
:scope {
|
|
19
|
+
display: grid;
|
|
20
|
+
grid-template-columns: var(--agent-admin-roster-w) var(--agent-admin-settings-w) 1fr;
|
|
21
|
+
min-height: 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
:scope > admin-roster-ui {
|
|
25
|
+
border-right: 1px solid var(--agent-admin-border);
|
|
26
|
+
overflow-y: auto;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
:scope > admin-settings-ui {
|
|
30
|
+
border-right: 1px solid var(--agent-admin-border);
|
|
31
|
+
overflow-y: auto;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:scope > chat-shell {
|
|
35
|
+
min-width: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@media (max-width: 900px) {
|
|
39
|
+
:scope {
|
|
40
|
+
grid-template-columns: 1fr;
|
|
41
|
+
grid-auto-rows: auto;
|
|
42
|
+
}
|
|
43
|
+
:scope > admin-roster-ui,
|
|
44
|
+
:scope > admin-settings-ui {
|
|
45
|
+
border-right: none;
|
|
46
|
+
border-bottom: 1px solid var(--agent-admin-border);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<agent-admin-ui>` — L4 admin composite: persona roster + settings pane
|
|
3
|
+
* + a chat pane, wired to the store-swap law (CHAT-HARNESS.md
|
|
4
|
+
* §Components row L4 + §Interfaces 5, gh#600 / WCH-5).
|
|
5
|
+
*
|
|
6
|
+
* Hand-authored alongside `<name>.yaml`; regenerate when the props /
|
|
7
|
+
* events shape changes.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { UIElement } from '../../../web-components/core/element.js';
|
|
11
|
+
import type { Persona, SettingsStore } from '@adia-ai/persona';
|
|
12
|
+
import type { AgentEvent } from '@adia-ai/agent';
|
|
13
|
+
|
|
14
|
+
export type AgentAdminRunTurn = (session: unknown, text: string) => AsyncIterable<AgentEvent>;
|
|
15
|
+
|
|
16
|
+
export interface AgentAdminPersonaActivatedDetail {
|
|
17
|
+
personaId: string;
|
|
18
|
+
reset: boolean;
|
|
19
|
+
}
|
|
20
|
+
export type AgentAdminPersonaActivatedEvent = CustomEvent<AgentAdminPersonaActivatedDetail>;
|
|
21
|
+
|
|
22
|
+
export interface AgentAdminPersonaImportedDetail {
|
|
23
|
+
persona: Persona;
|
|
24
|
+
}
|
|
25
|
+
export type AgentAdminPersonaImportedEvent = CustomEvent<AgentAdminPersonaImportedDetail>;
|
|
26
|
+
|
|
27
|
+
export class UIAgentAdmin extends UIElement {
|
|
28
|
+
personas: Persona[];
|
|
29
|
+
'active-id': string;
|
|
30
|
+
renderer: unknown;
|
|
31
|
+
runTurn: AgentAdminRunTurn | null;
|
|
32
|
+
readonly activePersona: Persona | null;
|
|
33
|
+
readonly activeStore: SettingsStore | null;
|
|
34
|
+
|
|
35
|
+
setActivePersona(id: string): { genuineSwap: boolean };
|
|
36
|
+
send(text: string): Promise<void>;
|
|
37
|
+
|
|
38
|
+
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
39
|
+
type: K,
|
|
40
|
+
listener: (this: UIAgentAdmin, ev: HTMLElementEventMap[K]) => unknown,
|
|
41
|
+
options?: boolean | AddEventListenerOptions,
|
|
42
|
+
): void;
|
|
43
|
+
addEventListener(type: 'persona-activated', listener: (ev: AgentAdminPersonaActivatedEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
44
|
+
addEventListener(type: 'persona-imported', listener: (ev: AgentAdminPersonaImportedEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
45
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <agent-admin-ui> — L4 admin composite: persona roster + settings pane +
|
|
3
|
+
* a chat pane, wired to the store-swap law (CHAT-HARNESS.md §Components
|
|
4
|
+
* row L4 + §Interfaces 5, gh#600 / WCH-5).
|
|
5
|
+
*
|
|
6
|
+
* Behavior-only orchestrator (mirrors <chat-shell>'s pattern): stamps no
|
|
7
|
+
* HTML of its own. Bespoke light-DOM children, author-supplied:
|
|
8
|
+
*
|
|
9
|
+
* <agent-admin-ui id="admin">
|
|
10
|
+
* <admin-roster-ui></admin-roster-ui>
|
|
11
|
+
* <admin-settings-ui></admin-settings-ui>
|
|
12
|
+
* <chat-shell>
|
|
13
|
+
* <chat-header><chat-status slot="status"></chat-status></chat-header>
|
|
14
|
+
* <chat-thread><chat-empty>...</chat-empty></chat-thread>
|
|
15
|
+
* <chat-composer><chat-input-ui></chat-input-ui></chat-composer>
|
|
16
|
+
* </chat-shell>
|
|
17
|
+
* </agent-admin-ui>
|
|
18
|
+
* <script>
|
|
19
|
+
* const admin = document.querySelector('#admin');
|
|
20
|
+
* admin.personas = [supportPersona, writerPersona]; // @adia-ai/persona Persona[]
|
|
21
|
+
* admin['active-id'] = 'support';
|
|
22
|
+
* </script>
|
|
23
|
+
*
|
|
24
|
+
* STORE-SWAP LAW (CHAT-HARNESS §Interfaces 5): one `SettingsStore` per
|
|
25
|
+
* persona id, memoized for the composite's lifetime. `setActivePersona`
|
|
26
|
+
* compares the resolved store's OBJECT REFERENCE against the currently
|
|
27
|
+
* bound one — a genuine reference change (switching to a different
|
|
28
|
+
* persona) resets the chat conversation (`chat-shell.clear()`), disposes
|
|
29
|
+
* every open generative-UI surface (`disposeAll()`), and mints a fresh
|
|
30
|
+
* session; re-selecting the ALREADY-active persona resolves to the SAME
|
|
31
|
+
* memoized store reference and resets nothing.
|
|
32
|
+
*
|
|
33
|
+
* TRANSPORT-BLIND (law 1): `runTurn(session, text) → AsyncIterable<AgentEvent>`
|
|
34
|
+
* is the only channel to a model. Packaged default is `undefined` — the
|
|
35
|
+
* composite falls back to a deterministic, keyless stub that compiles the
|
|
36
|
+
* ACTIVE persona fresh (via `readLivePersona` + `compilePersona`) on every
|
|
37
|
+
* call and visibly cites what it read, proving live-apply
|
|
38
|
+
* (fresh-read-at-turn-time, M-scope 3): edit an entry's content in
|
|
39
|
+
* `<admin-settings-ui>`, the very NEXT turn's stub output cites the new
|
|
40
|
+
* text — there is no propagation channel, no cache.
|
|
41
|
+
*
|
|
42
|
+
* `<admin-roster-ui>`'s surface-agnostic generative-UI hosting rides
|
|
43
|
+
* `chat/chat-surfaces.js`'s `createSurfaceRegistry` + `wireAgentEvents`
|
|
44
|
+
* (WCH-4 / gh#599) — the `renderer` prop is host-injected (private,
|
|
45
|
+
* unpublished `@genui/renderer` packages never ship inside this
|
|
46
|
+
* composite); omitted, surface events are safely dropped.
|
|
47
|
+
*
|
|
48
|
+
* Per ADR-0027 — a consumer imports <admin-roster-ui>, <admin-settings-ui>,
|
|
49
|
+
* and <chat-shell> (+ its own bespoke children) alongside this module.
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
import { UIElement } from '../../../web-components/core/element.js';
|
|
53
|
+
import { compilePersona, personaStore } from '@adia-ai/persona';
|
|
54
|
+
import { createSession, composeSystem } from '@adia-ai/agent';
|
|
55
|
+
import { createSurfaceRegistry, wireAgentEvents } from '../../chat/chat-surfaces/chat-surfaces.js';
|
|
56
|
+
import { readLivePersona } from '../live-persona.js';
|
|
57
|
+
|
|
58
|
+
const NOOP_REGISTRY = Object.freeze({
|
|
59
|
+
route: () => undefined,
|
|
60
|
+
close: () => undefined,
|
|
61
|
+
feed: () => undefined,
|
|
62
|
+
get: () => undefined,
|
|
63
|
+
disposeAll: () => {},
|
|
64
|
+
hosts: new Map(),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
class UIAgentAdmin extends UIElement {
|
|
68
|
+
static properties = {
|
|
69
|
+
'active-id': { type: String, default: '', reflect: true },
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
static template = () => null;
|
|
73
|
+
|
|
74
|
+
#personas = [];
|
|
75
|
+
#renderer = null;
|
|
76
|
+
#runTurn = null;
|
|
77
|
+
#roster = null;
|
|
78
|
+
#settings = null;
|
|
79
|
+
#chat = null;
|
|
80
|
+
#storesByPersonaId = new Map();
|
|
81
|
+
#activePersona = null;
|
|
82
|
+
#activeStore = null;
|
|
83
|
+
#session = null;
|
|
84
|
+
#surfaceRegistry = NOOP_REGISTRY;
|
|
85
|
+
|
|
86
|
+
get personas() { return this.#personas; }
|
|
87
|
+
set personas(value) {
|
|
88
|
+
this.#personas = Array.isArray(value) ? value : [];
|
|
89
|
+
this.#syncRosterSummaries();
|
|
90
|
+
this.#activateInitialIfNeeded();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get renderer() { return this.#renderer; }
|
|
94
|
+
set renderer(value) {
|
|
95
|
+
this.#renderer = value ?? null;
|
|
96
|
+
this.#surfaceRegistry = value ? createSurfaceRegistry({ renderer: value }) : NOOP_REGISTRY;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
get runTurn() { return this.#runTurn; }
|
|
100
|
+
set runTurn(fn) { this.#runTurn = typeof fn === 'function' ? fn : null; }
|
|
101
|
+
|
|
102
|
+
/** The currently-bound persona (post live-apply overlay is computed
|
|
103
|
+
* fresh per turn, not cached here — see #defaultRunTurn). */
|
|
104
|
+
get activePersona() { return this.#activePersona; }
|
|
105
|
+
/** The currently-bound SettingsStore — same object across a same-id
|
|
106
|
+
* reconnect, a new object across a genuine persona switch. */
|
|
107
|
+
get activeStore() { return this.#activeStore; }
|
|
108
|
+
|
|
109
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
110
|
+
if (name === 'personas') {
|
|
111
|
+
if (newVal == null) { this.personas = []; }
|
|
112
|
+
else {
|
|
113
|
+
try {
|
|
114
|
+
const parsed = JSON.parse(newVal);
|
|
115
|
+
this.personas = Array.isArray(parsed) ? parsed : [];
|
|
116
|
+
} catch { this.personas = []; }
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
super.attributeChangedCallback(name, oldVal, newVal);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
connected() {
|
|
124
|
+
this.#roster = this.querySelector('admin-roster-ui');
|
|
125
|
+
this.#settings = this.querySelector('admin-settings-ui');
|
|
126
|
+
this.#chat = this.querySelector('chat-shell');
|
|
127
|
+
|
|
128
|
+
this.#roster?.addEventListener('persona-change', this.#onPersonaChange);
|
|
129
|
+
this.#roster?.addEventListener('persona-import', this.#onPersonaImport);
|
|
130
|
+
this.#chat?.addEventListener('submit', this.#onChatSubmit);
|
|
131
|
+
|
|
132
|
+
this.#syncRosterSummaries();
|
|
133
|
+
this.#activateInitialIfNeeded();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#activateInitialIfNeeded() {
|
|
137
|
+
if (!this.isConnected || this.#activePersona) return;
|
|
138
|
+
const initialId = this['active-id'] || this.#personas[0]?.id || '';
|
|
139
|
+
if (initialId) this.setActivePersona(initialId);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
disconnected() {
|
|
143
|
+
this.#roster?.removeEventListener('persona-change', this.#onPersonaChange);
|
|
144
|
+
this.#roster?.removeEventListener('persona-import', this.#onPersonaImport);
|
|
145
|
+
this.#chat?.removeEventListener('submit', this.#onChatSubmit);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── the store-swap law ──────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @param {string} id
|
|
152
|
+
* @returns {{ genuineSwap: boolean }} — mostly for tests; the reset side
|
|
153
|
+
* effects already happened by the time this returns.
|
|
154
|
+
*/
|
|
155
|
+
setActivePersona(id) {
|
|
156
|
+
const persona = this.#personas.find((p) => p.id === id);
|
|
157
|
+
if (!persona) return { genuineSwap: false };
|
|
158
|
+
|
|
159
|
+
const store = this.#storeFor(persona);
|
|
160
|
+
const genuineSwap = store !== this.#activeStore;
|
|
161
|
+
|
|
162
|
+
this.#activePersona = persona;
|
|
163
|
+
this.#activeStore = store;
|
|
164
|
+
this['active-id'] = id;
|
|
165
|
+
if (this.#roster) this.#roster['active-id'] = id;
|
|
166
|
+
if (this.#settings) {
|
|
167
|
+
this.#settings.persona = persona;
|
|
168
|
+
this.#settings.store = store;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (genuineSwap) {
|
|
172
|
+
this.#chat?.clear?.();
|
|
173
|
+
this.#surfaceRegistry.disposeAll();
|
|
174
|
+
this.#session = createSession();
|
|
175
|
+
this.dispatchEvent(new CustomEvent('persona-activated', {
|
|
176
|
+
bubbles: true,
|
|
177
|
+
detail: { personaId: id, reset: true },
|
|
178
|
+
}));
|
|
179
|
+
} else {
|
|
180
|
+
this.dispatchEvent(new CustomEvent('persona-activated', {
|
|
181
|
+
bubbles: true,
|
|
182
|
+
detail: { personaId: id, reset: false },
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
return { genuineSwap };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
#storeFor(persona) {
|
|
189
|
+
let store = this.#storesByPersonaId.get(persona.id);
|
|
190
|
+
if (!store) {
|
|
191
|
+
store = personaStore(persona);
|
|
192
|
+
this.#storesByPersonaId.set(persona.id, store);
|
|
193
|
+
}
|
|
194
|
+
return store;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#syncRosterSummaries() {
|
|
198
|
+
if (!this.#roster) return;
|
|
199
|
+
this.#roster.personas = this.#personas.map((p) => ({ id: p.id, label: p.label, category: p.category }));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── turns ───────────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
async send(text) {
|
|
205
|
+
if (!this.#chat || !this.#activePersona || !text) return;
|
|
206
|
+
this.#chat.appendMessage({ role: 'user', content: text });
|
|
207
|
+
this.#chat.appendMessage({ role: 'assistant', content: '' });
|
|
208
|
+
this.#chat.startStreaming();
|
|
209
|
+
const statusEl = this.#chat.querySelector('chat-status');
|
|
210
|
+
const events = this.#runTurn
|
|
211
|
+
? this.#runTurn(this.#session, text)
|
|
212
|
+
: this.#defaultRunTurn(text);
|
|
213
|
+
await wireAgentEvents(this.#chat, this.#surfaceRegistry, { events, statusEl });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Fresh-read-at-turn-time (law 7 / M-scope 3): every call re-reads the
|
|
217
|
+
* bound store and re-compiles — no cache, no propagation channel. */
|
|
218
|
+
async *#defaultRunTurn(text) {
|
|
219
|
+
const live = readLivePersona(this.#activePersona, this.#activeStore);
|
|
220
|
+
const compiled = compilePersona(live);
|
|
221
|
+
const { text: systemText } = composeSystem(compiled.prompt);
|
|
222
|
+
yield { type: 'progress', stage: 'started' };
|
|
223
|
+
yield {
|
|
224
|
+
type: 'text',
|
|
225
|
+
text: `[stub — persona "${live.label}" (${live.id})]\n${systemText}\n\nYou said: ${text}`,
|
|
226
|
+
};
|
|
227
|
+
yield { type: 'progress', stage: 'done' };
|
|
228
|
+
yield { type: 'done' };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ── event delegation ────────────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
#onPersonaChange = (e) => {
|
|
234
|
+
const id = e.detail?.personaId;
|
|
235
|
+
if (id) this.setActivePersona(id);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
#onPersonaImport = (e) => {
|
|
239
|
+
const persona = e.detail?.persona;
|
|
240
|
+
if (!persona) return;
|
|
241
|
+
this.#personas = [...this.#personas, persona];
|
|
242
|
+
this.#syncRosterSummaries();
|
|
243
|
+
this.setActivePersona(persona.id);
|
|
244
|
+
this.dispatchEvent(new CustomEvent('persona-imported', { bubbles: true, detail: { persona } }));
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
#onChatSubmit = (e) => {
|
|
248
|
+
const text = e.detail?.text;
|
|
249
|
+
if (text) this.send(text);
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!customElements.get('agent-admin-ui')) {
|
|
254
|
+
customElements.define('agent-admin-ui', UIAgentAdmin);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export { UIAgentAdmin };
|