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,597 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Augment resolver — AugmentConfig[] → Augment[].
|
|
3
|
+
*
|
|
4
|
+
* Maps each augment declaration from agent.yaml to a concrete Augment
|
|
5
|
+
* object by dispatching to the appropriate factory function. Built-in
|
|
6
|
+
* augments resolve by type name; custom augments resolve by dynamic
|
|
7
|
+
* import of a local .ts file.
|
|
8
|
+
*
|
|
9
|
+
* Special handling:
|
|
10
|
+
* - supabaseMemory: constructs a SupabaseLikeClient from supabaseUrl
|
|
11
|
+
* + supabaseKey options via @supabase/supabase-js.
|
|
12
|
+
* - fileMemory, filesystem: resolves relative paths against agentDir.
|
|
13
|
+
* - All augments: overrides the auto-generated augment name with the
|
|
14
|
+
* operator's chosen instance name from the config.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { resolve } from "node:path";
|
|
18
|
+
import { pathToFileURL } from "node:url";
|
|
19
|
+
import { fileMemory } from "../augments/file-memory";
|
|
20
|
+
import { supabaseMemory } from "../augments/supabase-memory";
|
|
21
|
+
import { filesystem } from "../augments/filesystem";
|
|
22
|
+
import { webTransport } from "../transports/web-transport";
|
|
23
|
+
import { webFetch } from "../augments/web-fetch";
|
|
24
|
+
import { orgContext } from "../augments/org-context";
|
|
25
|
+
import { skills } from "../augments/skills";
|
|
26
|
+
import { bash } from "../augments/bash";
|
|
27
|
+
import { notify } from "../augments/notify";
|
|
28
|
+
import { telegramTransport } from "../augments/telegram-transport";
|
|
29
|
+
import { turnControl, type TurnControlOptions } from "../augments/turn-control";
|
|
30
|
+
import { visitorAuth } from "../augments/visitor-auth";
|
|
31
|
+
import type { VisitorAuthOptions, VisitorAuthAugmentExtras } from "../augments/visitor-auth/types";
|
|
32
|
+
import { link } from "../augments/link";
|
|
33
|
+
import type { LinkAugmentAgentCard, LinkAugmentOptions, LinkPeerConfig } from "../augments/link";
|
|
34
|
+
import type { Augment, NotifyAugmentOptions, TelegramTransportOptions } from "../types";
|
|
35
|
+
import type { AugmentConfig } from "./types";
|
|
36
|
+
import type { BudgetsAugmentOptions } from "../augments/budgets";
|
|
37
|
+
import { validateBundledSkills } from "./skill-validator";
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Path resolution helper
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
function resolvePath(path: string, agentDir: string): string {
|
|
44
|
+
if (path.startsWith("/")) return path;
|
|
45
|
+
return resolve(agentDir, path);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolve an orgContext baseUrl, normalizing relative `file://...` shapes
|
|
50
|
+
* against the agent dir so the augment factory only ever sees absolute
|
|
51
|
+
* file:// URLs.
|
|
52
|
+
*
|
|
53
|
+
* Accepted inputs:
|
|
54
|
+
* - `http://...` / `https://...` — passed through unchanged
|
|
55
|
+
* - `file:///abs/path` — passed through unchanged (already absolute)
|
|
56
|
+
* - `file://./relative/path` — resolved against agentDir, returned as
|
|
57
|
+
* an absolute file:// URL via `pathToFileURL`
|
|
58
|
+
* - `file://relative/path` — same; tolerated for ergonomics. The two-
|
|
59
|
+
* slash relative form mirrors how operators tend to write `file://`-style
|
|
60
|
+
* URLs in YAML config (`file://./org-context`).
|
|
61
|
+
*
|
|
62
|
+
* Rationale: keeping the relative→absolute conversion in the resolver avoids
|
|
63
|
+
* threading an `agentDir` construction parameter through to the augment
|
|
64
|
+
* factory (per ADR-024 — no new kernel surface; per the org-context augment's
|
|
65
|
+
* design — the factory accepts only absolute file:// URLs).
|
|
66
|
+
*/
|
|
67
|
+
function resolveOrgContextBaseUrl(baseUrl: string, agentDir: string): string {
|
|
68
|
+
if (!/^file:/i.test(baseUrl)) return baseUrl;
|
|
69
|
+
|
|
70
|
+
// Distinguishing absolute vs relative after stripping the `file:` scheme
|
|
71
|
+
// is ambiguous — both forms can produce a leading slash. So we count
|
|
72
|
+
// leading slashes BEFORE stripping:
|
|
73
|
+
// - `file:///abs/path` (three slashes) — POSIX-form absolute URL
|
|
74
|
+
// - `file:/abs/path` (one slash, no `//`) — uncommon but valid absolute
|
|
75
|
+
// - `file://./rel` (two slashes + `.`) — relative; this codebase's
|
|
76
|
+
// convention for "relative to agent dir"
|
|
77
|
+
// - `file://rel/path` (two slashes, no `.`) — also relative; tolerated
|
|
78
|
+
// for ergonomics (mirrors how operators write the URL in YAML config)
|
|
79
|
+
const afterScheme = baseUrl.replace(/^file:/i, "");
|
|
80
|
+
const isAbsoluteFileUrl =
|
|
81
|
+
afterScheme.startsWith("///") || (afterScheme.startsWith("/") && !afterScheme.startsWith("//"));
|
|
82
|
+
|
|
83
|
+
if (isAbsoluteFileUrl) {
|
|
84
|
+
// Already absolute — pass through unchanged.
|
|
85
|
+
return baseUrl;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Relative form. Compute the absolute path against agentDir and return as a
|
|
89
|
+
// proper file:// URL.
|
|
90
|
+
const relPath = afterScheme.replace(/^\/+/, "");
|
|
91
|
+
const absPath = resolve(agentDir, relPath);
|
|
92
|
+
return pathToFileURL(absPath).href;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Built-in resolvers
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
function resolveFileMemory(opts: Record<string, unknown>, agentDir: string): Augment {
|
|
100
|
+
return fileMemory({
|
|
101
|
+
label: opts.label as string,
|
|
102
|
+
source: resolvePath(opts.source as string, agentDir),
|
|
103
|
+
mutable: opts.mutable as boolean,
|
|
104
|
+
origin: opts.origin as "operator" | "system" | "agent" | "peer-derived",
|
|
105
|
+
priority: opts.priority as "required" | "high" | "normal" | "low" | "evictable",
|
|
106
|
+
placement: opts.placement as "system" | "preamble" | "assistant-preamble",
|
|
107
|
+
eviction: opts.eviction as "never" | "summarize" | "drop",
|
|
108
|
+
ttl: opts.ttl as "turn" | "session" | "persistent" | undefined,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function resolveLayeredMemory(
|
|
113
|
+
opts: Record<string, unknown>,
|
|
114
|
+
agentDir: string,
|
|
115
|
+
): Promise<Augment> {
|
|
116
|
+
const { layeredMemory } = await import("../augments/layered-memory");
|
|
117
|
+
const backend = (opts.backend as string | undefined) ?? "sqlite";
|
|
118
|
+
const namespace = (opts.namespace as string | undefined) ?? "ep";
|
|
119
|
+
const retentionDays = opts.retentionDays as number | undefined;
|
|
120
|
+
|
|
121
|
+
if (backend === "sqlite") {
|
|
122
|
+
const dbPath = opts.dbPath as string | undefined;
|
|
123
|
+
return layeredMemory({
|
|
124
|
+
backend: "sqlite",
|
|
125
|
+
dbPath: dbPath ? resolvePath(dbPath, agentDir) : resolvePath("./memory.db", agentDir),
|
|
126
|
+
namespace,
|
|
127
|
+
retentionDays,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (backend === "supabase") {
|
|
132
|
+
const supabaseUrl = opts.supabaseUrl as string | undefined;
|
|
133
|
+
const supabaseKey = opts.supabaseKey as string | undefined;
|
|
134
|
+
if (!supabaseUrl || !supabaseKey) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
"layeredMemory: supabase backend requires supabaseUrl and supabaseKey options",
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
const { createClient } = await import("@supabase/supabase-js");
|
|
140
|
+
const client = createClient(supabaseUrl, supabaseKey) as unknown as Parameters<
|
|
141
|
+
typeof layeredMemory
|
|
142
|
+
>[0]["client"];
|
|
143
|
+
return layeredMemory({
|
|
144
|
+
backend: "supabase",
|
|
145
|
+
client,
|
|
146
|
+
table: (opts.table as string | undefined) ?? "agent_memory",
|
|
147
|
+
namespace,
|
|
148
|
+
retentionDays,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
throw new Error(`layeredMemory: unknown backend "${backend}"`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function resolveSupabaseMemory(opts: Record<string, unknown>): Promise<Augment> {
|
|
156
|
+
const { supabaseUrl, supabaseKey, ...rest } = opts;
|
|
157
|
+
if (typeof supabaseUrl !== "string" || typeof supabaseKey !== "string") {
|
|
158
|
+
throw new Error(
|
|
159
|
+
"supabaseMemory requires supabaseUrl and supabaseKey options (use ${ENV_VAR} interpolation)",
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Lazy import — only load @supabase/supabase-js when supabaseMemory is used.
|
|
164
|
+
// The real SupabaseClient has narrower types than SupabaseLikeClient
|
|
165
|
+
// (e.g. data is null on error), so we cast through unknown.
|
|
166
|
+
const { createClient } = await import("@supabase/supabase-js");
|
|
167
|
+
const client = createClient(supabaseUrl, supabaseKey) as unknown as Parameters<
|
|
168
|
+
typeof supabaseMemory
|
|
169
|
+
>[0]["client"];
|
|
170
|
+
|
|
171
|
+
return supabaseMemory({
|
|
172
|
+
namespace: rest.namespace as string,
|
|
173
|
+
client,
|
|
174
|
+
table: rest.table as string,
|
|
175
|
+
mutable: rest.mutable as boolean,
|
|
176
|
+
origin: rest.origin as "operator" | "system" | "agent" | "peer-derived",
|
|
177
|
+
priority: rest.priority as "required" | "high" | "normal" | "low" | "evictable",
|
|
178
|
+
placement: rest.placement as "system" | "preamble" | "assistant-preamble",
|
|
179
|
+
eviction: rest.eviction as "never" | "summarize" | "drop",
|
|
180
|
+
searchLimit: rest.searchLimit as number | undefined,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function resolveFilesystem(opts: Record<string, unknown>, agentDir: string): Augment {
|
|
185
|
+
const mounts = (opts.mounts as Array<Record<string, unknown>>).map((m) => ({
|
|
186
|
+
name: m.name as string,
|
|
187
|
+
path: resolvePath(m.path as string, agentDir),
|
|
188
|
+
writable: m.writable as boolean | undefined,
|
|
189
|
+
deletable: m.deletable as boolean | undefined,
|
|
190
|
+
maxReadSize: m.maxReadSize as number | undefined,
|
|
191
|
+
maxWriteSize: m.maxWriteSize as number | undefined,
|
|
192
|
+
searchExcludes: m.searchExcludes as string[] | undefined,
|
|
193
|
+
}));
|
|
194
|
+
|
|
195
|
+
return filesystem({
|
|
196
|
+
mounts,
|
|
197
|
+
skillFile: opts.skillFile ? resolvePath(opts.skillFile as string, agentDir) : undefined,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Resolve the built-in `skills` augment (ADR-030). The `dir` option is
|
|
203
|
+
* resolved against agentDir using the same relative→absolute pattern as
|
|
204
|
+
* other agent-dir-relative paths, then handed to the augment factory.
|
|
205
|
+
*
|
|
206
|
+
* Default `dir` is `./skills` to match the scaffold layout (`auggy create`
|
|
207
|
+
* copies bundled skill folders to `<agentDir>/skills/<augment>/`).
|
|
208
|
+
*/
|
|
209
|
+
function resolveSkills(opts: Record<string, unknown>, agentDir: string): Augment {
|
|
210
|
+
const rawDir = (opts.dir as string | undefined) ?? "./skills";
|
|
211
|
+
return skills({ dir: resolvePath(rawDir, agentDir) });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function resolveWebTransport(
|
|
215
|
+
opts: Record<string, unknown>,
|
|
216
|
+
lateBindings: { revocationCheck: ((id: string) => boolean) | null },
|
|
217
|
+
): Augment {
|
|
218
|
+
const vtBase = opts.visitorTokens as
|
|
219
|
+
| { enabled?: boolean; ttlSeconds?: number; signingKey?: string; agentBinding?: string }
|
|
220
|
+
| undefined;
|
|
221
|
+
return webTransport({
|
|
222
|
+
port: opts.port as number,
|
|
223
|
+
auth: opts.auth as { type: "bearer"; token: string },
|
|
224
|
+
cors: opts.cors as { origins: string[] } | undefined,
|
|
225
|
+
maxMessageLength: opts.maxMessageLength as number | undefined,
|
|
226
|
+
access: opts.access as { agents?: Array<{ id: string; sharedSecret: string }> } | undefined,
|
|
227
|
+
concurrency: opts.concurrency as number | undefined,
|
|
228
|
+
maxQueueDepth: opts.maxQueueDepth as number | undefined,
|
|
229
|
+
rateLimitPerPeer: opts.rateLimitPerPeer as { maxPerMinute: number } | undefined,
|
|
230
|
+
visitorTokens: vtBase
|
|
231
|
+
? {
|
|
232
|
+
...vtBase,
|
|
233
|
+
revocationCheck: (id: string) => lateBindings.revocationCheck?.(id) ?? false,
|
|
234
|
+
}
|
|
235
|
+
: undefined,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function resolveWebFetch(opts: Record<string, unknown>): Augment {
|
|
240
|
+
return webFetch({
|
|
241
|
+
timeoutMs: opts.timeoutMs as number | undefined,
|
|
242
|
+
maxRedirects: opts.maxRedirects as number | undefined,
|
|
243
|
+
userAgent: opts.userAgent as string | undefined,
|
|
244
|
+
defaultHeaders: opts.defaultHeaders as Record<string, string> | undefined,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function resolveCustom(config: AugmentConfig, agentDir: string): Promise<Augment> {
|
|
249
|
+
if (!config.source) {
|
|
250
|
+
throw new Error(`Custom augment "${config.name}": source path is required`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const absPath = resolvePath(config.source, agentDir);
|
|
254
|
+
let mod: Record<string, unknown>;
|
|
255
|
+
try {
|
|
256
|
+
mod = await import(absPath);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
`Custom augment "${config.name}": failed to import "${absPath}": ${(err as Error).message}`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const factory = mod.default;
|
|
264
|
+
if (typeof factory !== "function") {
|
|
265
|
+
throw new Error(
|
|
266
|
+
`Custom augment "${config.name}": "${absPath}" must have a default export that is a function (got ${typeof factory})`,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return factory(config.options ?? {}) as Augment;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// Public API
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
function resolveBash(opts: Record<string, unknown>, agentDir: string): Augment {
|
|
278
|
+
const scripts = (opts.scripts as Array<Record<string, unknown>> | undefined)?.map((s) => ({
|
|
279
|
+
name: s.name as string,
|
|
280
|
+
description: s.description as string,
|
|
281
|
+
command: s.command as string,
|
|
282
|
+
workingDir: s.workingDir ? resolvePath(s.workingDir as string, agentDir) : undefined,
|
|
283
|
+
timeout: s.timeout as number | undefined,
|
|
284
|
+
}));
|
|
285
|
+
|
|
286
|
+
return bash({
|
|
287
|
+
risk: opts.risk as "scripts-only" | "restricted" | "standard" | "unrestricted" | undefined,
|
|
288
|
+
allowedCommands: opts.allowedCommands as string[] | undefined,
|
|
289
|
+
blockedCommands: opts.blockedCommands as string[] | undefined,
|
|
290
|
+
workingDir: opts.workingDir ? resolvePath(opts.workingDir as string, agentDir) : undefined,
|
|
291
|
+
inheritEnv: opts.inheritEnv as boolean | undefined,
|
|
292
|
+
env: opts.env as Record<string, string> | undefined,
|
|
293
|
+
timeout: opts.timeout as number | undefined,
|
|
294
|
+
maxOutputBytes: opts.maxOutputBytes as number | undefined,
|
|
295
|
+
maxToolCallsPerTurn: opts.maxToolCallsPerTurn as number | undefined,
|
|
296
|
+
scripts,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function resolveLink(opts: Record<string, unknown>, agentDir: string): Augment {
|
|
301
|
+
const card = opts.agentCard as Record<string, unknown>;
|
|
302
|
+
const agentCard: LinkAugmentAgentCard = {
|
|
303
|
+
id: card.id as string,
|
|
304
|
+
name: card.name as string,
|
|
305
|
+
description: card.description as string,
|
|
306
|
+
endpointUrl: card.endpointUrl as string,
|
|
307
|
+
capabilities: card.capabilities as string[] | undefined,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const peersRaw = (opts.peers as Record<string, Record<string, unknown>> | undefined) ?? {};
|
|
311
|
+
const peers: Record<string, LinkPeerConfig> = {};
|
|
312
|
+
for (const [name, p] of Object.entries(peersRaw)) {
|
|
313
|
+
peers[name] = {
|
|
314
|
+
url: p.url as string,
|
|
315
|
+
bearer: p.bearer as string,
|
|
316
|
+
participantId: p.participantId as string,
|
|
317
|
+
inboundBearer: p.inboundBearer as string,
|
|
318
|
+
inboundBearerId: p.inboundBearerId as string,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const linkOpts: LinkAugmentOptions = {
|
|
323
|
+
port: opts.port as number | undefined,
|
|
324
|
+
dbPath: resolvePath(opts.dbPath as string, agentDir),
|
|
325
|
+
agentCard,
|
|
326
|
+
peers,
|
|
327
|
+
};
|
|
328
|
+
return link(linkOpts);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function resolveVisitorAuth(opts: Record<string, unknown>, agentDir: string): Augment {
|
|
332
|
+
const dbPath = (opts.dbPath as string | undefined) ?? "./visitor-auth.db";
|
|
333
|
+
// CRITICAL: distinguish `null` (operator opt-out) from `undefined` (defaults to ./memory.db).
|
|
334
|
+
// Using ?? would coerce both to the default string — wrong for opt-out semantics.
|
|
335
|
+
const layeredMemoryDbPath =
|
|
336
|
+
opts.layeredMemoryDbPath === null
|
|
337
|
+
? null
|
|
338
|
+
: ((opts.layeredMemoryDbPath as string | undefined) ?? "./memory.db");
|
|
339
|
+
|
|
340
|
+
const config: VisitorAuthOptions = {
|
|
341
|
+
publicUrl: opts.publicUrl as string,
|
|
342
|
+
dbPath: resolvePath(dbPath, agentDir),
|
|
343
|
+
agentMail: opts.agentMail as VisitorAuthOptions["agentMail"],
|
|
344
|
+
signingKey: opts.signingKey as string,
|
|
345
|
+
agentBinding: opts.agentBinding as string | undefined,
|
|
346
|
+
rateLimit: opts.rateLimit as VisitorAuthOptions["rateLimit"],
|
|
347
|
+
reverifyAfterDays: opts.reverifyAfterDays as number | undefined,
|
|
348
|
+
tokenTtlMinutes: opts.tokenTtlMinutes as number | undefined,
|
|
349
|
+
notifyOnFirstVerify: opts.notifyOnFirstVerify as VisitorAuthOptions["notifyOnFirstVerify"],
|
|
350
|
+
layeredMemoryDbPath:
|
|
351
|
+
layeredMemoryDbPath === null ? null : resolvePath(layeredMemoryDbPath, agentDir),
|
|
352
|
+
};
|
|
353
|
+
return visitorAuth(config);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Resolve an array of augment configs into concrete Augment objects.
|
|
358
|
+
* Built-in types dispatch to their factory functions; custom types
|
|
359
|
+
* use dynamic import of local .ts files.
|
|
360
|
+
*/
|
|
361
|
+
export async function resolveAugments(
|
|
362
|
+
configs: AugmentConfig[],
|
|
363
|
+
agentDir: string,
|
|
364
|
+
): Promise<Augment[]> {
|
|
365
|
+
const augments: Augment[] = [];
|
|
366
|
+
|
|
367
|
+
// Deferred-closure for C1: webTransport gets a stable callback reference
|
|
368
|
+
// before visitorAuth is resolved; the callback reads lateBindings.revocationCheck
|
|
369
|
+
// which is populated after the loop completes.
|
|
370
|
+
const lateBindings: { revocationCheck: ((id: string) => boolean) | null } = {
|
|
371
|
+
revocationCheck: null,
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// Fix F2 — single-source signingKey + conservative handling of operator's
|
|
375
|
+
// explicit `enabled` setting.
|
|
376
|
+
//
|
|
377
|
+
// visitorAuth is the sole authority for signingKey: it mints tokens so it
|
|
378
|
+
// MUST own the key. webTransport only verifies them; receiving the key via
|
|
379
|
+
// injection avoids operators having to duplicate the secret across two
|
|
380
|
+
// config blocks (where a mismatch silently breaks the flow).
|
|
381
|
+
//
|
|
382
|
+
// Auto-defaulting rules:
|
|
383
|
+
// - When visitorAuth is absent: auto-disable ONLY when operator left enabled
|
|
384
|
+
// unset. Explicit enabled: true is respected (custom minter scenario).
|
|
385
|
+
// - When visitorAuth is present: inject signingKey + auto-enable ONLY when
|
|
386
|
+
// operator did not explicitly set enabled: false. Explicit false is
|
|
387
|
+
// respected (unusual but legal).
|
|
388
|
+
//
|
|
389
|
+
// Iterates ALL webTransport configs (not just the first) so a multi-
|
|
390
|
+
// transport setup gets consistent injection (fixes Codex C-H2).
|
|
391
|
+
{
|
|
392
|
+
const vaConfig = configs.find((c) => c.type === "visitorAuth");
|
|
393
|
+
const wtConfigs = configs.filter((c) => c.type === "webTransport");
|
|
394
|
+
|
|
395
|
+
for (const wtConfig of wtConfigs) {
|
|
396
|
+
const wtOpts = (wtConfig.options ?? {}) as Record<string, unknown>;
|
|
397
|
+
const vt = (wtOpts.visitorTokens ?? {}) as Record<string, unknown>;
|
|
398
|
+
|
|
399
|
+
// Track whether operator explicitly set `enabled` (truthy or false) vs left it undefined.
|
|
400
|
+
const enabledExplicit = "enabled" in vt;
|
|
401
|
+
|
|
402
|
+
if (!vaConfig) {
|
|
403
|
+
// No visitorAuth mounted.
|
|
404
|
+
// If signingKey is set, warn about potential identity loss (operator
|
|
405
|
+
// may have removed visitorAuth between boots, stranding issued tokens).
|
|
406
|
+
if (vt.signingKey !== undefined) {
|
|
407
|
+
console.warn(
|
|
408
|
+
`[augment-resolver] webTransport.visitorTokens.signingKey is set but no visitorAuth augment is mounted. Pre-existing visitor tokens may not be verified (no minter is registered). If you previously had visitorAuth mounted and removed it, all verified visitors will revert to anonymous on next request.`,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (!enabledExplicit) {
|
|
413
|
+
// Operator left enabled unset → auto-disable (no minter mounted).
|
|
414
|
+
vt.enabled = false;
|
|
415
|
+
wtOpts.visitorTokens = vt;
|
|
416
|
+
wtConfig.options = wtOpts;
|
|
417
|
+
} else if (vt.enabled === true) {
|
|
418
|
+
// Operator explicitly opted in without visitorAuth. Warn — likely a
|
|
419
|
+
// misconfig that previously silently worked via ephemeral fallback.
|
|
420
|
+
console.warn(
|
|
421
|
+
`[augment-resolver] webTransport.visitorTokens.enabled is true but no visitorAuth augment is mounted. Tokens will not be minted by visitorAuth; if you have a custom token-minter, set visitorTokens.signingKey explicitly. Otherwise, set enabled: false or mount visitorAuth.`,
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
// else: enabled: false explicitly set — nothing to do.
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// visitorAuth IS mounted.
|
|
429
|
+
const vaSigningKey = (vaConfig.options as Record<string, unknown> | undefined)?.signingKey as
|
|
430
|
+
| string
|
|
431
|
+
| undefined;
|
|
432
|
+
|
|
433
|
+
if (vt.enabled === false) {
|
|
434
|
+
// Operator explicitly disabled visitor tokens despite mounting visitorAuth.
|
|
435
|
+
// Respect — visitorAuth's request_auth tool still works, but webTransport
|
|
436
|
+
// won't honor any minted token. Unusual but legal.
|
|
437
|
+
console.warn(
|
|
438
|
+
`[augment-resolver] visitorAuth is mounted but webTransport.visitorTokens.enabled is explicitly false. Verified visitors will not be recognized at the wire. Remove the explicit false to enable visitor recognition.`,
|
|
439
|
+
);
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Normal path: visitorAuth mounted, enabled is true (or undefined → default to true).
|
|
444
|
+
if (vt.signingKey !== undefined && vt.signingKey !== vaSigningKey) {
|
|
445
|
+
console.warn(
|
|
446
|
+
"[augment-resolver] webTransport.visitorTokens.signingKey is set but visitorAuth.signingKey takes precedence. Remove the duplicate from webTransport's config.",
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
// Inject visitorAuth's signingKey and enable visitor tokens.
|
|
450
|
+
vt.signingKey = vaSigningKey;
|
|
451
|
+
vt.enabled = true;
|
|
452
|
+
wtOpts.visitorTokens = vt;
|
|
453
|
+
wtConfig.options = wtOpts;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
for (const config of configs) {
|
|
458
|
+
const opts = config.options ?? {};
|
|
459
|
+
let augment: Augment;
|
|
460
|
+
|
|
461
|
+
switch (config.type) {
|
|
462
|
+
case "fileMemory":
|
|
463
|
+
augment = resolveFileMemory(opts, agentDir);
|
|
464
|
+
break;
|
|
465
|
+
case "supabaseMemory":
|
|
466
|
+
augment = await resolveSupabaseMemory(opts);
|
|
467
|
+
break;
|
|
468
|
+
case "layeredMemory":
|
|
469
|
+
augment = await resolveLayeredMemory(opts, agentDir);
|
|
470
|
+
break;
|
|
471
|
+
case "filesystem":
|
|
472
|
+
augment = resolveFilesystem(opts, agentDir);
|
|
473
|
+
break;
|
|
474
|
+
case "webTransport":
|
|
475
|
+
augment = resolveWebTransport(opts, lateBindings);
|
|
476
|
+
break;
|
|
477
|
+
case "webFetch":
|
|
478
|
+
augment = resolveWebFetch(opts);
|
|
479
|
+
break;
|
|
480
|
+
case "orgContext":
|
|
481
|
+
augment = orgContext({
|
|
482
|
+
baseUrl: resolveOrgContextBaseUrl(opts.baseUrl as string, agentDir),
|
|
483
|
+
token: opts.token as string | undefined,
|
|
484
|
+
cacheTtlMs: opts.cacheTtlMs as number | undefined,
|
|
485
|
+
});
|
|
486
|
+
break;
|
|
487
|
+
case "skills":
|
|
488
|
+
augment = resolveSkills(opts, agentDir);
|
|
489
|
+
break;
|
|
490
|
+
case "bash":
|
|
491
|
+
augment = resolveBash(opts, agentDir);
|
|
492
|
+
break;
|
|
493
|
+
case "budgets": {
|
|
494
|
+
const { budgets } = await import("../augments/budgets");
|
|
495
|
+
const dbPath = (opts.dbPath as string | undefined) ?? "./budgets.db";
|
|
496
|
+
augment = budgets({
|
|
497
|
+
dbPath: resolvePath(dbPath, agentDir),
|
|
498
|
+
caps: opts.caps as BudgetsAugmentOptions["caps"],
|
|
499
|
+
anonymousGlobalLimit: opts.anonymousGlobalLimit as number | undefined,
|
|
500
|
+
dailyBudgetUsd: opts.dailyBudgetUsd as number | undefined,
|
|
501
|
+
cleanupWindowMs: opts.cleanupWindowMs as number | undefined,
|
|
502
|
+
});
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
case "notify": {
|
|
506
|
+
augment = notify({
|
|
507
|
+
destinations: opts.destinations as NotifyAugmentOptions["destinations"],
|
|
508
|
+
rateLimit: opts.rateLimit as NotifyAugmentOptions["rateLimit"],
|
|
509
|
+
});
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
case "telegramTransport":
|
|
513
|
+
augment = telegramTransport(opts as unknown as TelegramTransportOptions);
|
|
514
|
+
break;
|
|
515
|
+
case "turnControl":
|
|
516
|
+
augment = turnControl(opts as TurnControlOptions);
|
|
517
|
+
break;
|
|
518
|
+
case "visitorAuth":
|
|
519
|
+
augment = resolveVisitorAuth(opts, agentDir);
|
|
520
|
+
break;
|
|
521
|
+
case "link":
|
|
522
|
+
augment = resolveLink(opts, agentDir);
|
|
523
|
+
break;
|
|
524
|
+
case "custom":
|
|
525
|
+
augment = await resolveCustom(config, agentDir);
|
|
526
|
+
break;
|
|
527
|
+
default:
|
|
528
|
+
throw new Error(`Unknown augment type: "${config.type}"`);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Override the auto-generated augment name with the operator's choice.
|
|
532
|
+
augment = { ...augment, name: config.name };
|
|
533
|
+
augments.push(augment);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Fix C1: wire the visitorAuth revocation check into webTransport's
|
|
537
|
+
// deferred closure. The closure was passed to webTransport during the loop
|
|
538
|
+
// (before visitorAuth was necessarily resolved); populating lateBindings
|
|
539
|
+
// now makes the check active for all subsequent requests.
|
|
540
|
+
//
|
|
541
|
+
// Use index-based lookup (configs[i] → augments[i]) instead of name-based
|
|
542
|
+
// search so that operator-renamed visitorAuth augments (e.g. `name: my-auth`
|
|
543
|
+
// in agent.yaml) still get wired correctly. The `.name` property is
|
|
544
|
+
// overwritten with the config name at line 396 after each factory returns,
|
|
545
|
+
// so `augments.find(a => a.name === "visitor-auth")` would fail for any
|
|
546
|
+
// non-default name and silently disable revocation.
|
|
547
|
+
const vaIdx = configs.findIndex((c) => c.type === "visitorAuth");
|
|
548
|
+
const va = vaIdx >= 0 ? (augments[vaIdx] as Augment & VisitorAuthAugmentExtras) : undefined;
|
|
549
|
+
if (va?.isVisitorRevoked) {
|
|
550
|
+
lateBindings.revocationCheck = va.isVisitorRevoked.bind(va);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Fix F18: throw when multiple visitorAuth augments are declared.
|
|
554
|
+
// Both would attempt to register GET/POST /visitor-auth/verify routes
|
|
555
|
+
// (the route-collector hard-fails on duplicate registration anyway), and
|
|
556
|
+
// only the first's revocation state would be visible to webTransport.
|
|
557
|
+
// A hard error here is more honest than a warning for a state that's
|
|
558
|
+
// unreachable at runtime.
|
|
559
|
+
const vaCount = configs.filter((c) => c.type === "visitorAuth").length;
|
|
560
|
+
if (vaCount > 1) {
|
|
561
|
+
throw new Error(
|
|
562
|
+
`[augment-resolver] Multiple visitorAuth augments declared (${vaCount}). visitorAuth is supported as a single instance per agent — both would attempt to register GET/POST /visitor-auth/verify routes (rejected by route-collector) and only the first's revocation state would be visible to webTransport. Declare exactly one visitorAuth augment.`,
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Fix H3: cross-augment validation — visitorAuth.agentBinding MUST match
|
|
567
|
+
// webTransport.visitorTokens.agentBinding when both are configured. A mismatch
|
|
568
|
+
// silently strands visitors: the magic-link flow succeeds, but the next request
|
|
569
|
+
// rejects the minted token because the agentBinding field won't match.
|
|
570
|
+
const vaConfig = configs.find((c) => c.type === "visitorAuth");
|
|
571
|
+
const wtConfig = configs.find((c) => c.type === "webTransport");
|
|
572
|
+
if (vaConfig && wtConfig) {
|
|
573
|
+
const vaBinding = (vaConfig.options as Record<string, unknown> | undefined)?.agentBinding as
|
|
574
|
+
| string
|
|
575
|
+
| undefined;
|
|
576
|
+
const wtBinding = (
|
|
577
|
+
(wtConfig.options as Record<string, unknown> | undefined)?.visitorTokens as
|
|
578
|
+
| Record<string, unknown>
|
|
579
|
+
| undefined
|
|
580
|
+
)?.agentBinding as string | undefined;
|
|
581
|
+
if (vaBinding !== wtBinding) {
|
|
582
|
+
throw new Error(
|
|
583
|
+
`Cross-augment config mismatch: visitorAuth.agentBinding (${vaBinding ?? "unset"}) ` +
|
|
584
|
+
`must match webTransport.visitorTokens.agentBinding (${wtBinding ?? "unset"}). ` +
|
|
585
|
+
`Set them both to the same value (e.g., \${AUGGY_AGENT_ID}) in agent.yaml.`,
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Boot-time validation: warn (not error) for any tool-providing augment
|
|
591
|
+
// whose bundled skill is not mounted at <agent-dir>/skills/<folder>/SKILL.md.
|
|
592
|
+
// Per ADR-025 Decision 5 + spec §H. Runs after every factory has produced
|
|
593
|
+
// its tool surface so the discriminator (`tools.length > 0`) is final.
|
|
594
|
+
validateBundledSkills(configs, augments, agentDir);
|
|
595
|
+
|
|
596
|
+
return augments;
|
|
597
|
+
}
|