@agentproto/app-kit 1.3.1 → 1.4.1

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 CHANGED
@@ -91,6 +91,37 @@ An app may also declare `requires: ["@acme/shared", ...]` — app ids that must
91
91
  applied to the same scope before this one can run. The runtime validates the
92
92
  graph when mounting apps via `app_apply`.
93
93
 
94
+ ### Placement, requirements, and A2A exposure
95
+
96
+ Four optional `APP.md` keys describe where an app can run and what it exposes.
97
+ They are **semantics only** — nothing schedules or serves on them yet. All are
98
+ validated by `defineApp` (`AppDefinitionError`) and `loadAppHandle`
99
+ (`AppLoadError`), surfaced on `AppHandle`, and written by `emit` only when they
100
+ differ from the default.
101
+
102
+ ```yaml
103
+ placement: any # "local" | "box" | "any" | "split" (default "any")
104
+ requires:
105
+ browser: false # needs the user's real browser (Bureau / local-browser)
106
+ fs: false # needs the user's local filesystem beyond app data
107
+ gpu: false
108
+ secrets: [] # env/secret names the app needs
109
+ apps: [] # other app ids this app depends on
110
+ exposes: # A2A-visible surfaces (default: none exposed)
111
+ agents: [] # ids from `agents:`
112
+ workflows: [] # ids from `workflows:`
113
+ accepts:
114
+ tasks: false # accept A2A tasks (default false)
115
+ ```
116
+
117
+ - An unknown `placement`, or an `exposes.*` id that isn't one of the app's own
118
+ agents/workflows, is a validation error.
119
+ - `requires` also still accepts the legacy flat array (`requires: ["@acme/shared"]`,
120
+ equivalent to `requires: { apps: [...] }`).
121
+ - On the handle: `placement`, `exposes`, `accepts` and `requirements` (every key
122
+ resolved to its default) are always present. `handle.requires` stays the flat
123
+ list of app ids (absent when none) so existing dependency checks keep working.
124
+
94
125
  ## UI surfaces, artifacts, dev-launch, and the artifact surface
95
126
 
96
127
  Beyond agents and workflows, an app can declare four optional surfaces that
@@ -367,6 +398,61 @@ per bundle path, output captured to `.agentproto/ui-build.log`) → serve.
367
398
  Omit `build` to keep committing the bundle, unchanged from before this
368
399
  existed.
369
400
 
401
+ ## Boundaries — fs zones for app-spawned sessions
402
+
403
+ Set `boundaries: { enforce: "required" | "best-effort" }` on `defineApp` (or
404
+ `boundaries:` in `APP.md`'s frontmatter) to declare how strictly the daemon
405
+ must confine sessions the app spawns — `app_run`, and workflow agent steps
406
+ whose workflow carries this app's `appId`:
407
+
408
+ ```ts
409
+ defineApp({
410
+ id: "@acme/transcriber",
411
+ boundaries: { enforce: "required" },
412
+ // ...
413
+ })
414
+ ```
415
+
416
+ For every such session the daemon builds one **boundary** with three zones:
417
+
418
+ | Zone | Contents | Access |
419
+ | --- | --- | --- |
420
+ | read-only | the installed app dir (source, `.agentproto/`, `scripts/`) | read |
421
+ | writable | the app's `data/` dir, and the workflow run's `$run.workspace` (`app_run` has no run workspace: writable is `data/` only) | read + write |
422
+ | everything else | including the daemon's own workspace and any host monorepo the app happens to be installed inside | denied |
423
+
424
+ The boundary is enforced on two independent surfaces:
425
+
426
+ 1. **Daemon file/command tools** (`file_read`, `file_write`, `directory_*`,
427
+ `command_execute`, …) — always enforced, identity-based: the daemon
428
+ recovers the caller's boundary from its own session record, resolves
429
+ relative paths against the app dir, and rejects reads/writes outside the
430
+ zones (including through symlinks). This does not depend on the harness.
431
+ 2. **The harness's own native tools** (a claude-code session's Bash, Write,
432
+ Edit, …) — enforced by wrapping the harness in an OS sandbox
433
+ (`@agentproto/command-sandbox`: macOS Seatbelt or Linux bubblewrap) built
434
+ from the same zones, plus, on adapters that support it, excluding the host
435
+ repository's `CLAUDE.md`/`AGENTS.md` from context so an app session never
436
+ inherits instructions from the monorepo it's installed inside.
437
+
438
+ Surface 2 needs an adapter that supports fs zones and an available OS sandbox
439
+ backend. When it can't be enforced:
440
+
441
+ - `boundaries.enforce: "best-effort"` (the default) still spawns the session
442
+ — daemon tools remain confined — and emits a `session:harness-warning`
443
+ naming the reason (no sandbox backend on this platform, the adapter doesn't
444
+ support `fsZones`, or `commandSandbox: "off"` was requested explicitly).
445
+ - `boundaries.enforce: "required"` refuses the spawn instead of downgrading
446
+ silently (`app_boundary_unenforceable`).
447
+
448
+ **Deferred:** the `AGENT.md` `tools:` allowlist becoming an *enforced*
449
+ allowlist on adapters that support it (today it's advisory — an agent can
450
+ still reach native Bash next to an allowlisted `command_execute`). Also out
451
+ of scope for this phase: a network-capable sandboxed process reaching the
452
+ daemon's HTTP API directly (bypasses the identity-based check the MCP path
453
+ uses), and boundary enforcement on daemon-wide MCP tools other than
454
+ file/command/terminal.
455
+
370
456
  ## Runtime note
