@stratusagent/agents 0.11.4 → 0.11.5

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.
Files changed (53) hide show
  1. package/dist/define.d.ts +22 -0
  2. package/dist/define.d.ts.map +1 -0
  3. package/dist/define.js +64 -0
  4. package/dist/define.js.map +1 -0
  5. package/dist/frontmatter.d.ts +65 -0
  6. package/dist/frontmatter.d.ts.map +1 -0
  7. package/dist/frontmatter.js +260 -0
  8. package/dist/frontmatter.js.map +1 -0
  9. package/dist/identity.d.ts +139 -0
  10. package/dist/identity.d.ts.map +1 -0
  11. package/dist/identity.js +259 -0
  12. package/dist/identity.js.map +1 -0
  13. package/dist/index.d.ts +11 -654
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +16 -1641
  16. package/dist/index.js.map +1 -1
  17. package/dist/router.d.ts +18 -0
  18. package/dist/router.d.ts.map +1 -0
  19. package/dist/router.js +44 -0
  20. package/dist/router.js.map +1 -0
  21. package/dist/schedules.d.ts +163 -0
  22. package/dist/schedules.d.ts.map +1 -0
  23. package/dist/schedules.js +215 -0
  24. package/dist/schedules.js.map +1 -0
  25. package/dist/skills.d.ts +135 -0
  26. package/dist/skills.d.ts.map +1 -0
  27. package/dist/skills.js +167 -0
  28. package/dist/skills.js.map +1 -0
  29. package/dist/soul.d.ts +44 -0
  30. package/dist/soul.d.ts.map +1 -0
  31. package/dist/soul.js +98 -0
  32. package/dist/soul.js.map +1 -0
  33. package/dist/tools/delegate.d.ts +93 -0
  34. package/dist/tools/delegate.d.ts.map +1 -0
  35. package/dist/tools/delegate.js +223 -0
  36. package/dist/tools/delegate.js.map +1 -0
  37. package/dist/tools/gateway-only.d.ts +19 -0
  38. package/dist/tools/gateway-only.d.ts.map +1 -0
  39. package/dist/tools/gateway-only.js +29 -0
  40. package/dist/tools/gateway-only.js.map +1 -0
  41. package/dist/tools/memory.d.ts +51 -0
  42. package/dist/tools/memory.d.ts.map +1 -0
  43. package/dist/tools/memory.js +306 -0
  44. package/dist/tools/memory.js.map +1 -0
  45. package/dist/tools/message.d.ts +26 -0
  46. package/dist/tools/message.d.ts.map +1 -0
  47. package/dist/tools/message.js +46 -0
  48. package/dist/tools/message.js.map +1 -0
  49. package/dist/tools/schedule.d.ts +20 -0
  50. package/dist/tools/schedule.d.ts.map +1 -0
  51. package/dist/tools/schedule.js +146 -0
  52. package/dist/tools/schedule.js.map +1 -0
  53. package/package.json +2 -2
