glove-foundry 0.4.0 → 0.5.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/README.md +17 -1
- package/dist/chunk-5NFLGHOC.js +281 -0
- package/dist/chunk-J7ZBJM54.js +6 -0
- package/dist/{chunk-5LDW2N2E.js → chunk-KBMD4RYU.js} +1980 -3464
- package/dist/chunk-LW5PJ2FN.js +546 -0
- package/dist/{chunk-25BGNRJN.js → chunk-SGF3CFUR.js} +248 -120
- package/dist/chunk-TH5ETCG2.js +730 -0
- package/dist/cli.js +7 -3
- package/dist/{client-D0-pKEZM.d.ts → client-DcsnudMI.d.ts} +9 -3
- package/dist/client.d.ts +3 -1
- package/dist/config.d.ts +1 -0
- package/dist/execution-agent.js +9 -4
- package/dist/execution-daemon.d.ts +2 -0
- package/dist/execution-daemon.js +157 -0
- package/dist/execution-loader.d.ts +6 -0
- package/dist/execution-loader.js +20 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +32 -27
- package/dist/station.d.ts +35 -0
- package/dist/station.js +9 -0
- package/docs/architecture.md +1 -1
- package/docs/building-with-foundry.md +19 -2
- package/docs/execution-release.md +57 -0
- package/docs/execution.md +97 -0
- package/docs/release-verification.md +3 -1
- package/package.json +23 -15
- package/templates/minimal/README.md +1 -1
- package/templates/travel-concierge/README.md +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ for it to finish before showing next steps. In non-interactive commands, add
|
|
|
19
19
|
`--install` to install automatically; use `--no-install` to defer it explicitly.
|
|
20
20
|
|
|
21
21
|
Use Node.js 22.13+ (recommended, including SQLite memory support); the CLI requires
|
|
22
|
-
at least Node
|
|
22
|
+
at least Node 22. Run this in a terminal:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
25
|
npx glove-foundry init
|
|
@@ -409,3 +409,19 @@ POST /v1/chat/completions
|
|
|
409
409
|
The `/v1/chat/completions` endpoint accepts an agent instance id as `model`, supports SSE streaming, and returns `x-foundry-conversation-id`. Reuse `user`, `conversation_id`, or that header to continue the same durable Foundry conversation from an OpenAI-compatible client.
|
|
410
410
|
|
|
411
411
|
See [Building with Foundry](./docs/building-with-foundry.md), [Architecture](./docs/architecture.md), the compact [`examples/foundry-agent`](../../examples/foundry-agent), and the [Braind Storm workforce](../../examples/foundry-braind-storm).
|
|
412
|
+
|
|
413
|
+
## Browser, sandbox and daemon execution
|
|
414
|
+
|
|
415
|
+
Foundry runs agent jobs in a separate managed Station 3 daemon. Applications can
|
|
416
|
+
opt into `glove-execution` and call `mountBrowser` / `mountSandbox` in `configure`,
|
|
417
|
+
using the same mount approach as other capabilities. Browser workflows run as
|
|
418
|
+
scripts through `execute_browser`, with native screenshot observations. See the
|
|
419
|
+
[execution guide](docs/execution.md) for setup, ownership and lifecycle limits.
|
|
420
|
+
|
|
421
|
+
The [Operator example](../../examples/foundry-operator) combines these mounts into a general-purpose browser and coding agent with a local console, persistent sign-in, container services and a documented live verification.
|
|
422
|
+
|
|
423
|
+
### Optional resources on the managed Station
|
|
424
|
+
|
|
425
|
+
Use `daemon: stationDaemon({ stationId, resources, onReady })` in your application definition (`stationDaemon` is exported from `glove-foundry/station`). Foundry then runs agent jobs and the application-supplied browser/sandbox adapters on one managed Station. Resource factories run once inside the daemon; agents still receive capabilities through explicit mounts. Omit this option for jobs only. See [setup, ownership and private connections](./docs/execution.md#one-station-for-jobs-and-resources).
|
|
426
|
+
|
|
427
|
+
See the [execution release handoff](./docs/execution-release.md) for verification, operational limits and publication steps.
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// src/definition.ts
|
|
2
|
+
import { Displaymanager, Glove } from "glove-core";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
|
|
5
|
+
// src/identity.ts
|
|
6
|
+
var identities = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
function fileIdentified(value, kind, explicitId) {
|
|
8
|
+
const cell = {
|
|
9
|
+
kind,
|
|
10
|
+
...explicitId ? { id: explicitId } : {},
|
|
11
|
+
explicit: explicitId !== void 0
|
|
12
|
+
};
|
|
13
|
+
const identified = { ...value };
|
|
14
|
+
Object.defineProperty(identified, "id", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: false,
|
|
17
|
+
get() {
|
|
18
|
+
if (cell.id) return cell.id;
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Foundry ${kind} identity has not been bound yet. Default-export it from its convention file and let Foundry discovery derive the id.`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
identities.set(identified, cell);
|
|
25
|
+
return identified;
|
|
26
|
+
}
|
|
27
|
+
function bindFileIdentity(value, id, expectedKind) {
|
|
28
|
+
const cell = identities.get(value);
|
|
29
|
+
if (!cell) {
|
|
30
|
+
const current = value.id;
|
|
31
|
+
if (typeof current === "string" && current !== id) {
|
|
32
|
+
throw new Error(`Foundry file route resolves to "${id}" but the definition declares "${current}".`);
|
|
33
|
+
}
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (expectedKind && cell.kind !== expectedKind) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Foundry ${expectedKind} route "${id}" received a ${cell.kind} definition.`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (cell.id && cell.id !== id) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Foundry ${cell.kind} route resolves to "${id}" but the definition declares "${cell.id}". Remove the explicit id and let the file own identity.`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
cell.id = id;
|
|
47
|
+
}
|
|
48
|
+
function fileDefinitionKey(value) {
|
|
49
|
+
const cell = identities.get(value);
|
|
50
|
+
return cell ?? value;
|
|
51
|
+
}
|
|
52
|
+
function fileDefinitionLabel(value) {
|
|
53
|
+
const cell = identities.get(value);
|
|
54
|
+
return cell?.id ?? `<${cell?.kind ?? "definition"} file route>`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/definition.ts
|
|
58
|
+
var FOUNDRY_AGENT_DEFINITION_BRAND = /* @__PURE__ */ Symbol.for(
|
|
59
|
+
"glove-foundry-agent-definition"
|
|
60
|
+
);
|
|
61
|
+
var FOUNDRY_AGENT_BRAND = FOUNDRY_AGENT_DEFINITION_BRAND;
|
|
62
|
+
var FOUNDRY_EVENT_PREFIX = "__GLOVE_FOUNDRY_EVENT__";
|
|
63
|
+
var FOUNDRY_APPLICATION_ENV = "GLOVE_FOUNDRY_APPLICATION_FILE";
|
|
64
|
+
var FOUNDRY_AGENT_ROUTE_ENV = "GLOVE_FOUNDRY_AGENT_ROUTE";
|
|
65
|
+
var FOUNDRY_AGENT_FILE_ENV = "GLOVE_FOUNDRY_AGENT_FILE";
|
|
66
|
+
var FOUNDRY_EXECUTION_MARKER = "__glove_foundry_execution_v1";
|
|
67
|
+
function defineCall(options) {
|
|
68
|
+
if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(options.name)) {
|
|
69
|
+
throw new Error(`Invalid Foundry call name "${options.name}".`);
|
|
70
|
+
}
|
|
71
|
+
return Object.freeze({ ...options });
|
|
72
|
+
}
|
|
73
|
+
var ROUTE_PATTERN = /^[a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)*$/;
|
|
74
|
+
function assertAgentRoute(route) {
|
|
75
|
+
if (!ROUTE_PATTERN.test(route)) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Invalid Foundry agent id "${route}". Use lowercase path segments containing letters, digits, and hyphens.`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
if (route.includes("__")) {
|
|
81
|
+
throw new Error(`Invalid Foundry agent id "${route}": "__" is reserved.`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function internalAgentName(route) {
|
|
85
|
+
assertAgentRoute(route);
|
|
86
|
+
return `foundry_${route.replaceAll("/", "__").replaceAll("-", "_")}`;
|
|
87
|
+
}
|
|
88
|
+
function routeFromInternalAgentName(name) {
|
|
89
|
+
if (!name.startsWith("foundry_")) return name;
|
|
90
|
+
return name.slice("foundry_".length).replaceAll("__", "/").replaceAll("_", "-");
|
|
91
|
+
}
|
|
92
|
+
function isFoundryAgentDefinition(value) {
|
|
93
|
+
return Boolean(
|
|
94
|
+
value && typeof value === "object" && value[FOUNDRY_AGENT_DEFINITION_BRAND] === true
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
var isFoundryAgent = isFoundryAgentDefinition;
|
|
98
|
+
function validateDefinition(options, route) {
|
|
99
|
+
const id = route ?? options.id;
|
|
100
|
+
if (id) assertAgentRoute(id);
|
|
101
|
+
if (!options.model && !options.run && !options.handler && !options.spawn) {
|
|
102
|
+
throw new Error(`Foundry agent "${id ?? "<file route>"}" must define model, run, handler, or spawn.`);
|
|
103
|
+
}
|
|
104
|
+
if (options.model && options.systemPrompt === void 0) {
|
|
105
|
+
throw new Error(`Foundry agent "${id ?? "<file route>"}" must define systemPrompt when it defines a model.`);
|
|
106
|
+
}
|
|
107
|
+
if (options.inboxes !== void 0 && typeof options.inboxes !== "function") {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Foundry agent "${id ?? "<file route>"}" inboxes must be a lazy resolver function.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const forbidden = ["input", "output"];
|
|
113
|
+
for (const key of forbidden) {
|
|
114
|
+
if (key in options) {
|
|
115
|
+
throw new Error(`Foundry agent "${id ?? "<file route>"}" cannot define ${key}; invocation contracts are framework-owned.`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function defineAgent(options) {
|
|
120
|
+
validateDefinition(options);
|
|
121
|
+
const { id, ...definition } = options;
|
|
122
|
+
return Object.freeze(fileIdentified({
|
|
123
|
+
...definition,
|
|
124
|
+
tags: Object.freeze([...options.tags ?? []]),
|
|
125
|
+
[FOUNDRY_AGENT_DEFINITION_BRAND]: true
|
|
126
|
+
}, "agent", id));
|
|
127
|
+
}
|
|
128
|
+
var CONVENTION_EXPORTS = [
|
|
129
|
+
"id",
|
|
130
|
+
"description",
|
|
131
|
+
"tags",
|
|
132
|
+
"components",
|
|
133
|
+
"mcpAdapter",
|
|
134
|
+
"accountSessions",
|
|
135
|
+
"store",
|
|
136
|
+
"model",
|
|
137
|
+
"systemPrompt",
|
|
138
|
+
"displayManager",
|
|
139
|
+
"serverMode",
|
|
140
|
+
"maxRetries",
|
|
141
|
+
"maxConsecutiveErrors",
|
|
142
|
+
"compactionLimit",
|
|
143
|
+
"compactionInstructions",
|
|
144
|
+
"maxTurns",
|
|
145
|
+
"enableToolResultSummary",
|
|
146
|
+
"tools",
|
|
147
|
+
"hooks",
|
|
148
|
+
"skills",
|
|
149
|
+
"subagents",
|
|
150
|
+
"memory",
|
|
151
|
+
"goals",
|
|
152
|
+
"facts",
|
|
153
|
+
"forms",
|
|
154
|
+
"contextProviders",
|
|
155
|
+
"inboxes",
|
|
156
|
+
"subscribers",
|
|
157
|
+
"layers",
|
|
158
|
+
"calls",
|
|
159
|
+
"schedules",
|
|
160
|
+
"playbooks",
|
|
161
|
+
"mesh",
|
|
162
|
+
"workingEnvironment",
|
|
163
|
+
"repl",
|
|
164
|
+
"configure",
|
|
165
|
+
"build",
|
|
166
|
+
"spawn",
|
|
167
|
+
"run",
|
|
168
|
+
"handler"
|
|
169
|
+
];
|
|
170
|
+
function defineAgentFromModule(route, module) {
|
|
171
|
+
assertAgentRoute(route);
|
|
172
|
+
if (isFoundryAgentDefinition(module.default)) {
|
|
173
|
+
const definition2 = module.default;
|
|
174
|
+
bindFileIdentity(definition2, route, "agent");
|
|
175
|
+
validateDefinition(definition2, route);
|
|
176
|
+
return definition2;
|
|
177
|
+
}
|
|
178
|
+
if (!module.description) {
|
|
179
|
+
throw new Error(`Foundry route "${route}" must default-export defineAgent(...) or export description.`);
|
|
180
|
+
}
|
|
181
|
+
const options = {};
|
|
182
|
+
for (const key of CONVENTION_EXPORTS) {
|
|
183
|
+
if (module[key] !== void 0) options[key] = module[key];
|
|
184
|
+
}
|
|
185
|
+
const definition = defineAgent(options);
|
|
186
|
+
bindFileIdentity(definition, route, "agent");
|
|
187
|
+
return definition;
|
|
188
|
+
}
|
|
189
|
+
function defineSubagent(options) {
|
|
190
|
+
if (!/^[A-Za-z][\w-]*$/.test(options.name)) throw new Error(`Invalid Foundry subagent name "${options.name}".`);
|
|
191
|
+
return Object.freeze({
|
|
192
|
+
name: options.name,
|
|
193
|
+
description: options.description,
|
|
194
|
+
factory: async ({ parentStore, parentControls, prompt }) => {
|
|
195
|
+
const store = await parentStore.createSubAgentStore?.(options.name, options.durable ?? false) ?? void 0;
|
|
196
|
+
const tools = typeof options.tools === "function" ? await resolveResolvable(options.tools({
|
|
197
|
+
name: options.name,
|
|
198
|
+
prompt,
|
|
199
|
+
parent: parentControls.glove
|
|
200
|
+
})) : options.tools ?? [];
|
|
201
|
+
const glove = new Glove({
|
|
202
|
+
...store ? { store } : {},
|
|
203
|
+
model: options.model ?? parentControls.glove.model,
|
|
204
|
+
displayManager: parentControls.displayManager ?? new Displaymanager(),
|
|
205
|
+
systemPrompt: options.systemPrompt,
|
|
206
|
+
serverMode: options.serverMode ?? parentControls.glove.serverMode,
|
|
207
|
+
...options.maxRetries !== void 0 ? { maxRetries: options.maxRetries } : {},
|
|
208
|
+
...options.maxConsecutiveErrors !== void 0 ? { maxConsecutiveErrors: options.maxConsecutiveErrors } : {},
|
|
209
|
+
compaction_config: {
|
|
210
|
+
compaction_instructions: options.compactionInstructions ?? `Preserve the ${options.name} subagent's findings and unresolved work.`,
|
|
211
|
+
...options.maxTurns !== void 0 ? { max_turns: options.maxTurns } : {},
|
|
212
|
+
...options.compactionLimit !== void 0 ? { compaction_context_limit: options.compactionLimit } : {}
|
|
213
|
+
},
|
|
214
|
+
...options.enableToolResultSummary !== void 0 ? { enableToolResultSummary: options.enableToolResultSummary } : {}
|
|
215
|
+
}).build();
|
|
216
|
+
for (const tool of tools) glove.fold(tool);
|
|
217
|
+
for (const hook of options.hooks ?? []) glove.defineHook(hook.name, hook.handler);
|
|
218
|
+
for (const skill of options.skills ?? []) glove.defineSkill(skill);
|
|
219
|
+
for (const subagent of options.subagents ?? []) glove.defineSubAgent(subagent);
|
|
220
|
+
if (options.configure) {
|
|
221
|
+
const message = { sender: "user", text: prompt };
|
|
222
|
+
const request = {
|
|
223
|
+
agentId: options.name,
|
|
224
|
+
conversationId: `subagent:${options.name}`,
|
|
225
|
+
workspaceId: "subagent",
|
|
226
|
+
message: prompt,
|
|
227
|
+
source: { kind: "spawn" }
|
|
228
|
+
};
|
|
229
|
+
await resolveResolvable(options.configure({
|
|
230
|
+
definitionId: options.name,
|
|
231
|
+
agentId: options.name,
|
|
232
|
+
conversationId: request.conversationId,
|
|
233
|
+
workspaceId: request.workspaceId,
|
|
234
|
+
runId: `subagent:${options.name}`,
|
|
235
|
+
input: prompt,
|
|
236
|
+
request,
|
|
237
|
+
message,
|
|
238
|
+
messageInput: prompt,
|
|
239
|
+
messageText: prompt,
|
|
240
|
+
history: [],
|
|
241
|
+
messages: [message],
|
|
242
|
+
glove,
|
|
243
|
+
signal: new AbortController().signal,
|
|
244
|
+
emit: () => void 0
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
return glove;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
async function resolveResolvable(value) {
|
|
252
|
+
if (Effect.isEffect(value)) return Effect.runPromise(value);
|
|
253
|
+
return Promise.resolve(value);
|
|
254
|
+
}
|
|
255
|
+
function defineRoutes(routes) {
|
|
256
|
+
return Object.freeze({ ...routes });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export {
|
|
260
|
+
fileIdentified,
|
|
261
|
+
bindFileIdentity,
|
|
262
|
+
fileDefinitionKey,
|
|
263
|
+
fileDefinitionLabel,
|
|
264
|
+
FOUNDRY_AGENT_DEFINITION_BRAND,
|
|
265
|
+
FOUNDRY_AGENT_BRAND,
|
|
266
|
+
FOUNDRY_EVENT_PREFIX,
|
|
267
|
+
FOUNDRY_APPLICATION_ENV,
|
|
268
|
+
FOUNDRY_AGENT_ROUTE_ENV,
|
|
269
|
+
FOUNDRY_AGENT_FILE_ENV,
|
|
270
|
+
FOUNDRY_EXECUTION_MARKER,
|
|
271
|
+
defineCall,
|
|
272
|
+
internalAgentName,
|
|
273
|
+
routeFromInternalAgentName,
|
|
274
|
+
isFoundryAgentDefinition,
|
|
275
|
+
isFoundryAgent,
|
|
276
|
+
defineAgent,
|
|
277
|
+
defineAgentFromModule,
|
|
278
|
+
defineSubagent,
|
|
279
|
+
resolveResolvable,
|
|
280
|
+
defineRoutes
|
|
281
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// src/execution-protocol.ts
|
|
2
|
+
var executionEvents = ["onSignalDiscovered", "onRunDispatched", "onRunStarted", "onRunCompleted", "onRunTimeout", "onRunRetry", "onRunFailed", "onRunCancelled", "onRunSkipped", "onRunRescheduled", "onCompleteError", "onLogOutput"];
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
executionEvents
|
|
6
|
+
};
|