371
457
 
372
458
  `toMastraAgents` is the path where a `body` becomes a **true** model
@@ -1 +1 @@
1
- {"version":3,"file":"define-app.d.ts","sourceRoot":"","sources":["../src/define-app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,OAAO,KAAK,EAEV,aAAa,EACb,SAAS,EACT,aAAa,EAGd,MAAM,YAAY,CAAA;AAInB,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,aAAa,GAAG,SAAS,CA8IvD;AAiID,YAAY,EAAE,aAAa,EAAE,CAAA"}
1
+ {"version":3,"file":"define-app.d.ts","sourceRoot":"","sources":["../src/define-app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,OAAO,KAAK,EAEV,aAAa,EACb,SAAS,EACT,aAAa,EAGd,MAAM,YAAY,CAAA;AAKnB,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,aAAa,GAAG,SAAS,CAyKvD;AAiID,YAAY,EAAE,aAAa,EAAE,CAAA"}
package/dist/emit.d.ts CHANGED
@@ -32,7 +32,7 @@
32
32
  */
33
33
  import type { WorkflowHandle } from "@agentproto/workflow";
34
34
  import type { WorkspaceHandle } from "@agentproto/workspace";
35
- import type { AgentEntry, AppArtifactDecl, AppArtifactSurface, AppDataDefinition, AppDevDefinition, AppSkillSurface, AppUiDefinition, EmittedApp } from "./types.js";
35
+ import type { AgentEntry, AppAccepts, AppArtifactDecl, AppArtifactSurface, AppBoundariesDefinition, AppDataDefinition, AppDevDefinition, AppExposes, AppPlacement, AppRequirements, AppSkillSurface, AppUiDefinition, EmittedApp } from "./types.js";
36
36
  interface EmitInput {
37
37
  readonly agents: readonly AgentEntry[];
38
38
  readonly workflows: readonly WorkflowHandle[];
@@ -42,6 +42,10 @@ interface EmitInput {
42
42
  readonly version?: string;
43
43
  readonly description?: string;
44
44
  readonly requires?: readonly string[];
45
+ readonly requirements?: AppRequirements;
46
+ readonly placement?: AppPlacement;
47
+ readonly exposes?: AppExposes;
48
+ readonly accepts?: AppAccepts;
45
49
  readonly ui?: AppUiDefinition;
46
50
  readonly artifact?: AppArtifactSurface;
47
51
  readonly skill?: AppSkillSurface;
@@ -49,6 +53,7 @@ interface EmitInput {
49
53
  readonly dev?: AppDevDefinition;
50
54
  readonly data?: AppDataDefinition;
51
55
  readonly externalReadRoots?: readonly string[];
56
+ readonly boundaries?: AppBoundariesDefinition;
52
57
  readonly category?: string;
53
58
  }
54
59
  export declare function emitApp(app: EmitInput, dir: string): Promise<EmittedApp>;
@@ -1 +1 @@
1
- {"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../src/emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAGpK,UAAU,SAAS;IACjB,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAA;IACtC,QAAQ,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE,CAAA;IAC7C,QAAQ,CAAC,SAAS,CAAC,EAAE,eAAe,CAAA;IACpC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACrC,QAAQ,CAAC,EAAE,CAAC,EAAE,eAAe,CAAA;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAA;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAA;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;IAC/C,QAAQ,CAAC,GAAG,CAAC,EAAE,gBAAgB,CAAA;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,iBAAiB,CAAA;IACjC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,wBAAsB,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAiH9E"}
1
+ {"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../src/emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,UAAU,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAGpP,UAAU,SAAS;IACjB,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAA;IACtC,QAAQ,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE,CAAA;IAC7C,QAAQ,CAAC,SAAS,CAAC,EAAE,eAAe,CAAA;IACpC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACrC,QAAQ,CAAC,YAAY,CAAC,EAAE,eAAe,CAAA;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,YAAY,CAAA;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,CAAA;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,CAAA;IAC7B,QAAQ,CAAC,EAAE,CAAC,EAAE,eAAe,CAAA;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAA;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAA;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;IAC/C,QAAQ,CAAC,GAAG,CAAC,EAAE,gBAAgB,CAAA;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,iBAAiB,CAAA;IACjC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9C,QAAQ,CAAC,UAAU,CAAC,EAAE,uBAAuB,CAAA;IAC7C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,wBAAsB,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAuH9E"}
package/dist/index.d.ts CHANGED
@@ -41,6 +41,8 @@ export { emitApp } from "./emit.js";
41
41
  export { loadAppHandle, AppLoadError, resolveAppUIRoot, peekAppUi } from "./load-app.js";
42
42
  export type { AppUiPeek } from "./load-app.js";
43
43
  export { loadAppBundledTools } from "./load-app-tools.js";
44
+ export { packApp, unpackApp, aggregateSha256, collectFiles, isManifest, safeId, AGENTAPP_FORMAT, AgentAppPackError, } from "./pack.js";
45
+ export type { AgentAppManifest, BundleFile, AgentAppPackErrorCode } from "./pack.js";
44
46
  export { refKey, stripOwner } from "./refs.js";
45
- export type { AppDefinition, AppHandle, AgentEntry, DoctypeHandle, WorkspaceShorthand, WorkspaceInput, AppUiDefinition, AppUiBuildConfig, AppArtifactSurface, AppSkillSurface, AppArtifactDecl, AppDevLaunchConfig, AppDataDefinition, AppDevDefinition, ToMastraAgentOptions, EmittedApp, } from "./types.js";
47
+ export type { AppDefinition, AppHandle, AgentEntry, DoctypeHandle, WorkspaceShorthand, WorkspaceInput, AppUiDefinition, AppUiBuildConfig, AppArtifactSurface, AppSkillSurface, AppArtifactDecl, AppDevLaunchConfig, AppBoundariesDefinition, AppDataDefinition, AppDevDefinition, AppPlacement, AppRequirements, AppExposes, AppAccepts, ToMastraAgentOptions, EmittedApp, } from "./types.js";
46
48
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACxF,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC9C,YAAY,EACV,aAAa,EACb,SAAS,EACT,UAAU,EACV,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,UAAU,GACX,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACxF,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,EACL,OAAO,EACP,SAAS,EACT,eAAe,EACf,YAAY,EACZ,UAAU,EACV,MAAM,EACN,eAAe,EACf,iBAAiB,GAClB,MAAM,WAAW,CAAA;AAClB,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAA;AACpF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC9C,YAAY,EACV,aAAa,EACb,SAAS,EACT,UAAU,EACV,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,UAAU,EACV,UAAU,EACV,oBAAoB,EACpB,UAAU,GACX,MAAM,YAAY,CAAA"}
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { join, relative, isAbsolute, dirname, resolve } from 'path';
1
+ import { join, relative, isAbsolute, dirname, resolve, basename } from 'path';
2
2
  import { defineWorkspace } from '@agentproto/workspace';
3
- import { mkdir, writeFile, copyFile, cp, readFile, readdir } from 'fs/promises';
3
+ import { mkdir, writeFile, copyFile, cp, readFile, mkdtemp, readdir, rm, stat } from 'fs/promises';
4
4
  import matter2 from 'gray-matter';
5
5
  import { parseAgentManifest, agentFromManifest } from '@agentproto/agent/manifest';
6
6
  import { loadWorkflowHandle } from '@agentproto/workflow-loader';
@@ -10,6 +10,9 @@ import { parseDriverManifest, driverDefinitionFromManifest, driverFromManifest }
10
10
  import { normalizeToolId } from '@agentproto/driver';
11
11
  import { defineCliDriver } from '@agentproto/driver-cli';
12
12
  import { defineHttpDriver } from '@agentproto/driver-http';
13
+ import { createHash } from 'crypto';
14
+ import { tmpdir } from 'os';
15
+ import { spawn } from 'child_process';
13
16
 
14
17
  /**
15
18
  * @agentproto/app-kit v0.1.0
@@ -112,7 +115,10 @@ async function emitApp(app, dir) {
112
115
  agents: agentRefs,
113
116
  workflows: workflowRefs2,
114
117
  ...app.workspace ? { workspace: app.workspace.id } : {},
115
- ...app.requires !== void 0 ? { requires: app.requires } : {},
118
+ ...requiresFrontmatter(app),
119
+ ...app.placement !== void 0 && app.placement !== "any" ? { placement: app.placement } : {},
120
+ ...app.exposes && (app.exposes.agents.length > 0 || app.exposes.workflows.length > 0) ? { exposes: { agents: app.exposes.agents, workflows: app.exposes.workflows } } : {},
121
+ ...app.accepts?.tasks ? { accepts: { tasks: true } } : {},
116
122
  ...uiFrontmatter !== void 0 ? { ui: uiFrontmatter } : {},
117
123
  ...artifactFrontmatter !== void 0 ? { artifact: artifactFrontmatter } : {},
118
124
  ...skillFrontmatter !== void 0 ? { skill: skillFrontmatter } : {},
@@ -120,6 +126,7 @@ async function emitApp(app, dir) {
120
126
  ...app.dev !== void 0 ? { dev: app.dev } : {},
121
127
  ...app.data !== void 0 ? { data: app.data } : {},
122
128
  ...app.externalReadRoots !== void 0 ? { externalReadRoots: app.externalReadRoots } : {},
129
+ ...app.boundaries !== void 0 ? { boundaries: app.boundaries } : {},
123
130
  ...app.category !== void 0 ? { category: app.category } : {}
124
131
  };
125
132
  await writeFile(appPath, toManifest(appFrontmatter, app.description ?? ""), "utf8");
@@ -133,6 +140,15 @@ async function emitApp(app, dir) {
133
140
  ...skillPath ? { skillPath } : {}
134
141
  };
135
142
  }
143
+ function requiresFrontmatter(app) {
144
+ const r = app.requirements;
145
+ if (r && (r.browser || r.fs || r.gpu || r.secrets.length > 0)) {
146
+ return {
147
+ requires: { browser: r.browser, fs: r.fs, gpu: r.gpu, secrets: r.secrets, apps: r.apps }
148
+ };
149
+ }
150
+ return app.requires !== void 0 ? { requires: app.requires } : {};
151
+ }
136
152
  function toManifest(handle, body) {
137
153
  const data = JSON.parse(JSON.stringify(handle));
138
154
  return matter2.stringify(`
@@ -140,6 +156,86 @@ ${body}
140
156
  `, data);
141
157
  }
142
158
 
159
+ // src/manifest-fields.ts
160
+ var APP_PLACEMENTS = ["local", "box", "any", "split"];
161
+ function isRecord(v) {
162
+ return typeof v === "object" && v !== null && !Array.isArray(v);
163
+ }
164
+ function stringList(v, key, fail) {
165
+ if (v === void 0) return [];
166
+ if (!Array.isArray(v) || !v.every((e) => typeof e === "string" && e.trim() !== "")) {
167
+ throw fail(`'${key}' must be an array of non-empty strings.`);
168
+ }
169
+ return [...v];
170
+ }
171
+ function bool(v, key, fail) {
172
+ if (v === void 0) return false;
173
+ if (typeof v !== "boolean") throw fail(`'${key}' must be a boolean.`);
174
+ return v;
175
+ }
176
+ function normalizeManifestFields(input, declared, fail) {
177
+ let placement = "any";
178
+ if (input.placement !== void 0) {
179
+ if (typeof input.placement !== "string" || !APP_PLACEMENTS.includes(input.placement)) {
180
+ throw fail(
181
+ `'placement' must be one of ${APP_PLACEMENTS.map((p) => `"${p}"`).join(", ")}, got ${JSON.stringify(input.placement)}.`
182
+ );
183
+ }
184
+ placement = input.placement;
185
+ }
186
+ let requires;
187
+ let requirements = { browser: false, fs: false, gpu: false, secrets: [], apps: [] };
188
+ const r = input.requires;
189
+ if (r !== void 0) {
190
+ if (Array.isArray(r)) {
191
+ if (!r.every((e) => typeof e === "string")) {
192
+ throw fail("'requires' must be an array of strings or an object { browser, fs, gpu, secrets, apps }.");
193
+ }
194
+ requires = [...r];
195
+ requirements = { ...requirements, apps: requires };
196
+ } else if (isRecord(r)) {
197
+ const apps = stringList(r.apps, "requires.apps", fail);
198
+ requirements = {
199
+ browser: bool(r.browser, "requires.browser", fail),
200
+ fs: bool(r.fs, "requires.fs", fail),
201
+ gpu: bool(r.gpu, "requires.gpu", fail),
202
+ secrets: stringList(r.secrets, "requires.secrets", fail),
203
+ apps
204
+ };
205
+ if (apps.length > 0) requires = apps;
206
+ } else {
207
+ throw fail("'requires' must be an array of strings or an object { browser, fs, gpu, secrets, apps }.");
208
+ }
209
+ }
210
+ let exposes = { agents: [], workflows: [] };
211
+ if (input.exposes !== void 0) {
212
+ if (!isRecord(input.exposes)) throw fail("'exposes' must be an object { agents, workflows }.");
213
+ const agents = stringList(input.exposes.agents, "exposes.agents", fail);
214
+ const workflows = stringList(input.exposes.workflows, "exposes.workflows", fail);
215
+ for (const id of agents) {
216
+ if (!declared.agents.includes(id)) {
217
+ throw fail(
218
+ `'exposes.agents' names '${id}' but the app declares no such agent. Declared: [${declared.agents.join(", ") || "none"}].`
219
+ );
220
+ }
221
+ }
222
+ for (const id of workflows) {
223
+ if (!declared.workflows.includes(id)) {
224
+ throw fail(
225
+ `'exposes.workflows' names '${id}' but the app declares no such workflow. Declared: [${declared.workflows.join(", ") || "none"}].`
226
+ );
227
+ }
228
+ }
229
+ exposes = { agents, workflows };
230
+ }
231
+ let accepts = { tasks: false };
232
+ if (input.accepts !== void 0) {
233
+ if (!isRecord(input.accepts)) throw fail("'accepts' must be an object { tasks }.");
234
+ accepts = { tasks: bool(input.accepts.tasks, "accepts.tasks", fail) };
235
+ }
236
+ return { placement, requires, requirements, exposes, accepts };
237
+ }
238
+
143
239
  // src/define-app.ts
144
240
  var AppDefinitionError = class extends Error {
145
241
  constructor(message) {
@@ -194,7 +290,23 @@ function defineApp(def) {
194
290
  const name = def.name;
195
291
  const version = def.version ?? (id ? "0.1.0" : void 0);
196
292
  const description = def.description;
197
- const requires = def.requires ? Object.freeze([...def.requires]) : void 0;
293
+ const fields = normalizeManifestFields(
294
+ { placement: def.placement, requires: def.requires, exposes: def.exposes, accepts: def.accepts },
295
+ { agents: agents.map((e) => e.agent.id), workflows: workflows.map((w) => w.id) },
296
+ (msg) => new AppDefinitionError(msg)
297
+ );
298
+ const requires = fields.requires ? Object.freeze([...fields.requires]) : void 0;
299
+ const requirements = Object.freeze({
300
+ ...fields.requirements,
301
+ secrets: Object.freeze([...fields.requirements.secrets]),
302
+ apps: Object.freeze([...fields.requirements.apps])
303
+ });
304
+ const placement = fields.placement;
305
+ const exposes = Object.freeze({
306
+ agents: Object.freeze([...fields.exposes.agents]),
307
+ workflows: Object.freeze([...fields.exposes.workflows])
308
+ });
309
+ const accepts = Object.freeze({ ...fields.accepts });
198
310
  const ui = def.ui ? Object.freeze({ ...def.ui }) : void 0;
199
311
  const artifact = def.artifact ? Object.freeze({ ...def.artifact }) : void 0;
200
312
  const skill = def.skill ? Object.freeze({ ...def.skill }) : void 0;
@@ -202,6 +314,7 @@ function defineApp(def) {
202
314
  const dev = def.dev ? Object.freeze({ launch: Object.freeze(def.dev.launch.map((l) => Object.freeze({ ...l }))) }) : void 0;
203
315
  const externalReadRoots = def.externalReadRoots ? Object.freeze([...def.externalReadRoots]) : void 0;
204
316
  const data = def.data ? Object.freeze({ ...def.data }) : void 0;
317
+ const boundaries = def.boundaries ? Object.freeze({ ...def.boundaries }) : void 0;
205
318
  const category = def.category;
206
319
  validateAttachment(agents, workflows);
207
320
  validateUniqueIds(tools, "tool");
@@ -223,6 +336,10 @@ function defineApp(def) {
223
336
  ...version !== void 0 ? { version } : {},
224
337
  ...description !== void 0 ? { description } : {},
225
338
  ...requires !== void 0 ? { requires } : {},
339
+ requirements,
340
+ placement,
341
+ exposes,
342
+ accepts,
226
343
  ...ui !== void 0 ? { ui } : {},
227
344
  ...artifact !== void 0 ? { artifact } : {},
228
345
  ...skill !== void 0 ? { skill } : {},
@@ -230,6 +347,7 @@ function defineApp(def) {
230
347
  ...dev !== void 0 ? { dev } : {},
231
348
  ...data !== void 0 ? { data } : {},
232
349
  ...externalReadRoots !== void 0 ? { externalReadRoots } : {},
350
+ ...boundaries !== void 0 ? { boundaries } : {},
233
351
  ...category !== void 0 ? { category } : {},
234
352
  async toMastraAgents(opts, only) {
235
353
  const targets = only ? selectAgents(frozenAgents, only) : frozenAgents;
@@ -261,6 +379,10 @@ function defineApp(def) {
261
379
  ...version !== void 0 ? { version } : {},
262
380
  ...description !== void 0 ? { description } : {},
263
381
  ...requires !== void 0 ? { requires } : {},
382
+ requirements,
383
+ placement,
384
+ exposes,
385
+ accepts,
264
386
  ...ui !== void 0 ? { ui } : {},
265
387
  ...artifact !== void 0 ? { artifact } : {},
266
388
  ...skill !== void 0 ? { skill } : {},
@@ -268,6 +390,7 @@ function defineApp(def) {
268
390
  ...dev !== void 0 ? { dev } : {},
269
391
  ...data !== void 0 ? { data } : {},
270
392
  ...externalReadRoots !== void 0 ? { externalReadRoots } : {},
393
+ ...boundaries !== void 0 ? { boundaries } : {},
271
394
  ...category !== void 0 ? { category } : {}
272
395
  },
273
396
  dir
@@ -595,11 +718,11 @@ function parseAppFrontmatter(data, appPath) {
595
718
  `'${appPath}': frontmatter 'workflows' must be an array of { id, path }.`
596
719
  );
597
720
  }
598
- if (data.requires !== void 0) {
599
- if (!Array.isArray(data.requires) || !data.requires.every((e) => typeof e === "string")) {
600
- throw new AppLoadError(`'${appPath}': frontmatter 'requires' must be an array of strings.`);
601
- }
602
- }
721
+ normalizeManifestFields(
722
+ data,
723
+ { agents: data.agents.map((a) => a.id), workflows: data.workflows.map((w) => w.id) },
724
+ (msg) => new AppLoadError(`'${appPath}': frontmatter ${msg}`)
725
+ );
603
726
  if (data.data !== void 0) {
604
727
  const d = data.data;
605
728
  if (typeof d !== "object" || d === null || d.dir !== void 0 && (typeof d.dir !== "string" || d.dir.trim() === "")) {
@@ -615,6 +738,14 @@ function parseAppFrontmatter(data, appPath) {
615
738
  );
616
739
  }
617
740
  }
741
+ if (data.boundaries !== void 0) {
742
+ const b = data.boundaries;
743
+ if (typeof b !== "object" || b === null || Array.isArray(b) || b.enforce !== void 0 && b.enforce !== "required" && b.enforce !== "best-effort") {
744
+ throw new AppLoadError(
745
+ `'${appPath}': frontmatter 'boundaries' must be an object whose optional 'enforce' is "required" or "best-effort".`
746
+ );
747
+ }
748
+ }
618
749
  if (data.category !== void 0 && (typeof data.category !== "string" || data.category.trim() === "")) {
619
750
  throw new AppLoadError(`'${appPath}': frontmatter 'category' must be a non-empty string.`);
620
751
  }
@@ -725,6 +856,9 @@ async function loadAppHandle(dir) {
725
856
  ...fm.version !== void 0 ? { version: fm.version } : {},
726
857
  ...fm.description !== void 0 ? { description: fm.description } : {},
727
858
  ...fm.requires !== void 0 ? { requires: fm.requires } : {},
859
+ ...fm.placement !== void 0 ? { placement: fm.placement } : {},
860
+ ...fm.exposes !== void 0 ? { exposes: fm.exposes } : {},
861
+ ...fm.accepts !== void 0 ? { accepts: fm.accepts } : {},
728
862
  ...ui !== void 0 ? { ui } : {},
729
863
  ...fm.artifact !== void 0 ? { artifact: { path: resolveRef(dir, fm.artifact.path), ...fm.artifact.title !== void 0 ? { title: fm.artifact.title } : {}, ...fm.artifact.description !== void 0 ? { description: fm.artifact.description } : {} } } : {},
730
864
  ...fm.skill !== void 0 ? { skill: { path: resolveRef(dir, fm.skill.path), ...fm.skill.title !== void 0 ? { title: fm.skill.title } : {}, ...fm.skill.description !== void 0 ? { description: fm.skill.description } : {} } } : {},
@@ -732,10 +866,230 @@ async function loadAppHandle(dir) {
732
866
  ...fm.dev !== void 0 ? { dev: fm.dev } : {},
733
867
  ...fm.data !== void 0 ? { data: fm.data } : {},
734
868
  ...fm.externalReadRoots !== void 0 ? { externalReadRoots: fm.externalReadRoots } : {},
869
+ ...fm.boundaries !== void 0 ? { boundaries: fm.boundaries } : {},
735
870
  ...fm.category !== void 0 ? { category: fm.category } : {}
736
871
  });
737
872
  }
873
+ var AGENTAPP_FORMAT = "agentapp/v1";
874
+ var DEFAULT_VERSION = "0.1.0";
875
+ var SKIP_DIR_NAMES = /* @__PURE__ */ new Set(["node_modules", ".git"]);
876
+ var AgentAppPackError = class extends Error {
877
+ code;
878
+ constructor(code, message) {
879
+ super(message);
880
+ this.name = "AgentAppPackError";
881
+ this.code = code;
882
+ }
883
+ };
884
+ async function pathExists(p) {
885
+ try {
886
+ await stat(p);
887
+ return true;
888
+ } catch {
889
+ return false;
890
+ }
891
+ }
892
+ function safeId(id) {
893
+ return String(id || "app").replace(/^@/, "").replace(/[^A-Za-z0-9._-]+/g, "-");
894
+ }
895
+ function isManifest(value) {
896
+ if (typeof value !== "object" || value === null) return false;
897
+ const m = value;
898
+ return typeof m.format === "string" && typeof m.id === "string" && typeof m.version === "string" && Array.isArray(m.agents) && Array.isArray(m.workflows) && Array.isArray(m.files) && typeof m.fileCount === "number" && typeof m.totalSize === "number" && typeof m.sha256 === "string";
899
+ }
900
+ async function collectFiles(root) {
901
+ const files = [];
902
+ async function walk(relDir) {
903
+ const absDir = join(root, relDir);
904
+ for (const entry of await readdir(absDir, { withFileTypes: true })) {
905
+ if (entry.isDirectory() && SKIP_DIR_NAMES.has(entry.name)) continue;
906
+ const rel = relDir ? join(relDir, entry.name) : entry.name;
907
+ const abs = join(absDir, entry.name);
908
+ if (entry.isDirectory()) {
909
+ await walk(rel);
910
+ } else if (entry.isFile()) {
911
+ const st = await stat(abs);
912
+ files.push({ path: rel, size: st.size });
913
+ }
914
+ }
915
+ }
916
+ await walk("");
917
+ return files;
918
+ }
919
+ async function aggregateSha256(root, files) {
920
+ const hash = createHash("sha256");
921
+ for (const file of files) {
922
+ hash.update(await readFile(join(root, file)));
923
+ }
924
+ return hash.digest("hex");
925
+ }
926
+ function runTar(args, opts) {
927
+ return new Promise((resolvePromise, reject) => {
928
+ const child = spawn("tar", args, { stdio: "ignore", cwd: opts?.cwd });
929
+ child.once("error", reject);
930
+ child.once("exit", (code) => resolvePromise(code ?? 0));
931
+ });
932
+ }
933
+ function extractIds(value) {
934
+ if (!Array.isArray(value)) return [];
935
+ const out = [];
936
+ for (const entry of value) {
937
+ if (typeof entry === "string") {
938
+ if (entry) out.push(entry);
939
+ } else if (entry && typeof entry === "object") {
940
+ const id = entry.id;
941
+ if (typeof id === "string" && id) out.push(id);
942
+ }
943
+ }
944
+ return out;
945
+ }
946
+ function extractPaths(front, key) {
947
+ const value = front[key];
948
+ if (value === void 0 || value === null) return void 0;
949
+ const items = Array.isArray(value) ? value : [value];
950
+ const out = [];
951
+ for (const item of items) {
952
+ if (typeof item === "string") {
953
+ if (item) out.push(basename(item));
954
+ } else if (item && typeof item === "object") {
955
+ const p = item.path;
956
+ if (typeof p === "string" && p) out.push(basename(p));
957
+ }
958
+ }
959
+ return out.length > 0 ? out : void 0;
960
+ }
961
+ function extractMeta(front) {
962
+ const id = typeof front.id === "string" && front.id.length > 0 ? front.id : typeof front.slug === "string" && front.slug.length > 0 ? front.slug : "app";
963
+ const version = typeof front.version === "string" && front.version.length > 0 ? front.version : DEFAULT_VERSION;
964
+ const name = typeof front.name === "string" ? front.name : void 0;
965
+ const description = typeof front.description === "string" ? front.description : void 0;
966
+ const ui = extractPaths(front, "ui");
967
+ return {
968
+ id,
969
+ ...name !== void 0 ? { name } : {},
970
+ version,
971
+ ...description !== void 0 ? { description } : {},
972
+ agents: extractIds(front.agents),
973
+ workflows: extractIds(front.workflows),
974
+ ...ui !== void 0 ? { ui } : {}
975
+ };
976
+ }
977
+ async function packApp(input) {
978
+ const appDirAbs = resolve(input.appDir);
979
+ const appMdPath = join(appDirAbs, ".agentproto", "APP.md");
980
+ if (!await pathExists(appMdPath)) {
981
+ throw new AgentAppPackError(
982
+ "not-an-app",
983
+ `${appDirAbs} is not an agentproto app (missing ${appMdPath}).`
984
+ );
985
+ }
986
+ const raw = await readFile(appMdPath, "utf8");
987
+ const front = matter2(raw).data;
988
+ const meta = extractMeta(front);
989
+ const outAbs = input.out !== void 0 ? resolve(input.out) : resolve(process.cwd(), `${safeId(meta.id)}-${meta.version}.agentapp`);
990
+ await mkdir(dirname(outAbs), { recursive: true });
991
+ const files = (await collectFiles(appDirAbs)).filter((f) => f.path !== "manifest.json").filter((f) => join(appDirAbs, f.path) !== outAbs).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
992
+ const fileCount = files.length;
993
+ const totalSize = files.reduce((sum, f) => sum + f.size, 0);
994
+ const sha256 = await aggregateSha256(
995
+ appDirAbs,
996
+ files.map((f) => f.path)
997
+ );
998
+ const manifest = {
999
+ format: AGENTAPP_FORMAT,
1000
+ id: meta.id,
1001
+ ...meta.name !== void 0 ? { name: meta.name } : {},
1002
+ version: meta.version,
1003
+ ...meta.description !== void 0 ? { description: meta.description } : {},
1004
+ agents: meta.agents,
1005
+ workflows: meta.workflows,
1006
+ ...meta.ui !== void 0 ? { ui: meta.ui } : {},
1007
+ files: files.map((f) => f.path),
1008
+ fileCount,
1009
+ totalSize,
1010
+ sha256,
1011
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1012
+ agentprotoVersion: ">=0.1.0"
1013
+ };
1014
+ const staging = await mkdtemp(join(tmpdir(), "agentapp-"));
1015
+ try {
1016
+ await cp(appDirAbs, staging, {
1017
+ recursive: true,
1018
+ filter: (source) => !SKIP_DIR_NAMES.has(basename(source))
1019
+ });
1020
+ await writeFile(join(staging, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
1021
+ const entries = (await readdir(staging)).sort();
1022
+ const packCode = await runTar(["-czf", outAbs, ...entries], { cwd: staging });
1023
+ if (packCode !== 0) {
1024
+ throw new AgentAppPackError("tar-failed", `tar failed with exit code ${packCode}`);
1025
+ }
1026
+ } finally {
1027
+ await rm(staging, { recursive: true, force: true });
1028
+ }
1029
+ return { file: outAbs, manifest };
1030
+ }
1031
+ function isUnsafeBundlePath(p) {
1032
+ if (p === "" || isAbsolute(p)) return true;
1033
+ return p.split(/[\\/]/).includes("..");
1034
+ }
1035
+ async function unpackApp(input) {
1036
+ const bundleAbs = resolve(input.file);
1037
+ if (!await pathExists(bundleAbs)) {
1038
+ throw new AgentAppPackError("bundle-not-found", `bundle not found: ${bundleAbs}`);
1039
+ }
1040
+ const temp = await mkdtemp(join(tmpdir(), "agentapp-"));
1041
+ try {
1042
+ const unpackCode = await runTar(["-xzf", bundleAbs, "-C", temp]);
1043
+ if (unpackCode !== 0) {
1044
+ throw new AgentAppPackError("tar-failed", `tar failed with exit code ${unpackCode}`);
1045
+ }
1046
+ const manifestPath = join(temp, "manifest.json");
1047
+ if (!await pathExists(manifestPath)) {
1048
+ throw new AgentAppPackError(
1049
+ "missing-manifest",
1050
+ `${bundleAbs} is not a valid .agentapp (missing manifest.json).`
1051
+ );
1052
+ }
1053
+ let parsed;
1054
+ try {
1055
+ parsed = JSON.parse(await readFile(manifestPath, "utf8"));
1056
+ } catch {
1057
+ throw new AgentAppPackError("malformed-manifest", `${bundleAbs} has a malformed manifest.json.`);
1058
+ }
1059
+ if (!isManifest(parsed)) {
1060
+ throw new AgentAppPackError("malformed-manifest", `${bundleAbs} has a malformed manifest.json.`);
1061
+ }
1062
+ const manifest = parsed;
1063
+ if (manifest.format !== AGENTAPP_FORMAT) {
1064
+ throw new AgentAppPackError(
1065
+ "unsupported-format",
1066
+ `unsupported bundle format '${manifest.format}' (expected ${AGENTAPP_FORMAT}).`
1067
+ );
1068
+ }
1069
+ const unsafe = manifest.files.find((f) => typeof f !== "string" || isUnsafeBundlePath(f));
1070
+ if (unsafe !== void 0) {
1071
+ throw new AgentAppPackError(
1072
+ "unsafe-path",
1073
+ `manifest.json lists a path outside the bundle root: ${JSON.stringify(unsafe)}.`
1074
+ );
1075
+ }
1076
+ const actual = await aggregateSha256(temp, manifest.files);
1077
+ if (actual !== manifest.sha256) {
1078
+ throw new AgentAppPackError(
1079
+ "digest-mismatch",
1080
+ `SHA-256 mismatch (expected ${manifest.sha256}, got ${actual}). Bundle is corrupted.`
1081
+ );
1082
+ }
1083
+ const outDir = input.dest !== void 0 ? resolve(input.dest) : resolve(process.cwd(), `${safeId(manifest.id)}-${manifest.version}`);
1084
+ await rm(manifestPath, { force: true });
1085
+ await mkdir(outDir, { recursive: true });
1086
+ await cp(temp, outDir, { recursive: true });
1087
+ return { dir: outDir, manifest };
1088
+ } finally {
1089
+ await rm(temp, { recursive: true, force: true });
1090
+ }
1091
+ }
738
1092
 
739
- export { AppDefinitionError, AppLoadError, defineApp, emitApp, loadAppBundledTools, loadAppHandle, peekAppUi, refKey, resolveAppUIRoot, stripOwner };
1093
+ export { AGENTAPP_FORMAT, AgentAppPackError, AppDefinitionError, AppLoadError, aggregateSha256, collectFiles, defineApp, emitApp, isManifest, loadAppBundledTools, loadAppHandle, packApp, peekAppUi, refKey, resolveAppUIRoot, safeId, stripOwner, unpackApp };
740
1094
  //# sourceMappingURL=index.mjs.map
741
1095
  //# sourceMappingURL=index.mjs.map