@stratusagent/agents 0.11.4 → 0.11.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/define.d.ts +22 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +64 -0
- package/dist/define.js.map +1 -0
- package/dist/frontmatter.d.ts +65 -0
- package/dist/frontmatter.d.ts.map +1 -0
- package/dist/frontmatter.js +260 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/identity.d.ts +139 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +259 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +11 -654
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -1641
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +18 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +44 -0
- package/dist/router.js.map +1 -0
- package/dist/schedules.d.ts +163 -0
- package/dist/schedules.d.ts.map +1 -0
- package/dist/schedules.js +215 -0
- package/dist/schedules.js.map +1 -0
- package/dist/skills.d.ts +135 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +167 -0
- package/dist/skills.js.map +1 -0
- package/dist/soul.d.ts +44 -0
- package/dist/soul.d.ts.map +1 -0
- package/dist/soul.js +98 -0
- package/dist/soul.js.map +1 -0
- package/dist/tools/delegate.d.ts +93 -0
- package/dist/tools/delegate.d.ts.map +1 -0
- package/dist/tools/delegate.js +223 -0
- package/dist/tools/delegate.js.map +1 -0
- package/dist/tools/gateway-only.d.ts +19 -0
- package/dist/tools/gateway-only.d.ts.map +1 -0
- package/dist/tools/gateway-only.js +29 -0
- package/dist/tools/gateway-only.js.map +1 -0
- package/dist/tools/memory.d.ts +51 -0
- package/dist/tools/memory.d.ts.map +1 -0
- package/dist/tools/memory.js +306 -0
- package/dist/tools/memory.js.map +1 -0
- package/dist/tools/message.d.ts +26 -0
- package/dist/tools/message.d.ts.map +1 -0
- package/dist/tools/message.js +46 -0
- package/dist/tools/message.js.map +1 -0
- package/dist/tools/schedule.d.ts +20 -0
- package/dist/tools/schedule.d.ts.map +1 -0
- package/dist/tools/schedule.js +146 -0
- package/dist/tools/schedule.js.map +1 -0
- package/package.json +2 -2
package/dist/define.d.ts
ADDED
|
@@ -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"}
|