@@ -0,0 +1,22 @@
1
+ import type { ListensMode, AgentDefinition, AvatarTheme } from '@stratusagent/core';
2
+ export interface DefineAgentInput {
3
+ name?: string;
4
+ id?: string;
5
+ instructions?: string;
6
+ tools?: string[];
7
+ skills?: string[];
8
+ credentials?: string[];
9
+ delegates?: string[];
10
+ /** See `AgentDefinition.listens`. */
11
+ listens?: ListensMode;
12
+ avatar?: AvatarTheme;
13
+ /** Seed for deterministic identity generation (used in tests). */
14
+ seed?: string;
15
+ }
16
+ /**
17
+ * One-call agent creation. Everything is optional: with no input you get a
18
+ * fresh identity with a human-ish name and a matching avatar theme.
19
+ * Explicitly named agents keep a clean, predictable slug id.
20
+ */
21
+ export declare const defineAgent: (input?: DefineAgentInput) => AgentDefinition;
22
+ //# sourceMappingURL=define.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAWpF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,qCAAqC;IACrC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,QAAO,gBAAqB,KAAG,eA8D1D,CAAC"}
package/dist/define.js ADDED
@@ -0,0 +1,64 @@
1
+ import { generateAgentName, generateAvatarTheme, slugify, isValidAgentId, isValidDelegateEntry, agentIdWithSuffix, generatedIdSuffix, } from "./identity.js";
2
+ /**
3
+ * One-call agent creation. Everything is optional: with no input you get a
4
+ * fresh identity with a human-ish name and a matching avatar theme.
5
+ * Explicitly named agents keep a clean, predictable slug id.
6
+ */
7
+ export const defineAgent = (input = {}) => {
8
+ const nameWasGenerated = input.name === undefined;
9
+ const name = input.name ?? generateAgentName(input.seed);
10
+ // Only an explicit id is checked: a derived one comes out of slugify,
11
+ // which cannot produce anything unsafe.
12
+ if (input.id === '*') {
13
+ // The wildcard was an accepted id until `delegates` gave it a meaning,
14
+ // so a soul written before then can still carry it. The path-safety
15
+ // message would be wrong for it — `*` breaks none of those rules — and
16
+ // the roster reports this error with the file's path, so name the real
17
+ // reason and the fix rather than leaving the operator to guess why an
18
+ // agent that loaded yesterday is skipped today.
19
+ throw new Error('Invalid agent id: "*". * is the delegates wildcard (delegates: [\'*\'] means any agent on the roster), '
20
+ + 'so no agent may have it as an id — give this agent another id. Its sessions, memory, and credentials '
21
+ + 'are keyed by the old id and stay on disk.');
22
+ }
23
+ if (input.id !== undefined && !isValidAgentId(input.id)) {
24
+ throw new Error(`Invalid agent id: ${JSON.stringify(input.id)}. An id becomes a path segment, so it may not start with `
25
+ + 'a dot or contain a slash, a backslash, a control character, or leading or trailing whitespace — '
26
+ + 'it keys files and credentials, not just labels.');
27
+ }
28
+ // Each entry is an id or the wildcard. Refused here, not in the tool: a
29
+ // list holding a value no agent can have would be a grant to nobody
30
+ // that read as a grant.
31
+ for (const entry of input.delegates ?? []) {
32
+ if (!isValidDelegateEntry(entry)) {
33
+ throw new Error(`Invalid delegates entry: ${JSON.stringify(entry)}. Each entry is an agent id, or * for any agent on the roster.`);
34
+ }
35
+ }
36
+ // A chosen name's slug is used whole. It is not this function's to
37
+ // shorten: the same name has resolved to the same id in every release,
38
+ // and two long names that differ only past the bound are two agents,
39
+ // not one roster-refusing collision.
40
+ const derived = nameWasGenerated
41
+ ? agentIdWithSuffix(slugify(name), generatedIdSuffix(input.seed))
42
+ : slugify(name);
43
+ return {
44
+ // Derived ids are safe by construction with one exception: `constructor`
45
+ // is a perfectly ordinary slug and an Object.prototype key, so the name
46
+ // "Constructor" reaches a rule that only ran on explicit ids. Checked
47
+ // rather than special-cased, so what this returns is *an id that
48
+ // validates* — not one that passes the cases anyone thought of. The
49
+ // suffix is seeded by the slug, so the name still answers the same way
50
+ // every time.
51
+ id: input.id ?? (isValidAgentId(derived)
52
+ ? derived
53
+ : agentIdWithSuffix(derived, generatedIdSuffix(derived))),
54
+ name,
55
+ ...(input.instructions ? { instructions: input.instructions } : {}),
56
+ avatar: input.avatar ?? generateAvatarTheme(name),
57
+ ...(input.tools ? { tools: input.tools } : {}),
58
+ ...(input.skills ? { skills: input.skills } : {}),
59
+ ...(input.credentials ? { credentials: input.credentials } : {}),
60
+ ...(input.delegates ? { delegates: input.delegates } : {}),
61
+ ...(input.listens ? { listens: input.listens } : {}),
62
+ };
63
+ };
64
+ //# sourceMappingURL=define.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define.js","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ export declare const unquote: (value: string) => string;
2
+ interface FrontmatterShape {
3
+ /** Lowercased document kind for error messages ("soul", "skill"). */
4
+ kind: string;
5
+ scalarKeys: readonly string[];
6
+ listKeys: readonly string[];
7
+ /**
8
+ * Keys whose value is a block of `key: value` string pairs (the Agent
9
+ * Skills spec's `metadata:`). Read only in tolerant mode, which is the
10
+ * only dialect that writes one.
11
+ */
12
+ mapKeys?: readonly string[];
13
+ /**
14
+ * Ignore what the shape does not name instead of refusing it — unknown
15
+ * keys, whatever indented block sits under one, and the YAML block and
16
+ * multi-line scalars other ecosystems write.
17
+ *
18
+ * Skills opt in: they travel an ecosystem whose frontmatter carries
19
+ * fields other hosts own (`license`, `metadata`, `allowed-tools`), and
20
+ * refusing those would refuse most published skills over metadata that
21
+ * changes nothing here. Souls stay strict, deliberately — an unknown
22
+ * soul key can be a typo'd allowlist, and a soul that loads with
23
+ * silently weaker access is the worse failure.
24
+ */
25
+ tolerant?: boolean;
26
+ }
27
+ export interface ParsedFrontmatter {
28
+ scalars: Partial<Record<string, string>>;
29
+ lists: Partial<Record<string, string[]>>;
30
+ maps: Partial<Record<string, Record<string, string>>>;
31
+ /**
32
+ * Top-level keys the shape does not name, in file order. Tolerant mode
33
+ * skips them; this is how a validator still gets to say which ones it
34
+ * skipped, so a misspelled field is a warning somebody reads instead of
35
+ * silence.
36
+ */
37
+ unknownKeys: string[];
38
+ }
39
+ export declare const parseFrontmatterLines: (lines: string[], shape: FrontmatterShape) => ParsedFrontmatter;
40
+ /**
41
+ * Split a markdown document into its `---`-fenced frontmatter lines (if
42
+ * any) and the body after them. Shared by souls and skills — one reading
43
+ * of what the fences mean, wherever the dialect appears.
44
+ */
45
+ export declare const extractFrontmatter: (source: string, kind: string) => {
46
+ lines: string[] | undefined;
47
+ body: string;
48
+ };
49
+ /**
50
+ * Render an agent definition as a soul file, ready to save and edit. The
51
+ * inverse of parseSoul for round-tripping `stratus agent new` output.
52
+ */
53
+ /**
54
+ * A scalar or list entry as the soul writer emits it. The parser strips
55
+ * one layer of matching quotes from every value, so a value that begins
56
+ * and ends with the same quote — an agent id like `'bea'`, which
57
+ * {@link isValidAgentId} allows — would come back as `bea`: for a
58
+ * `delegates` entry that is a permission changed by an unrelated edit,
59
+ * since the control API renders a soul through here on every field edit.
60
+ * Such a value is wrapped in the other quote, which the parser strips
61
+ * back off; so is one the parser's trim would alter.
62
+ */
63
+ export declare const writeScalar: (value: string) => string;
64
+ export {};
65
+ //# sourceMappingURL=frontmatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../src/frontmatter.ts"],"names":[],"mappings":"AAqCA,eAAO,MAAM,OAAO,GAAI,OAAO,MAAM,KAAG,MAUvC,CAAC;AAEF,UAAU,gBAAgB;IACxB,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACzC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IACzC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACtD;;;;;OAKG;IACH,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAOD,eAAO,MAAM,qBAAqB,GAAI,OAAO,MAAM,EAAE,EAAE,OAAO,gBAAgB,KAAG,iBA4LhF,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,MAAM,EACd,MAAM,MAAM,KACX;IAAE,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAe7C,CAAC;AAEF;;;GAGG;AACH;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,MAC8C,CAAC"}
@@ -0,0 +1,260 @@
1
+ /**
2
+ * The entries of an inline list, `[a, 'b,c', "d"]`, split on the commas
3
+ * that are outside quotes. Splitting on every comma and unquoting after
4
+ * turned `['foo,bar']` into the two entries `'foo` and `bar'` — for a
5
+ * `delegates` list, a grant to two agents nobody meant and none to the
6
+ * one they did, from a soul that loaded without complaint.
7
+ */
8
+ const splitInlineList = (inline) => {
9
+ const entries = [];
10
+ let current = '';
11
+ let quote;
12
+ for (const character of inline) {
13
+ // A quote opens an entry only at the entry's start (after any leading
14
+ // space): an apostrophe inside an unquoted id — o'brien — is part of
15
+ // the id, and reading it as an opening quote would swallow every comma
16
+ // after it into one entry no agent has.
17
+ if (quote === undefined && (character === '"' || character === "'") && current.trim().length === 0) {
18
+ quote = character;
19
+ }
20
+ else if (character === quote) {
21
+ quote = undefined;
22
+ }
23
+ else if (character === ',' && quote === undefined) {
24
+ entries.push(current);
25
+ current = '';
26
+ continue;
27
+ }
28
+ current += character;
29
+ }
30
+ // A quote left open would have swallowed every comma after it into one
31
+ // entry no agent has — for a delegates list, a typo that changes who is
32
+ // authorized while the soul loads without complaint. Refused instead.
33
+ if (quote !== undefined) {
34
+ return undefined;
35
+ }
36
+ entries.push(current);
37
+ return entries;
38
+ };
39
+ export const unquote = (value) => {
40
+ const trimmed = value.trim();
41
+ if (trimmed.length >= 2 &&
42
+ ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
43
+ (trimmed.startsWith("'") && trimmed.endsWith("'")))) {
44
+ return trimmed.slice(1, -1);
45
+ }
46
+ return trimmed;
47
+ };
48
+ // A deliberately tiny frontmatter dialect: `key: value` scalars and block
49
+ // lists of `- item` lines. Enough for souls and skills, no YAML dependency.
50
+ // Tolerant mode (see FrontmatterShape) additionally reads the block-scalar
51
+ // forms (`description: >-` and friends) and plain multi-line scalars, and
52
+ // skips unknown keys with their nested blocks.
53
+ export const parseFrontmatterLines = (lines, shape) => {
54
+ const scalars = {};
55
+ const lists = {};
56
+ const maps = {};
57
+ const unknownKeys = [];
58
+ const tolerant = shape.tolerant ?? false;
59
+ let currentList;
60
+ let currentMap;
61
+ // Tolerant-mode line context: an unknown key whose indented block is
62
+ // being skipped, or a known scalar whose value continues on indented
63
+ // lines (a YAML block scalar, or a plain scalar that wraps). Inside
64
+ // either, blank lines and lines starting with # are content or skipped
65
+ // block — never the comments the top level treats them as — so both are
66
+ // handled before the comment filter below.
67
+ let skippingUnknownBlock = false;
68
+ let continuation;
69
+ const isIndented = (line) => /^\s/.test(line);
70
+ for (const rawLine of lines) {
71
+ const line = rawLine.trimEnd();
72
+ const blank = line.trim().length === 0;
73
+ if (tolerant && skippingUnknownBlock && (blank || isIndented(line))) {
74
+ continue;
75
+ }
76
+ if (tolerant && continuation) {
77
+ if (blank) {
78
+ // A blank line is a line break in a literal block and a paragraph
79
+ // break in a folded or plain one — never the end of the value.
80
+ if (continuation.literal) {
81
+ const existing = scalars[continuation.key] ?? '';
82
+ if (existing.length > 0) {
83
+ scalars[continuation.key] = `${existing}\n`;
84
+ }
85
+ }
86
+ else {
87
+ continuation.pendingBreak = true;
88
+ }
89
+ continue;
90
+ }
91
+ if (isIndented(line)) {
92
+ const existing = scalars[continuation.key] ?? '';
93
+ const separator = continuation.literal || continuation.pendingBreak ? '\n' : ' ';
94
+ continuation.pendingBreak = false;
95
+ // The raw content, not unquote(): inside a block scalar a quote or
96
+ // a # is text, and YAML agrees.
97
+ const piece = line.trim();
98
+ scalars[continuation.key] = existing.length > 0 ? `${existing}${separator}${piece}` : piece;
99
+ continue;
100
+ }
101
+ // A non-indented line ends the value; fall through to read it.
102
+ }
103
+ if (blank || line.trim().startsWith('#')) {
104
+ continue;
105
+ }
106
+ if (currentMap && isIndented(line)) {
107
+ // One level of `key: value` pairs, every value a string — the
108
+ // spec's shape for `metadata`. Anything deeper is not a string
109
+ // value, and refusing it beats reading the wrong half of it.
110
+ // Any string is a key — quoted (`"vendor/key": v`), or bare up to
111
+ // the first colon — since the spec types the map as string to
112
+ // string and says nothing about what a key looks like.
113
+ const pair = /^\s+(?:"([^"]*)"|'([^']*)'|([^\s"'#][^:]*?))\s*:\s*(.*)$/.exec(line);
114
+ const mapKey = pair?.[1] ?? pair?.[2] ?? pair?.[3]?.trim();
115
+ if (!pair || mapKey === undefined || mapKey.length === 0 || pair[4] === undefined || unquote(pair[4]).length === 0) {
116
+ throw new Error(`${capitalize(shape.kind)} frontmatter metadata entries must be "key: value" strings, one per line: "${line.trim()}"`);
117
+ }
118
+ currentMap[mapKey] = unquote(pair[4]);
119
+ continue;
120
+ }
121
+ const listItem = /^\s+-\s*(.*)$/.exec(line);
122
+ if (listItem) {
123
+ if (!currentList) {
124
+ throw new Error(`${capitalize(shape.kind)} frontmatter has a list item outside a list: "${line.trim()}"`);
125
+ }
126
+ const item = unquote(listItem[1] ?? '');
127
+ if (item.length > 0) {
128
+ currentList.push(item);
129
+ }
130
+ continue;
131
+ }
132
+ const entry = /^([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(line);
133
+ if (!entry) {
134
+ throw new Error(`${capitalize(shape.kind)} frontmatter line is not "key: value": "${line.trim()}"`);
135
+ }
136
+ const key = entry[1] ?? '';
137
+ const value = entry[2] ?? '';
138
+ currentList = undefined;
139
+ currentMap = undefined;
140
+ skippingUnknownBlock = false;
141
+ continuation = undefined;
142
+ if (tolerant && shape.mapKeys?.includes(key)) {
143
+ const inline = value.trim();
144
+ if (inline.length > 0 && inline !== '{}') {
145
+ throw new Error(`${capitalize(shape.kind)} frontmatter "${key}" must be a block of indented "key: value" lines: "${inline}"`);
146
+ }
147
+ const map = {};
148
+ maps[key] = map;
149
+ currentMap = map;
150
+ continue;
151
+ }
152
+ if (shape.listKeys.includes(key)) {
153
+ const list = [];
154
+ lists[key] = list;
155
+ const inline = value.trim();
156
+ if (inline.length > 0) {
157
+ // Inline form: tools: [a, b]
158
+ const match = /^\[(.*)\]$/.exec(inline);
159
+ if (!match) {
160
+ throw new Error(`${capitalize(shape.kind)} frontmatter list "${key}" must be a block list or [a, b]: "${inline}"`);
161
+ }
162
+ const items = splitInlineList(match[1] ?? '');
163
+ if (items === undefined) {
164
+ throw new Error(`${capitalize(shape.kind)} frontmatter list "${key}" has an unterminated quote: "${inline}"`);
165
+ }
166
+ for (const item of items) {
167
+ const cleaned = unquote(item);
168
+ if (cleaned.length > 0) {
169
+ list.push(cleaned);
170
+ }
171
+ }
172
+ }
173
+ else {
174
+ currentList = list;
175
+ }
176
+ continue;
177
+ }
178
+ if (!shape.scalarKeys.includes(key)) {
179
+ if (tolerant) {
180
+ // The key and anything nested under it belong to some other host.
181
+ // Remembered, not refused: a validator reports them.
182
+ unknownKeys.push(key);
183
+ skippingUnknownBlock = true;
184
+ continue;
185
+ }
186
+ throw new Error(`Unknown ${shape.kind} frontmatter key: "${key}". Supported keys: ${[...shape.scalarKeys, ...shape.listKeys].join(', ')}.`);
187
+ }
188
+ const inline = value.trim();
189
+ // YAML block-scalar headers: `>` folds continuation lines with spaces,
190
+ // `|` keeps their line breaks; either may carry an indentation digit
191
+ // and a chomping sign in either order, and a trailing comment. Only
192
+ // meaningful in tolerant mode — the strict dialect never wrote them.
193
+ if (tolerant && /^[>|](?:[1-9][+-]?|[+-][1-9]?)?(?:\s+#.*)?$/.test(inline)) {
194
+ scalars[key] = '';
195
+ continuation = { key, literal: inline.startsWith('|'), pendingBreak: false };
196
+ continue;
197
+ }
198
+ const scalar = unquote(value);
199
+ if (scalar.length === 0) {
200
+ if (tolerant) {
201
+ // May be a plain scalar continuing on indented lines; empty stays
202
+ // empty and is dropped below if nothing follows.
203
+ scalars[key] = '';
204
+ continuation = { key, literal: false, pendingBreak: false };
205
+ continue;
206
+ }
207
+ throw new Error(`${capitalize(shape.kind)} frontmatter key "${key}" has no value.`);
208
+ }
209
+ scalars[key] = scalar;
210
+ if (tolerant) {
211
+ continuation = { key, literal: false, pendingBreak: false };
212
+ }
213
+ }
214
+ // A tolerant block scalar that never got a body reads as absent, not as
215
+ // an empty string that would satisfy a required-field check.
216
+ for (const [key, value] of Object.entries(scalars)) {
217
+ if (value !== undefined && value.length === 0) {
218
+ delete scalars[key];
219
+ }
220
+ }
221
+ return { scalars, lists, maps, unknownKeys };
222
+ };
223
+ const capitalize = (word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`;
224
+ /**
225
+ * Split a markdown document into its `---`-fenced frontmatter lines (if
226
+ * any) and the body after them. Shared by souls and skills — one reading
227
+ * of what the fences mean, wherever the dialect appears.
228
+ */
229
+ export const extractFrontmatter = (source, kind) => {
230
+ const normalized = source.replace(/\r\n/g, '\n');
231
+ const opener = /^---[ \t]*\n/.exec(normalized);
232
+ if (!opener) {
233
+ return { lines: undefined, body: normalized };
234
+ }
235
+ const closer = /\n---[ \t]*(\n|$)/.exec(normalized.slice(opener[0].length - 1));
236
+ if (!closer || closer.index === undefined) {
237
+ throw new Error(`${capitalize(kind)} frontmatter opened with --- but never closed.`);
238
+ }
239
+ const frontmatterEnd = opener[0].length - 1 + closer.index;
240
+ return {
241
+ lines: normalized.slice(opener[0].length, frontmatterEnd).split('\n'),
242
+ body: normalized.slice(frontmatterEnd + closer[0].length),
243
+ };
244
+ };
245
+ /**
246
+ * Render an agent definition as a soul file, ready to save and edit. The
247
+ * inverse of parseSoul for round-tripping `stratus agent new` output.
248
+ */
249
+ /**
250
+ * A scalar or list entry as the soul writer emits it. The parser strips
251
+ * one layer of matching quotes from every value, so a value that begins
252
+ * and ends with the same quote — an agent id like `'bea'`, which
253
+ * {@link isValidAgentId} allows — would come back as `bea`: for a
254
+ * `delegates` entry that is a permission changed by an unrelated edit,
255
+ * since the control API renders a soul through here on every field edit.
256
+ * Such a value is wrapped in the other quote, which the parser strips
257
+ * back off; so is one the parser's trim would alter.
258
+ */
259
+ export const writeScalar = (value) => unquote(value) === value ? value : (value.startsWith('"') ? `'${value}'` : `"${value}"`);
260
+ //# sourceMappingURL=frontmatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../src/frontmatter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,139 @@
1
+ import type { AvatarTheme } from '@stratusagent/core';
2
+ /**
3
+ * The one house style every Stratus agent avatar is drawn in. Surfaces
4
+ * (CLI, web, macOS) render this style; agents differ by their name-derived
5
+ * hue and palette, so the team looks cohesive while each member is
6
+ * recognizable.
7
+ */
8
+ export declare const AVATAR_STYLE = "stratus";
9
+ /**
10
+ * Generate a human-ish first name. Deterministic when a seed is supplied;
11
+ * random otherwise.
12
+ */
13
+ export declare const generateAgentName: (seed?: string) => string;
14
+ /**
15
+ * Derive an avatar theme from a name: a stable hue and a small palette in
16
+ * the shared Stratus house style. Consumers (CLI, web, macOS) draw the
17
+ * actual image from this so the agent looks the same on every surface.
18
+ */
19
+ export declare const generateAvatarTheme: (name: string) => AvatarTheme;
20
+ export declare const slugify: (name: string) => string;
21
+ /**
22
+ * The shape an id is *minted* in: lowercase alphanumerics and internal
23
+ * hyphens, starting with an alphanumeric. Everything `slugify` produces
24
+ * matches it, so every id this module derives does.
25
+ *
26
+ * Not what validation enforces. A hand-written `id:` predates any shape
27
+ * rule and `Ava_1` was as valid as `ava` — see `isValidAgentId`.
28
+ */
29
+ export declare const AGENT_ID_PATTERN: RegExp;
30
+ /**
31
+ * How long an id we *build* is allowed to get. A construction bound, not a
32
+ * validation rule — `isValidAgentId` deliberately does not check it.
33
+ *
34
+ * Every id derived from a long name predates this bound, and enforcing it
35
+ * retroactively drops working agents off the roster, while trimming them
36
+ * silently re-keys their sessions, memory, and credentials to an id nobody
37
+ * has ever used. So the bound applies where an id is minted fresh and
38
+ * nothing is keyed to it yet, and nowhere else. File systems still have
39
+ * limits, and an id long enough to hit them fails at the join with a name
40
+ * that says so.
41
+ */
42
+ export declare const MAX_AGENT_ID_LENGTH = 64;
43
+ /**
44
+ * Whether `id` is safe to key an agent's resources and paths by.
45
+ *
46
+ * Ids are not labels. They key sessions, memory, credentials, Slack channel
47
+ * tokens, and every per-agent path on disk — and an explicit frontmatter
48
+ * `id` is untrusted input: a soul file travels with a repository, and in
49
+ * the hosted profile it comes from a tenant. `id: ../../escape` reaches
50
+ * every one of those joins intact, so it is stopped once here rather than
51
+ * defended at each of them.
52
+ *
53
+ * **Unsafe is the rule, not un-sluglike.** Nothing before this checked an
54
+ * explicit id at all, so `Ava_1`, `team.alpha`, and `AVA` are all out there
55
+ * keying real sessions and real credentials. Holding them to the shape ids
56
+ * are minted in would not make anything safer — none of them can leave
57
+ * their directory — and `loadRosterSouls` degrades a soul that will not
58
+ * parse to a warning, so the only thing it would accomplish is dropping
59
+ * those agents off the roster on upgrade, quietly, while their data stays
60
+ * behind under the old id. Reserving the slug shape for ids nobody has
61
+ * chosen yet costs nothing; imposing it on ids people are already using
62
+ * costs them their agent.
63
+ *
64
+ * Rejected, never sanitized: rewriting `../../escape` into `escape` hands
65
+ * back an agent nobody asked for, keyed to resources nobody named.
66
+ */
67
+ export declare const isValidAgentId: (id: string) => boolean;
68
+ /**
69
+ * Whether a `delegates` entry is one the soul can hold: any agent id, or
70
+ * the wildcard — the same rule as {@link isValidAgentId}, so every agent a
71
+ * roster can hold can be named as a target. Exported so the control API
72
+ * refuses a bad entry as the client's error rather than discovering it in
73
+ * the soul round-trip.
74
+ */
75
+ export declare const isValidDelegateEntry: (entry: string) => boolean;
76
+ /**
77
+ * How much of a new session id is the *client's* to spend, on top of the
78
+ * agent id it addresses.
79
+ *
80
+ * Every id in circulation is a colon-joined address that embeds the agent
81
+ * id — `web:<agentId>:<uuid>` from the dashboard, a channel key of
82
+ * `<channel>:<agentId>:<team>:<conversation>:<thread>`, with a bare UUID
83
+ * from the CLI the one exception. So a flat cap is the wrong shape: agent
84
+ * ids are deliberately unbounded (see {@link isValidAgentId}), and a flat
85
+ * 200 refused every dashboard conversation for an agent whose id ran past
86
+ * 159 characters — an agent that stays on the roster and simply stops being
87
+ * chattable, which is the quiet-breakage failure the unbounded rule exists
88
+ * to avoid.
89
+ *
90
+ * Budgeting the composition instead keeps the bound meaningful without
91
+ * capping the agent id through the back door. It cannot be gamed to widen
92
+ * itself: the allowance comes from an agent id already checked against the
93
+ * roster, not from the caller's own string.
94
+ */
95
+ export declare const MAX_SESSION_ID_LENGTH = 200;
96
+ /**
97
+ * Whether `id` may open a *new* conversation.
98
+ *
99
+ * Session ids are client-minted and a `POST` to an unknown one creates it,
100
+ * so this is the only thing standing between an HTTP caller and a durable
101
+ * row. `undefined`, `null`, and a bare space have all reached the session
102
+ * table through that door — the dashboard shipped the first of them.
103
+ *
104
+ * **Checked where an id is minted, never where one is used.** An id already
105
+ * in the store addresses a real conversation, whatever shape it is, and
106
+ * re-validating on read would lock someone out of their own history to
107
+ * enforce a rule that postdates it. Same reasoning as
108
+ * {@link MAX_AGENT_ID_LENGTH}, which is why the length bound lives here and
109
+ * not in {@link isValidAgentId}.
110
+ *
111
+ * `agentId` is the agent the id addresses, when the caller knows it — every
112
+ * client convention embeds it, so its length is part of the budget rather
113
+ * than something the bound gets to cap. The allowance applies only to an id
114
+ * that actually contains it, since those are the characters being paid for;
115
+ * a bare UUID and an unrelated string are both held to
116
+ * {@link MAX_SESSION_ID_LENGTH} alone, as they are when `agentId` is
117
+ * omitted.
118
+ *
119
+ * Shape is deliberately not enforced. Every id above is a legitimate
120
+ * address, and no pattern admitting all of them excludes `undefined` — so
121
+ * the safety rule, a bound, and {@link STRINGIFIED_NOTHING} are what catch
122
+ * the real mistakes. Rejected, never sanitized: trimming an id hands the
123
+ * caller back a conversation at an address it never asked for.
124
+ */
125
+ export declare const isValidSessionId: (id: string, agentId?: string) => boolean;
126
+ /**
127
+ * A freshly minted id, bounded — `base` trimmed so that appending `suffix`
128
+ * still fits, with any hyphen left dangling by the trim removed.
129
+ *
130
+ * Only for ids nothing is keyed to yet: a generated agent's
131
+ * name-plus-suffix, and `agent new` retrying a filename collision. Both mint
132
+ * an id in the same breath as the agent, so trimming takes nothing away.
133
+ * Shared rather than re-derived so the two do not each own half of the bound
134
+ * and drift. An id derived from a name someone chose does *not* come through
135
+ * here — that name may already have an agent behind it.
136
+ */
137
+ export declare const agentIdWithSuffix: (base: string, suffix?: string) => string;
138
+ export declare const generatedIdSuffix: (seed?: string) => string;
139
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAgBtD;;;;;GAKG;AACH,eAAO,MAAM,YAAY,YAAY,CAAC;AAwBtC;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,KAAG,MAKjD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,MAAM,KAAG,WAalD,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,MAIA,CAAC;AAExC;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,QAAyB,CAAC;AAEvD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAiCtC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAKH,eAAO,MAAM,cAAc,GAAI,IAAI,MAAM,KAAG,OAA4C,CAAC;AAEzF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,OAAO,MAAM,KAAG,OACb,CAAC;AAEzC;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAmBzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,gBAAgB,GAAI,IAAI,MAAM,EAAE,UAAU,MAAM,KAAG,OAQ/B,CAAC;AAElC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,EAAE,SAAS,MAAM,KAAG,MAIjE,CAAC;AAKF,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,KAAG,MAKjD,CAAC"}