@adia-ai/persona 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 +10 -0
- package/README.md +144 -0
- package/compile.d.ts +48 -0
- package/compile.d.ts.map +1 -0
- package/compile.js +93 -0
- package/compile.js.map +1 -0
- package/entry.d.ts +34 -0
- package/entry.d.ts.map +1 -0
- package/entry.js +40 -0
- package/entry.js.map +1 -0
- package/index.d.ts +19 -0
- package/index.d.ts.map +1 -0
- package/index.js +15 -0
- package/index.js.map +1 -0
- package/package.json +41 -0
- package/persona-file.d.ts +15 -0
- package/persona-file.d.ts.map +1 -0
- package/persona-file.js +93 -0
- package/persona-file.js.map +1 -0
- package/persona.d.ts +24 -0
- package/persona.d.ts.map +1 -0
- package/persona.js +25 -0
- package/persona.js.map +1 -0
- package/store.d.ts +42 -0
- package/store.d.ts.map +1 -0
- package/store.js +110 -0
- package/store.js.map +1 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.8.26] — 2026-08-05
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **`createMemoryStore().set()` no longer notifies on identical-value writes** (Object.is) — UI consumers echo rendered state back through `set()`, and a notifying no-op write closed that echo into a subscribe → re-render → set livelock ("signals: drain loop exceeded", caught by the v0.8.26 pre-flight).
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- **Initial release — `@adia-ai/persona`, the CHAT-HARNESS L2 package (WCH-2, gh#597).** The generic `Entry` primitive (law 7 — `kind: string`, `KNOWN_KINDS` for display grouping only, `sortedEnabled()`, `seedEntry()`/`seedEntries()`) and `Persona` (`seedVersion` + `entries[]` + optional `model`) with `definePersona()` validation (unique entry ids, `seedVersion >= 1`). `SettingsStore` contract + `createMemoryStore()` reference impl (localStorage when available, in-memory otherwise) and `personaStore()` (one store per persona id, persisted values win over seeds, a `seedVersion` bump forces exactly one reset). Pure `compilePersona(persona, opts?) → AgentConfig` compiling enabled entries into `@adia-ai/agent` prompt layers — `PRECEDENCE_SENTENCE` rides first (law 4), tool-kind entries stay tier-1 prose (`opts.integrations` passes tier-2 `ToolDef[]` through untouched, never built from an entry — law 5). `checkModalityNeutral()` warning-level lint (law 4) against a seeded dialect-term list plus a `*-ui` tag-name pattern. Versioned persona files (`exportPersona`/`importPersona`) with an enumerated key allowlist; import always mints a new, collision-safe persona id and never overwrites an existing preset.
|
|
10
|
+
- **Joins the `@adia-ai` lockstep roster (gh#607).** No longer ships `private: true` — `web-modules`' peerDependency on `@adia-ai/persona` (added alongside `@adia-ai/agent`) needs a resolvable published package; the roster join was deferred in #597/#579 and un-deferred once that peerDep made deferral unviable.
|
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# `@adia-ai/persona`
|
|
2
|
+
|
|
3
|
+
The CHAT-HARNESS L2 package — an agent as **DATA**. Six things a persona can
|
|
4
|
+
hold (prompt sections, skills, workflows, resources, tools, pattern-sources)
|
|
5
|
+
are six instantiations of ONE `Entry` shape, never six code paths
|
|
6
|
+
([CHAT-HARNESS.md](https://github.com/adiahealth/genui-system/blob/main/docs/CHAT-HARNESS.md)
|
|
7
|
+
law 7). This package renders nothing and executes nothing — it produces the
|
|
8
|
+
`AgentConfig` that [`@adia-ai/agent`](../agent/README.md) (L1) consumes.
|
|
9
|
+
|
|
10
|
+
> **Private** until the lockstep-roster decision (issue acceptance criterion
|
|
11
|
+
> 3) is made with the release seat — tracked as an open item on the PR that
|
|
12
|
+
> introduced this package.
|
|
13
|
+
|
|
14
|
+
## The shape
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
interface Entry {
|
|
18
|
+
id: string; kind: string; label: string; description?: string;
|
|
19
|
+
content: string; order: number; enabled: boolean; builtin: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface Persona {
|
|
23
|
+
id: string; label: string; category?: string;
|
|
24
|
+
seedVersion: number; // bump forces a one-time store reset
|
|
25
|
+
entries: Entry[];
|
|
26
|
+
model?: { provider?: string; model?: string };
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`kind` is a plain string. `KNOWN_KINDS` (`'prompt-section' | 'skill' |
|
|
31
|
+
'workflow' | 'resource' | 'tool' | 'pattern-source'`) exists for **display
|
|
32
|
+
grouping only** — a seventh kind is seed data, never a code change.
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
import {
|
|
38
|
+
seedEntries, definePersona, compilePersona, personaStore,
|
|
39
|
+
exportPersona, importPersona, checkModalityNeutral,
|
|
40
|
+
} from '@adia-ai/persona';
|
|
41
|
+
import { createAgent } from '@adia-ai/agent';
|
|
42
|
+
|
|
43
|
+
const persona = definePersona({
|
|
44
|
+
id: 'copilot', label: 'AdiaUI Copilot', seedVersion: 1,
|
|
45
|
+
entries: seedEntries([
|
|
46
|
+
{ id: 'identity', kind: 'prompt-section', label: 'Identity', content: 'You help build UI.' },
|
|
47
|
+
{ id: 'search', kind: 'skill', label: 'Search', content: 'Look things up before guessing.' },
|
|
48
|
+
]),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const config = compilePersona(persona); // pure — same input twice, same output
|
|
52
|
+
const agent = createAgent({ ...config, llm: { model: 'claude-sonnet-4-6', proxyUrl: '/api/chat' } });
|
|
53
|
+
|
|
54
|
+
const store = personaStore(persona); // persisted edits win over the seed
|
|
55
|
+
store.set('entry.identity.enabled', false);
|
|
56
|
+
|
|
57
|
+
const file = exportPersona(persona);
|
|
58
|
+
const copy = importPersona(file, ['copilot']); // mints 'copilot-imported', never overwrites
|
|
59
|
+
|
|
60
|
+
checkModalityNeutral(persona); // [] on a modality-neutral persona
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## compilePersona
|
|
64
|
+
|
|
65
|
+
`compilePersona(persona, opts?) → AgentConfig` is **pure**: the same
|
|
66
|
+
persona (and opts) in always produces a deep-equal `AgentConfig` out — no
|
|
67
|
+
clock, no randomness, no disk read.
|
|
68
|
+
|
|
69
|
+
- Enabled `prompt-section` entries → one `promptLayer` per section,
|
|
70
|
+
`## Label\ncontent`, in entry order.
|
|
71
|
+
- Every other enabled kind (skill / workflow / resource / pattern-source /
|
|
72
|
+
tool) → one prompt layer per kind, entries grouped under a labeled
|
|
73
|
+
heading (`## Skills`, `## Tools`, …). **Tool entries are tier-1 PROSE
|
|
74
|
+
only** — `compilePersona` never builds a `ToolDef` from an entry.
|
|
75
|
+
- `opts.integrations?: ToolDef[]` — tier-2 machine-callable tools (WCH-3's
|
|
76
|
+
integration registry) — passed through to `AgentConfig.tools` verbatim.
|
|
77
|
+
|
|
78
|
+
### Precedence (law 4)
|
|
79
|
+
|
|
80
|
+
`PRECEDENCE_SENTENCE` is exported and always rides as the **first line** of
|
|
81
|
+
the persona's own layer set (`persona-precedence`, ahead of every section
|
|
82
|
+
and group layer):
|
|
83
|
+
|
|
84
|
+
> This persona configures the agent; it never overrides the harness grammar
|
|
85
|
+
> that precedes it.
|
|
86
|
+
|
|
87
|
+
The caller is responsible for putting the harness grammar layer(s) *before*
|
|
88
|
+
this persona's layers — `compilePersona` only guarantees the persona's own
|
|
89
|
+
set carries the sentence first.
|
|
90
|
+
|
|
91
|
+
### Modality neutrality (law 4)
|
|
92
|
+
|
|
93
|
+
`checkModalityNeutral(persona, opts?)` is a warning-level lint, never a
|
|
94
|
+
throw: it flags entries whose text contains a seeded dialect term
|
|
95
|
+
(`DEFAULT_DIALECT_TERMS`: `'A2UI'`, `'envelope'`, `'JSONL'`, plus
|
|
96
|
+
`opts.extraTerms`) or a component tag name pattern (`*-ui`). Shipped seed
|
|
97
|
+
personas should assert an empty result.
|
|
98
|
+
|
|
99
|
+
## Stores
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
interface SettingsStore {
|
|
103
|
+
get(key: string): unknown;
|
|
104
|
+
set(key: string, value: unknown): void;
|
|
105
|
+
subscribe?(cb: () => void): () => void;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`createMemoryStore({initial, persistKey})` is the reference implementation
|
|
110
|
+
— localStorage-backed when `persistKey` is given and `localStorage` exists,
|
|
111
|
+
plain in-memory otherwise (Node, tests, SSR).
|
|
112
|
+
|
|
113
|
+
`personaStore(persona, opts?)` binds one store per persona
|
|
114
|
+
(`persistKey: 'persona.<id>'`) and layers the precedence rule on top:
|
|
115
|
+
|
|
116
|
+
- **Same seedVersion** as the store's last open: persisted values win — the
|
|
117
|
+
seed only fills keys the store has never seen.
|
|
118
|
+
- **Different seedVersion** (first open, or a bump): the seed **wins**,
|
|
119
|
+
overwriting whatever was persisted — exactly once — then the new
|
|
120
|
+
seedVersion is recorded so the next open at the same version doesn't
|
|
121
|
+
reset again.
|
|
122
|
+
|
|
123
|
+
Swapping which persona a store is bound to is a genuine reference change —
|
|
124
|
+
callers that key UI state off `store` identity get the reset-on-swap
|
|
125
|
+
behavior CHAT-HARNESS §Interfaces 5 describes for free.
|
|
126
|
+
|
|
127
|
+
## Persona files
|
|
128
|
+
|
|
129
|
+
`{kind:'persona', version:1, id, label, category?, seedVersion, entries,
|
|
130
|
+
model?}` — an ENUMERATED key allowlist; any other top-level key rejects the
|
|
131
|
+
import outright.
|
|
132
|
+
|
|
133
|
+
`exportPersona(persona) → string` — pretty-printed JSON.
|
|
134
|
+
|
|
135
|
+
`importPersona(json, existingIds) → Persona` — validates, then always
|
|
136
|
+
**mints a new persona**: id becomes `<id>-imported` (or `-imported-2`, …
|
|
137
|
+
against `existingIds`), label gains `' (imported)'` — it never overwrites
|
|
138
|
+
an existing preset, even one sharing the file's id. Imported entries are
|
|
139
|
+
marked `builtin: false`.
|
|
140
|
+
|
|
141
|
+
## Testing
|
|
142
|
+
|
|
143
|
+
Tests run against the **built** output (`npm run build -w @adia-ai/persona`
|
|
144
|
+
first) — same convention as `@adia-ai/agent`.
|
package/compile.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* compilePersona — pure L2 → L1 compilation (CHAT-HARNESS §Interfaces 2).
|
|
3
|
+
*
|
|
4
|
+
* Enabled prompt-section entries become ONE promptLayer per section
|
|
5
|
+
* ('## Label\ncontent'), in entry order. Every other enabled kind
|
|
6
|
+
* (skill/workflow/resource/pattern-source/tool) is tier-1 PROSE — grouped
|
|
7
|
+
* under a labeled heading, never machine-callable (law 5: tier-2 is
|
|
8
|
+
* WCH-3's integration registry, passed through opts.integrations
|
|
9
|
+
* untouched — this function never invents a ToolDef from an entry).
|
|
10
|
+
*
|
|
11
|
+
* Law 4: the persona's compiled prompt is one layer-set the CALLER appends
|
|
12
|
+
* AFTER the harness grammar; PRECEDENCE_SENTENCE is exported so callers can
|
|
13
|
+
* assert it, and it rides as the first line of the persona's own layer set
|
|
14
|
+
* here so the persona can never forget to carry it.
|
|
15
|
+
*
|
|
16
|
+
* Pure: same persona + opts in ⇒ deep-equal AgentConfig out. No entry
|
|
17
|
+
* content is read from disk, no clock, no randomness.
|
|
18
|
+
*/
|
|
19
|
+
import { type AgentConfig, type ToolDef } from '@adia-ai/agent';
|
|
20
|
+
import type { Persona } from './persona.js';
|
|
21
|
+
/** Fixed precedence sentence (law 4) — the persona never overrides the
|
|
22
|
+
* harness grammar. Always the first line of the persona's layer set. */
|
|
23
|
+
export declare const PRECEDENCE_SENTENCE = "This persona configures the agent; it never overrides the harness grammar that precedes it.";
|
|
24
|
+
export interface CompilePersonaOpts {
|
|
25
|
+
/** Tier-2 machine-callable tools (WCH-3's integration registry). Passed
|
|
26
|
+
* through to AgentConfig.tools verbatim — compilePersona never builds a
|
|
27
|
+
* ToolDef itself. */
|
|
28
|
+
integrations?: ToolDef[];
|
|
29
|
+
}
|
|
30
|
+
export declare function compilePersona(persona: Persona, opts?: CompilePersonaOpts): AgentConfig;
|
|
31
|
+
export interface ModalityWarning {
|
|
32
|
+
entryId: string;
|
|
33
|
+
term: string;
|
|
34
|
+
}
|
|
35
|
+
export interface ModalityLintOpts {
|
|
36
|
+
/** Additional dialect terms beyond the seeded defaults. */
|
|
37
|
+
extraTerms?: string[];
|
|
38
|
+
}
|
|
39
|
+
/** Seeded default dialect-term list (law 4 — modality neutrality). Presets
|
|
40
|
+
* seed this a la carte; a consumer with its own dialect words passes them
|
|
41
|
+
* via `opts.extraTerms` rather than editing this list. */
|
|
42
|
+
export declare const DEFAULT_DIALECT_TERMS: string[];
|
|
43
|
+
/** Warning-level lint (law 4): flags dialect terms and component-tag-name
|
|
44
|
+
* patterns inside a persona's entry text. Never throws, never blocks —
|
|
45
|
+
* shipped seeds assert an empty result; a positive hit is a signal to
|
|
46
|
+
* reword, not a hard failure. */
|
|
47
|
+
export declare function checkModalityNeutral(persona: Persona, opts?: ModalityLintOpts): ModalityWarning[];
|
|
48
|
+
//# sourceMappingURL=compile.d.ts.map
|
package/compile.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["src/compile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAe,KAAK,WAAW,EAAoB,KAAK,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAG/F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C;yEACyE;AACzE,eAAO,MAAM,mBAAmB,gGAC+D,CAAC;AAmBhG,MAAM,WAAW,kBAAkB;IACjC;;0BAEsB;IACtB,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC;CAC1B;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,kBAAuB,GAAG,WAAW,CA6B3F;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;2DAE2D;AAC3D,eAAO,MAAM,qBAAqB,UAAgC,CAAC;AAKnE;;;kCAGkC;AAClC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,gBAAqB,GAAG,eAAe,EAAE,CAiBrG"}
|
package/compile.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* compilePersona — pure L2 → L1 compilation (CHAT-HARNESS §Interfaces 2).
|
|
3
|
+
*
|
|
4
|
+
* Enabled prompt-section entries become ONE promptLayer per section
|
|
5
|
+
* ('## Label\ncontent'), in entry order. Every other enabled kind
|
|
6
|
+
* (skill/workflow/resource/pattern-source/tool) is tier-1 PROSE — grouped
|
|
7
|
+
* under a labeled heading, never machine-callable (law 5: tier-2 is
|
|
8
|
+
* WCH-3's integration registry, passed through opts.integrations
|
|
9
|
+
* untouched — this function never invents a ToolDef from an entry).
|
|
10
|
+
*
|
|
11
|
+
* Law 4: the persona's compiled prompt is one layer-set the CALLER appends
|
|
12
|
+
* AFTER the harness grammar; PRECEDENCE_SENTENCE is exported so callers can
|
|
13
|
+
* assert it, and it rides as the first line of the persona's own layer set
|
|
14
|
+
* here so the persona can never forget to carry it.
|
|
15
|
+
*
|
|
16
|
+
* Pure: same persona + opts in ⇒ deep-equal AgentConfig out. No entry
|
|
17
|
+
* content is read from disk, no clock, no randomness.
|
|
18
|
+
*/
|
|
19
|
+
import { promptLayer } from '@adia-ai/agent';
|
|
20
|
+
import { sortedEnabled } from './entry.js';
|
|
21
|
+
/** Fixed precedence sentence (law 4) — the persona never overrides the
|
|
22
|
+
* harness grammar. Always the first line of the persona's layer set. */
|
|
23
|
+
export const PRECEDENCE_SENTENCE = 'This persona configures the agent; it never overrides the harness grammar that precedes it.';
|
|
24
|
+
const GROUP_HEADING = {
|
|
25
|
+
skill: 'Skills',
|
|
26
|
+
workflow: 'Workflows',
|
|
27
|
+
resource: 'Resources',
|
|
28
|
+
'pattern-source': 'Pattern sources',
|
|
29
|
+
tool: 'Tools',
|
|
30
|
+
};
|
|
31
|
+
function groupHeading(kind) {
|
|
32
|
+
return GROUP_HEADING[kind] ?? kind;
|
|
33
|
+
}
|
|
34
|
+
function renderGroup(kind, entries) {
|
|
35
|
+
const body = entries.map(e => `### ${e.label}\n${e.content}`).join('\n\n');
|
|
36
|
+
return promptLayer(`persona-${kind}`, `## ${groupHeading(kind)}\n${body}`);
|
|
37
|
+
}
|
|
38
|
+
export function compilePersona(persona, opts = {}) {
|
|
39
|
+
const enabled = sortedEnabled(persona.entries);
|
|
40
|
+
const promptSections = enabled.filter(e => e.kind === 'prompt-section');
|
|
41
|
+
const grouped = enabled.filter(e => e.kind !== 'prompt-section');
|
|
42
|
+
const layers = [promptLayer('persona-precedence', PRECEDENCE_SENTENCE)];
|
|
43
|
+
for (const entry of promptSections) {
|
|
44
|
+
layers.push(promptLayer(`persona-section-${entry.id}`, `## ${entry.label}\n${entry.content}`));
|
|
45
|
+
}
|
|
46
|
+
const byKind = new Map();
|
|
47
|
+
for (const entry of grouped) {
|
|
48
|
+
const list = byKind.get(entry.kind);
|
|
49
|
+
if (list)
|
|
50
|
+
list.push(entry);
|
|
51
|
+
else
|
|
52
|
+
byKind.set(entry.kind, [entry]);
|
|
53
|
+
}
|
|
54
|
+
for (const [kind, entries] of byKind) {
|
|
55
|
+
layers.push(renderGroup(kind, entries));
|
|
56
|
+
}
|
|
57
|
+
const config = {
|
|
58
|
+
llm: { ...(persona.model ?? {}) },
|
|
59
|
+
prompt: layers,
|
|
60
|
+
};
|
|
61
|
+
if (opts.integrations && opts.integrations.length > 0) {
|
|
62
|
+
config.tools = opts.integrations;
|
|
63
|
+
}
|
|
64
|
+
return config;
|
|
65
|
+
}
|
|
66
|
+
/** Seeded default dialect-term list (law 4 — modality neutrality). Presets
|
|
67
|
+
* seed this a la carte; a consumer with its own dialect words passes them
|
|
68
|
+
* via `opts.extraTerms` rather than editing this list. */
|
|
69
|
+
export const DEFAULT_DIALECT_TERMS = ['A2UI', 'envelope', 'JSONL'];
|
|
70
|
+
/** Component tag names — `foo-ui`, `data-table-ui`, … */
|
|
71
|
+
const TAG_PATTERN = /\b[a-z][a-z0-9]*-ui\b/gi;
|
|
72
|
+
/** Warning-level lint (law 4): flags dialect terms and component-tag-name
|
|
73
|
+
* patterns inside a persona's entry text. Never throws, never blocks —
|
|
74
|
+
* shipped seeds assert an empty result; a positive hit is a signal to
|
|
75
|
+
* reword, not a hard failure. */
|
|
76
|
+
export function checkModalityNeutral(persona, opts = {}) {
|
|
77
|
+
const terms = [...DEFAULT_DIALECT_TERMS, ...(opts.extraTerms ?? [])];
|
|
78
|
+
const warnings = [];
|
|
79
|
+
for (const entry of persona.entries) {
|
|
80
|
+
const haystack = `${entry.label}\n${entry.description ?? ''}\n${entry.content}`;
|
|
81
|
+
const lower = haystack.toLowerCase();
|
|
82
|
+
for (const term of terms) {
|
|
83
|
+
if (lower.includes(term.toLowerCase())) {
|
|
84
|
+
warnings.push({ entryId: entry.id, term });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const match of haystack.matchAll(TAG_PATTERN)) {
|
|
88
|
+
warnings.push({ entryId: entry.id, term: match[0] });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return warnings;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=compile.js.map
|
package/compile.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.js","sourceRoot":"","sources":["src/compile.ts"],"names":[],"mappings":"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"}
|
package/entry.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry — the ONE generic primitive (CHAT-HARNESS law 7).
|
|
3
|
+
*
|
|
4
|
+
* Prompt sections, skills, workflows, resources, tools, pattern-sources are
|
|
5
|
+
* six INSTANTIATIONS of this one shape, not six code paths. `kind` is a
|
|
6
|
+
* plain string — KNOWN_KINDS exists for display grouping only; nothing in
|
|
7
|
+
* this package (or its caller) is allowed to switch on it for logic. A
|
|
8
|
+
* seventh kind is seed data, never a code change.
|
|
9
|
+
*/
|
|
10
|
+
export interface Entry {
|
|
11
|
+
id: string;
|
|
12
|
+
kind: string;
|
|
13
|
+
label: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
content: string;
|
|
16
|
+
order: number;
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
builtin: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** The six kinds this suite ships today — DISPLAY grouping only. */
|
|
21
|
+
export declare const KNOWN_KINDS: readonly ["prompt-section", "skill", "workflow", "resource", "tool", "pattern-source"];
|
|
22
|
+
export type KnownKind = (typeof KNOWN_KINDS)[number];
|
|
23
|
+
/** Enabled entries, in composition order. The only order compilePersona
|
|
24
|
+
* (or any consumer) should ever read entries in. */
|
|
25
|
+
export declare function sortedEnabled(entries: Entry[]): Entry[];
|
|
26
|
+
export type SeedEntryInput = Omit<Entry, 'order' | 'enabled' | 'builtin'> & Partial<Pick<Entry, 'order' | 'enabled' | 'builtin'>>;
|
|
27
|
+
/** One seed entry with sane defaults: enabled, builtin, order 0 unless
|
|
28
|
+
* given. Presets author entries with this rather than the raw shape. */
|
|
29
|
+
export declare function seedEntry(entry: SeedEntryInput): Entry;
|
|
30
|
+
/** A whole seed list at once — entries that omit `order` get one assigned
|
|
31
|
+
* by their position in the array, so a preset author never hand-numbers a
|
|
32
|
+
* long list. Entries that DO specify `order` keep it untouched. */
|
|
33
|
+
export declare function seedEntries(entries: SeedEntryInput[]): Entry[];
|
|
34
|
+
//# sourceMappingURL=entry.d.ts.map
|
package/entry.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["src/entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,oEAAoE;AACpE,eAAO,MAAM,WAAW,wFAOd,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD;qDACqD;AACrD,wBAAgB,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CAEvD;AAED,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC,GACvE,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC;AAExD;yEACyE;AACzE,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,KAAK,CAOtD;AAED;;oEAEoE;AACpE,wBAAgB,WAAW,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,KAAK,EAAE,CAE9D"}
|
package/entry.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry — the ONE generic primitive (CHAT-HARNESS law 7).
|
|
3
|
+
*
|
|
4
|
+
* Prompt sections, skills, workflows, resources, tools, pattern-sources are
|
|
5
|
+
* six INSTANTIATIONS of this one shape, not six code paths. `kind` is a
|
|
6
|
+
* plain string — KNOWN_KINDS exists for display grouping only; nothing in
|
|
7
|
+
* this package (or its caller) is allowed to switch on it for logic. A
|
|
8
|
+
* seventh kind is seed data, never a code change.
|
|
9
|
+
*/
|
|
10
|
+
/** The six kinds this suite ships today — DISPLAY grouping only. */
|
|
11
|
+
export const KNOWN_KINDS = [
|
|
12
|
+
'prompt-section',
|
|
13
|
+
'skill',
|
|
14
|
+
'workflow',
|
|
15
|
+
'resource',
|
|
16
|
+
'tool',
|
|
17
|
+
'pattern-source',
|
|
18
|
+
];
|
|
19
|
+
/** Enabled entries, in composition order. The only order compilePersona
|
|
20
|
+
* (or any consumer) should ever read entries in. */
|
|
21
|
+
export function sortedEnabled(entries) {
|
|
22
|
+
return entries.filter(e => e.enabled).sort((a, b) => a.order - b.order);
|
|
23
|
+
}
|
|
24
|
+
/** One seed entry with sane defaults: enabled, builtin, order 0 unless
|
|
25
|
+
* given. Presets author entries with this rather than the raw shape. */
|
|
26
|
+
export function seedEntry(entry) {
|
|
27
|
+
return {
|
|
28
|
+
order: 0,
|
|
29
|
+
enabled: true,
|
|
30
|
+
builtin: true,
|
|
31
|
+
...entry,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** A whole seed list at once — entries that omit `order` get one assigned
|
|
35
|
+
* by their position in the array, so a preset author never hand-numbers a
|
|
36
|
+
* long list. Entries that DO specify `order` keep it untouched. */
|
|
37
|
+
export function seedEntries(entries) {
|
|
38
|
+
return entries.map((entry, i) => seedEntry({ order: i, ...entry }));
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=entry.js.map
|
package/entry.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["src/entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,oEAAoE;AACpE,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,gBAAgB;IAChB,OAAO;IACP,UAAU;IACV,UAAU;IACV,MAAM;IACN,gBAAgB;CACR,CAAC;AAIX;qDACqD;AACrD,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;AAC1E,CAAC;AAKD;yEACyE;AACzE,MAAM,UAAU,SAAS,CAAC,KAAqB;IAC7C,OAAO;QACL,KAAK,EAAE,CAAC;QACR,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,IAAI;QACb,GAAG,KAAK;KACT,CAAC;AACJ,CAAC;AAED;;oEAEoE;AACpE,MAAM,UAAU,WAAW,CAAC,OAAyB;IACnD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC;AACtE,CAAC"}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @adia-ai/persona — CHAT-HARNESS L2: an agent as DATA.
|
|
3
|
+
*
|
|
4
|
+
* Entry (six kinds, one shape) + Persona (seedVersion + entries) +
|
|
5
|
+
* SettingsStore (per-persona persistence) + compilePersona (pure, →
|
|
6
|
+
* @adia-ai/agent's AgentConfig) + versioned persona files. Renders
|
|
7
|
+
* nothing, executes nothing — L1 (@adia-ai/agent) and L4 (an admin
|
|
8
|
+
* composite) are the consumers.
|
|
9
|
+
*/
|
|
10
|
+
export { KNOWN_KINDS, seedEntry, seedEntries, sortedEnabled } from './entry.js';
|
|
11
|
+
export type { Entry, KnownKind, SeedEntryInput } from './entry.js';
|
|
12
|
+
export { definePersona } from './persona.js';
|
|
13
|
+
export type { Persona } from './persona.js';
|
|
14
|
+
export { createMemoryStore, personaStore } from './store.js';
|
|
15
|
+
export type { MemoryStoreOpts, PersonaStoreOpts, SettingsStore } from './store.js';
|
|
16
|
+
export { checkModalityNeutral, compilePersona, DEFAULT_DIALECT_TERMS, PRECEDENCE_SENTENCE } from './compile.js';
|
|
17
|
+
export type { CompilePersonaOpts, ModalityLintOpts, ModalityWarning } from './compile.js';
|
|
18
|
+
export { exportPersona, importPersona, PERSONA_FILE_KIND, PERSONA_FILE_VERSION } from './persona-file.js';
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
package/index.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChF,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEnE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC7D,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEnF,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAChH,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE1F,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @adia-ai/persona — CHAT-HARNESS L2: an agent as DATA.
|
|
3
|
+
*
|
|
4
|
+
* Entry (six kinds, one shape) + Persona (seedVersion + entries) +
|
|
5
|
+
* SettingsStore (per-persona persistence) + compilePersona (pure, →
|
|
6
|
+
* @adia-ai/agent's AgentConfig) + versioned persona files. Renders
|
|
7
|
+
* nothing, executes nothing — L1 (@adia-ai/agent) and L4 (an admin
|
|
8
|
+
* composite) are the consumers.
|
|
9
|
+
*/
|
|
10
|
+
export { KNOWN_KINDS, seedEntry, seedEntries, sortedEnabled } from './entry.js';
|
|
11
|
+
export { definePersona } from './persona.js';
|
|
12
|
+
export { createMemoryStore, personaStore } from './store.js';
|
|
13
|
+
export { checkModalityNeutral, compilePersona, DEFAULT_DIALECT_TERMS, PRECEDENCE_SENTENCE } from './compile.js';
|
|
14
|
+
export { exportPersona, importPersona, PERSONA_FILE_KIND, PERSONA_FILE_VERSION } from './persona-file.js';
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
package/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGhF,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG7C,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG7D,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGhH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adia-ai/persona",
|
|
3
|
+
"version": "0.8.26",
|
|
4
|
+
"description": "Agent-as-data (CHAT-HARNESS L2): Entry primitive, Persona + SettingsStore contracts, pure compilePersona → @adia-ai/agent AgentConfig, versioned persona file import/export.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./index.d.ts",
|
|
9
|
+
"import": "./index.js",
|
|
10
|
+
"default": "./index.js"
|
|
11
|
+
},
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"*.js",
|
|
16
|
+
"*.d.ts",
|
|
17
|
+
"*.d.ts.map",
|
|
18
|
+
"*.js.map",
|
|
19
|
+
"README.md",
|
|
20
|
+
"CHANGELOG.md",
|
|
21
|
+
"!**/*.test.js"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc --build tsconfig.build.json",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@adia-ai/agent": "^0.8.0"
|
|
29
|
+
},
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public",
|
|
33
|
+
"registry": "https://registry.npmjs.org"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/adiahealth/gen-ui-kit.git",
|
|
38
|
+
"directory": "packages/persona"
|
|
39
|
+
},
|
|
40
|
+
"license": "MIT"
|
|
41
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona files — versioned export/import (CHAT-HARNESS §Interfaces 1).
|
|
3
|
+
*
|
|
4
|
+
* `{kind:'persona', version:1, ...}` with an ENUMERATED key allowlist:
|
|
5
|
+
* anything else in the JSON is a rejection, not a silently-dropped field.
|
|
6
|
+
* Import always MINTS a new persona (collision-safe id suffix, label
|
|
7
|
+
* " (imported)") — it never overwrites an existing preset, even one with
|
|
8
|
+
* the same id in the file.
|
|
9
|
+
*/
|
|
10
|
+
import { type Persona } from './persona.js';
|
|
11
|
+
export declare const PERSONA_FILE_KIND = "persona";
|
|
12
|
+
export declare const PERSONA_FILE_VERSION = 1;
|
|
13
|
+
export declare function exportPersona(persona: Persona): string;
|
|
14
|
+
export declare function importPersona(json: string, existingIds: Iterable<string>): Persona;
|
|
15
|
+
//# sourceMappingURL=persona-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persona-file.d.ts","sourceRoot":"","sources":["src/persona-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAE3D,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAC3C,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAItC,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAYtD;AAcD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAsDlF"}
|
package/persona-file.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona files — versioned export/import (CHAT-HARNESS §Interfaces 1).
|
|
3
|
+
*
|
|
4
|
+
* `{kind:'persona', version:1, ...}` with an ENUMERATED key allowlist:
|
|
5
|
+
* anything else in the JSON is a rejection, not a silently-dropped field.
|
|
6
|
+
* Import always MINTS a new persona (collision-safe id suffix, label
|
|
7
|
+
* " (imported)") — it never overwrites an existing preset, even one with
|
|
8
|
+
* the same id in the file.
|
|
9
|
+
*/
|
|
10
|
+
import { definePersona } from './persona.js';
|
|
11
|
+
export const PERSONA_FILE_KIND = 'persona';
|
|
12
|
+
export const PERSONA_FILE_VERSION = 1;
|
|
13
|
+
const ALLOWED_KEYS = new Set(['kind', 'version', 'id', 'label', 'category', 'seedVersion', 'entries', 'model']);
|
|
14
|
+
export function exportPersona(persona) {
|
|
15
|
+
const file = {
|
|
16
|
+
kind: PERSONA_FILE_KIND,
|
|
17
|
+
version: PERSONA_FILE_VERSION,
|
|
18
|
+
id: persona.id,
|
|
19
|
+
label: persona.label,
|
|
20
|
+
seedVersion: persona.seedVersion,
|
|
21
|
+
entries: persona.entries,
|
|
22
|
+
};
|
|
23
|
+
if (persona.category !== undefined)
|
|
24
|
+
file['category'] = persona.category;
|
|
25
|
+
if (persona.model !== undefined)
|
|
26
|
+
file['model'] = persona.model;
|
|
27
|
+
return JSON.stringify(file, null, 2);
|
|
28
|
+
}
|
|
29
|
+
/** Mint a collision-safe id: `<base>-imported`, then `<base>-imported-2`,
|
|
30
|
+
* `-3`, … against the given existing id set. */
|
|
31
|
+
function mintImportedId(baseId, existingIds) {
|
|
32
|
+
let candidate = `${baseId}-imported`;
|
|
33
|
+
let n = 2;
|
|
34
|
+
while (existingIds.has(candidate)) {
|
|
35
|
+
candidate = `${baseId}-imported-${n}`;
|
|
36
|
+
n += 1;
|
|
37
|
+
}
|
|
38
|
+
return candidate;
|
|
39
|
+
}
|
|
40
|
+
export function importPersona(json, existingIds) {
|
|
41
|
+
let parsed;
|
|
42
|
+
try {
|
|
43
|
+
parsed = JSON.parse(json);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
throw new Error(`importPersona: invalid JSON — ${err.message}`);
|
|
47
|
+
}
|
|
48
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
49
|
+
throw new Error('importPersona: expected a JSON object');
|
|
50
|
+
}
|
|
51
|
+
const obj = parsed;
|
|
52
|
+
const unknownKeys = Object.keys(obj).filter(k => !ALLOWED_KEYS.has(k));
|
|
53
|
+
if (unknownKeys.length > 0) {
|
|
54
|
+
throw new Error(`importPersona: unknown key(s): ${unknownKeys.join(', ')}`);
|
|
55
|
+
}
|
|
56
|
+
if (obj['kind'] !== PERSONA_FILE_KIND) {
|
|
57
|
+
throw new Error(`importPersona: expected kind "${PERSONA_FILE_KIND}", got ${JSON.stringify(obj['kind'])}`);
|
|
58
|
+
}
|
|
59
|
+
if (obj['version'] !== PERSONA_FILE_VERSION) {
|
|
60
|
+
throw new Error(`importPersona: unsupported version ${JSON.stringify(obj['version'])}`);
|
|
61
|
+
}
|
|
62
|
+
if (typeof obj['id'] !== 'string' || !obj['id']) {
|
|
63
|
+
throw new Error('importPersona: "id" must be a non-empty string');
|
|
64
|
+
}
|
|
65
|
+
if (typeof obj['label'] !== 'string' || !obj['label']) {
|
|
66
|
+
throw new Error('importPersona: "label" must be a non-empty string');
|
|
67
|
+
}
|
|
68
|
+
if (typeof obj['seedVersion'] !== 'number') {
|
|
69
|
+
throw new Error('importPersona: "seedVersion" must be a number');
|
|
70
|
+
}
|
|
71
|
+
if (!Array.isArray(obj['entries'])) {
|
|
72
|
+
throw new Error('importPersona: "entries" must be an array');
|
|
73
|
+
}
|
|
74
|
+
const existing = new Set(existingIds);
|
|
75
|
+
const id = mintImportedId(obj['id'], existing);
|
|
76
|
+
const persona = {
|
|
77
|
+
id,
|
|
78
|
+
label: `${obj['label']} (imported)`,
|
|
79
|
+
seedVersion: obj['seedVersion'],
|
|
80
|
+
// Imported entries are never builtin — they came from a file, not a
|
|
81
|
+
// shipped preset — everything else on each entry rides through as-is.
|
|
82
|
+
entries: obj['entries'].map(entry => ({ ...entry, builtin: false })),
|
|
83
|
+
};
|
|
84
|
+
const category = obj['category'];
|
|
85
|
+
if (typeof category === 'string')
|
|
86
|
+
persona.category = category;
|
|
87
|
+
const model = obj['model'];
|
|
88
|
+
if (model && typeof model === 'object' && !Array.isArray(model)) {
|
|
89
|
+
persona.model = model;
|
|
90
|
+
}
|
|
91
|
+
return definePersona(persona);
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=persona-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persona-file.js","sourceRoot":"","sources":["src/persona-file.ts"],"names":[],"mappings":"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"}
|
package/persona.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona — an agent as DATA (CHAT-HARNESS §Interfaces 1).
|
|
3
|
+
*
|
|
4
|
+
* A persona is a bag of Entry rows plus the two axes that aren't entries:
|
|
5
|
+
* seedVersion (forces a one-time store reset — see store.ts) and an
|
|
6
|
+
* optional model hint. `definePersona` validates the invariants a bad seed
|
|
7
|
+
* would otherwise violate silently: unique entry ids, seedVersion >= 1.
|
|
8
|
+
*/
|
|
9
|
+
import type { Entry } from './entry.js';
|
|
10
|
+
export interface Persona {
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
category?: string;
|
|
14
|
+
/** Bumping this forces every per-persona store to reset persisted edits
|
|
15
|
+
* back to the seed once (store.ts personaStore). */
|
|
16
|
+
seedVersion: number;
|
|
17
|
+
entries: Entry[];
|
|
18
|
+
model?: {
|
|
19
|
+
provider?: string;
|
|
20
|
+
model?: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export declare function definePersona(persona: Persona): Persona;
|
|
24
|
+
//# sourceMappingURL=persona.d.ts.map
|
package/persona.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persona.d.ts","sourceRoot":"","sources":["src/persona.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;yDACqD;IACrD,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,KAAK,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAevD"}
|
package/persona.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona — an agent as DATA (CHAT-HARNESS §Interfaces 1).
|
|
3
|
+
*
|
|
4
|
+
* A persona is a bag of Entry rows plus the two axes that aren't entries:
|
|
5
|
+
* seedVersion (forces a one-time store reset — see store.ts) and an
|
|
6
|
+
* optional model hint. `definePersona` validates the invariants a bad seed
|
|
7
|
+
* would otherwise violate silently: unique entry ids, seedVersion >= 1.
|
|
8
|
+
*/
|
|
9
|
+
export function definePersona(persona) {
|
|
10
|
+
if (!persona.id) {
|
|
11
|
+
throw new Error('definePersona: "id" is required');
|
|
12
|
+
}
|
|
13
|
+
if (!Number.isInteger(persona.seedVersion) || persona.seedVersion < 1) {
|
|
14
|
+
throw new Error(`definePersona "${persona.id}": seedVersion must be an integer >= 1, got ${persona.seedVersion}`);
|
|
15
|
+
}
|
|
16
|
+
const seen = new Set();
|
|
17
|
+
for (const entry of persona.entries) {
|
|
18
|
+
if (seen.has(entry.id)) {
|
|
19
|
+
throw new Error(`definePersona "${persona.id}": duplicate entry id "${entry.id}"`);
|
|
20
|
+
}
|
|
21
|
+
seen.add(entry.id);
|
|
22
|
+
}
|
|
23
|
+
return persona;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=persona.js.map
|
package/persona.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persona.js","sourceRoot":"","sources":["src/persona.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,kBAAkB,OAAO,CAAC,EAAE,+CAA+C,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IACpH,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,kBAAkB,OAAO,CAAC,EAAE,0BAA0B,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;QACrF,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/store.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SettingsStore — the L2 persistence seam (CHAT-HARNESS §Interfaces 5).
|
|
3
|
+
*
|
|
4
|
+
* `createMemoryStore` is the reference implementation: localStorage-backed
|
|
5
|
+
* when available (browser), plain in-memory otherwise (Node, tests, SSR).
|
|
6
|
+
* `personaStore` layers the per-persona precedence rule on top of any
|
|
7
|
+
* SettingsStore factory: persisted values win over seed data across round
|
|
8
|
+
* trips, and a seedVersion bump forces exactly one reset back to the seed.
|
|
9
|
+
*/
|
|
10
|
+
import type { Persona } from './persona.js';
|
|
11
|
+
export interface SettingsStore {
|
|
12
|
+
get(key: string): unknown;
|
|
13
|
+
set(key: string, value: unknown): void;
|
|
14
|
+
subscribe?(cb: () => void): () => void;
|
|
15
|
+
}
|
|
16
|
+
export interface MemoryStoreOpts {
|
|
17
|
+
initial?: Record<string, unknown>;
|
|
18
|
+
/** When set and `localStorage` exists, reads/writes persist there under
|
|
19
|
+
* this key (one JSON blob). Absent — or no localStorage — stays in
|
|
20
|
+
* memory for the life of the store object. */
|
|
21
|
+
persistKey?: string;
|
|
22
|
+
}
|
|
23
|
+
/** Reference SettingsStore: localStorage when available and a persistKey is
|
|
24
|
+
* given, in-memory otherwise. `subscribe` fires on every `set`, including
|
|
25
|
+
* ones that don't change the value — callers debounce if they need to. */
|
|
26
|
+
export declare function createMemoryStore(opts?: MemoryStoreOpts): SettingsStore;
|
|
27
|
+
export interface PersonaStoreOpts {
|
|
28
|
+
/** Store factory to bind — defaults to createMemoryStore. Tests inject a
|
|
29
|
+
* bare in-memory factory to avoid localStorage entirely. */
|
|
30
|
+
createStore?: (opts: MemoryStoreOpts) => SettingsStore;
|
|
31
|
+
}
|
|
32
|
+
/** One SettingsStore bound to one persona (persistKey `persona.<id>`).
|
|
33
|
+
*
|
|
34
|
+
* Precedence (CHAT-HARNESS §Interfaces 5):
|
|
35
|
+
* - Same seedVersion as last time this store was opened: persisted values
|
|
36
|
+
* win — the seed only fills keys the store has never seen.
|
|
37
|
+
* - Different seedVersion (first open, or a bump): the seed WINS — every
|
|
38
|
+
* seed key is written, overwriting whatever was persisted — exactly
|
|
39
|
+
* once, then the new seedVersion is recorded so the next open at the
|
|
40
|
+
* same version doesn't reset again. */
|
|
41
|
+
export declare function personaStore(persona: Persona, opts?: PersonaStoreOpts): SettingsStore;
|
|
42
|
+
//# sourceMappingURL=store.d.ts.map
|
package/store.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["src/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC,SAAS,CAAC,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC;;mDAE+C;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD;;2EAE2E;AAC3E,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,eAAoB,GAAG,aAAa,CA+C3E;AAED,MAAM,WAAW,gBAAgB;IAC/B;iEAC6D;IAC7D,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,aAAa,CAAC;CACxD;AAiBD;;;;;;;;0CAQ0C;AAC1C,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,gBAAqB,GAAG,aAAa,CAezF"}
|
package/store.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SettingsStore — the L2 persistence seam (CHAT-HARNESS §Interfaces 5).
|
|
3
|
+
*
|
|
4
|
+
* `createMemoryStore` is the reference implementation: localStorage-backed
|
|
5
|
+
* when available (browser), plain in-memory otherwise (Node, tests, SSR).
|
|
6
|
+
* `personaStore` layers the per-persona precedence rule on top of any
|
|
7
|
+
* SettingsStore factory: persisted values win over seed data across round
|
|
8
|
+
* trips, and a seedVersion bump forces exactly one reset back to the seed.
|
|
9
|
+
*/
|
|
10
|
+
function hasLocalStorage() {
|
|
11
|
+
try {
|
|
12
|
+
return typeof localStorage !== 'undefined';
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** Reference SettingsStore: localStorage when available and a persistKey is
|
|
19
|
+
* given, in-memory otherwise. `subscribe` fires on every `set`, including
|
|
20
|
+
* ones that don't change the value — callers debounce if they need to. */
|
|
21
|
+
export function createMemoryStore(opts = {}) {
|
|
22
|
+
const persistKey = opts.persistKey;
|
|
23
|
+
const persistent = !!persistKey && hasLocalStorage();
|
|
24
|
+
let data = { ...(opts.initial ?? {}) };
|
|
25
|
+
if (persistent) {
|
|
26
|
+
try {
|
|
27
|
+
const raw = localStorage.getItem(persistKey);
|
|
28
|
+
if (raw)
|
|
29
|
+
data = { ...data, ...JSON.parse(raw) };
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Corrupt or inaccessible storage — fall back to `initial` only.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const subscribers = new Set();
|
|
36
|
+
function persist() {
|
|
37
|
+
if (!persistent)
|
|
38
|
+
return;
|
|
39
|
+
try {
|
|
40
|
+
localStorage.setItem(persistKey, JSON.stringify(data));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Quota/denied — the in-memory copy still holds for this session.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
get(key) {
|
|
48
|
+
return data[key];
|
|
49
|
+
},
|
|
50
|
+
set(key, value) {
|
|
51
|
+
// A write of the identical value must NOT notify: UI consumers echo
|
|
52
|
+
// rendered state back through set() (a render assigns .checked, the
|
|
53
|
+
// primitive emits change, the handler writes the same value) — and a
|
|
54
|
+
// notifying no-op write closes that echo into a subscribe → re-render
|
|
55
|
+
// → set loop (the v0.8.26 pre-flight's "signals: drain loop exceeded"
|
|
56
|
+
// livelock in admin-settings). Object.is matches the reactive kernel's
|
|
57
|
+
// own equality cutoff.
|
|
58
|
+
if (key in data && Object.is(data[key], value))
|
|
59
|
+
return;
|
|
60
|
+
data = { ...data, [key]: value };
|
|
61
|
+
persist();
|
|
62
|
+
for (const cb of subscribers)
|
|
63
|
+
cb();
|
|
64
|
+
},
|
|
65
|
+
subscribe(cb) {
|
|
66
|
+
subscribers.add(cb);
|
|
67
|
+
return () => subscribers.delete(cb);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const SEED_VERSION_KEY = '__seedVersion';
|
|
72
|
+
function entryKey(entryId, field) {
|
|
73
|
+
return `entry.${entryId}.${field}`;
|
|
74
|
+
}
|
|
75
|
+
function seedFromPersona(persona) {
|
|
76
|
+
const seed = {};
|
|
77
|
+
for (const entry of persona.entries) {
|
|
78
|
+
seed[entryKey(entry.id, 'enabled')] = entry.enabled;
|
|
79
|
+
seed[entryKey(entry.id, 'order')] = entry.order;
|
|
80
|
+
}
|
|
81
|
+
return seed;
|
|
82
|
+
}
|
|
83
|
+
/** One SettingsStore bound to one persona (persistKey `persona.<id>`).
|
|
84
|
+
*
|
|
85
|
+
* Precedence (CHAT-HARNESS §Interfaces 5):
|
|
86
|
+
* - Same seedVersion as last time this store was opened: persisted values
|
|
87
|
+
* win — the seed only fills keys the store has never seen.
|
|
88
|
+
* - Different seedVersion (first open, or a bump): the seed WINS — every
|
|
89
|
+
* seed key is written, overwriting whatever was persisted — exactly
|
|
90
|
+
* once, then the new seedVersion is recorded so the next open at the
|
|
91
|
+
* same version doesn't reset again. */
|
|
92
|
+
export function personaStore(persona, opts = {}) {
|
|
93
|
+
const createStore = opts.createStore ?? createMemoryStore;
|
|
94
|
+
const store = createStore({ persistKey: `persona.${persona.id}` });
|
|
95
|
+
const seed = seedFromPersona(persona);
|
|
96
|
+
const storedVersion = store.get(SEED_VERSION_KEY);
|
|
97
|
+
if (storedVersion !== persona.seedVersion) {
|
|
98
|
+
for (const [key, value] of Object.entries(seed))
|
|
99
|
+
store.set(key, value);
|
|
100
|
+
store.set(SEED_VERSION_KEY, persona.seedVersion);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
for (const [key, value] of Object.entries(seed)) {
|
|
104
|
+
if (store.get(key) === undefined)
|
|
105
|
+
store.set(key, value);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return store;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=store.js.map
|
package/store.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["src/store.ts"],"names":[],"mappings":"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"}
|