@agentproto/app-kit 1.3.1 → 1.4.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 +86 -0
- package/dist/define-app.d.ts.map +1 -1
- package/dist/emit.d.ts +6 -1
- package/dist/emit.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +364 -10
- package/dist/index.mjs.map +1 -1
- package/dist/load-app.d.ts.map +1 -1
- package/dist/manifest-fields.d.ts +30 -0
- package/dist/manifest-fields.d.ts.map +1 -0
- package/dist/pack.d.ts +90 -0
- package/dist/pack.d.ts.map +1 -0
- package/dist/types.d.ts +73 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +12 -12
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
|
package/dist/define-app.d.ts.map
CHANGED
|
@@ -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;
|
|
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>;
|
package/dist/emit.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|