auggy 0.3.0
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 +96 -0
- package/LICENSE +201 -0
- package/README.md +161 -0
- package/package.json +76 -0
- package/src/agent-card.ts +39 -0
- package/src/agent.ts +283 -0
- package/src/agentmail-client.ts +138 -0
- package/src/augments/bash/index.ts +463 -0
- package/src/augments/bash/skill/SKILL.md +156 -0
- package/src/augments/budgets/budget-store.ts +513 -0
- package/src/augments/budgets/index.ts +134 -0
- package/src/augments/budgets/preamble.ts +93 -0
- package/src/augments/budgets/types.ts +89 -0
- package/src/augments/file-memory/index.ts +71 -0
- package/src/augments/filesystem/index.ts +533 -0
- package/src/augments/filesystem/skill/SKILL.md +142 -0
- package/src/augments/filesystem/skill/references/mount-permissions.md +81 -0
- package/src/augments/layered-memory/extractor/buffer.ts +56 -0
- package/src/augments/layered-memory/extractor/frequency.ts +79 -0
- package/src/augments/layered-memory/extractor/inject-handler.ts +103 -0
- package/src/augments/layered-memory/extractor/parse.ts +75 -0
- package/src/augments/layered-memory/extractor/prompt.md +26 -0
- package/src/augments/layered-memory/index.ts +757 -0
- package/src/augments/layered-memory/skill/SKILL.md +153 -0
- package/src/augments/layered-memory/storage/migrations/README.md +16 -0
- package/src/augments/layered-memory/storage/migrations/supabase-add-fact-fields.sql +9 -0
- package/src/augments/layered-memory/storage/sqlite-store.ts +352 -0
- package/src/augments/layered-memory/storage/supabase-store.ts +263 -0
- package/src/augments/layered-memory/storage/types.ts +98 -0
- package/src/augments/link/index.ts +489 -0
- package/src/augments/link/translate.ts +261 -0
- package/src/augments/notify/adapters/agentmail.ts +70 -0
- package/src/augments/notify/adapters/telegram.ts +60 -0
- package/src/augments/notify/adapters/webhook.ts +55 -0
- package/src/augments/notify/index.ts +284 -0
- package/src/augments/notify/skill/SKILL.md +150 -0
- package/src/augments/org-context/index.ts +721 -0
- package/src/augments/org-context/skill/SKILL.md +96 -0
- package/src/augments/skills/index.ts +103 -0
- package/src/augments/supabase-memory/index.ts +151 -0
- package/src/augments/telegram-transport/index.ts +312 -0
- package/src/augments/telegram-transport/polling.ts +55 -0
- package/src/augments/telegram-transport/webhook.ts +56 -0
- package/src/augments/turn-control/index.ts +61 -0
- package/src/augments/turn-control/skill/SKILL.md +155 -0
- package/src/augments/visitor-auth/email-validation.ts +66 -0
- package/src/augments/visitor-auth/index.ts +779 -0
- package/src/augments/visitor-auth/rate-limiter.ts +90 -0
- package/src/augments/visitor-auth/skill/SKILL.md +55 -0
- package/src/augments/visitor-auth/storage/sqlite-store.ts +398 -0
- package/src/augments/visitor-auth/storage/types.ts +164 -0
- package/src/augments/visitor-auth/types.ts +123 -0
- package/src/augments/visitor-auth/verify-page.ts +179 -0
- package/src/augments/web-fetch/index.ts +331 -0
- package/src/augments/web-fetch/skill/SKILL.md +100 -0
- package/src/cli/agent-index.ts +289 -0
- package/src/cli/augment-catalog.ts +320 -0
- package/src/cli/augment-resolver.ts +597 -0
- package/src/cli/commands/add-skill.ts +194 -0
- package/src/cli/commands/add.ts +87 -0
- package/src/cli/commands/chat.ts +207 -0
- package/src/cli/commands/create.ts +462 -0
- package/src/cli/commands/dev.ts +139 -0
- package/src/cli/commands/eval.ts +180 -0
- package/src/cli/commands/ls.ts +66 -0
- package/src/cli/commands/remove.ts +95 -0
- package/src/cli/commands/restart.ts +40 -0
- package/src/cli/commands/start.ts +123 -0
- package/src/cli/commands/status.ts +104 -0
- package/src/cli/commands/stop.ts +84 -0
- package/src/cli/commands/visitors-revoke.ts +155 -0
- package/src/cli/commands/visitors.ts +101 -0
- package/src/cli/config-parser.ts +1034 -0
- package/src/cli/engine-resolver.ts +68 -0
- package/src/cli/index.ts +178 -0
- package/src/cli/model-picker.ts +89 -0
- package/src/cli/pid-registry.ts +146 -0
- package/src/cli/plist-generator.ts +117 -0
- package/src/cli/resolve-config.ts +56 -0
- package/src/cli/scaffold-skills.ts +158 -0
- package/src/cli/scaffold.ts +291 -0
- package/src/cli/skill-frontmatter.ts +51 -0
- package/src/cli/skill-validator.ts +151 -0
- package/src/cli/types.ts +228 -0
- package/src/cli/yaml-helpers.ts +66 -0
- package/src/engines/_shared/cost.ts +55 -0
- package/src/engines/_shared/schema-normalize.ts +75 -0
- package/src/engines/anthropic/pricing.ts +117 -0
- package/src/engines/anthropic.ts +483 -0
- package/src/engines/openai/pricing.ts +67 -0
- package/src/engines/openai.ts +446 -0
- package/src/engines/openrouter/pricing.ts +83 -0
- package/src/engines/openrouter.ts +185 -0
- package/src/helpers.ts +24 -0
- package/src/http.ts +387 -0
- package/src/index.ts +165 -0
- package/src/kernel/capability-table.ts +172 -0
- package/src/kernel/context-allocator.ts +161 -0
- package/src/kernel/history-manager.ts +198 -0
- package/src/kernel/lifecycle-manager.ts +106 -0
- package/src/kernel/output-validator.ts +35 -0
- package/src/kernel/preamble.ts +23 -0
- package/src/kernel/route-collector.ts +97 -0
- package/src/kernel/timeout.ts +21 -0
- package/src/kernel/tool-selector.ts +47 -0
- package/src/kernel/trace-emitter.ts +66 -0
- package/src/kernel/transport-queue.ts +147 -0
- package/src/kernel/turn-loop.ts +1148 -0
- package/src/memory/context-synthesis.ts +83 -0
- package/src/memory/memory-bus.ts +61 -0
- package/src/memory/registry.ts +80 -0
- package/src/memory/tools.ts +320 -0
- package/src/memory/types.ts +8 -0
- package/src/parts.ts +30 -0
- package/src/scaffold-templates/identity.md +31 -0
- package/src/telegram-client.ts +145 -0
- package/src/tokenizer.ts +14 -0
- package/src/transports/ag-ui-events.ts +253 -0
- package/src/transports/visitor-token.ts +82 -0
- package/src/transports/web-transport.ts +948 -0
- package/src/types.ts +1009 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffold-skill helpers — copy bundled `src/augments/<name>/skill/` folders
|
|
3
|
+
* into a scaffolded agent's `<agent-dir>/skills/<augment-name>/` directory,
|
|
4
|
+
* and render identity.md from the bundled template.
|
|
5
|
+
*
|
|
6
|
+
* Per ADR-025 (augment-as-folder + skill bundling) Decision 3 and ADR-030
|
|
7
|
+
* (model-facing skill surface separation): identity.md no longer carries
|
|
8
|
+
* the skill manifest. The runtime's `skills` augment surfaces the listing
|
|
9
|
+
* from each SKILL.md's YAML frontmatter; this module only handles disk-copy
|
|
10
|
+
* + template substitution for the three identity-level placeholders
|
|
11
|
+
* (AGENT_NAME, PURPOSE, OPERATOR_NAME).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { cpSync, existsSync, readFileSync } from "node:fs";
|
|
15
|
+
import { join, resolve } from "node:path";
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Augment-name resolution
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Map an augment `type` (the YAML `type:` field, e.g. `webFetch`) to the
|
|
23
|
+
* folder name under `src/augments/`. The folder convention is kebab-case;
|
|
24
|
+
* the type identifier is camelCase. This lookup is the inverse of the
|
|
25
|
+
* augment-resolver dispatch.
|
|
26
|
+
*
|
|
27
|
+
* Returned name doubles as the `<agent-dir>/skills/<name>/` directory name
|
|
28
|
+
* so the scaffolded layout matches what the runtime `skills` augment scans.
|
|
29
|
+
*/
|
|
30
|
+
const TYPE_TO_AUGMENT_FOLDER: Record<string, string> = {
|
|
31
|
+
filesystem: "filesystem",
|
|
32
|
+
layeredMemory: "layered-memory",
|
|
33
|
+
webFetch: "web-fetch",
|
|
34
|
+
orgContext: "org-context",
|
|
35
|
+
bash: "bash",
|
|
36
|
+
notify: "notify",
|
|
37
|
+
turnControl: "turn-control",
|
|
38
|
+
visitorAuth: "visitor-auth",
|
|
39
|
+
// Augments below intentionally have no skill folder today (no model-callable
|
|
40
|
+
// tools, transport-only, or legacy). Listed for completeness so a lookup
|
|
41
|
+
// never silently returns undefined for a known type.
|
|
42
|
+
fileMemory: "file-memory",
|
|
43
|
+
supabaseMemory: "supabase-memory",
|
|
44
|
+
budgets: "budgets",
|
|
45
|
+
webTransport: "web-transport",
|
|
46
|
+
telegramTransport: "telegram-transport",
|
|
47
|
+
// ADR-030: the 'skills' augment is the runtime skill surface itself; it
|
|
48
|
+
// carries no SKILL.md of its own (the augment IS the listing).
|
|
49
|
+
skills: "skills",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** Return the augment folder name for a YAML `type:` value, or `null` if unknown. */
|
|
53
|
+
export function augmentFolderForType(type: string): string | null {
|
|
54
|
+
return TYPE_TO_AUGMENT_FOLDER[type] ?? null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Bundled-skill source resolution
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resolve the on-disk location of the bundled `src/augments/<folder>/skill/`
|
|
63
|
+
* directory relative to this module. Returns `null` if the augment has no
|
|
64
|
+
* bundled skill folder.
|
|
65
|
+
*/
|
|
66
|
+
function bundledSkillDir(folder: string): string | null {
|
|
67
|
+
const dir = resolve(import.meta.dir, "../augments", folder, "skill");
|
|
68
|
+
return existsSync(dir) ? dir : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Skill copy
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Copy the bundled skill folder for the given augment type into the agent
|
|
77
|
+
* directory. No-op when the augment has no bundled skill (e.g. budgets,
|
|
78
|
+
* file-memory, transport-only augments, the `skills` augment itself) or when
|
|
79
|
+
* the source folder is not present on disk.
|
|
80
|
+
*
|
|
81
|
+
* Idempotent: re-running overwrites existing files (per ADR-025 Decision 2 —
|
|
82
|
+
* operators opt into updates by re-scaffolding).
|
|
83
|
+
*/
|
|
84
|
+
export function copyBundledSkill(type: string, agentDir: string): boolean {
|
|
85
|
+
const folder = TYPE_TO_AUGMENT_FOLDER[type];
|
|
86
|
+
if (!folder) return false;
|
|
87
|
+
const src = bundledSkillDir(folder);
|
|
88
|
+
if (!src) return false;
|
|
89
|
+
const dest = join(agentDir, "skills", folder);
|
|
90
|
+
cpSync(src, dest, { recursive: true });
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// identity.md template substitution
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The on-disk path to the identity.md template shipped under
|
|
100
|
+
* `src/scaffold-templates/`. Resolved relative to this module so it works
|
|
101
|
+
* both from source (Bun) and from a bundle that preserves the layout.
|
|
102
|
+
*/
|
|
103
|
+
const IDENTITY_TEMPLATE_PATH = resolve(import.meta.dir, "../scaffold-templates/identity.md");
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Read the identity.md template once. The template is part of the source
|
|
107
|
+
* tree, never user-supplied, so failure to read it is a programming error.
|
|
108
|
+
*/
|
|
109
|
+
function readIdentityTemplate(): string {
|
|
110
|
+
return readFileSync(IDENTITY_TEMPLATE_PATH, "utf-8");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface IdentityTemplateValues {
|
|
114
|
+
/** The agent's name as written in agent.yaml. */
|
|
115
|
+
agentName: string;
|
|
116
|
+
/** A one-sentence agent purpose ("a helpful assistant" by default). */
|
|
117
|
+
purpose: string;
|
|
118
|
+
/** The operator's name ("the operator" by default). */
|
|
119
|
+
operatorName: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Render identity.md from the bundled template using the provided values.
|
|
124
|
+
*
|
|
125
|
+
* Substitution targets three placeholder tokens by exact name (`{AGENT_NAME}`,
|
|
126
|
+
* `{PURPOSE}`, `{OPERATOR_NAME}`) — not a generic `{...}` regex — so
|
|
127
|
+
* operator-supplied values containing literal braces pass through unmodified.
|
|
128
|
+
* Single-pass replacement means an operator-supplied value containing
|
|
129
|
+
* `{AGENT_NAME}` or any other placeholder is emitted verbatim and is NOT
|
|
130
|
+
* re-scanned by a subsequent substitution.
|
|
131
|
+
*
|
|
132
|
+
* Per ADR-030: the previous `{SKILL_MANIFEST}` placeholder is gone. Skill
|
|
133
|
+
* discovery is the runtime `skills` augment's responsibility, not the
|
|
134
|
+
* identity file's.
|
|
135
|
+
*
|
|
136
|
+
* Limitation: operator values land inside markdown text. An operator who
|
|
137
|
+
* names their agent something that looks like a heading (`# Sneaky`) will
|
|
138
|
+
* break the document's outline — but only for their own identity.md, with
|
|
139
|
+
* no security impact (security rules sit outside the substitution targets
|
|
140
|
+
* and the `{OPERATOR_NAME}` reference is inline prose, not a structural
|
|
141
|
+
* element). Documented for posterity; not mitigated at v1.0.
|
|
142
|
+
*/
|
|
143
|
+
export function renderIdentityFromTemplate(values: IdentityTemplateValues): string {
|
|
144
|
+
const template = readIdentityTemplate();
|
|
145
|
+
|
|
146
|
+
return template.replace(/\{(AGENT_NAME|PURPOSE|OPERATOR_NAME)\}/g, (match, token: string) => {
|
|
147
|
+
switch (token) {
|
|
148
|
+
case "AGENT_NAME":
|
|
149
|
+
return values.agentName;
|
|
150
|
+
case "PURPOSE":
|
|
151
|
+
return values.purpose;
|
|
152
|
+
case "OPERATOR_NAME":
|
|
153
|
+
return values.operatorName;
|
|
154
|
+
default:
|
|
155
|
+
return match;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffold — generates a new agent directory with auggy create.
|
|
3
|
+
*
|
|
4
|
+
* Creates the standard agent directory convention:
|
|
5
|
+
* <name>/
|
|
6
|
+
* agent.yaml Config (source of truth) — uses `identity:` shorthand
|
|
7
|
+
* .env Secrets template (gitignored)
|
|
8
|
+
* identity.md Who the agent is — security rules + skill manifest
|
|
9
|
+
* learned.md What the agent has learned (mutable)
|
|
10
|
+
* skills/ Skill folders (read-only fs mount), one per
|
|
11
|
+
* tool-providing augment, copied from src/augments/<name>/skill/
|
|
12
|
+
* workspace/ Agent's mutable workspace
|
|
13
|
+
* augments/ Custom augments directory
|
|
14
|
+
* .gitignore Ignores .env, workspace/, *.log, *.db, memory.sqlite
|
|
15
|
+
*
|
|
16
|
+
* Per ADR-025 (augment-as-folder + skill bundling) and the PR α foundation
|
|
17
|
+
* spec: scaffold copies bundled skills, uses the `identity:` YAML shorthand,
|
|
18
|
+
* includes layeredMemory by default, and writes identity.md from a template
|
|
19
|
+
* with the four security rules baked in.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
23
|
+
import { join, resolve } from "node:path";
|
|
24
|
+
import { randomUUID } from "node:crypto";
|
|
25
|
+
import { copyBundledSkill, renderIdentityFromTemplate } from "./scaffold-skills";
|
|
26
|
+
|
|
27
|
+
export interface ScaffoldOptions {
|
|
28
|
+
/** Agent name. */
|
|
29
|
+
name: string;
|
|
30
|
+
/** Target directory (defaults to ./<name>). */
|
|
31
|
+
targetDir?: string;
|
|
32
|
+
/** Optional purpose string for the agent (default: "a helpful assistant"). */
|
|
33
|
+
purpose?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Operator name used to populate the operator-identity reference in the
|
|
36
|
+
* scaffolded identity.md security rules and the agent.yaml `operators[]`
|
|
37
|
+
* array. Defaults to "the operator" — matches the security-eval test
|
|
38
|
+
* fixture's fallback behavior so non-interactive scaffolding stays
|
|
39
|
+
* compatible with the canonical eval suite.
|
|
40
|
+
*/
|
|
41
|
+
operatorName?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Default values used when prompts are skipped (non-interactive). */
|
|
45
|
+
const DEFAULT_OPERATOR_NAME = "the operator";
|
|
46
|
+
const DEFAULT_PURPOSE = "a helpful assistant";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Scaffold a new agent directory.
|
|
50
|
+
* Throws if the target directory already exists.
|
|
51
|
+
*/
|
|
52
|
+
export function scaffoldAgent(opts: ScaffoldOptions): string {
|
|
53
|
+
const dir = resolve(opts.targetDir ?? `./${opts.name}`);
|
|
54
|
+
|
|
55
|
+
if (existsSync(dir)) {
|
|
56
|
+
throw new Error(`Directory already exists: ${dir}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const id = `aug1_${randomUUID()}`;
|
|
60
|
+
const purpose = opts.purpose ?? DEFAULT_PURPOSE;
|
|
61
|
+
const operatorName = opts.operatorName ?? DEFAULT_OPERATOR_NAME;
|
|
62
|
+
|
|
63
|
+
// The augment types this scaffold installs by default. Drives the bundled-
|
|
64
|
+
// skill copy (which copies SKILL.md files into <agent>/skills/<augment>/).
|
|
65
|
+
// The runtime `skills` augment surfaces them to the model from disk at
|
|
66
|
+
// every context() call — no longer threaded into identity.md per ADR-030.
|
|
67
|
+
const augmentTypes = [
|
|
68
|
+
"fileMemory", // identity (mounted via shorthand) + learned.md
|
|
69
|
+
"filesystem",
|
|
70
|
+
"layeredMemory",
|
|
71
|
+
"budgets",
|
|
72
|
+
"webFetch",
|
|
73
|
+
"turnControl",
|
|
74
|
+
"webTransport",
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
// Tool-providing types whose bundled skills should be copied. Subset of
|
|
78
|
+
// augmentTypes — fileMemory / budgets / webTransport contribute no tools.
|
|
79
|
+
const skillProvidingTypes = augmentTypes.filter((t) =>
|
|
80
|
+
["filesystem", "layeredMemory", "webFetch", "turnControl"].includes(t),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Create directory structure.
|
|
84
|
+
mkdirSync(dir, { recursive: true });
|
|
85
|
+
mkdirSync(join(dir, "skills"), { recursive: true });
|
|
86
|
+
mkdirSync(join(dir, "workspace"), { recursive: true });
|
|
87
|
+
mkdirSync(join(dir, "augments"), { recursive: true });
|
|
88
|
+
|
|
89
|
+
// Copy bundled skill folders for each tool-providing augment. Idempotent —
|
|
90
|
+
// re-running the scaffold overwrites; per ADR-025 Decision 2 operators opt
|
|
91
|
+
// into updates by re-scaffolding.
|
|
92
|
+
for (const type of skillProvidingTypes) {
|
|
93
|
+
copyBundledSkill(type, dir);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Write identity.md from the bundled template (security rules only —
|
|
97
|
+
// skill manifest moved out per ADR-030).
|
|
98
|
+
writeFileSync(
|
|
99
|
+
join(dir, "identity.md"),
|
|
100
|
+
renderIdentityFromTemplate({
|
|
101
|
+
agentName: opts.name,
|
|
102
|
+
purpose,
|
|
103
|
+
operatorName,
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Write learned.md (empty, agent appends as it learns).
|
|
108
|
+
writeFileSync(join(dir, "learned.md"), "");
|
|
109
|
+
|
|
110
|
+
// Write agent.yaml using the identity: shorthand (per α-5).
|
|
111
|
+
writeFileSync(join(dir, "agent.yaml"), agentYamlTemplate(id, opts.name, purpose, operatorName));
|
|
112
|
+
|
|
113
|
+
// Write .env with empty values — operator fills in secrets before first run.
|
|
114
|
+
writeFileSync(join(dir, ".env"), ENV_TEMPLATE);
|
|
115
|
+
|
|
116
|
+
// Write .gitignore.
|
|
117
|
+
writeFileSync(join(dir, ".gitignore"), GITIGNORE_TEMPLATE);
|
|
118
|
+
|
|
119
|
+
return dir;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Templates
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Render a string as a YAML-safe scalar. Operator-supplied free-text values
|
|
128
|
+
* (purpose, operatorName) reach this function; without escaping, a value
|
|
129
|
+
* containing a quote, newline, or backslash would corrupt the generated
|
|
130
|
+
* agent.yaml. JSON-encoded strings are valid YAML scalars in flow shape,
|
|
131
|
+
* so JSON.stringify produces a double-quoted, escaped form that YAML parses
|
|
132
|
+
* back to the original string.
|
|
133
|
+
*
|
|
134
|
+
* The interactive `auggy create` flow already routes operator input through
|
|
135
|
+
* yaml.stringify; this helper closes the parallel gap in `scaffoldAgent`,
|
|
136
|
+
* the programmatic entry point used by tests and any third-party scaffolder.
|
|
137
|
+
*/
|
|
138
|
+
function yamlScalar(s: string): string {
|
|
139
|
+
return JSON.stringify(s);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function agentYamlTemplate(
|
|
143
|
+
id: string,
|
|
144
|
+
name: string,
|
|
145
|
+
purpose: string,
|
|
146
|
+
operatorName: string,
|
|
147
|
+
): string {
|
|
148
|
+
return `# Agent configuration — the source of truth for this Auggy agent.
|
|
149
|
+
# See docs at augment-1/docs/ for field reference.
|
|
150
|
+
|
|
151
|
+
id: ${yamlScalar(id)}
|
|
152
|
+
name: ${yamlScalar(name)}
|
|
153
|
+
purpose: ${yamlScalar(purpose)}
|
|
154
|
+
operators:
|
|
155
|
+
- ${yamlScalar(operatorName)}
|
|
156
|
+
|
|
157
|
+
# identity shorthand — synthesizes a fileMemory@system entry from ./identity.md
|
|
158
|
+
# at parse time. Operators wanting non-default options (e.g. mutable: true)
|
|
159
|
+
# should drop this and add an explicit fileMemory augment instead.
|
|
160
|
+
identity: ./identity.md
|
|
161
|
+
|
|
162
|
+
engine:
|
|
163
|
+
provider: anthropic # or: openai, openrouter
|
|
164
|
+
model: claude-sonnet-4-6 # openai: gpt-5 | openrouter: qwen/qwen3.5-397b-a17b
|
|
165
|
+
maxContextTokens: 200000 # for openrouter, set per-model — defaults vary
|
|
166
|
+
maxTokens: 4096 # sent as max_completion_tokens for openai/openrouter
|
|
167
|
+
# reasoningEffort: medium # optional: none|minimal|low|medium|high|xhigh
|
|
168
|
+
# providerRouting: # openrouter only — slugs not semantically validated
|
|
169
|
+
# only: [OpenAI]
|
|
170
|
+
# sort: price
|
|
171
|
+
|
|
172
|
+
settings:
|
|
173
|
+
compactionStrategy: truncate
|
|
174
|
+
maxInferenceLoops: 10
|
|
175
|
+
|
|
176
|
+
augments:
|
|
177
|
+
- name: learned
|
|
178
|
+
type: fileMemory
|
|
179
|
+
options:
|
|
180
|
+
label: learned
|
|
181
|
+
source: ./learned.md
|
|
182
|
+
mutable: true
|
|
183
|
+
origin: system
|
|
184
|
+
priority: high
|
|
185
|
+
placement: preamble
|
|
186
|
+
eviction: drop
|
|
187
|
+
|
|
188
|
+
- name: memory
|
|
189
|
+
type: layeredMemory
|
|
190
|
+
options:
|
|
191
|
+
backend: sqlite
|
|
192
|
+
namespace: ${yamlScalar(name)}
|
|
193
|
+
dbPath: ./memory.sqlite
|
|
194
|
+
retentionDays: 90
|
|
195
|
+
|
|
196
|
+
- name: budgets
|
|
197
|
+
type: budgets
|
|
198
|
+
options:
|
|
199
|
+
dbPath: ./budgets.db
|
|
200
|
+
caps:
|
|
201
|
+
public:
|
|
202
|
+
recognized:
|
|
203
|
+
maxTurnsPerThread: 20
|
|
204
|
+
maxTurnsPerDay: 50
|
|
205
|
+
maxUsdPerDay: 1
|
|
206
|
+
anonymous:
|
|
207
|
+
maxTurnsPerThread: 5
|
|
208
|
+
anonymousGlobalLimit: 30
|
|
209
|
+
dailyBudgetUsd: 5
|
|
210
|
+
|
|
211
|
+
- name: files
|
|
212
|
+
type: filesystem
|
|
213
|
+
options:
|
|
214
|
+
mounts:
|
|
215
|
+
- name: skills
|
|
216
|
+
path: ./skills
|
|
217
|
+
writable: false
|
|
218
|
+
- name: workspace
|
|
219
|
+
path: ./workspace
|
|
220
|
+
writable: true
|
|
221
|
+
deletable: true
|
|
222
|
+
|
|
223
|
+
# ADR-030: surfaces the skill manifest to the model (name + description from
|
|
224
|
+
# each SKILL.md's YAML frontmatter). Activation is fs_read via the filesystem
|
|
225
|
+
# mount above. Required for the model to discover its skills.
|
|
226
|
+
- name: skills
|
|
227
|
+
type: skills
|
|
228
|
+
options:
|
|
229
|
+
dir: ./skills
|
|
230
|
+
|
|
231
|
+
- name: fetch
|
|
232
|
+
type: webFetch
|
|
233
|
+
options:
|
|
234
|
+
timeoutMs: 15000
|
|
235
|
+
|
|
236
|
+
- name: turn-control
|
|
237
|
+
type: turnControl
|
|
238
|
+
|
|
239
|
+
- name: web
|
|
240
|
+
type: webTransport
|
|
241
|
+
options:
|
|
242
|
+
port: 8080
|
|
243
|
+
auth:
|
|
244
|
+
type: bearer
|
|
245
|
+
token: \${AUGGY_WEB_TOKEN}
|
|
246
|
+
visitorTokens:
|
|
247
|
+
# signingKey is NOT set here — visitorAuth is the single source of truth
|
|
248
|
+
# and the resolver injects it at boot. Setting it here would trigger the
|
|
249
|
+
# duplicate-key warning on every start.
|
|
250
|
+
agentBinding: \${AUGGY_AGENT_ID}
|
|
251
|
+
`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const ENV_TEMPLATE = `# Auggy agent secrets — this file is gitignored.
|
|
255
|
+
# Add your API keys and tokens here. Only the key matching the
|
|
256
|
+
# configured engine.provider in agent.yaml needs to be filled in.
|
|
257
|
+
|
|
258
|
+
ANTHROPIC_API_KEY=
|
|
259
|
+
# OPENAI_API_KEY=
|
|
260
|
+
# OPENROUTER_API_KEY=
|
|
261
|
+
AUGGY_WEB_TOKEN=
|
|
262
|
+
# Uncomment when visitorAuth augment is added (signingKey is owned by visitorAuth,
|
|
263
|
+
# injected into webTransport at boot — no need to set it in webTransport's config).
|
|
264
|
+
# VISITOR_SIGNING_KEY=
|
|
265
|
+
# Stable identifier for visitor-auth tokens — must be unique per agent
|
|
266
|
+
# if multiple agents share VISITOR_SIGNING_KEY (otherwise tokens are
|
|
267
|
+
# cross-replayable). Pattern: short slug or the agent's id.
|
|
268
|
+
AUGGY_AGENT_ID=
|
|
269
|
+
# SUPABASE_URL=
|
|
270
|
+
# SUPABASE_SERVICE_KEY=
|
|
271
|
+
`;
|
|
272
|
+
|
|
273
|
+
const GITIGNORE_TEMPLATE = `.env
|
|
274
|
+
.env.local
|
|
275
|
+
workspace/
|
|
276
|
+
*.log
|
|
277
|
+
*.err
|
|
278
|
+
node_modules/
|
|
279
|
+
memory.sqlite
|
|
280
|
+
memory.sqlite-journal
|
|
281
|
+
memory.sqlite-wal
|
|
282
|
+
memory.sqlite-shm
|
|
283
|
+
memory.db
|
|
284
|
+
memory.db-journal
|
|
285
|
+
memory.db-wal
|
|
286
|
+
memory.db-shm
|
|
287
|
+
budgets.db
|
|
288
|
+
budgets.db-journal
|
|
289
|
+
budgets.db-wal
|
|
290
|
+
budgets.db-shm
|
|
291
|
+
`;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill frontmatter reader for ADR-030.
|
|
3
|
+
*
|
|
4
|
+
* Reads the YAML frontmatter block from a SKILL.md file. Returns null when the
|
|
5
|
+
* frontmatter is absent, malformed, or missing required fields — callers
|
|
6
|
+
* always have a graceful-fallback path (skill is not listed) rather than a
|
|
7
|
+
* crash. The boot-time skill validator (`skill-validator.ts`) catches the
|
|
8
|
+
* "tool-providing augment with no parseable frontmatter" case separately.
|
|
9
|
+
*
|
|
10
|
+
* Source of truth for skill `name` + `description` per agentskills.io.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { parse as parseYaml } from "yaml";
|
|
15
|
+
|
|
16
|
+
export interface SkillFrontmatter {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n/;
|
|
22
|
+
|
|
23
|
+
export function parseSkillFrontmatter(content: string): SkillFrontmatter | null {
|
|
24
|
+
const match = content.match(FRONTMATTER_RE);
|
|
25
|
+
if (!match) return null;
|
|
26
|
+
const raw = match[1]!;
|
|
27
|
+
|
|
28
|
+
let parsed: unknown;
|
|
29
|
+
try {
|
|
30
|
+
parsed = parseYaml(raw);
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (parsed === null || typeof parsed !== "object") return null;
|
|
36
|
+
const obj = parsed as Record<string, unknown>;
|
|
37
|
+
if (typeof obj.name !== "string" || obj.name.length === 0) return null;
|
|
38
|
+
if (typeof obj.description !== "string" || obj.description.length === 0) return null;
|
|
39
|
+
|
|
40
|
+
return { name: obj.name, description: obj.description };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function readSkillFrontmatter(path: string): SkillFrontmatter | null {
|
|
44
|
+
let content: string;
|
|
45
|
+
try {
|
|
46
|
+
content = readFileSync(path, "utf-8");
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return parseSkillFrontmatter(content);
|
|
51
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot-time skill validator.
|
|
3
|
+
*
|
|
4
|
+
* Per ADR-025 Decision 5 + PR α foundation spec §H. After augments are
|
|
5
|
+
* resolved at agent boot, scan: for each augment that contributes tools
|
|
6
|
+
* to the model but lacks a corresponding `<agent-dir>/skills/<augment-
|
|
7
|
+
* folder>/SKILL.md`, emit a one-line warning. Operators see the gap at
|
|
8
|
+
* startup, not in production-failure mode where the model guesses.
|
|
9
|
+
*
|
|
10
|
+
* Discriminator (model-perspective): an augment contributes tools if EITHER
|
|
11
|
+
* (a) `augment.tools.length > 0` — tools declared on the factory return,
|
|
12
|
+
* (b) `augment.memory?.owns?.kind === "namespace"` — namespace memory
|
|
13
|
+
* provider; the kernel-synthesized memory-bus exposes 5 generic
|
|
14
|
+
* tools (memory_read / memory_write / memory_search / memory_list /
|
|
15
|
+
* memory_forget — see src/memory/tools.ts) keyed off its prefix.
|
|
16
|
+
*
|
|
17
|
+
* The model can't tell where the tools came from; from its perspective
|
|
18
|
+
* both routes produce model-callable tools that need teaching. The strict
|
|
19
|
+
* spec wording ("non-empty tools[]") would have missed (b), and (b) is
|
|
20
|
+
* the most common real case — layered-memory is the default-scaffold
|
|
21
|
+
* memory augment.
|
|
22
|
+
*
|
|
23
|
+
* Tool-less augments (fileMemory + supabaseMemory static providers,
|
|
24
|
+
* transports, budgets) intentionally do NOT trigger the warning — they
|
|
25
|
+
* contribute only `context()` blocks or admission gates, no model-
|
|
26
|
+
* callable tools.
|
|
27
|
+
*
|
|
28
|
+
* Warning, not error. The agent still boots successfully. An opt-out
|
|
29
|
+
* flag is deferred per spec §Decision 7.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Number of tools the kernel memory-bus synthesizes for a namespace
|
|
34
|
+
* memory provider. Source: src/memory/tools.ts (memory_read / memory_write
|
|
35
|
+
* / memory_search / memory_list / memory_forget). If that surface changes,
|
|
36
|
+
* update this constant — tests asserting the count will catch drift.
|
|
37
|
+
*/
|
|
38
|
+
const NAMESPACE_MEMORY_TOOL_COUNT = 5;
|
|
39
|
+
|
|
40
|
+
import { statSync } from "node:fs";
|
|
41
|
+
import { join } from "node:path";
|
|
42
|
+
import type { Augment } from "../types";
|
|
43
|
+
import type { AugmentConfig } from "./types";
|
|
44
|
+
import { augmentFolderForType } from "./scaffold-skills";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Filesystem probe outcome for the skill SKILL.md file.
|
|
48
|
+
*
|
|
49
|
+
* - "present": file exists and is readable as a regular file.
|
|
50
|
+
* - "missing": parent dir is reachable; SKILL.md is absent (ENOENT). The
|
|
51
|
+
* normal "operator hasn't installed this skill" case.
|
|
52
|
+
* - "unreadable": stat surfaced a non-ENOENT error (e.g. EACCES). The
|
|
53
|
+
* skill MAY be present on disk but the runtime cannot confirm; surfaced
|
|
54
|
+
* as a different warning class so an operator with a misconfigured
|
|
55
|
+
* permissions setup doesn't get fooled into thinking the skill is there.
|
|
56
|
+
*/
|
|
57
|
+
type SkillProbe =
|
|
58
|
+
| { kind: "present" }
|
|
59
|
+
| { kind: "missing" }
|
|
60
|
+
| { kind: "unreadable"; reason: string };
|
|
61
|
+
|
|
62
|
+
function probeSkillFile(skillPath: string): SkillProbe {
|
|
63
|
+
try {
|
|
64
|
+
const stats = statSync(skillPath);
|
|
65
|
+
if (stats.isFile()) return { kind: "present" };
|
|
66
|
+
// SKILL.md exists but is a directory or other non-file — surface as
|
|
67
|
+
// unreadable rather than silently treating as present. Rare misconfig.
|
|
68
|
+
return { kind: "unreadable", reason: `path is not a regular file` };
|
|
69
|
+
} catch (err) {
|
|
70
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
71
|
+
if (code === "ENOENT") return { kind: "missing" };
|
|
72
|
+
return {
|
|
73
|
+
kind: "unreadable",
|
|
74
|
+
reason: `${code ?? "unknown"}: ${(err as Error).message}`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Scan resolved augments paired with their source configs and warn for
|
|
81
|
+
* any tool-providing augment whose skill is missing or unreadable. Walks
|
|
82
|
+
* configs and augments in lockstep — `resolveAugments` preserves order
|
|
83
|
+
* and never drops entries, so index alignment is exact.
|
|
84
|
+
*
|
|
85
|
+
* Hook point: called from `resolveAugments` after all augment factories
|
|
86
|
+
* have run, so the agent's tool surface is finalized before the warning
|
|
87
|
+
* decision is made.
|
|
88
|
+
*/
|
|
89
|
+
export function validateBundledSkills(
|
|
90
|
+
configs: AugmentConfig[],
|
|
91
|
+
augments: Augment[],
|
|
92
|
+
agentDir: string,
|
|
93
|
+
): void {
|
|
94
|
+
// Defensive: if the caller passed mismatched arrays, skip validation
|
|
95
|
+
// entirely rather than emit confusing warnings against the wrong
|
|
96
|
+
// augment. resolveAugments today always produces matched pairs.
|
|
97
|
+
if (configs.length !== augments.length) return;
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < augments.length; i++) {
|
|
100
|
+
const aug = augments[i]!;
|
|
101
|
+
const cfg = configs[i]!;
|
|
102
|
+
|
|
103
|
+
const factoryToolCount = aug.tools?.length ?? 0;
|
|
104
|
+
const isNamespaceMemory = aug.memory?.owns?.kind === "namespace";
|
|
105
|
+
const toolCount = factoryToolCount + (isNamespaceMemory ? NAMESPACE_MEMORY_TOOL_COUNT : 0);
|
|
106
|
+
if (toolCount === 0) continue;
|
|
107
|
+
|
|
108
|
+
// Custom augments (operator-authored) do not have a bundled skill folder
|
|
109
|
+
// by convention; the operator owns their own teaching. Don't warn —
|
|
110
|
+
// the `auggy add-skill` command is built-in-only too.
|
|
111
|
+
if (cfg.type === "custom") continue;
|
|
112
|
+
|
|
113
|
+
const folder = augmentFolderForType(cfg.type);
|
|
114
|
+
if (!folder) {
|
|
115
|
+
// Unknown built-in type. resolveAugments would have thrown earlier;
|
|
116
|
+
// this branch only fires if the type-to-folder map drifts from
|
|
117
|
+
// the resolver's switch. Emit a diagnostic so the drift is visible.
|
|
118
|
+
console.warn(
|
|
119
|
+
`[augment-resolver] augment "${aug.name}" (type "${cfg.type}") exposes ${toolCount} ` +
|
|
120
|
+
`tool${toolCount === 1 ? "" : "s"} but has no folder mapping in scaffold-skills. ` +
|
|
121
|
+
`Skill validation skipped — file an issue if this augment ships skill content.`,
|
|
122
|
+
);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const skillPath = join(agentDir, "skills", folder, "SKILL.md");
|
|
127
|
+
const probe = probeSkillFile(skillPath);
|
|
128
|
+
if (probe.kind === "present") continue;
|
|
129
|
+
|
|
130
|
+
if (probe.kind === "missing") {
|
|
131
|
+
console.warn(
|
|
132
|
+
`[augment-resolver] augment "${folder}" exposes ${toolCount} tool${
|
|
133
|
+
toolCount === 1 ? "" : "s"
|
|
134
|
+
} with no skill mounted at\n` +
|
|
135
|
+
` ${skillPath}. Model will guess at tool usage.\n` +
|
|
136
|
+
` Run \`auggy add-skill ${folder}\` to install the bundled teaching, OR copy\n` +
|
|
137
|
+
` src/augments/${folder}/skill/* into the agent's skills/${folder}/ directory.`,
|
|
138
|
+
);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// probe.kind === "unreadable"
|
|
143
|
+
console.warn(
|
|
144
|
+
`[augment-resolver] augment "${folder}" exposes ${toolCount} tool${
|
|
145
|
+
toolCount === 1 ? "" : "s"
|
|
146
|
+
} and a skill file at\n` +
|
|
147
|
+
` ${skillPath} is unreadable (${probe.reason}).\n` +
|
|
148
|
+
` Skill teaching cannot be confirmed; check filesystem permissions.`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|