@tealbrick/kit 0.2.6 → 0.3.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BOOTSTRAP.md +24 -13
- package/NATIVE.md +77 -0
- package/README.md +4 -4
- package/dist/cli.js +24 -0
- package/dist/index.d.ts +24 -1
- package/dist/index.js +31 -3
- package/dist/native-child-guard.d.ts +1 -0
- package/dist/native-child-guard.js +17 -0
- package/dist/native-selection.d.ts +27 -0
- package/dist/native-selection.js +72 -0
- package/dist/native-setup.d.ts +63 -0
- package/dist/native-setup.js +99 -0
- package/dist/native.d.ts +39 -0
- package/dist/native.js +104 -0
- package/dist/onboarding.d.ts +1 -0
- package/dist/onboarding.js +1 -1
- package/dist/runtime-onboarding.js +1 -1
- package/package.json +17 -10
package/BOOTSTRAP.md
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
>
|
|
1
|
+
> Prerelease 0.3.0-rc.1 targets fresh Eve 0.70 installations. Use the explicit candidate version or `next` tag after publication. Stable `latest` remains 0.2.7 for existing Eve 0.66 installations; durable cross-version session migration is not validated.
|
|
2
2
|
|
|
3
3
|
# Deployment guide: Eve agent + Teal Brick kit
|
|
4
4
|
|
|
5
5
|
This guide installs the seven implemented packages through one kit and activates
|
|
6
|
-
only the capabilities you select. The supported harness line is **Eve 0.
|
|
7
|
-
**0.
|
|
6
|
+
only the capabilities you select. The supported harness line is **Eve 0.70.x**;
|
|
7
|
+
**0.70.0** and **AI SDK 7.0.127** are the current tested versions. Use Node **24**.
|
|
8
8
|
|
|
9
|
-
**Release status:**
|
|
10
|
-
complete suite. Use the npm installation in step 3. Do not substitute an unrelated unscoped `tealbrick` package.
|
|
9
|
+
**Release status:** this prerelease is prepared for the `next` tag. Until publication, use the reviewed local tarballs. The versioned npm commands apply after publication. Native onboarding additionally requires the matching Portal runtime setup endpoints.
|
|
11
10
|
|
|
12
11
|
## What installs what
|
|
13
12
|
|
|
@@ -19,12 +18,12 @@ Eve runs the agent and needs its own working primary-model authentication.
|
|
|
19
18
|
For a fresh machine, the sequence is:
|
|
20
19
|
|
|
21
20
|
1. Install Node 24 and npm.
|
|
22
|
-
2. Create an Eve 0.
|
|
23
|
-
3. Install `@tealbrick/kit@0.
|
|
21
|
+
2. Create an Eve 0.70.0 project and configure its primary model credentials.
|
|
22
|
+
3. Install `@tealbrick/kit@0.3.0-rc.1` inside that project.
|
|
24
23
|
4. Run `tealbrick setup` to sign in, select capabilities and register the card.
|
|
25
24
|
5. Build and start Eve, then check a real chat from desktop.
|
|
26
25
|
|
|
27
|
-
For an **existing Eve 0.
|
|
26
|
+
For an **existing Eve 0.70.0 project**, start at step 3 from its project directory.
|
|
28
27
|
Do not run `eve init` over an existing agent. Preserve its model configuration and
|
|
29
28
|
review any existing Eve channel before setup, as explained under Portal below.
|
|
30
29
|
|
|
@@ -40,14 +39,14 @@ npm --version
|
|
|
40
39
|
|
|
41
40
|
The first command should report `v24.x`. Run the remaining commands as the account
|
|
42
41
|
that will own the new agent project. For our deployment this account runs on
|
|
43
|
-
|
|
42
|
+
an AVMM guest on Neuu after the September23 migration. This package guide does not provision a VM or install AVMM.
|
|
44
43
|
|
|
45
44
|
## 2. Create a fresh Eve project
|
|
46
45
|
|
|
47
46
|
Choose an unused project directory, outside any existing live agent:
|
|
48
47
|
|
|
49
48
|
```bash
|
|
50
|
-
npx --yes eve@0.
|
|
49
|
+
npx --yes eve@0.70.0 init tealbrick-agent --model openai/gpt-5.6-terra
|
|
51
50
|
```
|
|
52
51
|
|
|
53
52
|
Finish scaffolding and return to the terminal before continuing. If Eve starts
|
|
@@ -55,12 +54,12 @@ Finish scaffolding and return to the terminal before continuing. If Eve starts
|
|
|
55
54
|
|
|
56
55
|
```bash
|
|
57
56
|
cd tealbrick-agent
|
|
58
|
-
npm install --save-exact eve@0.
|
|
57
|
+
npm install --save-exact eve@0.70.0 ai@7.0.127
|
|
59
58
|
```
|
|
60
59
|
|
|
61
60
|
Keep `package-lock.json` under version control.
|
|
62
61
|
The exact Eve pin is intentional: the compatibility workflow validates patches
|
|
63
|
-
before advancing it
|
|
62
|
+
before advancing it. No production pin changes automatically.
|
|
64
63
|
|
|
65
64
|
The example model uses the Vercel AI Gateway. To supply its key for this terminal
|
|
66
65
|
without putting the key in shell history, in Bash run:
|
|
@@ -82,7 +81,7 @@ Run this inside the Eve project created in step 2 (or your existing compatible
|
|
|
82
81
|
project), not in an empty directory or as a global install:
|
|
83
82
|
|
|
84
83
|
```bash
|
|
85
|
-
npm install --save-exact @tealbrick/kit@0.
|
|
84
|
+
npm install --save-exact @tealbrick/kit@0.3.0-rc.1
|
|
86
85
|
```
|
|
87
86
|
|
|
88
87
|
This installs Portal, AVM, Voice, Vision, Deliver and the shared provider transport
|
|
@@ -245,3 +244,15 @@ Restart the running Eve process after the rebuild. Deselecting a capability remo
|
|
|
245
244
|
its kit-owned mounts. Setup refuses to overwrite hand-edited mounts. Saved settings
|
|
246
245
|
and optional local secrets live under ignored `.tealbrick/`; secrets are not embedded
|
|
247
246
|
in generated source. Disabling a capability does not revoke or delete its credentials.
|
|
247
|
+
|
|
248
|
+
## Eve 0.66 deployment additions (suite 0.2.7)
|
|
249
|
+
|
|
250
|
+
`tealbrick setup` now includes Persistent routines and Production self-evolution.
|
|
251
|
+
Routines require an enrolled host bridge, scope and runtime token; setup mounts
|
|
252
|
+
CRUD, production dispatch and desktop delivery together. Self-evolution requires
|
|
253
|
+
an existing `deployment/*.ts` native Eve policy module outside the writable agent
|
|
254
|
+
tree. Native proposals require the deployment's existing governed activation path.
|
|
255
|
+
|
|
256
|
+
AVMM/guests migrated to Neuu on September23. Verify current canonical migration
|
|
257
|
+
records before targeting either host. Do not create new canary agents. Committed
|
|
258
|
+
source, npm publication and installed deployment are separate states.
|
package/NATIVE.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Native agent setup — Stage A candidate
|
|
2
|
+
|
|
3
|
+
Prerelease suite **0.3.0-rc.1** targets the `next` tag. Stable npm latest remains 0.2.7, which lacks this flow. The `tealbrick` facade forwards the existing kit CLI; it is not another SDK. Matching Portal runtime setup endpoints must be deployed before native onboarding can succeed.
|
|
4
|
+
|
|
5
|
+
## Install and connect
|
|
6
|
+
|
|
7
|
+
Install the review tarballs with the supplied checksum-verifying `install-candidate.mjs`. Once the release is actually published, the intended installation is `npm install --save-exact tealbrick@0.3.0-rc.1`. Today that registry command is not a tested installation path.
|
|
8
|
+
|
|
9
|
+
From your native agent workspace:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npx --no-install tealbrick setup
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Choose Claude Agent SDK, Codex TypeScript SDK, LangChain or the existing Eve setup. Native setup then:
|
|
16
|
+
|
|
17
|
+
1. Opens the existing Portal device-consent flow for your account.
|
|
18
|
+
2. Lets you choose or create an owned workspace.
|
|
19
|
+
3. Lets you create an accurately labelled agent or select an owned existing one.
|
|
20
|
+
4. Creates/reuses its stable card through Portal, then enrolls its runtime connection.
|
|
21
|
+
5. Saves ordinary SDK configuration at `.tealbrick/native-sdk.json`.
|
|
22
|
+
|
|
23
|
+
No pre-created card, endpoint, Fleet licence, or UUID lookup is required. Names that match multiple identities require an explicit selection. Creation does not grant apps. Eve identities cannot be silently converted to native identities.
|
|
24
|
+
|
|
25
|
+
Named selections also work:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npx --no-install tealbrick setup --harness claude --create-workspace Community --create-agent Researcher
|
|
29
|
+
npx --no-install tealbrick setup --harness langchain --workspace-name Community --agent-name Researcher
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The second example requires an existing **LangChain** identity. `--workspace ID --agent ID [--node ID]` remain advanced selections. Setup requires the Portal candidate's authenticated `GET/POST /api/runtime/setup`; a public gateway must expose that reviewed route. It then uses the unchanged `/api/runtime/connections` credential exchange.
|
|
33
|
+
|
|
34
|
+
Repeat setup in the same workspace to reconnect. `.tealbrick/native-setup.json` retains the account-bound identity intent and idempotency key, without credentials. A lost response or enrollment failure reuses that key on retry. The local setup lock prevents concurrent installers. If the installer is killed, verify that the PID in `.tealbrick/native-setup.lock` is no longer running before removing **only that lock** and retrying; retain the journal. A different account/harness or an attempt to replace an already-bound identity fails closed.
|
|
35
|
+
|
|
36
|
+
## Native SDK configuration
|
|
37
|
+
|
|
38
|
+
Load `.tealbrick/native-sdk.json` into the SDK's ordinary options. No tokens are present; the installed CLI path and workspace root are absolute. Keep the package at that path. Use native SDK session persistence and continuation.
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
const config = JSON.parse(await readFile('.tealbrick/native-sdk.json', 'utf8'));
|
|
42
|
+
// Claude Agent SDK 0.3.287: preserve the generated settings hook when merging.
|
|
43
|
+
const stream = query({prompt, options: {...config, model: selectedModel,
|
|
44
|
+
...(savedSessionId ? {resume: savedSessionId} : {})}});
|
|
45
|
+
// Codex TypeScript SDK 0.160.0 (not OpenAI Agents SDK)
|
|
46
|
+
const codex = new Codex(config);
|
|
47
|
+
const thread = savedThreadId ? codex.resumeThread(savedThreadId, options) : codex.startThread(options);
|
|
48
|
+
// LangChain 1.5.15 and MCP adapters 2.0.0
|
|
49
|
+
const adapter = new MCPAdapter(config);
|
|
50
|
+
const agent = createAgent({model: configuredModel, tools: await adapter.listTools(), checkpointer});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Configure provider authentication on the native host through its supported SDK. Kit does not copy login caches, choose providers or change billing. No Eve daemon, AVMM or inbound bridge is required. Native mode emits no Eve mounts and uses the existing kit state and owner-only credential file.
|
|
54
|
+
|
|
55
|
+
## Children and host authority
|
|
56
|
+
|
|
57
|
+
- **Claude:** generated `settings.hooks.PreToolUse` executes the packaged guard for `mcp__tealbrick__*`. It denies requests with SDK-authored `agent_id`; parent calls receive no permission override. It preserves the host's `Agent` tool, independently scoped agents and non-Tealbrick tools. Merge this hook with existing settings instead of dropping it. Unit/process tests verify the hook; live delegated execution is still unverified.
|
|
58
|
+
- **Codex:** no verified per-child credential boundary exists in this attachment. Setup explicitly asks before generating a profile with `multi_agent` and `multi_agent_v2` disabled. For scripted selection use `--delegation disabled`; the same flag is required for `native config`. It never edits the host's existing Codex config. Do not merge this profile into a delegation-enabled agent and claim isolation.
|
|
59
|
+
- **LangChain:** `createAgent` receives only the two Tealbrick tools; no child tool is introduced. Do not pass this adapter into another agent. Child grants are unsupported.
|
|
60
|
+
|
|
61
|
+
These controls do not isolate processes sharing an OS user. A child with unrestricted filesystem/shell access can read parent files. Keep the credential directory outside child filesystem authority; independently scoped child runtimes must receive their own grants, not the parent root or credential. No full delegated-access acceptance is claimed.
|
|
62
|
+
|
|
63
|
+
## Knowledge and revocation
|
|
64
|
+
|
|
65
|
+
In the selected workspace, licence/configure Knowledge, verify its instance using the existing `tealbrick app claim` flow, and save explicit CRUD tethers. The instance admin approves the claim; runtime uses a separate scoped service-principal token. Nothing automatically grants all products.
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
npx --no-install tealbrick app claim --workspace WORKSPACE_ID --node KNOWLEDGE_NODE_ID --binding community --endpoint https://YOUR_KNOWLEDGE_ORIGIN --company YOUR_PARTITION --issuer https://portal.tealbrick.com
|
|
69
|
+
npx --no-install tealbrick native disable
|
|
70
|
+
npx --no-install tealbrick native enable
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Each request reloads local activation and fetches signed Portal authorization. Revoke or unavailable authorization blocks the next app request. In-flight side effects are not undone; cancellation reaches the app request but does not prove a write rolled back. No automatic write retry is added. Re-enrollment rotates the runtime credential and keeps the logical agent. Portal logout does not revoke that connection.
|
|
74
|
+
|
|
75
|
+
## Acceptance boundary
|
|
76
|
+
|
|
77
|
+
Disposable installed-artifact tests cover empty-account creation, named selection, permission/ambiguity denial, lost-response retry, signed config, scoped Knowledge-fixture read, revoke/reconnect, deactivation and MCP cancellation. They are not real browser consent or real Knowledge product proof. Root's separate Foxy receipt proves the earlier packed candidate's live Claude MCP inactive denial, exact-session process restart and compaction. Codex and LangChain have independent narrower fixture evidence, not live-provider parity. Marketplace, voice/vision and delegated Tealbrick grants are outside this Stage A claim.
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
>
|
|
1
|
+
> Prerelease 0.3.0-rc.1 targets fresh Eve 0.70 installations. Use the explicit candidate version or `next` tag after publication. Stable `latest` remains 0.2.7 for existing Eve 0.66 installations; durable cross-version session migration is not validated.
|
|
2
2
|
|
|
3
3
|
# @tealbrick/kit
|
|
4
4
|
|
|
@@ -6,16 +6,16 @@ One installation, explicitly selected capabilities. The kit includes the current
|
|
|
6
6
|
implemented Portal, Voice, Vision, Deliver and AVM packages. Dependencies are code
|
|
7
7
|
availability, not agent permissions. Nothing is mounted by installing the kit.
|
|
8
8
|
|
|
9
|
-
Install inside an existing Eve 0.
|
|
9
|
+
Install inside an existing Eve 0.70.0 project:
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
npm install --save-exact @tealbrick/kit@0.
|
|
12
|
+
npm install --save-exact @tealbrick/kit@0.3.0-rc.1
|
|
13
13
|
npx --no-install tealbrick setup
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
The suite is published on npm. The kit does not install, create or start Eve.
|
|
17
17
|
Create the Eve project first and configure its primary-model authentication.
|
|
18
|
-
Eve 0.
|
|
18
|
+
Eve 0.70.0 and Node 24 are the validated host versions; VM provisioning remains
|
|
19
19
|
host setup. After Teal Brick setup, build and start Eve to make the agent usable.
|
|
20
20
|
|
|
21
21
|
See [BOOTSTRAP.md](BOOTSTRAP.md) for the complete deployment guide, including fresh-machine and existing-agent paths, provider prompts and verification.
|
package/dist/cli.js
CHANGED
|
@@ -4,9 +4,22 @@ import { readFile } from 'node:fs/promises';
|
|
|
4
4
|
import { loginPortal, registerPortalCard, portalOrigin, agentOrigin } from './onboarding.js';
|
|
5
5
|
import { collectSetup } from '@tealbrick/deliver/setup';
|
|
6
6
|
import { applyKit, planKit, readKit } from './index.js';
|
|
7
|
+
import { nativeCommand } from './native.js';
|
|
7
8
|
import { runtimeOnboarding } from './runtime-onboarding.js';
|
|
8
9
|
async function main() {
|
|
9
10
|
const command = process.argv[2], root = process.cwd();
|
|
11
|
+
if (command === 'setup') {
|
|
12
|
+
const args = process.argv.slice(3), index = args.indexOf('--harness');
|
|
13
|
+
const harness = index >= 0 ? args[index + 1] : await select({ message: 'Agent harness', choices: [{ name: 'Claude Agent SDK', value: 'claude' }, { name: 'Codex TypeScript SDK', value: 'codex' }, { name: 'LangChain', value: 'langchain' }, { name: 'Eve', value: 'eve' }] });
|
|
14
|
+
if (harness !== 'eve') {
|
|
15
|
+
await nativeCommand(['setup', ...args, ...(index < 0 ? ['--harness', harness] : [])], root);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (command === 'native') {
|
|
20
|
+
await nativeCommand(process.argv.slice(3), root);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
10
23
|
if (command === 'connect') {
|
|
11
24
|
await runtimeOnboarding('connect', process.argv.slice(3), root);
|
|
12
25
|
return;
|
|
@@ -37,6 +50,8 @@ async function main() {
|
|
|
37
50
|
{ name: 'Voice: live-session / STT / TTS endpoints', value: 'voice', checked: !!old.voice },
|
|
38
51
|
{ name: 'Vision API (native tool requires a trusted turn-auth adapter)', value: 'vision', checked: !!old.vision },
|
|
39
52
|
{ name: 'Deliver files to chat', value: 'deliver', checked: old.deliver !== undefined },
|
|
53
|
+
{ name: 'Production self-evolution (requires deployment policy)', value: 'selfEvolution', checked: !!old.selfEvolution },
|
|
54
|
+
{ name: 'Persistent routines, desktop management and AVMM wake', value: 'routines', checked: !!old.routines },
|
|
40
55
|
{ name: 'AVM inspection over SSH', value: 'avm', checked: !!old.avm },
|
|
41
56
|
] });
|
|
42
57
|
const config = { version: 1 }, credentials = {};
|
|
@@ -95,6 +110,15 @@ async function main() {
|
|
|
95
110
|
delete config.avm.identityTokenFile;
|
|
96
111
|
}
|
|
97
112
|
}
|
|
113
|
+
if (selected.includes('selfEvolution'))
|
|
114
|
+
config.selfEvolution = { policyModule: await input({ message: 'Deployment policy module outside agent/ (deployment/name.ts)', default: old.selfEvolution?.policyModule }) };
|
|
115
|
+
if (selected.includes('routines')) {
|
|
116
|
+
const credentialRef = 'TEALBRICK_ROUTINE_BRIDGE_TOKEN';
|
|
117
|
+
const token = await password({ message: 'Host routine bridge token (blank keeps existing runtime secret)', mask: '*' });
|
|
118
|
+
if (token)
|
|
119
|
+
credentials[credentialRef] = token;
|
|
120
|
+
config.routines = { url: await input({ message: 'Enrolled host routine bridge URL including /routines', default: old.routines?.url }), credentialRef, scope: { org: login.org, workspace: workspaceId, agent: name, owner: await input({ message: 'Portal managing user ID (must match the host enrollment)', default: old.routines?.scope.owner }) }, buildCommand: JSON.parse(await input({ message: 'Build command JSON argv', default: JSON.stringify(old.routines?.buildCommand ?? ['npm', 'run', 'build']) })), restartCommand: JSON.parse(await input({ message: 'Operator-configured restart command JSON argv', default: old.routines ? JSON.stringify(old.routines.restartCommand) : undefined })) };
|
|
121
|
+
}
|
|
98
122
|
console.log(JSON.stringify({ enabled: planKit(config).enabled, mounts: Object.keys(planKit(config).files) }, null, 2));
|
|
99
123
|
if (!await confirm({ message: 'Apply settings and register this agent in the selected Portal workspace? Rebuild/restart Eve afterward.', default: false }))
|
|
100
124
|
return;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
declare const schema: z.ZodObject<{
|
|
3
3
|
version: z.ZodLiteral<1>;
|
|
4
|
+
native: z.ZodOptional<z.ZodObject<{
|
|
5
|
+
harness: z.ZodEnum<{
|
|
6
|
+
claude: "claude";
|
|
7
|
+
codex: "codex";
|
|
8
|
+
langchain: "langchain";
|
|
9
|
+
}>;
|
|
10
|
+
active: z.ZodBoolean;
|
|
11
|
+
}, z.core.$strict>>;
|
|
12
|
+
selfEvolution: z.ZodOptional<z.ZodObject<{
|
|
13
|
+
policyModule: z.ZodString;
|
|
14
|
+
}, z.core.$strict>>;
|
|
15
|
+
routines: z.ZodOptional<z.ZodObject<{
|
|
16
|
+
url: z.ZodString;
|
|
17
|
+
credentialRef: z.ZodString;
|
|
18
|
+
scope: z.ZodObject<{
|
|
19
|
+
org: z.ZodString;
|
|
20
|
+
workspace: z.ZodString;
|
|
21
|
+
agent: z.ZodString;
|
|
22
|
+
owner: z.ZodString;
|
|
23
|
+
}, z.core.$strict>;
|
|
24
|
+
buildCommand: z.ZodTuple<[z.ZodString], z.ZodString>;
|
|
25
|
+
restartCommand: z.ZodTuple<[z.ZodString], z.ZodString>;
|
|
26
|
+
}, z.core.$strict>>;
|
|
4
27
|
portal: z.ZodOptional<z.ZodObject<{
|
|
5
28
|
issuer: z.ZodString;
|
|
6
29
|
org: z.ZodString;
|
|
@@ -922,7 +945,7 @@ declare const schema: z.ZodObject<{
|
|
|
922
945
|
}>>>;
|
|
923
946
|
}, z.core.$strict>;
|
|
924
947
|
export type KitConfig = z.input<typeof schema>;
|
|
925
|
-
export declare const capabilities: readonly ["portal", "runtime", "voice", "vision", "deliver", "avm"];
|
|
948
|
+
export declare const capabilities: readonly ["portal", "runtime", "voice", "vision", "deliver", "avm", "routines", "selfEvolution"];
|
|
926
949
|
export interface MountPlan {
|
|
927
950
|
enabled: string[];
|
|
928
951
|
files: Record<string, string>;
|
package/dist/index.js
CHANGED
|
@@ -10,19 +10,23 @@ const auth = z.object({ issuer: z.string().url().refine(v => new URL(v).protocol
|
|
|
10
10
|
const media = z.object({ settings: z.unknown(), authorizationModule: z.string().optional() }).strict();
|
|
11
11
|
const ref = z.string().regex(/^TEALBRICK_[A-Z0-9_]+$/);
|
|
12
12
|
const runtime = z.object({ issuer: z.string().url(), org: z.string().min(1), workspaceId: z.string().min(1), agentId: z.string().min(1), connectionId: z.string().min(1), credentialRef: ref, bindings: z.record(z.string(), z.object({ endpoint: z.string().url(), instanceId: z.string().min(1), companyId: z.string().min(1), credentialRef: ref }).strict()) }).strict();
|
|
13
|
-
const
|
|
14
|
-
|
|
13
|
+
const command = z.tuple([z.string().min(1)]).rest(z.string());
|
|
14
|
+
const routines = z.object({ url: z.string().url(), credentialRef: ref, scope: z.object({ org: z.string().min(1), workspace: z.string().min(1), agent: z.string().min(1), owner: z.string().min(1) }).strict(), buildCommand: command, restartCommand: command }).strict();
|
|
15
|
+
const schema = z.object({ version: z.literal(1), native: z.object({ harness: z.enum(['claude', 'codex', 'langchain']), active: z.boolean() }).strict().optional(), selfEvolution: z.object({ policyModule: z.string().regex(/^deployment\/[a-zA-Z0-9_-]+\.(ts|js|mjs)$/) }).strict().optional(), routines: routines.optional(), portal: auth.optional(), runtime: runtime.optional(), voice: media.optional(), vision: media.optional(), deliver: z.unknown().optional(), avm: z.custom(v => avmConfigSchema.safeParse(v).success).transform(v => 'target' in v ? { target: v.target, sudo: v.sudo ?? false, ...Object.fromEntries(Object.entries(v).filter(([k]) => k !== 'target' && k !== 'sudo')) } : v).optional() }).strict();
|
|
16
|
+
export const capabilities = ['portal', 'runtime', 'voice', 'vision', 'deliver', 'avm', 'routines', 'selfEvolution'];
|
|
15
17
|
const marker = '// Managed by @tealbrick/kit. Change selection with tealbrick setup.\n';
|
|
16
18
|
const render = (body) => marker + body + '\n';
|
|
17
19
|
const hash = (data) => createHash('sha256').update(data).digest('hex');
|
|
18
20
|
/** Pure mount plan: dependencies alone never contribute capabilities to Eve. */
|
|
19
21
|
export function planKit(value) {
|
|
20
22
|
const c = schema.parse(value), files = {};
|
|
23
|
+
if (c.native && (c.portal || c.voice || c.vision || c.deliver !== undefined || c.avm || c.routines || c.selfEvolution))
|
|
24
|
+
throw Error('kit_native_eve_configuration_conflict');
|
|
21
25
|
if ((c.voice || c.vision) && !c.portal)
|
|
22
26
|
throw Error('kit_media_requires_portal_binding');
|
|
23
27
|
if (c.portal)
|
|
24
28
|
files['agent/channels/eve.ts'] = render(`import {eveChannel} from 'eve/channels/eve';\nimport {portalAuth} from '@tealbrick/portal/eve';\nexport default eveChannel({auth:[portalAuth(${JSON.stringify(c.portal)})]});`);
|
|
25
|
-
if (c.runtime) {
|
|
29
|
+
if (c.runtime && !c.native) {
|
|
26
30
|
files['agent/tools/tealbrick_capabilities.ts'] = render(`import {capabilitiesTool} from '@tealbrick/kit/runtime-eve';\nexport default capabilitiesTool(${JSON.stringify(c.runtime)});`);
|
|
27
31
|
files['agent/tools/tealbrick_call.ts'] = render(`import {appCallTool} from '@tealbrick/kit/runtime-eve';\nexport default appCallTool(${JSON.stringify(c.runtime)});`);
|
|
28
32
|
}
|
|
@@ -65,6 +69,25 @@ export function planKit(value) {
|
|
|
65
69
|
}
|
|
66
70
|
if (c.avm)
|
|
67
71
|
files['agent/extensions/tealbrick-avm.ts'] = render(`import avm from '@tealbrick/avm/eve';\nexport default avm(${JSON.stringify(c.avm)});`);
|
|
72
|
+
if (c.selfEvolution) {
|
|
73
|
+
if (!c.portal)
|
|
74
|
+
throw Error('kit_evolution_requires_portal');
|
|
75
|
+
// Native deployed authoring policy lives outside the agent-authored tree.
|
|
76
|
+
const module = '../../' + c.selfEvolution.policyModule;
|
|
77
|
+
files['agent/extensions/tealbrick-self-evolution.ts'] = render(`import selfModification from 'eve/self-modification';\nimport policy from ${JSON.stringify(module)};\nexport default selfModification(policy);`);
|
|
78
|
+
}
|
|
79
|
+
if (c.routines) {
|
|
80
|
+
if (!c.portal || c.routines.scope.org !== c.portal.org || c.routines.scope.agent !== c.portal.agent)
|
|
81
|
+
throw Error('kit_routines_require_matching_portal');
|
|
82
|
+
const r = c.routines;
|
|
83
|
+
const endpoint = new URL(r.url);
|
|
84
|
+
if (!['http:', 'https:'].includes(endpoint.protocol) || endpoint.username || endpoint.password || endpoint.search || endpoint.hash)
|
|
85
|
+
throw Error('kit_invalid_routine_endpoint');
|
|
86
|
+
files['agent/lib/tealbrick-routines.ts'] = render(`import {createRoutineClient} from '@tealbrick/avm/routines-client';\nimport {runtimeSecret} from '@tealbrick/kit/secrets';\nexport const scope=${JSON.stringify(r.scope)};\nexport const bridge=createRoutineClient({url:${JSON.stringify(r.url)},token:async()=>{const token=await runtimeSecret(${JSON.stringify(r.credentialRef)});if(!token)throw Error('routine_credential_missing');return token;}});`);
|
|
87
|
+
files['agent/tools/tealbrick_routines.ts'] = render(`import {routineTool} from '@tealbrick/avm/routines-eve';\nimport {bridge,scope} from '../lib/tealbrick-routines';\nexport default routineTool({bridge,scope});`);
|
|
88
|
+
files['agent/channels/management.ts'] = render(`import {managementChannel} from '@tealbrick/portal/management/eve';\nimport {bridge,scope} from '../lib/tealbrick-routines';\nexport default managementChannel({auth:${JSON.stringify(c.portal)},appRoot:process.cwd(),routines:{owner:scope.owner,call:(action,input)=>bridge.call(action,input)},buildCommand:${JSON.stringify(r.buildCommand)},restartCommand:${JSON.stringify(r.restartCommand)}});`);
|
|
89
|
+
files['agent/schedules/tealbrick-routines.ts'] = render(`import {routineDispatcher} from '@tealbrick/avm/routines-eve';\nimport {bridge} from '../lib/tealbrick-routines';\nimport delivery from '../channels/management';\nexport default routineDispatcher({bridge,delivery});`);
|
|
90
|
+
}
|
|
68
91
|
return { enabled: capabilities.filter(k => c[k] !== undefined), files };
|
|
69
92
|
}
|
|
70
93
|
async function optional(path) { try {
|
|
@@ -128,6 +151,11 @@ export async function applyKit(root, config, credentials = {}) {
|
|
|
128
151
|
else if (content !== null)
|
|
129
152
|
throw Error(`kit_mount_conflict:${name}`);
|
|
130
153
|
}
|
|
154
|
+
if (config.selfEvolution) {
|
|
155
|
+
const path = await safe(root, config.selfEvolution.policyModule);
|
|
156
|
+
if (await optional(path) === null)
|
|
157
|
+
throw Error('kit_evolution_policy_module_missing');
|
|
158
|
+
}
|
|
131
159
|
if (config.vision?.authorizationModule) {
|
|
132
160
|
const path = await safe(root, config.vision.authorizationModule);
|
|
133
161
|
if (await optional(path) === null)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Claude SDK command hook: protect this MCP attachment without disabling unrelated experts.
|
|
2
|
+
* agent_id is SDK-authored context, never a model tool argument. This is not OS isolation. */
|
|
3
|
+
let output = { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: 'Tealbrick child authority is not configured' } };
|
|
4
|
+
try {
|
|
5
|
+
let text = '';
|
|
6
|
+
for await (const chunk of process.stdin) {
|
|
7
|
+
text += chunk;
|
|
8
|
+
if (text.length > 65536)
|
|
9
|
+
throw Error('oversized_hook');
|
|
10
|
+
}
|
|
11
|
+
const input = JSON.parse(text);
|
|
12
|
+
if (input.hook_event_name === 'PreToolUse' && typeof input.tool_name === 'string' && input.tool_name.startsWith('mcp__tealbrick__') && input.agent_id === undefined)
|
|
13
|
+
output = {};
|
|
14
|
+
}
|
|
15
|
+
catch { }
|
|
16
|
+
process.stdout.write(JSON.stringify(output) + '\n');
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PortalLogin } from './onboarding.js';
|
|
2
|
+
export interface SetupPrompts {
|
|
3
|
+
choose(message: string, choices: Array<{
|
|
4
|
+
name: string;
|
|
5
|
+
value: string;
|
|
6
|
+
}>): Promise<string>;
|
|
7
|
+
name(message: string): Promise<string>;
|
|
8
|
+
}
|
|
9
|
+
export interface NativeSelection {
|
|
10
|
+
workspace: {
|
|
11
|
+
id: string;
|
|
12
|
+
} | {
|
|
13
|
+
name: string;
|
|
14
|
+
};
|
|
15
|
+
agent: {
|
|
16
|
+
id: string;
|
|
17
|
+
} | {
|
|
18
|
+
name: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export declare const harnessLabels: {
|
|
22
|
+
readonly claude: "Claude Agent SDK";
|
|
23
|
+
readonly codex: "Codex";
|
|
24
|
+
readonly langchain: "LangChain";
|
|
25
|
+
};
|
|
26
|
+
/** Names are display/selection hints only; Portal remains the identity and ownership authority. */
|
|
27
|
+
export declare function selectNativeIdentity(root: string, flags: Record<string, string>, login: PortalLogin, prompts: SetupPrompts): Promise<NativeSelection>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readKit } from './index.js';
|
|
2
|
+
export const harnessLabels = { claude: 'Claude Agent SDK', codex: 'Codex', langchain: 'LangChain' };
|
|
3
|
+
const validName = (value) => { const name = value.trim(); if (!name || name.length > 80)
|
|
4
|
+
throw Error('kit_native_name_required'); return name; };
|
|
5
|
+
/** Names are display/selection hints only; Portal remains the identity and ownership authority. */
|
|
6
|
+
export async function selectNativeIdentity(root, flags, login, prompts) {
|
|
7
|
+
const old = await readKit(root), harness = harnessLabels[flags.harness];
|
|
8
|
+
if (!harness)
|
|
9
|
+
throw Error('kit_native_harness_required');
|
|
10
|
+
if ([flags.workspace, flags['workspace-name'], flags['create-workspace']].filter(Boolean).length > 1 || [flags.agent, flags['agent-name'], flags['create-agent']].filter(Boolean).length > 1)
|
|
11
|
+
throw Error('kit_native_conflicting_selection');
|
|
12
|
+
if (flags.node && !flags.agent)
|
|
13
|
+
throw Error('kit_native_agent_required');
|
|
14
|
+
const directory = await login.client.request('/api/runtime/setup');
|
|
15
|
+
let workspaceId = flags.workspace, workspaceName = flags['create-workspace'];
|
|
16
|
+
if (flags['workspace-name']) {
|
|
17
|
+
const matches = directory.workspaces.filter(w => w.name === flags['workspace-name']);
|
|
18
|
+
if (matches.length !== 1)
|
|
19
|
+
throw Error(matches.length ? 'kit_native_ambiguous_workspace_name' : 'kit_native_workspace_not_found');
|
|
20
|
+
workspaceId = matches[0].id;
|
|
21
|
+
}
|
|
22
|
+
if (!workspaceId && !workspaceName && old.runtime?.issuer === login.issuer && old.runtime.org === login.org)
|
|
23
|
+
workspaceId = old.runtime.workspaceId;
|
|
24
|
+
if (workspaceId && !directory.workspaces.some(w => w.id === workspaceId))
|
|
25
|
+
throw Error('kit_portal_workspace_required');
|
|
26
|
+
if (!workspaceId && !workspaceName) {
|
|
27
|
+
const choice = directory.workspaces.length ? await prompts.choose('Choose a Portal workspace', [...directory.workspaces.map(w => ({ name: `${w.name} (${w.id})`, value: w.id })), { name: 'Create a workspace', value: '__create__' }]) : '__create__';
|
|
28
|
+
if (choice === '__create__')
|
|
29
|
+
workspaceName = await prompts.name('New workspace name');
|
|
30
|
+
else {
|
|
31
|
+
if (!directory.workspaces.some(w => w.id === choice))
|
|
32
|
+
throw Error('kit_portal_workspace_required');
|
|
33
|
+
workspaceId = choice;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const workspace = workspaceName ? { name: validName(workspaceName) } : { id: workspaceId };
|
|
37
|
+
if (workspaceName && flags.node)
|
|
38
|
+
throw Error('kit_native_existing_node_requires_workspace');
|
|
39
|
+
const cards = directory.agents.filter(a => a.harness === harness).map(agent => ({ agent }));
|
|
40
|
+
let agentId = flags.agent;
|
|
41
|
+
if (flags['agent-name']) {
|
|
42
|
+
const matches = cards.filter((c) => c.agent.name === flags['agent-name']);
|
|
43
|
+
if (matches.length !== 1)
|
|
44
|
+
throw Error(matches.length ? 'kit_native_ambiguous_agent_name' : 'kit_native_agent_not_found');
|
|
45
|
+
agentId = matches[0].agent.id;
|
|
46
|
+
}
|
|
47
|
+
if (!agentId && !flags['create-agent'] && old.runtime && old.runtime.workspaceId === workspaceId && old.runtime.org === login.org && old.runtime.issuer === login.issuer)
|
|
48
|
+
agentId = old.runtime.agentId;
|
|
49
|
+
let name = flags['create-agent'];
|
|
50
|
+
if (!agentId && !name) {
|
|
51
|
+
const choice = cards.length ? await prompts.choose('Choose an agent to attach', [...cards.map((c) => ({ name: `${c.agent.name} (${c.agent.id})`, value: c.agent.id })), { name: 'Create an agent', value: '__create__' }]) : '__create__';
|
|
52
|
+
if (choice === '__create__')
|
|
53
|
+
name = await prompts.name('New agent name');
|
|
54
|
+
else {
|
|
55
|
+
const card = cards.find((c) => c.agent.id === choice);
|
|
56
|
+
if (!card)
|
|
57
|
+
throw Error('kit_owned_agent_card_required');
|
|
58
|
+
agentId = card.agent.id;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (name)
|
|
62
|
+
return { workspace, agent: { name: validName(name) } };
|
|
63
|
+
const matches = cards.filter((c) => c.agent.id === agentId);
|
|
64
|
+
if (matches.length !== 1)
|
|
65
|
+
throw Error('kit_owned_agent_card_required');
|
|
66
|
+
if (flags.node) {
|
|
67
|
+
const snapshot = await login.client.request('/api/workspace?workspaceId=' + encodeURIComponent(workspaceId));
|
|
68
|
+
if (!snapshot.draft?.nodes?.some((n) => n.id === flags.node && n.data?.agentId === agentId && n.data?.harness === harness))
|
|
69
|
+
throw Error('kit_owned_agent_card_required');
|
|
70
|
+
}
|
|
71
|
+
return { workspace, agent: { id: matches[0].agent.id } };
|
|
72
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { PortalLogin } from './onboarding.js';
|
|
2
|
+
import { type SetupPrompts } from './native-selection.js';
|
|
3
|
+
import { type NativeHarness } from './native.js';
|
|
4
|
+
export declare function nativeSdkConfig(harness: NativeHarness, root: string, options?: {
|
|
5
|
+
codexDelegationDisabled?: boolean;
|
|
6
|
+
}): {
|
|
7
|
+
config: {
|
|
8
|
+
features: {
|
|
9
|
+
multi_agent: boolean;
|
|
10
|
+
multi_agent_v2: boolean;
|
|
11
|
+
};
|
|
12
|
+
mcp_servers: {
|
|
13
|
+
tealbrick: {
|
|
14
|
+
command: string;
|
|
15
|
+
args: string[];
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
servers?: undefined;
|
|
20
|
+
settings?: undefined;
|
|
21
|
+
mcpServers?: undefined;
|
|
22
|
+
} | {
|
|
23
|
+
servers: {
|
|
24
|
+
tealbrick: {
|
|
25
|
+
command: string;
|
|
26
|
+
args: string[];
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
config?: undefined;
|
|
30
|
+
settings?: undefined;
|
|
31
|
+
mcpServers?: undefined;
|
|
32
|
+
} | {
|
|
33
|
+
settings: {
|
|
34
|
+
hooks: {
|
|
35
|
+
PreToolUse: {
|
|
36
|
+
matcher: string;
|
|
37
|
+
hooks: {
|
|
38
|
+
type: string;
|
|
39
|
+
command: string;
|
|
40
|
+
args: string[];
|
|
41
|
+
}[];
|
|
42
|
+
}[];
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
mcpServers: {
|
|
46
|
+
tealbrick: {
|
|
47
|
+
command: string;
|
|
48
|
+
args: string[];
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
config?: undefined;
|
|
52
|
+
servers?: undefined;
|
|
53
|
+
};
|
|
54
|
+
/** Private setup journal stores only identity intent, never account/runtime credentials. */
|
|
55
|
+
export declare function guidedNativeSetup(root: string, flags: Record<string, string>, login: PortalLogin, prompts: SetupPrompts): Promise<{
|
|
56
|
+
workspaceId: string;
|
|
57
|
+
nodeId: string;
|
|
58
|
+
configPath: string;
|
|
59
|
+
connected: boolean;
|
|
60
|
+
agentId: string;
|
|
61
|
+
connectionId: string;
|
|
62
|
+
harness: "claude" | "codex" | "langchain";
|
|
63
|
+
}>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { mkdir, lstat, readFile, writeFile, rename, unlink, realpath } from 'node:fs/promises';
|
|
5
|
+
import { readKit } from './index.js';
|
|
6
|
+
import { selectNativeIdentity, harnessLabels } from './native-selection.js';
|
|
7
|
+
import { setupNative } from './native.js';
|
|
8
|
+
export function nativeSdkConfig(harness, root, options = {}) {
|
|
9
|
+
if (harness === 'codex' && !options.codexDelegationDisabled)
|
|
10
|
+
throw Error('kit_native_codex_delegation_opt_in_required');
|
|
11
|
+
const server = { command: process.execPath, args: [fileURLToPath(new URL('./cli.js', import.meta.url)), 'native', 'mcp', '--root', resolve(root)] };
|
|
12
|
+
return harness === 'codex' ? { config: { features: { multi_agent: false, multi_agent_v2: false }, mcp_servers: { tealbrick: server } } } : harness === 'langchain' ? { servers: { tealbrick: server } } : { settings: { hooks: { PreToolUse: [{ matcher: 'mcp__tealbrick__.*', hooks: [{ type: 'command', command: process.execPath, args: [fileURLToPath(new URL('./native-child-guard.js', import.meta.url))] }] }] } }, mcpServers: { tealbrick: server } };
|
|
13
|
+
}
|
|
14
|
+
/** Private setup journal stores only identity intent, never account/runtime credentials. */
|
|
15
|
+
export async function guidedNativeSetup(root, flags, login, prompts) {
|
|
16
|
+
root = await realpath(root);
|
|
17
|
+
const dir = join(root, '.tealbrick');
|
|
18
|
+
try {
|
|
19
|
+
if ((await lstat(dir)).isSymbolicLink())
|
|
20
|
+
throw Error('kit_symlink_path');
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
if (e.code !== 'ENOENT')
|
|
24
|
+
throw e;
|
|
25
|
+
}
|
|
26
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
27
|
+
const paths = ['native-setup.json', 'native-setup.lock', 'native-sdk.json'].map(name => join(dir, name));
|
|
28
|
+
for (const path of paths) {
|
|
29
|
+
try {
|
|
30
|
+
if ((await lstat(path)).isSymbolicLink())
|
|
31
|
+
throw Error('kit_symlink_path');
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
if (e.code !== 'ENOENT')
|
|
35
|
+
throw e;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const [journalPath, lockPath, configPath] = paths;
|
|
39
|
+
try {
|
|
40
|
+
await writeFile(lockPath, String(process.pid), { flag: 'wx', mode: 0o600 });
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
if (e.code === 'EEXIST')
|
|
44
|
+
throw Error('kit_native_setup_locked');
|
|
45
|
+
throw e;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const harness = flags.harness;
|
|
49
|
+
if (harness === 'codex' && flags.delegation !== 'disabled')
|
|
50
|
+
throw Error('kit_native_codex_delegation_opt_in_required');
|
|
51
|
+
if (!Object.hasOwn(harnessLabels, harness))
|
|
52
|
+
throw Error('kit_native_harness_required');
|
|
53
|
+
const old = await readKit(root);
|
|
54
|
+
if (old.runtime && (old.runtime.issuer !== login.issuer || old.runtime.org !== login.org) || old.native && old.native.harness !== harness)
|
|
55
|
+
throw Error('kit_runtime_existing_binding_mismatch');
|
|
56
|
+
if (old.portal || old.voice || old.vision || old.deliver !== undefined || old.avm || old.routines || old.selfEvolution || old.runtime && !old.native)
|
|
57
|
+
throw Error('kit_native_eve_configuration_conflict');
|
|
58
|
+
let journal;
|
|
59
|
+
try {
|
|
60
|
+
journal = JSON.parse(await readFile(journalPath, 'utf8'));
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
if (e.code !== 'ENOENT')
|
|
64
|
+
throw e;
|
|
65
|
+
}
|
|
66
|
+
const stamp = { issuer: login.issuer, org: login.org, userId: login.userId, harness };
|
|
67
|
+
if (journal && JSON.stringify(journal.stamp) !== JSON.stringify(stamp))
|
|
68
|
+
throw Error('kit_native_setup_account_mismatch');
|
|
69
|
+
const explicit = ['workspace', 'workspace-name', 'create-workspace', 'agent', 'node', 'agent-name', 'create-agent'].some(key => flags[key]);
|
|
70
|
+
let request = journal && !explicit ? journal.request : undefined;
|
|
71
|
+
if (!request) {
|
|
72
|
+
const chosen = await selectNativeIdentity(root, flags, login, prompts);
|
|
73
|
+
const agent = 'id' in chosen.agent ? { mode: 'attach', id: chosen.agent.id, harness: harnessLabels[harness] } : { mode: 'create', name: chosen.agent.name, harness: harnessLabels[harness] };
|
|
74
|
+
const selection = { workspace: chosen.workspace, agent };
|
|
75
|
+
if (journal && JSON.stringify({ workspace: journal.request.workspace, agent: journal.request.agent }) === JSON.stringify(selection))
|
|
76
|
+
request = journal.request;
|
|
77
|
+
else {
|
|
78
|
+
if (old.runtime)
|
|
79
|
+
throw Error('kit_runtime_existing_binding_mismatch');
|
|
80
|
+
request = { ...selection, idempotencyKey: randomUUID() };
|
|
81
|
+
const tmp = journalPath + '.' + randomUUID();
|
|
82
|
+
await writeFile(tmp, JSON.stringify({ version: 1, stamp, request }) + '\n', { flag: 'wx', mode: 0o600 });
|
|
83
|
+
await rename(tmp, journalPath);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const prepared = await login.client.request('/api/runtime/setup', request);
|
|
87
|
+
if (!prepared.workspace?.id || !prepared.agent?.id || !prepared.nodeId || prepared.agent.harness !== harnessLabels[harness])
|
|
88
|
+
throw Error('kit_native_setup_response_invalid');
|
|
89
|
+
const authenticated = { ...login, workspaces: [...login.workspaces.filter(w => w.id !== prepared.workspace.id), prepared.workspace] };
|
|
90
|
+
const result = await setupNative(root, { harness, workspaceId: prepared.workspace.id, nodeId: prepared.nodeId, agentId: prepared.agent.id }, authenticated);
|
|
91
|
+
const tmp = configPath + '.' + randomUUID();
|
|
92
|
+
await writeFile(tmp, JSON.stringify(nativeSdkConfig(harness, root, { codexDelegationDisabled: flags.delegation === 'disabled' }), null, 2) + '\n', { flag: 'wx', mode: 0o600 });
|
|
93
|
+
await rename(tmp, configPath);
|
|
94
|
+
return { ...result, workspaceId: prepared.workspace.id, nodeId: prepared.nodeId, configPath };
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
await unlink(lockPath);
|
|
98
|
+
}
|
|
99
|
+
}
|
package/dist/native.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { guidedNativeSetup, nativeSdkConfig } from './native-setup.js';
|
|
2
|
+
import { type PortalLogin } from './onboarding.js';
|
|
3
|
+
import { type KnowledgeOperation } from '@tealbrick/portal/runtime';
|
|
4
|
+
declare const harnesses: {
|
|
5
|
+
readonly claude: "Claude Agent SDK";
|
|
6
|
+
readonly codex: "Codex";
|
|
7
|
+
readonly langchain: "LangChain";
|
|
8
|
+
};
|
|
9
|
+
export type NativeHarness = keyof typeof harnesses;
|
|
10
|
+
/** Enroll an existing, accurately labelled owned card. Registration belongs to Portal. */
|
|
11
|
+
export declare function setupNative(root: string, input: {
|
|
12
|
+
harness: NativeHarness;
|
|
13
|
+
workspaceId: string;
|
|
14
|
+
nodeId: string;
|
|
15
|
+
agentId: string;
|
|
16
|
+
}, login: PortalLogin): Promise<{
|
|
17
|
+
connected: boolean;
|
|
18
|
+
agentId: string;
|
|
19
|
+
connectionId: string;
|
|
20
|
+
harness: "claude" | "codex" | "langchain";
|
|
21
|
+
}>;
|
|
22
|
+
/** A fresh connector per invocation rechecks Portal and local activation. No authorization cache
|
|
23
|
+
* survives MCP reconnect, local deactivation or a request boundary. No session is created here. */
|
|
24
|
+
export declare function nativeConnector(): {
|
|
25
|
+
capabilities: () => Promise<{
|
|
26
|
+
revision: string;
|
|
27
|
+
expiresAt: number;
|
|
28
|
+
apps: {
|
|
29
|
+
registrationId: string;
|
|
30
|
+
appId: "knowledge";
|
|
31
|
+
actions: import("@tealbrick/portal/runtime").CrudAction[];
|
|
32
|
+
available: boolean;
|
|
33
|
+
operations: KnowledgeOperation[];
|
|
34
|
+
}[];
|
|
35
|
+
}>;
|
|
36
|
+
call: (registrationId: string, operation: KnowledgeOperation, input?: Record<string, unknown>, signal?: AbortSignal) => Promise<any>;
|
|
37
|
+
};
|
|
38
|
+
export declare function nativeCommand(args: string[], root: string): Promise<void>;
|
|
39
|
+
export { guidedNativeSetup, nativeSdkConfig };
|
package/dist/native.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { guidedNativeSetup, nativeSdkConfig } from './native-setup.js';
|
|
3
|
+
import { applyKit, readKit } from './index.js';
|
|
4
|
+
import { loginPortal, portalOrigin } from './onboarding.js';
|
|
5
|
+
import { PortalHttpError } from '@tealbrick/portal';
|
|
6
|
+
import { RuntimeConnector } from '@tealbrick/portal/runtime';
|
|
7
|
+
import { serveMcp } from '@tealbrick/portal/mcp';
|
|
8
|
+
import { runtimeSecret } from './secrets.js';
|
|
9
|
+
const harnesses = { claude: 'Claude Agent SDK', codex: 'Codex', langchain: 'LangChain' };
|
|
10
|
+
/** Enroll an existing, accurately labelled owned card. Registration belongs to Portal. */
|
|
11
|
+
export async function setupNative(root, input, login) {
|
|
12
|
+
if (!Object.hasOwn(harnesses, input.harness))
|
|
13
|
+
throw Error('kit_native_harness_required');
|
|
14
|
+
if (!login.workspaces.some(w => w.id === input.workspaceId))
|
|
15
|
+
throw Error('kit_portal_workspace_required');
|
|
16
|
+
const old = await readKit(root);
|
|
17
|
+
if (old.runtime && (old.runtime.issuer !== login.issuer || old.runtime.org !== login.org || old.runtime.workspaceId !== input.workspaceId || old.runtime.agentId !== input.agentId))
|
|
18
|
+
throw Error('kit_runtime_existing_binding_mismatch');
|
|
19
|
+
if (old.native && old.native.harness !== input.harness)
|
|
20
|
+
throw Error('kit_native_harness_mismatch');
|
|
21
|
+
if (old.portal || old.voice || old.vision || old.deliver !== undefined || old.avm || old.routines || old.selfEvolution || old.runtime && !old.native)
|
|
22
|
+
throw Error('kit_native_eve_configuration_conflict');
|
|
23
|
+
const snapshot = await login.client.request('/api/workspace?workspaceId=' + encodeURIComponent(input.workspaceId));
|
|
24
|
+
const agent = snapshot.agents?.find((a) => a.id === input.agentId && a.bindable);
|
|
25
|
+
const node = snapshot.draft?.nodes?.find((n) => n.id === input.nodeId && n.data?.agentId === input.agentId && n.data.kind === 'agent');
|
|
26
|
+
if (snapshot.workspace?.id !== input.workspaceId || !agent || !node)
|
|
27
|
+
throw Error('kit_owned_agent_card_required');
|
|
28
|
+
if (agent.runtime !== harnesses[input.harness] || node.data.harness !== harnesses[input.harness])
|
|
29
|
+
throw Error('kit_native_portal_harness_mismatch');
|
|
30
|
+
const enrolled = await login.client.request('/api/runtime/connections', { workspaceId: input.workspaceId, nodeId: input.nodeId, agentId: input.agentId });
|
|
31
|
+
if (!enrolled.connectionId || !enrolled.credential || !Number.isFinite(enrolled.expiresAt) || enrolled.expiresAt <= Date.now())
|
|
32
|
+
throw Error('kit_runtime_enrollment_failed');
|
|
33
|
+
await applyKit(root, { version: 1, native: { harness: input.harness, active: true }, runtime: { issuer: login.issuer, org: login.org, workspaceId: input.workspaceId, agentId: input.agentId, connectionId: enrolled.connectionId, credentialRef: 'TEALBRICK_RUNTIME_CREDENTIAL', bindings: old.runtime?.bindings ?? {} } }, { TEALBRICK_RUNTIME_CREDENTIAL: enrolled.credential });
|
|
34
|
+
return { connected: true, agentId: input.agentId, connectionId: enrolled.connectionId, harness: input.harness };
|
|
35
|
+
}
|
|
36
|
+
/** A fresh connector per invocation rechecks Portal and local activation. No authorization cache
|
|
37
|
+
* survives MCP reconnect, local deactivation or a request boundary. No session is created here. */
|
|
38
|
+
export function nativeConnector() {
|
|
39
|
+
async function current() {
|
|
40
|
+
const config = await readKit(process.cwd());
|
|
41
|
+
if (!config.native?.active || !config.runtime)
|
|
42
|
+
throw Error('runtime_product_inactive');
|
|
43
|
+
const { credentialRef, ...runtime } = config.runtime;
|
|
44
|
+
return new RuntimeConnector({ ...runtime, credential: () => runtimeSecret(credentialRef), resolveCredential: runtimeSecret });
|
|
45
|
+
}
|
|
46
|
+
return { capabilities: async () => (await current()).capabilities(true), call: async (registrationId, operation, input = {}, signal) => (await current()).call(registrationId, operation, input, signal) };
|
|
47
|
+
}
|
|
48
|
+
export async function nativeCommand(args, root) {
|
|
49
|
+
const command = args[0], flags = {};
|
|
50
|
+
for (let i = 1; i < args.length; i += 2) {
|
|
51
|
+
if (!/^--(root|harness|workspace|workspace-name|create-workspace|node|agent|agent-name|create-agent|delegation|issuer)$/.test(args[i]) || !args[i + 1] || flags[args[i].slice(2)])
|
|
52
|
+
throw Error('kit_native_invalid_arguments');
|
|
53
|
+
flags[args[i].slice(2)] = args[i + 1];
|
|
54
|
+
}
|
|
55
|
+
if (flags.root) {
|
|
56
|
+
root = resolve(flags.root);
|
|
57
|
+
process.chdir(root);
|
|
58
|
+
}
|
|
59
|
+
if (command === 'setup') {
|
|
60
|
+
if (flags.harness && !Object.hasOwn(harnesses, flags.harness))
|
|
61
|
+
throw Error('kit_native_harness_required');
|
|
62
|
+
if (!flags.harness)
|
|
63
|
+
throw Error('kit_native_harness_required');
|
|
64
|
+
if (flags.harness === 'codex' && flags.delegation !== 'disabled') {
|
|
65
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
66
|
+
if (flags.delegation || !await confirm({ message: 'This Codex Tealbrick profile requires native delegation disabled until scoped child grants are supported. Continue with delegation disabled?', default: false }))
|
|
67
|
+
throw Error('kit_native_codex_delegation_opt_in_required');
|
|
68
|
+
flags.delegation = 'disabled';
|
|
69
|
+
}
|
|
70
|
+
const issuer = portalOrigin(flags.issuer ?? 'https://portal.tealbrick.com');
|
|
71
|
+
const login = await loginPortal(issuer, (url, code) => console.error(`Approve Tealbrick setup: ${url}\nCode: ${code}`));
|
|
72
|
+
const { select, input } = await import('@inquirer/prompts');
|
|
73
|
+
try {
|
|
74
|
+
console.log(JSON.stringify(await guidedNativeSetup(root, flags, login, { choose: (message, choices) => select({ message, choices }), name: message => input({ message }) })));
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof PortalHttpError && /^[a-z_]+$/.test(error.code ?? ''))
|
|
78
|
+
throw Error('kit_portal_' + error.code);
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (command === 'config') {
|
|
84
|
+
const state = await readKit(root);
|
|
85
|
+
if (!state.native)
|
|
86
|
+
throw Error('kit_native_setup_required');
|
|
87
|
+
console.log(JSON.stringify(nativeSdkConfig(state.native.harness, root, { codexDelegationDisabled: flags.delegation === 'disabled' }), null, 2));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (command === 'mcp') {
|
|
91
|
+
await serveMcp(nativeConnector());
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (command === 'disable' || command === 'enable') {
|
|
95
|
+
const old = await readKit(root);
|
|
96
|
+
if (!old.native)
|
|
97
|
+
throw Error('kit_native_setup_required');
|
|
98
|
+
await applyKit(root, { ...old, native: { ...old.native, active: command === 'enable' } });
|
|
99
|
+
console.log(JSON.stringify({ active: command === 'enable' }));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
throw Error('Usage: tealbrick native setup --harness claude|codex|langchain [--workspace-name NAME | --create-workspace NAME] [--agent-name NAME | --create-agent NAME] [--issuer HTTPS_ORIGIN] | native mcp [--root DIRECTORY] | native enable|disable');
|
|
103
|
+
}
|
|
104
|
+
export { guidedNativeSetup, nativeSdkConfig };
|
package/dist/onboarding.d.ts
CHANGED
package/dist/onboarding.js
CHANGED
|
@@ -53,7 +53,7 @@ export async function loginPortal(issuer, show, options = {}) {
|
|
|
53
53
|
const directory = await authenticated.request('/api/me/workspaces');
|
|
54
54
|
if (!account.user?.id || !account.user.orgId || directory.accountId !== account.user.id || directory.organizationId !== account.user.orgId || !Array.isArray(directory.workspaces) || directory.workspaces.some(w => !w.id || !w.name))
|
|
55
55
|
throw Error('kit_portal_account_mismatch');
|
|
56
|
-
return { client: authenticated, org: account.user.orgId, userId: account.user.id, workspaces: directory.workspaces };
|
|
56
|
+
return { issuer, client: authenticated, org: account.user.orgId, userId: account.user.id, workspaces: directory.workspaces };
|
|
57
57
|
}
|
|
58
58
|
/** Reuse Portal's owned registration + revision-checked canvas APIs, not a parallel card store. */
|
|
59
59
|
export async function registerPortalCard(login, input) {
|
|
@@ -50,7 +50,7 @@ export async function runtimeOnboarding(command, args, root) {
|
|
|
50
50
|
const runtime = { issuer, org: login.org, workspaceId, agentId, connectionId: enrolled.connectionId, credentialRef: 'TEALBRICK_RUNTIME_CREDENTIAL', bindings: old.runtime?.bindings ?? {} };
|
|
51
51
|
await applyKit(root, { ...old, runtime }, { TEALBRICK_RUNTIME_CREDENTIAL: enrolled.credential });
|
|
52
52
|
console.log(JSON.stringify({ connectionId: enrolled.connectionId, workspaceId, agentId, expiresAt: enrolled.expiresAt, requiresReload: true }));
|
|
53
|
-
console.log('Connected.
|
|
53
|
+
console.log(old.native ? 'Connected. Native MCP will reload the current connection on each call.' : 'Connected. Customer-side tools are mounted; rebuild/restart Eve.');
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
if (!old.runtime || old.runtime.issuer !== issuer || old.runtime.workspaceId !== workspaceId || old.runtime.org !== login.org)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tealbrick/kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-rc.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Complete Teal Brick package kit with explicit Eve capability activation",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"LICENSE",
|
|
14
14
|
"NOTICE",
|
|
15
15
|
"BOOTSTRAP.md",
|
|
16
|
-
"RUNTIME.md"
|
|
16
|
+
"RUNTIME.md",
|
|
17
|
+
"NATIVE.md"
|
|
17
18
|
],
|
|
18
19
|
"bin": {
|
|
19
20
|
"tealbrick": "dist/cli.js"
|
|
@@ -30,23 +31,28 @@
|
|
|
30
31
|
"./secrets": {
|
|
31
32
|
"types": "./dist/secrets.d.ts",
|
|
32
33
|
"import": "./dist/secrets.js"
|
|
33
|
-
}
|
|
34
|
+
},
|
|
35
|
+
"./native": {
|
|
36
|
+
"types": "./dist/native.d.ts",
|
|
37
|
+
"import": "./dist/native.js"
|
|
38
|
+
},
|
|
39
|
+
"./cli": "./dist/cli.js"
|
|
34
40
|
},
|
|
35
41
|
"scripts": {
|
|
36
42
|
"build": "npm run build --workspace @tealbrick/provider-transport --workspace @tealbrick/portal --workspace @tealbrick/voice --workspace @tealbrick/vision --workspace @tealbrick/deliver --workspace @tealbrick/avm && tsc -p tsconfig.json",
|
|
37
43
|
"test": "node --test test/*.test.mjs"
|
|
38
44
|
},
|
|
39
45
|
"dependencies": {
|
|
40
|
-
"@tealbrick/portal": "0.
|
|
41
|
-
"@tealbrick/avm": "0.
|
|
42
|
-
"@tealbrick/voice": "0.
|
|
43
|
-
"@tealbrick/vision": "0.
|
|
44
|
-
"@tealbrick/deliver": "0.
|
|
46
|
+
"@tealbrick/portal": "0.3.0-rc.1",
|
|
47
|
+
"@tealbrick/avm": "0.3.0-rc.1",
|
|
48
|
+
"@tealbrick/voice": "0.3.0-rc.1",
|
|
49
|
+
"@tealbrick/vision": "0.3.0-rc.1",
|
|
50
|
+
"@tealbrick/deliver": "0.3.0-rc.1",
|
|
45
51
|
"@inquirer/prompts": "8.7.2",
|
|
46
52
|
"zod": "^4.0.0"
|
|
47
53
|
},
|
|
48
54
|
"peerDependencies": {
|
|
49
|
-
"eve": ">=0.
|
|
55
|
+
"eve": ">=0.70.0 <0.71.0"
|
|
50
56
|
},
|
|
51
57
|
"peerDependenciesMeta": {
|
|
52
58
|
"eve": {
|
|
@@ -59,6 +65,7 @@
|
|
|
59
65
|
},
|
|
60
66
|
"publishConfig": {
|
|
61
67
|
"access": "public",
|
|
62
|
-
"registry": "https://registry.npmjs.org"
|
|
68
|
+
"registry": "https://registry.npmjs.org",
|
|
69
|
+
"tag": "next"
|
|
63
70
|
}
|
|
64
71
|
}
|