@intentius/chant-lexicon-fountain 0.90.0 → 0.91.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/dist/composites/box.d.ts +59 -16
- package/dist/composites/box.d.ts.map +1 -1
- package/dist/composites/steward.d.ts +63 -1
- package/dist/composites/steward.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/package.json +2 -2
- package/src/composites/box.ts +82 -24
- package/src/composites/composites.test.ts +171 -5
- package/src/composites/steward.ts +81 -2
- package/src/index.ts +9 -2
package/dist/composites/box.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Box — a persistent machine that serves an app repo and its tools (#2705).
|
|
3
3
|
*
|
|
4
|
-
* A box is an `Environment` whose
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* A box is an `Environment` whose `setup_script` provisions the machine (for
|
|
5
|
+
* an arugula studio box, the studio's `box/provision-template.sh`), an
|
|
6
|
+
* `Agent` on a persistent sandbox provisioned from it, and optionally a
|
|
7
|
+
* `Vault` for the secrets every box of this kind shares. `repo` is optional:
|
|
8
|
+
* when given, the Environment's `repositories` clones it in before setup
|
|
9
|
+
* runs; when omitted (#2759 — a blank-slate app, such as the one hud's
|
|
10
|
+
* `init` makes, has no git remote yet), the Environment holds no
|
|
11
|
+
* `repositories` at all and `setup_script` still runs, against whatever
|
|
12
|
+
* working tree is already on the sandbox (a self-hosted `runner`'s own).
|
|
9
13
|
*
|
|
10
14
|
* ```ts
|
|
11
15
|
* export const { environment, agent, vault } = Box({
|
|
@@ -14,7 +18,7 @@
|
|
|
14
18
|
* setupScript: readFileSync("box/provision-template.sh", "utf8"),
|
|
15
19
|
* permissionPolicy: { default: "auto_allow" },
|
|
16
20
|
* allowedHosts: ["registry.npmjs.org", "github.com"],
|
|
17
|
-
* vault: { secrets: [{ key: "STUDIO_SECRET", value:
|
|
21
|
+
* vault: { secrets: [{ key: "STUDIO_SECRET", value: "${STUDIO_SECRET}" }] },
|
|
18
22
|
* });
|
|
19
23
|
* ```
|
|
20
24
|
*
|
|
@@ -22,18 +26,34 @@
|
|
|
22
26
|
* whose every turn is a chant command line, bound to a teammate with a
|
|
23
27
|
* standing thread and schedules. A box is where people and their agents work
|
|
24
28
|
* on an app: a conversational runtime (`claude` by default), the app repo
|
|
25
|
-
* cloned in, the provisioning script run as setup, and
|
|
26
|
-
* declares no teammate and no schedule, and nothing here
|
|
27
|
-
* conversation. Starting and reaping a box stays a runtime act, as
|
|
28
|
-
* hud's box runtime.
|
|
29
|
+
* cloned in (when there is one), the provisioning script run as setup, and
|
|
30
|
+
* one served port. It declares no teammate and no schedule, and nothing here
|
|
31
|
+
* starts a conversation. Starting and reaping a box stays a runtime act, as
|
|
32
|
+
* it is in hud's box runtime.
|
|
29
33
|
*
|
|
30
34
|
* The defaults are the closed ones, as `ConciergeStack`'s are, and loosening
|
|
31
35
|
* one is a visible parameter: networking is `limited` with an empty allowlist
|
|
32
36
|
* until `allowedHosts` names hosts or `unrestrictedNetworking: true` opens it
|
|
33
37
|
* (which FTN011 then warns about); `allowed_vault_ids` holds the box's own
|
|
34
|
-
* vault, or nothing, until `allowedVaults` widens it
|
|
35
|
-
*
|
|
36
|
-
*
|
|
38
|
+
* vault, or nothing, until `allowedVaults` widens it; `sandbox_provider` is
|
|
39
|
+
* unset (the instance default) until `sandboxProvider` names one of the
|
|
40
|
+
* pinned spec's `sprites`, `e2b`, `daytona` or `runner`. `permission_policy`
|
|
41
|
+
* has no default at all, because fountain's unset policy is `auto_allow` and
|
|
42
|
+
* that should be a choice someone wrote down.
|
|
43
|
+
*
|
|
44
|
+
* ## Credentials
|
|
45
|
+
*
|
|
46
|
+
* A box holds no credential (#2726). What it reaches outside itself
|
|
47
|
+
* (inference, Fountain's own API) goes through a broker, such as a studio's
|
|
48
|
+
* lobby, that holds the key and enforces a scope. Those capabilities are
|
|
49
|
+
* declared in the workspace declaration's `box` block on the member that
|
|
50
|
+
* holds this composite, not here: `chant workspace check` and the read
|
|
51
|
+
* contract read the declaration without running this code. Here, a vault
|
|
52
|
+
* secret's value and an `envVars` value are references (`${VAR}`, `op://`,
|
|
53
|
+
* `bws://`, `infisical://`) that resolve at apply, never a literal, and never
|
|
54
|
+
* `process.env.X`, which writes the secret into the built manifest. `chant
|
|
55
|
+
* workspace check` fails a box member whose files hold one
|
|
56
|
+
* (`box-credential-declared`).
|
|
37
57
|
*
|
|
38
58
|
* ## The port
|
|
39
59
|
*
|
|
@@ -41,10 +61,31 @@
|
|
|
41
61
|
* pinned spec, v0.21.0). The port is recorded in the metadata of both, under
|
|
42
62
|
* `box-port`, and returned as `port`, so whatever builds the box's URL reads
|
|
43
63
|
* it from the declaration or from fountain's own record without a guess.
|
|
64
|
+
*
|
|
65
|
+
* ## Allowed vaults in the read contract
|
|
66
|
+
*
|
|
67
|
+
* `sandbox_provider` is a plain `Agent` field, so `chant workspace graph
|
|
68
|
+
* --json` (and `chant graph`) already carry it on the agent's node like any
|
|
69
|
+
* other declared attribute. `allowed_vault_ids` is not as plain to read:
|
|
70
|
+
* fountain's own convention (and this composite's, per `allowedVaults`
|
|
71
|
+
* above) is that an *unset* list means "any vault the tenant owns", so a
|
|
72
|
+
* graph reader can't tell "no vaults declared" from "any vault" just by the
|
|
73
|
+
* key's absence. #2759 — a runtime that creates a vault per box (hud's box
|
|
74
|
+
* runtime) needs to check that the declaration permits it — so the resolved
|
|
75
|
+
* list, or the literal string `"any"`, is also recorded in the agent's
|
|
76
|
+
* metadata under `box-allowed-vault-ids` (`BOX_ALLOWED_VAULTS_METADATA_KEY`).
|
|
77
|
+
* It is read-only bookkeeping, alongside `box-port`: the manifest's real
|
|
78
|
+
* `allowed_vault_ids` field is what fountain applies.
|
|
44
79
|
*/
|
|
45
80
|
import { Agent, Environment, Vault } from "../generated/index.js";
|
|
46
81
|
/** The metadata key a box's served port is recorded under, on its Environment and Agent. */
|
|
47
82
|
export declare const BOX_PORT_METADATA_KEY = "box-port";
|
|
83
|
+
/** The metadata key a box agent's resolved allowed vaults (or `"any"`) are recorded under, for the read contract. */
|
|
84
|
+
export declare const BOX_ALLOWED_VAULTS_METADATA_KEY = "box-allowed-vault-ids";
|
|
85
|
+
/** `Agent.sandbox_provider`'s values at the pinned spec. */
|
|
86
|
+
export declare const BOX_SANDBOX_PROVIDERS: readonly ["sprites", "e2b", "daytona", "runner"];
|
|
87
|
+
/** A sandbox backend, per the pinned spec's `sandbox_provider` enum. */
|
|
88
|
+
export type BoxSandboxProvider = (typeof BOX_SANDBOX_PROVIDERS)[number];
|
|
48
89
|
/** The port a box serves when none is given: the door's. */
|
|
49
90
|
export declare const BOX_DEFAULT_PORT = 8080;
|
|
50
91
|
/** Where the app repo is cloned when no `mountPath` is given. */
|
|
@@ -67,7 +108,7 @@ export interface BoxVaultOpts {
|
|
|
67
108
|
/** Vault name. Default `<name>-secrets`. */
|
|
68
109
|
name?: string;
|
|
69
110
|
description?: string;
|
|
70
|
-
/** Written at apply. A reference that resolves at
|
|
111
|
+
/** Written at apply. A reference such as `${VAR}` or `op://...` that resolves at apply, never a literal (FTN001, and `box-credential-declared` in `chant workspace check`). */
|
|
71
112
|
secrets?: {
|
|
72
113
|
key: string;
|
|
73
114
|
value: string;
|
|
@@ -76,8 +117,8 @@ export interface BoxVaultOpts {
|
|
|
76
117
|
export interface BoxOpts {
|
|
77
118
|
/** The Agent's name. The Environment is `<name>-env`. */
|
|
78
119
|
name: string;
|
|
79
|
-
/** The app repo the box serves, cloned into the sandbox before setup runs. */
|
|
80
|
-
repo
|
|
120
|
+
/** The app repo the box serves, cloned into the sandbox before setup runs. Omit for a blank-slate app with no repo yet: the Environment declares no `repositories`, and `setupScript` runs against whatever working tree the sandbox already has. */
|
|
121
|
+
repo?: BoxRepositoryOpts;
|
|
81
122
|
/** The provisioning script's text, run as the Environment's `setup_script`. */
|
|
82
123
|
setupScript: string;
|
|
83
124
|
/** Setup exec timeout, 1 to 900 seconds (FTN024). fountain's default is 120. */
|
|
@@ -86,6 +127,8 @@ export interface BoxOpts {
|
|
|
86
127
|
runtime?: "claude" | "codex" | "gemini" | "opencode";
|
|
87
128
|
/** Canonical provider/model_id. Omitted, fountain's default for the runtime. */
|
|
88
129
|
model?: string;
|
|
130
|
+
/** Sandbox backend override. Omitted, the instance default (`SANDBOX_PROVIDER`). A plain `Agent.sandbox_provider` field, so it is already visible on the graph node without extra bookkeeping (unlike `allowedVaults`; see "Allowed vaults in the read contract" above). */
|
|
131
|
+
sandboxProvider?: BoxSandboxProvider;
|
|
89
132
|
/** Per-tool permission policy. Required: fountain's unset policy is `auto_allow`. */
|
|
90
133
|
permissionPolicy: BoxPermissionPolicy;
|
|
91
134
|
/** Environment packages, passed through. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"box.d.ts","sourceRoot":"","sources":["../../src/composites/box.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"box.d.ts","sourceRoot":"","sources":["../../src/composites/box.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8EG;AAEH,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE/D,4FAA4F;AAC5F,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAEhD,qHAAqH;AACrH,eAAO,MAAM,+BAA+B,0BAA0B,CAAC;AAEvE,4DAA4D;AAC5D,eAAO,MAAM,qBAAqB,kDAAmD,CAAC;AAEtF,wEAAwE;AACxE,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,4DAA4D;AAC5D,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,iEAAiE;AACjE,eAAO,MAAM,sBAAsB,mBAAmB,CAAC;AAEvD,gFAAgF;AAChF,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,GAAG,YAAY,GAAG,WAAW,GAAG,MAAM,CAAC,CAAC;AAE9F,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,mFAAmF;AACnF,MAAM,WAAW,YAAY;IAC3B,4CAA4C;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+KAA+K;IAC/K,OAAO,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,OAAO;IACtB,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,qPAAqP;IACrP,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,+EAA+E;IAC/E,WAAW,EAAE,MAAM,CAAC;IACpB,gFAAgF;IAChF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,uCAAuC;IACvC,OAAO,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC;IACrD,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4QAA4Q;IAC5Q,eAAe,CAAC,EAAE,kBAAkB,CAAC;IACrC,qFAAqF;IACrF,gBAAgB,EAAE,mBAAmB,CAAC;IACtC,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,mFAAmF;IACnF,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,oDAAoD;IACpD,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB;;;;OAIG;IACH,aAAa,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC,OAAO,KAAK,CAAC,GAAG,MAAM,CAAC,CAAC;IACnE,0FAA0F;IAC1F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,YAAY,CAAC,OAAO,WAAW,CAAC,CAAC;IAC9C,KAAK,EAAE,YAAY,CAAC,OAAO,KAAK,CAAC,CAAC;IAClC,KAAK,CAAC,EAAE,YAAY,CAAC,OAAO,KAAK,CAAC,CAAC;IACnC,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,GAAG,CAAC,IAAI,EAAE,OAAO,GAAG,YAAY,CAgF/C"}
|
|
@@ -44,7 +44,43 @@
|
|
|
44
44
|
* the server does not keep. And a webhook url FTN022 would reject is refused
|
|
45
45
|
* here rather than at synth, because a composite that constructed it would be
|
|
46
46
|
* handing the author a lint error about a resource they never typed.
|
|
47
|
+
*
|
|
48
|
+
* ## One steward whatever the preset (#2731)
|
|
49
|
+
*
|
|
50
|
+
* The composite also returns `declaration`, the steward as core reads it
|
|
51
|
+
* (`declareSteward` in `@intentius/chant/op`): its name, its Ops and its
|
|
52
|
+
* `form`. A box whose preset has no Fountain in some environment (a docker
|
|
53
|
+
* compose box, a Sprites box) sets `form` so that environment is `local`, and
|
|
54
|
+
* there `chant operator --steward <name>` runs the same Ops on the same crons,
|
|
55
|
+
* one at a time, each under its operator lease and the whole process under the
|
|
56
|
+
* steward's own. `chant operator --steward` refuses an environment whose form
|
|
57
|
+
* is `fountain`, because this Agent and Teammate are the steward there. Export
|
|
58
|
+
* `declaration` from an `*.op.ts` file so the operator and `chant workspace
|
|
59
|
+
* status` find it.
|
|
60
|
+
*
|
|
61
|
+
* ## Credentials (#2726)
|
|
62
|
+
*
|
|
63
|
+
* A box's steward holds no credential. Name the box capabilities its Ops use
|
|
64
|
+
* in `capabilities` (the names the member's `box` block in the workspace
|
|
65
|
+
* declaration gives them) and leave `vault` out: the broker holds the key and
|
|
66
|
+
* enforces the scope. `vault` together with `capabilities` is refused.
|
|
67
|
+
* `chant workspace check` fails a box member whose files hold a literal secret
|
|
68
|
+
* (`box-credential-declared`), and `chant workspace status --json` lists each
|
|
69
|
+
* capability the steward names with the broker the box block gives it.
|
|
70
|
+
*
|
|
71
|
+
* ## The coding agent beside it
|
|
72
|
+
*
|
|
73
|
+
* A box also has a coding agent editing the app. They share the checkout by
|
|
74
|
+
* writing different things. The steward's writes are chant writes: run,
|
|
75
|
+
* converge and gate records on the `chant/lifecycle` branch and lease refs
|
|
76
|
+
* under `refs/chant/lease/`, made with git plumbing that never reads or writes
|
|
77
|
+
* the index or the working tree. The app's files, the index and commits on the
|
|
78
|
+
* working branch are the coding agent's. An Op a steward runs does not edit the
|
|
79
|
+
* app. A change to the checkout that an Op has to make (applying a build, a
|
|
80
|
+
* workspace upgrade) belongs under the work item's lease (ws-055), on a branch
|
|
81
|
+
* of its own, never in the coding agent's working tree.
|
|
47
82
|
*/
|
|
83
|
+
import { type StewardDeclaration, type StewardFormSpec } from "@intentius/chant/op";
|
|
48
84
|
import type { OpConfig, OpResource } from "@intentius/chant/op";
|
|
49
85
|
import { Agent, Environment, Schedule, Teammate, Vault, Webhook } from "../generated/index.js";
|
|
50
86
|
/**
|
|
@@ -71,8 +107,18 @@ export interface StewardOpts {
|
|
|
71
107
|
name: string;
|
|
72
108
|
/** The environment its computer is provisioned from: repo, chant, tooling. */
|
|
73
109
|
environment: InstanceType<typeof Environment>;
|
|
74
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* Secrets layered on top, for a steward that isn't behind a broker. Omit it
|
|
112
|
+
* under a broker, which holds them, and name `capabilities` instead.
|
|
113
|
+
*/
|
|
75
114
|
vault?: InstanceType<typeof Vault>;
|
|
115
|
+
/**
|
|
116
|
+
* The box capabilities this steward's Ops reach through the box's broker
|
|
117
|
+
* (#2726), by the names the member's `box` block declares, such as
|
|
118
|
+
* `fountain` or `inference`. Refused together with `vault`: a brokered
|
|
119
|
+
* steward holds no credential.
|
|
120
|
+
*/
|
|
121
|
+
capabilities?: string[];
|
|
76
122
|
/**
|
|
77
123
|
* The ops this steward runs. An op with a `schedule` gets a `Schedule`; one
|
|
78
124
|
* without is still listed, so `chant run <op> --on fountain` knows which
|
|
@@ -82,6 +128,16 @@ export interface StewardOpts {
|
|
|
82
128
|
webhook?: StewardWebhookOpts;
|
|
83
129
|
/** Extra metadata merged over the `managed-by` marker on the Agent. */
|
|
84
130
|
metadata?: Record<string, unknown>;
|
|
131
|
+
/**
|
|
132
|
+
* Where this steward runs (#2731). Default `fountain`: this composite's
|
|
133
|
+
* Agent and Teammate are the steward. A box whose preset has no Fountain in
|
|
134
|
+
* some environment names it here, e.g. `{ default: "fountain", environments:
|
|
135
|
+
* { minimal: "local" } }`, and `chant operator --steward <name> --env minimal`
|
|
136
|
+
* runs the same Ops there. The Fountain resources are declared either way; a
|
|
137
|
+
* form that is `local` everywhere is refused, since it needs none of them
|
|
138
|
+
* (use core's `declareSteward` for that).
|
|
139
|
+
*/
|
|
140
|
+
form?: StewardFormSpec;
|
|
85
141
|
}
|
|
86
142
|
export interface StewardResources {
|
|
87
143
|
agent: InstanceType<typeof Agent>;
|
|
@@ -89,6 +145,12 @@ export interface StewardResources {
|
|
|
89
145
|
/** One per op that carries a `schedule`, in `ops` order. */
|
|
90
146
|
schedules: InstanceType<typeof Schedule>[];
|
|
91
147
|
webhook?: InstanceType<typeof Webhook>;
|
|
148
|
+
/**
|
|
149
|
+
* The steward as core reads it (#2731): name, Ops and form. Export it from
|
|
150
|
+
* an `*.op.ts` file so `chant operator --steward` and `chant workspace
|
|
151
|
+
* status` find it.
|
|
152
|
+
*/
|
|
153
|
+
declaration: StewardDeclaration;
|
|
92
154
|
}
|
|
93
155
|
/** The command an `acp` agent speaks the protocol over (#2125). */
|
|
94
156
|
export declare const STEWARD_RUNTIME_COMMAND = "chant acp";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"steward.d.ts","sourceRoot":"","sources":["../../src/composites/steward.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"steward.d.ts","sourceRoot":"","sources":["../../src/composites/steward.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiFG;AAEH,OAAO,EAAkB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAK5F;;;;;;;;;GASG;AACH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,YAAY,CAAC,OAAO,UAAU,CAAC,CAAC;AAQnE,kFAAkF;AAClF,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;IACZ,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,WAAW,EAAE,YAAY,CAAC,OAAO,WAAW,CAAC,CAAC;IAC9C;;;OAGG;IACH,KAAK,CAAC,EAAE,YAAY,CAAC,OAAO,KAAK,CAAC,CAAC;IACnC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;;;OAIG;IACH,GAAG,EAAE,SAAS,EAAE,CAAC;IACjB,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,eAAe,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,YAAY,CAAC,OAAO,KAAK,CAAC,CAAC;IAClC,QAAQ,EAAE,YAAY,CAAC,OAAO,QAAQ,CAAC,CAAC;IACxC,4DAA4D;IAC5D,SAAS,EAAE,YAAY,CAAC,OAAO,QAAQ,CAAC,EAAE,CAAC;IAC3C,OAAO,CAAC,EAAE,YAAY,CAAC,OAAO,OAAO,CAAC,CAAC;IACvC;;;;OAIG;IACH,WAAW,EAAE,kBAAkB,CAAC;CACjC;AAED,mEAAmE;AACnE,eAAO,MAAM,uBAAuB,cAAc,CAAC;AAiBnD,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE3D;AAED,iFAAiF;AACjF,wBAAgB,uBAAuB,IAAI,IAAI,CAG9C;AA8BD,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,gBAAgB,CAuG3D"}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,8 +14,8 @@ export { ConciergeStack } from "./composites/concierge-stack.js";
|
|
|
14
14
|
export type { ConciergeStackOpts, ConciergeStackResources } from "./composites/concierge-stack.js";
|
|
15
15
|
export { Steward, stewardForOp, STEWARD_RUNTIME_COMMAND } from "./composites/steward.js";
|
|
16
16
|
export type { StewardOp, StewardOpts, StewardResources, StewardWebhookOpts } from "./composites/steward.js";
|
|
17
|
-
export { Box, BOX_PORT_METADATA_KEY, BOX_DEFAULT_PORT, BOX_DEFAULT_MOUNT_PATH } from "./composites/box.js";
|
|
18
|
-
export type { BoxOpts, BoxResources, BoxRepositoryOpts, BoxVaultOpts, BoxPermissionPolicy } from "./composites/box.js";
|
|
17
|
+
export { Box, BOX_PORT_METADATA_KEY, BOX_ALLOWED_VAULTS_METADATA_KEY, BOX_DEFAULT_PORT, BOX_DEFAULT_MOUNT_PATH, BOX_SANDBOX_PROVIDERS, } from "./composites/box.js";
|
|
18
|
+
export type { BoxOpts, BoxResources, BoxRepositoryOpts, BoxVaultOpts, BoxPermissionPolicy, BoxSandboxProvider } from "./composites/box.js";
|
|
19
19
|
export { acpCommandGroup } from "./acp/index.js";
|
|
20
20
|
export { AcpServer } from "./acp/server.js";
|
|
21
21
|
export type { AcpServerOptions } from "./acp/server.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAIlD,OAAO,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AAC9D,YAAY,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EACL,8BAA8B,EAC9B,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAG9B,cAAc,mBAAmB,CAAC;AAIlC,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AACxF,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAInH,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAIhE,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,8BAA8B,GAC/B,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,wBAAwB,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAG5F,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAChG,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACtF,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACzG,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAIlD,OAAO,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AAC9D,YAAY,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EACL,8BAA8B,EAC9B,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAG9B,cAAc,mBAAmB,CAAC;AAIlC,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AACxF,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAInH,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAIhE,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,8BAA8B,GAC/B,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,wBAAwB,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAG5F,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAChG,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACtF,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACzG,OAAO,EACL,GAAG,EACH,qBAAqB,EACrB,+BAA+B,EAC/B,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAIxI,OAAO,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "f55b301389d73946391200a1788013f3b7f368586b497af230a6813a0acda4e8",
|
|
5
5
|
"meta.json": "3301437a2a56eaf1a5cc8f6eabb18a40767f916e09701b2d9bf20aba4066738c",
|
|
6
6
|
"types/index.d.ts": "cf6b634fe0a8e9e96f1d9d07e3df53838def2683dc93dbe3c7fd971c6d3908ae",
|
|
7
7
|
"rules/ftn001-no-secret-literals.ts": "a417b194cd7039911cca9a0f14c49b042dfa6b1ee48517f7c2e22b16590a8599",
|
|
@@ -23,5 +23,5 @@
|
|
|
23
23
|
"skills/chant-fountain-ops.md": "4e72f0a0c7477a9e98f85ea45e49ba69e8a699f9c13ba0f0cf1cb20a88151373",
|
|
24
24
|
"skills/chant-fountain-locked-sandboxes.md": "14def4c68b13c2bd561f75819b188968cec85073a5dd8b59e91708e1af26daae"
|
|
25
25
|
},
|
|
26
|
-
"composite": "
|
|
26
|
+
"composite": "d961f51d2dbd943d5da0fb56f8c82649c3392c02c62222d68c81069b9c9cdefb"
|
|
27
27
|
}
|
package/dist/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-fountain",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.91.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Fountain lexicon for chant — sandboxed agent environments, vaults, and agents as typed estate",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"bundle": "tsx src/package-cli.ts"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@intentius/chant": "^0.
|
|
53
|
+
"@intentius/chant": "^0.91.0",
|
|
54
54
|
"typescript": "^5.9.3",
|
|
55
55
|
"zod": "^4.3.6"
|
|
56
56
|
},
|
package/src/composites/box.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Box — a persistent machine that serves an app repo and its tools (#2705).
|
|
3
3
|
*
|
|
4
|
-
* A box is an `Environment` whose
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* A box is an `Environment` whose `setup_script` provisions the machine (for
|
|
5
|
+
* an arugula studio box, the studio's `box/provision-template.sh`), an
|
|
6
|
+
* `Agent` on a persistent sandbox provisioned from it, and optionally a
|
|
7
|
+
* `Vault` for the secrets every box of this kind shares. `repo` is optional:
|
|
8
|
+
* when given, the Environment's `repositories` clones it in before setup
|
|
9
|
+
* runs; when omitted (#2759 — a blank-slate app, such as the one hud's
|
|
10
|
+
* `init` makes, has no git remote yet), the Environment holds no
|
|
11
|
+
* `repositories` at all and `setup_script` still runs, against whatever
|
|
12
|
+
* working tree is already on the sandbox (a self-hosted `runner`'s own).
|
|
9
13
|
*
|
|
10
14
|
* ```ts
|
|
11
15
|
* export const { environment, agent, vault } = Box({
|
|
@@ -14,7 +18,7 @@
|
|
|
14
18
|
* setupScript: readFileSync("box/provision-template.sh", "utf8"),
|
|
15
19
|
* permissionPolicy: { default: "auto_allow" },
|
|
16
20
|
* allowedHosts: ["registry.npmjs.org", "github.com"],
|
|
17
|
-
* vault: { secrets: [{ key: "STUDIO_SECRET", value:
|
|
21
|
+
* vault: { secrets: [{ key: "STUDIO_SECRET", value: "${STUDIO_SECRET}" }] },
|
|
18
22
|
* });
|
|
19
23
|
* ```
|
|
20
24
|
*
|
|
@@ -22,18 +26,34 @@
|
|
|
22
26
|
* whose every turn is a chant command line, bound to a teammate with a
|
|
23
27
|
* standing thread and schedules. A box is where people and their agents work
|
|
24
28
|
* on an app: a conversational runtime (`claude` by default), the app repo
|
|
25
|
-
* cloned in, the provisioning script run as setup, and
|
|
26
|
-
* declares no teammate and no schedule, and nothing here
|
|
27
|
-
* conversation. Starting and reaping a box stays a runtime act, as
|
|
28
|
-
* hud's box runtime.
|
|
29
|
+
* cloned in (when there is one), the provisioning script run as setup, and
|
|
30
|
+
* one served port. It declares no teammate and no schedule, and nothing here
|
|
31
|
+
* starts a conversation. Starting and reaping a box stays a runtime act, as
|
|
32
|
+
* it is in hud's box runtime.
|
|
29
33
|
*
|
|
30
34
|
* The defaults are the closed ones, as `ConciergeStack`'s are, and loosening
|
|
31
35
|
* one is a visible parameter: networking is `limited` with an empty allowlist
|
|
32
36
|
* until `allowedHosts` names hosts or `unrestrictedNetworking: true` opens it
|
|
33
37
|
* (which FTN011 then warns about); `allowed_vault_ids` holds the box's own
|
|
34
|
-
* vault, or nothing, until `allowedVaults` widens it
|
|
35
|
-
*
|
|
36
|
-
*
|
|
38
|
+
* vault, or nothing, until `allowedVaults` widens it; `sandbox_provider` is
|
|
39
|
+
* unset (the instance default) until `sandboxProvider` names one of the
|
|
40
|
+
* pinned spec's `sprites`, `e2b`, `daytona` or `runner`. `permission_policy`
|
|
41
|
+
* has no default at all, because fountain's unset policy is `auto_allow` and
|
|
42
|
+
* that should be a choice someone wrote down.
|
|
43
|
+
*
|
|
44
|
+
* ## Credentials
|
|
45
|
+
*
|
|
46
|
+
* A box holds no credential (#2726). What it reaches outside itself
|
|
47
|
+
* (inference, Fountain's own API) goes through a broker, such as a studio's
|
|
48
|
+
* lobby, that holds the key and enforces a scope. Those capabilities are
|
|
49
|
+
* declared in the workspace declaration's `box` block on the member that
|
|
50
|
+
* holds this composite, not here: `chant workspace check` and the read
|
|
51
|
+
* contract read the declaration without running this code. Here, a vault
|
|
52
|
+
* secret's value and an `envVars` value are references (`${VAR}`, `op://`,
|
|
53
|
+
* `bws://`, `infisical://`) that resolve at apply, never a literal, and never
|
|
54
|
+
* `process.env.X`, which writes the secret into the built manifest. `chant
|
|
55
|
+
* workspace check` fails a box member whose files hold one
|
|
56
|
+
* (`box-credential-declared`).
|
|
37
57
|
*
|
|
38
58
|
* ## The port
|
|
39
59
|
*
|
|
@@ -41,6 +61,21 @@
|
|
|
41
61
|
* pinned spec, v0.21.0). The port is recorded in the metadata of both, under
|
|
42
62
|
* `box-port`, and returned as `port`, so whatever builds the box's URL reads
|
|
43
63
|
* it from the declaration or from fountain's own record without a guess.
|
|
64
|
+
*
|
|
65
|
+
* ## Allowed vaults in the read contract
|
|
66
|
+
*
|
|
67
|
+
* `sandbox_provider` is a plain `Agent` field, so `chant workspace graph
|
|
68
|
+
* --json` (and `chant graph`) already carry it on the agent's node like any
|
|
69
|
+
* other declared attribute. `allowed_vault_ids` is not as plain to read:
|
|
70
|
+
* fountain's own convention (and this composite's, per `allowedVaults`
|
|
71
|
+
* above) is that an *unset* list means "any vault the tenant owns", so a
|
|
72
|
+
* graph reader can't tell "no vaults declared" from "any vault" just by the
|
|
73
|
+
* key's absence. #2759 — a runtime that creates a vault per box (hud's box
|
|
74
|
+
* runtime) needs to check that the declaration permits it — so the resolved
|
|
75
|
+
* list, or the literal string `"any"`, is also recorded in the agent's
|
|
76
|
+
* metadata under `box-allowed-vault-ids` (`BOX_ALLOWED_VAULTS_METADATA_KEY`).
|
|
77
|
+
* It is read-only bookkeeping, alongside `box-port`: the manifest's real
|
|
78
|
+
* `allowed_vault_ids` field is what fountain applies.
|
|
44
79
|
*/
|
|
45
80
|
|
|
46
81
|
import { Agent, Environment, Vault } from "../generated/index";
|
|
@@ -48,6 +83,15 @@ import { Agent, Environment, Vault } from "../generated/index";
|
|
|
48
83
|
/** The metadata key a box's served port is recorded under, on its Environment and Agent. */
|
|
49
84
|
export const BOX_PORT_METADATA_KEY = "box-port";
|
|
50
85
|
|
|
86
|
+
/** The metadata key a box agent's resolved allowed vaults (or `"any"`) are recorded under, for the read contract. */
|
|
87
|
+
export const BOX_ALLOWED_VAULTS_METADATA_KEY = "box-allowed-vault-ids";
|
|
88
|
+
|
|
89
|
+
/** `Agent.sandbox_provider`'s values at the pinned spec. */
|
|
90
|
+
export const BOX_SANDBOX_PROVIDERS = ["sprites", "e2b", "daytona", "runner"] as const;
|
|
91
|
+
|
|
92
|
+
/** A sandbox backend, per the pinned spec's `sandbox_provider` enum. */
|
|
93
|
+
export type BoxSandboxProvider = (typeof BOX_SANDBOX_PROVIDERS)[number];
|
|
94
|
+
|
|
51
95
|
/** The port a box serves when none is given: the door's. */
|
|
52
96
|
export const BOX_DEFAULT_PORT = 8080;
|
|
53
97
|
|
|
@@ -74,15 +118,15 @@ export interface BoxVaultOpts {
|
|
|
74
118
|
/** Vault name. Default `<name>-secrets`. */
|
|
75
119
|
name?: string;
|
|
76
120
|
description?: string;
|
|
77
|
-
/** Written at apply. A reference that resolves at
|
|
121
|
+
/** Written at apply. A reference such as `${VAR}` or `op://...` that resolves at apply, never a literal (FTN001, and `box-credential-declared` in `chant workspace check`). */
|
|
78
122
|
secrets?: { key: string; value: string }[];
|
|
79
123
|
}
|
|
80
124
|
|
|
81
125
|
export interface BoxOpts {
|
|
82
126
|
/** The Agent's name. The Environment is `<name>-env`. */
|
|
83
127
|
name: string;
|
|
84
|
-
/** The app repo the box serves, cloned into the sandbox before setup runs. */
|
|
85
|
-
repo
|
|
128
|
+
/** The app repo the box serves, cloned into the sandbox before setup runs. Omit for a blank-slate app with no repo yet: the Environment declares no `repositories`, and `setupScript` runs against whatever working tree the sandbox already has. */
|
|
129
|
+
repo?: BoxRepositoryOpts;
|
|
86
130
|
/** The provisioning script's text, run as the Environment's `setup_script`. */
|
|
87
131
|
setupScript: string;
|
|
88
132
|
/** Setup exec timeout, 1 to 900 seconds (FTN024). fountain's default is 120. */
|
|
@@ -91,6 +135,8 @@ export interface BoxOpts {
|
|
|
91
135
|
runtime?: "claude" | "codex" | "gemini" | "opencode";
|
|
92
136
|
/** Canonical provider/model_id. Omitted, fountain's default for the runtime. */
|
|
93
137
|
model?: string;
|
|
138
|
+
/** Sandbox backend override. Omitted, the instance default (`SANDBOX_PROVIDER`). A plain `Agent.sandbox_provider` field, so it is already visible on the graph node without extra bookkeeping (unlike `allowedVaults`; see "Allowed vaults in the read contract" above). */
|
|
139
|
+
sandboxProvider?: BoxSandboxProvider;
|
|
94
140
|
/** Per-tool permission policy. Required: fountain's unset policy is `auto_allow`. */
|
|
95
141
|
permissionPolicy: BoxPermissionPolicy;
|
|
96
142
|
/** Environment packages, passed through. */
|
|
@@ -144,16 +190,18 @@ export function Box(opts: BoxOpts): BoxResources {
|
|
|
144
190
|
const owned = { "managed-by": "chant", ...(opts.metadata ?? {}) };
|
|
145
191
|
const metadata = { ...owned, [BOX_PORT_METADATA_KEY]: port };
|
|
146
192
|
|
|
147
|
-
const repository =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
193
|
+
const repository = opts.repo
|
|
194
|
+
? {
|
|
195
|
+
url: opts.repo.url,
|
|
196
|
+
mount_path: opts.repo.mountPath ?? BOX_DEFAULT_MOUNT_PATH,
|
|
197
|
+
...(opts.repo.ref !== undefined ? { ref: opts.repo.ref } : {}),
|
|
198
|
+
...(opts.repo.secretKey !== undefined ? { secret_key: opts.repo.secretKey } : {}),
|
|
199
|
+
}
|
|
200
|
+
: undefined;
|
|
153
201
|
|
|
154
202
|
const environment = new Environment({
|
|
155
203
|
name: `${opts.name}-env`,
|
|
156
|
-
repositories: [repository],
|
|
204
|
+
...(repository ? { repositories: [repository] } : {}),
|
|
157
205
|
setup_script: opts.setupScript,
|
|
158
206
|
...(opts.setupTimeoutSeconds !== undefined ? { setup_timeout_seconds: opts.setupTimeoutSeconds } : {}),
|
|
159
207
|
...(opts.packages ? { packages: opts.packages } : {}),
|
|
@@ -179,18 +227,28 @@ export function Box(opts: BoxOpts): BoxResources {
|
|
|
179
227
|
const allowedVaults =
|
|
180
228
|
opts.allowedVaults === "any" ? undefined : (opts.allowedVaults ?? (vault ? [vault] : []));
|
|
181
229
|
|
|
230
|
+
// The resolved allowed-vaults value, for the read contract (see "Allowed
|
|
231
|
+
// vaults in the read contract" above) — "any" when the list is left unset,
|
|
232
|
+
// otherwise each entry's own declared name.
|
|
233
|
+
const allowedVaultsForReadContract: "any" | string[] =
|
|
234
|
+
opts.allowedVaults === "any"
|
|
235
|
+
? "any"
|
|
236
|
+
: allowedVaults!.map((v) => (typeof v === "string" ? v : (v as unknown as { props: { name: string } }).props.name));
|
|
237
|
+
const agentMetadata = { ...metadata, [BOX_ALLOWED_VAULTS_METADATA_KEY]: allowedVaultsForReadContract };
|
|
238
|
+
|
|
182
239
|
const agent = new Agent({
|
|
183
240
|
name: opts.name,
|
|
184
241
|
runtime: opts.runtime ?? "claude",
|
|
185
242
|
...(opts.model !== undefined ? { model: opts.model } : {}),
|
|
186
243
|
sandbox_mode: "persistent",
|
|
244
|
+
...(opts.sandboxProvider !== undefined ? { sandbox_provider: opts.sandboxProvider } : {}),
|
|
187
245
|
environment,
|
|
188
246
|
permission_policy: opts.permissionPolicy,
|
|
189
247
|
...(allowedVaults !== undefined ? { allowed_vault_ids: allowedVaults } : {}),
|
|
190
248
|
...(opts.skills ? { skills: opts.skills } : {}),
|
|
191
249
|
...(opts.mcpServers ? { mcp_servers: opts.mcpServers } : {}),
|
|
192
250
|
...(opts.system !== undefined ? { system: opts.system } : {}),
|
|
193
|
-
metadata,
|
|
251
|
+
metadata: agentMetadata,
|
|
194
252
|
});
|
|
195
253
|
|
|
196
254
|
return { environment, agent, ...(vault ? { vault } : {}), port };
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { WatchOp, type OpConfig } from "@intentius/chant/op";
|
|
2
|
+
import { WatchOp, isStewardDeclaration, stewardFormFor, type OpConfig } from "@intentius/chant/op";
|
|
3
3
|
import { ConciergeStack } from "./concierge-stack";
|
|
4
4
|
import { Steward, stewardForOp, __resetStewardsForTests } from "./steward";
|
|
5
|
-
import { Box, BOX_PORT_METADATA_KEY } from "./box";
|
|
5
|
+
import { Box, BOX_ALLOWED_VAULTS_METADATA_KEY, BOX_PORT_METADATA_KEY } from "./box";
|
|
6
6
|
import { postSynthChecks } from "../lint/post-synth/index";
|
|
7
7
|
import spec from "../spec/fountain-openapi.snapshot.json";
|
|
8
8
|
import { Environment, Vault } from "../generated/index";
|
|
9
9
|
import { fountainSerializer } from "../serializer";
|
|
10
|
+
import { buildGraphIr, resolveAttrRefs } from "@intentius/chant";
|
|
10
11
|
import type { Declarable } from "@intentius/chant";
|
|
11
12
|
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
12
13
|
import { runtimeModelValidCheck } from "../lint/post-synth/ftn016-runtime-model-valid";
|
|
@@ -238,6 +239,77 @@ describe("Steward", () => {
|
|
|
238
239
|
});
|
|
239
240
|
});
|
|
240
241
|
|
|
242
|
+
// ── Steward: the declaration core reads, and its form (#2731) ─────────────
|
|
243
|
+
|
|
244
|
+
describe("Steward declaration and form", () => {
|
|
245
|
+
beforeEach(() => {
|
|
246
|
+
__resetStewardsForTests();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const toolchain = () => new Environment({ name: "toolchain" });
|
|
250
|
+
|
|
251
|
+
it("returns the declaration core reads: name, every op, and fountain as the default form", () => {
|
|
252
|
+
const { declaration } = Steward({
|
|
253
|
+
name: "box-steward",
|
|
254
|
+
environment: toolchain(),
|
|
255
|
+
ops: [op("box-converge", { cron: "* * * * *" }), op("box-release")],
|
|
256
|
+
});
|
|
257
|
+
expect(isStewardDeclaration(declaration)).toBe(true);
|
|
258
|
+
expect(declaration.name).toBe("box-steward");
|
|
259
|
+
expect(declaration.ops.map((o) => o.name)).toEqual(["box-converge", "box-release"]);
|
|
260
|
+
expect(declaration.form).toEqual({ default: "fountain", environments: {} });
|
|
261
|
+
expect(stewardFormFor(declaration, "prod")).toBe("fountain");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("names the environments where the same steward runs locally", () => {
|
|
265
|
+
const { declaration, schedules } = Steward({
|
|
266
|
+
name: "box-steward",
|
|
267
|
+
environment: toolchain(),
|
|
268
|
+
ops: [op("box-converge", { cron: "* * * * *" })],
|
|
269
|
+
form: { default: "fountain", environments: { minimal: "local", spritzer: "local" } },
|
|
270
|
+
});
|
|
271
|
+
expect(stewardFormFor(declaration, "fountain-k3d")).toBe("fountain");
|
|
272
|
+
expect(stewardFormFor(declaration, "minimal")).toBe("local");
|
|
273
|
+
expect(stewardFormFor(declaration, "spritzer")).toBe("local");
|
|
274
|
+
// The Fountain resources are declared whatever the form: which environment
|
|
275
|
+
// they run in is the deploy's business, not the declaration's.
|
|
276
|
+
expect(schedules).toHaveLength(1);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("refuses a form that is local everywhere, which needs none of the Fountain resources", () => {
|
|
280
|
+
expect(() =>
|
|
281
|
+
Steward({ name: "box-steward", environment: toolchain(), ops: [], form: "local" }),
|
|
282
|
+
).toThrow(/local" in every environment/);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("refuses an op listed twice, which would run twice per fire", () => {
|
|
286
|
+
const converge = op("box-converge", { cron: "* * * * *" });
|
|
287
|
+
expect(() =>
|
|
288
|
+
Steward({ name: "box-steward", environment: toolchain(), ops: [converge, converge] }),
|
|
289
|
+
).toThrow(/listed twice/);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("records the brokered capabilities, and refuses them beside a vault (#2726)", () => {
|
|
293
|
+
const { declaration } = Steward({ name: "box-steward", environment: toolchain(), ops: [], capabilities: ["fountain"] });
|
|
294
|
+
expect(declaration.capabilities).toEqual(["fountain"]);
|
|
295
|
+
expect(declaration.vault).toBeNull();
|
|
296
|
+
__resetStewardsForTests();
|
|
297
|
+
expect(
|
|
298
|
+
Steward({ name: "prod-steward", environment: toolchain(), vault: new Vault({ name: "prod-creds" }), ops: [] }).declaration.vault,
|
|
299
|
+
).toBe("prod-creds");
|
|
300
|
+
__resetStewardsForTests();
|
|
301
|
+
expect(() =>
|
|
302
|
+
Steward({ name: "x", environment: toolchain(), vault: new Vault({ name: "v" }), ops: [], capabilities: ["fountain"] }),
|
|
303
|
+
).toThrow(/holds no credential/);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("refuses an unknown form", () => {
|
|
307
|
+
expect(() =>
|
|
308
|
+
Steward({ name: "box-steward", environment: toolchain(), ops: [], form: "cloud" as never }),
|
|
309
|
+
).toThrow(/"local" or "fountain"/);
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
241
313
|
// ── Box ───────────────────────────────────────────────────────────────────
|
|
242
314
|
|
|
243
315
|
describe("Box", () => {
|
|
@@ -267,13 +339,18 @@ describe("Box", () => {
|
|
|
267
339
|
expect(a.environment).toBe(environment);
|
|
268
340
|
expect(a.permission_policy).toEqual({ default: "auto_allow" });
|
|
269
341
|
expect(a.allowed_vault_ids).toEqual([]);
|
|
270
|
-
expect(a.
|
|
342
|
+
expect(a.sandbox_provider).toBeUndefined();
|
|
343
|
+
expect(a.metadata).toEqual({
|
|
344
|
+
"managed-by": "chant",
|
|
345
|
+
"box-port": 8080,
|
|
346
|
+
[BOX_ALLOWED_VAULTS_METADATA_KEY]: [],
|
|
347
|
+
});
|
|
271
348
|
|
|
272
349
|
expect(vault).toBeUndefined();
|
|
273
350
|
expect(port).toBe(8080);
|
|
274
351
|
});
|
|
275
352
|
|
|
276
|
-
it("declares the shared vault and scopes the agent to it", () => {
|
|
353
|
+
it("declares the shared vault and scopes the agent to it, named in the read contract", () => {
|
|
277
354
|
const { agent, vault } = Box({
|
|
278
355
|
...base,
|
|
279
356
|
vault: { secrets: [{ key: "STUDIO_SECRET", value: "${STUDIO_SECRET}" }] },
|
|
@@ -282,6 +359,18 @@ describe("Box", () => {
|
|
|
282
359
|
expect(props(vault).secrets).toEqual([{ key: "STUDIO_SECRET", value: "${STUDIO_SECRET}" }]);
|
|
283
360
|
expect(props(vault).metadata).toEqual({ "managed-by": "chant" });
|
|
284
361
|
expect(props(agent).allowed_vault_ids).toEqual([vault]);
|
|
362
|
+
expect((props(agent).metadata as Record<string, unknown>)[BOX_ALLOWED_VAULTS_METADATA_KEY]).toEqual([
|
|
363
|
+
"studio-box-secrets",
|
|
364
|
+
]);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it("resolves an explicit allowedVaults list to names in the read contract", () => {
|
|
368
|
+
const { agent } = Box({ ...base, allowedVaults: ["other-secrets", "studio-box-secrets"] });
|
|
369
|
+
expect(props(agent).allowed_vault_ids).toEqual(["other-secrets", "studio-box-secrets"]);
|
|
370
|
+
expect((props(agent).metadata as Record<string, unknown>)[BOX_ALLOWED_VAULTS_METADATA_KEY]).toEqual([
|
|
371
|
+
"other-secrets",
|
|
372
|
+
"studio-box-secrets",
|
|
373
|
+
]);
|
|
285
374
|
});
|
|
286
375
|
|
|
287
376
|
it("loosening is a visible parameter", () => {
|
|
@@ -292,6 +381,7 @@ describe("Box", () => {
|
|
|
292
381
|
port: 3000,
|
|
293
382
|
unrestrictedNetworking: true,
|
|
294
383
|
allowedVaults: "any",
|
|
384
|
+
sandboxProvider: "e2b",
|
|
295
385
|
repo: { url: "https://github.com/o/app", mountPath: "/srv/app", ref: "v1", secretKey: "GH_TOKEN" },
|
|
296
386
|
envVars: { NODE_ENV: "production" },
|
|
297
387
|
packages: { apt: ["podman"] },
|
|
@@ -309,14 +399,32 @@ describe("Box", () => {
|
|
|
309
399
|
const a = props(open.agent);
|
|
310
400
|
expect(a.runtime).toBe("codex");
|
|
311
401
|
expect(a.model).toBe("openai/gpt-5");
|
|
312
|
-
|
|
402
|
+
expect(a.sandbox_provider).toBe("e2b");
|
|
403
|
+
// "any" leaves the manifest's own list unset: fountain reads that as any
|
|
404
|
+
// vault the tenant owns. The read contract still names it explicitly.
|
|
313
405
|
expect("allowed_vault_ids" in a).toBe(false);
|
|
406
|
+
expect((a.metadata as Record<string, unknown>)[BOX_ALLOWED_VAULTS_METADATA_KEY]).toBe("any");
|
|
314
407
|
expect(open.port).toBe(3000);
|
|
315
408
|
|
|
316
409
|
const listed = Box({ ...base, allowedHosts: ["registry.npmjs.org"] });
|
|
317
410
|
expect(props(listed.environment).networking_config).toEqual({ allowed_hosts: ["registry.npmjs.org"] });
|
|
318
411
|
});
|
|
319
412
|
|
|
413
|
+
it("builds with no repo — no clone, the Environment declares no repositories", () => {
|
|
414
|
+
const { environment, agent, port } = Box({
|
|
415
|
+
name: "blank-slate",
|
|
416
|
+
setupScript: "#!/bin/bash\nnpm install\n",
|
|
417
|
+
permissionPolicy: { default: "auto_allow" as const },
|
|
418
|
+
sandboxProvider: "runner",
|
|
419
|
+
});
|
|
420
|
+
const env = props(environment);
|
|
421
|
+
expect("repositories" in env).toBe(false);
|
|
422
|
+
expect(env.setup_script).toBe("#!/bin/bash\nnpm install\n");
|
|
423
|
+
const a = props(agent);
|
|
424
|
+
expect(a.sandbox_provider).toBe("runner");
|
|
425
|
+
expect(port).toBe(8080);
|
|
426
|
+
});
|
|
427
|
+
|
|
320
428
|
it("refuses what it cannot mean", () => {
|
|
321
429
|
expect(() => Box({ ...base, port: 0 })).toThrow(/not a TCP port/);
|
|
322
430
|
expect(() => Box({ ...base, port: 8080.5 })).toThrow(/not a TCP port/);
|
|
@@ -366,4 +474,62 @@ describe("Box", () => {
|
|
|
366
474
|
const diagnostics = postSynthChecks.flatMap((c) => c.check(ctx));
|
|
367
475
|
expect(diagnostics).toEqual([]);
|
|
368
476
|
});
|
|
477
|
+
|
|
478
|
+
it("a no-repo, sandboxProvider: runner box builds a manifest whose every field exists in the pinned spec, clean under every post-synth check", () => {
|
|
479
|
+
const { environment, agent } = Box({
|
|
480
|
+
name: "blank-slate",
|
|
481
|
+
setupScript: "#!/bin/bash\nnpm install\n",
|
|
482
|
+
permissionPolicy: { default: "auto_allow" as const },
|
|
483
|
+
sandboxProvider: "runner",
|
|
484
|
+
});
|
|
485
|
+
const entities = new Map<string, Declarable>([
|
|
486
|
+
["boxAgent", agent as unknown as Declarable],
|
|
487
|
+
["boxEnv", environment as unknown as Declarable],
|
|
488
|
+
]);
|
|
489
|
+
|
|
490
|
+
const yaml = fountainSerializer.serialize(entities) as string;
|
|
491
|
+
expect([...yaml.matchAll(/^kind: (\w+)$/gm)].map((m) => m[1])).toEqual(["Environment", "Agent"]);
|
|
492
|
+
expect(yaml).not.toContain("repositories:");
|
|
493
|
+
expect(yaml).toContain("sandbox_provider: runner");
|
|
494
|
+
|
|
495
|
+
const schemas = (spec as unknown as { components: { schemas: Record<string, { properties: Record<string, unknown> }> } })
|
|
496
|
+
.components.schemas;
|
|
497
|
+
const manifestOnly = new Set(["environment", "secrets"]);
|
|
498
|
+
for (const [kind, entity] of [
|
|
499
|
+
["EnvironmentRequest", environment],
|
|
500
|
+
["AgentRequest", agent],
|
|
501
|
+
] as const) {
|
|
502
|
+
const fields = Object.keys(schemas[kind].properties);
|
|
503
|
+
for (const key of Object.keys(props(entity))) {
|
|
504
|
+
if (key === "name" || manifestOnly.has(key)) continue;
|
|
505
|
+
expect(fields, `${kind} has no field ${key}`).toContain(key);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const ctx = { outputs: new Map(), entities, buildResult: { warnings: [], errors: [] } } as unknown as PostSynthContext;
|
|
510
|
+
const diagnostics = postSynthChecks.flatMap((c) => c.check(ctx));
|
|
511
|
+
expect(diagnostics).toEqual([]);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it("shows the agent's sandbox provider and allowed vaults on the graph node (#2759)", () => {
|
|
515
|
+
const { agent, environment } = Box({
|
|
516
|
+
...base,
|
|
517
|
+
sandboxProvider: "runner",
|
|
518
|
+
allowedVaults: "any",
|
|
519
|
+
});
|
|
520
|
+
const entities = new Map<string, Declarable>([
|
|
521
|
+
["boxAgent", agent as unknown as Declarable],
|
|
522
|
+
["boxEnv", environment as unknown as Declarable],
|
|
523
|
+
]);
|
|
524
|
+
resolveAttrRefs(entities);
|
|
525
|
+
|
|
526
|
+
const ir = buildGraphIr(entities);
|
|
527
|
+
const agentNode = ir.nodes.find((n) => n.id === "boxAgent")!;
|
|
528
|
+
expect(agentNode.attrs.sandbox_provider).toBe("runner");
|
|
529
|
+
// "any" leaves the manifest's allowed_vault_ids unset, so it's the
|
|
530
|
+
// metadata marker a graph reader checks, not an absent key it has to
|
|
531
|
+
// interpret.
|
|
532
|
+
expect(agentNode.attrs.allowed_vault_ids).toBeUndefined();
|
|
533
|
+
expect((agentNode.attrs.metadata as Record<string, unknown>)[BOX_ALLOWED_VAULTS_METADATA_KEY]).toBe("any");
|
|
534
|
+
});
|
|
369
535
|
});
|
|
@@ -44,8 +44,44 @@
|
|
|
44
44
|
* the server does not keep. And a webhook url FTN022 would reject is refused
|
|
45
45
|
* here rather than at synth, because a composite that constructed it would be
|
|
46
46
|
* handing the author a lint error about a resource they never typed.
|
|
47
|
+
*
|
|
48
|
+
* ## One steward whatever the preset (#2731)
|
|
49
|
+
*
|
|
50
|
+
* The composite also returns `declaration`, the steward as core reads it
|
|
51
|
+
* (`declareSteward` in `@intentius/chant/op`): its name, its Ops and its
|
|
52
|
+
* `form`. A box whose preset has no Fountain in some environment (a docker
|
|
53
|
+
* compose box, a Sprites box) sets `form` so that environment is `local`, and
|
|
54
|
+
* there `chant operator --steward <name>` runs the same Ops on the same crons,
|
|
55
|
+
* one at a time, each under its operator lease and the whole process under the
|
|
56
|
+
* steward's own. `chant operator --steward` refuses an environment whose form
|
|
57
|
+
* is `fountain`, because this Agent and Teammate are the steward there. Export
|
|
58
|
+
* `declaration` from an `*.op.ts` file so the operator and `chant workspace
|
|
59
|
+
* status` find it.
|
|
60
|
+
*
|
|
61
|
+
* ## Credentials (#2726)
|
|
62
|
+
*
|
|
63
|
+
* A box's steward holds no credential. Name the box capabilities its Ops use
|
|
64
|
+
* in `capabilities` (the names the member's `box` block in the workspace
|
|
65
|
+
* declaration gives them) and leave `vault` out: the broker holds the key and
|
|
66
|
+
* enforces the scope. `vault` together with `capabilities` is refused.
|
|
67
|
+
* `chant workspace check` fails a box member whose files hold a literal secret
|
|
68
|
+
* (`box-credential-declared`), and `chant workspace status --json` lists each
|
|
69
|
+
* capability the steward names with the broker the box block gives it.
|
|
70
|
+
*
|
|
71
|
+
* ## The coding agent beside it
|
|
72
|
+
*
|
|
73
|
+
* A box also has a coding agent editing the app. They share the checkout by
|
|
74
|
+
* writing different things. The steward's writes are chant writes: run,
|
|
75
|
+
* converge and gate records on the `chant/lifecycle` branch and lease refs
|
|
76
|
+
* under `refs/chant/lease/`, made with git plumbing that never reads or writes
|
|
77
|
+
* the index or the working tree. The app's files, the index and commits on the
|
|
78
|
+
* working branch are the coding agent's. An Op a steward runs does not edit the
|
|
79
|
+
* app. A change to the checkout that an Op has to make (applying a build, a
|
|
80
|
+
* workspace upgrade) belongs under the work item's lease (ws-055), on a branch
|
|
81
|
+
* of its own, never in the coding agent's working tree.
|
|
47
82
|
*/
|
|
48
83
|
|
|
84
|
+
import { declareSteward, type StewardDeclaration, type StewardFormSpec } from "@intentius/chant/op";
|
|
49
85
|
import type { OpConfig, OpResource } from "@intentius/chant/op";
|
|
50
86
|
import { Agent, Environment, Schedule, Teammate, Vault, Webhook } from "../generated/index";
|
|
51
87
|
import { isPrivateHost } from "../lint/post-synth/ftn022-webhook-url-public-https";
|
|
@@ -84,8 +120,18 @@ export interface StewardOpts {
|
|
|
84
120
|
name: string;
|
|
85
121
|
/** The environment its computer is provisioned from: repo, chant, tooling. */
|
|
86
122
|
environment: InstanceType<typeof Environment>;
|
|
87
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* Secrets layered on top, for a steward that isn't behind a broker. Omit it
|
|
125
|
+
* under a broker, which holds them, and name `capabilities` instead.
|
|
126
|
+
*/
|
|
88
127
|
vault?: InstanceType<typeof Vault>;
|
|
128
|
+
/**
|
|
129
|
+
* The box capabilities this steward's Ops reach through the box's broker
|
|
130
|
+
* (#2726), by the names the member's `box` block declares, such as
|
|
131
|
+
* `fountain` or `inference`. Refused together with `vault`: a brokered
|
|
132
|
+
* steward holds no credential.
|
|
133
|
+
*/
|
|
134
|
+
capabilities?: string[];
|
|
89
135
|
/**
|
|
90
136
|
* The ops this steward runs. An op with a `schedule` gets a `Schedule`; one
|
|
91
137
|
* without is still listed, so `chant run <op> --on fountain` knows which
|
|
@@ -95,6 +141,16 @@ export interface StewardOpts {
|
|
|
95
141
|
webhook?: StewardWebhookOpts;
|
|
96
142
|
/** Extra metadata merged over the `managed-by` marker on the Agent. */
|
|
97
143
|
metadata?: Record<string, unknown>;
|
|
144
|
+
/**
|
|
145
|
+
* Where this steward runs (#2731). Default `fountain`: this composite's
|
|
146
|
+
* Agent and Teammate are the steward. A box whose preset has no Fountain in
|
|
147
|
+
* some environment names it here, e.g. `{ default: "fountain", environments:
|
|
148
|
+
* { minimal: "local" } }`, and `chant operator --steward <name> --env minimal`
|
|
149
|
+
* runs the same Ops there. The Fountain resources are declared either way; a
|
|
150
|
+
* form that is `local` everywhere is refused, since it needs none of them
|
|
151
|
+
* (use core's `declareSteward` for that).
|
|
152
|
+
*/
|
|
153
|
+
form?: StewardFormSpec;
|
|
98
154
|
}
|
|
99
155
|
|
|
100
156
|
export interface StewardResources {
|
|
@@ -103,6 +159,12 @@ export interface StewardResources {
|
|
|
103
159
|
/** One per op that carries a `schedule`, in `ops` order. */
|
|
104
160
|
schedules: InstanceType<typeof Schedule>[];
|
|
105
161
|
webhook?: InstanceType<typeof Webhook>;
|
|
162
|
+
/**
|
|
163
|
+
* The steward as core reads it (#2731): name, Ops and form. Export it from
|
|
164
|
+
* an `*.op.ts` file so `chant operator --steward` and `chant workspace
|
|
165
|
+
* status` find it.
|
|
166
|
+
*/
|
|
167
|
+
declaration: StewardDeclaration;
|
|
106
168
|
}
|
|
107
169
|
|
|
108
170
|
/** The command an `acp` agent speaks the protocol over (#2125). */
|
|
@@ -195,6 +257,23 @@ export function Steward(opts: StewardOpts): StewardResources {
|
|
|
195
257
|
|
|
196
258
|
if (opts.webhook) checkWebhookUrl(opts.name, opts.webhook.url);
|
|
197
259
|
|
|
260
|
+
// The declaration core reads. Built here, before any resource, so a bad
|
|
261
|
+
// name, a duplicate op or an unknown form is refused with nothing declared.
|
|
262
|
+
const declaration = declareSteward({
|
|
263
|
+
name: opts.name,
|
|
264
|
+
ops,
|
|
265
|
+
form: opts.form ?? "fountain",
|
|
266
|
+
...(opts.capabilities ? { capabilities: opts.capabilities } : {}),
|
|
267
|
+
...(opts.vault ? { vault: vaultName } : {}),
|
|
268
|
+
});
|
|
269
|
+
const envForms = [declaration.form.default, ...Object.values(declaration.form.environments)];
|
|
270
|
+
if (!envForms.includes("fountain")) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
`Steward "${opts.name}": form is "local" in every environment, so none of the Fountain resources this ` +
|
|
273
|
+
`composite declares would ever run. Declare it with declareSteward from @intentius/chant/op instead.`,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
198
277
|
const agent = new Agent({
|
|
199
278
|
name: opts.name,
|
|
200
279
|
runtime: "acp",
|
|
@@ -247,5 +326,5 @@ export function Steward(opts: StewardOpts): StewardResources {
|
|
|
247
326
|
bindings.set(binding, opts.name);
|
|
248
327
|
for (const op of ops) opStewards.set(op.name, opts.name);
|
|
249
328
|
|
|
250
|
-
return { agent, teammate, schedules, ...(webhook ? { webhook } : {}) };
|
|
329
|
+
return { agent, teammate, schedules, ...(webhook ? { webhook } : {}), declaration };
|
|
251
330
|
}
|
package/src/index.ts
CHANGED
|
@@ -41,8 +41,15 @@ export { ConciergeStack } from "./composites/concierge-stack";
|
|
|
41
41
|
export type { ConciergeStackOpts, ConciergeStackResources } from "./composites/concierge-stack";
|
|
42
42
|
export { Steward, stewardForOp, STEWARD_RUNTIME_COMMAND } from "./composites/steward";
|
|
43
43
|
export type { StewardOp, StewardOpts, StewardResources, StewardWebhookOpts } from "./composites/steward";
|
|
44
|
-
export {
|
|
45
|
-
|
|
44
|
+
export {
|
|
45
|
+
Box,
|
|
46
|
+
BOX_PORT_METADATA_KEY,
|
|
47
|
+
BOX_ALLOWED_VAULTS_METADATA_KEY,
|
|
48
|
+
BOX_DEFAULT_PORT,
|
|
49
|
+
BOX_DEFAULT_MOUNT_PATH,
|
|
50
|
+
BOX_SANDBOX_PROVIDERS,
|
|
51
|
+
} from "./composites/box";
|
|
52
|
+
export type { BoxOpts, BoxResources, BoxRepositoryOpts, BoxVaultOpts, BoxPermissionPolicy, BoxSandboxProvider } from "./composites/box";
|
|
46
53
|
|
|
47
54
|
// `chant acp` (#2125) — the ACP server, mounted through the plugin's command
|
|
48
55
|
// group. Exported so an embedder can serve it over its own transport.
|