@amodalai/runtime 0.3.49 → 0.3.51
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/src/agent/completion-tools.integration.test.d.ts +6 -0
- package/dist/src/agent/completion-tools.integration.test.js +263 -0
- package/dist/src/agent/completion-tools.integration.test.js.map +1 -0
- package/dist/src/agent/load-template-plan.integration.test.d.ts +6 -0
- package/dist/src/agent/load-template-plan.integration.test.js +152 -0
- package/dist/src/agent/load-template-plan.integration.test.js.map +1 -0
- package/dist/src/agent/local-server.js +64 -9
- package/dist/src/agent/local-server.js.map +1 -1
- package/dist/src/agent/loop-types.d.ts +12 -2
- package/dist/src/agent/loop.test.js +5 -2
- package/dist/src/agent/loop.test.js.map +1 -1
- package/dist/src/agent/propose-plan.integration.test.d.ts +6 -0
- package/dist/src/agent/propose-plan.integration.test.js +186 -0
- package/dist/src/agent/propose-plan.integration.test.js.map +1 -0
- package/dist/src/agent/routes/package-updates.d.ts +42 -0
- package/dist/src/agent/routes/package-updates.js +207 -0
- package/dist/src/agent/routes/package-updates.js.map +1 -0
- package/dist/src/agent/routes/package-updates.test.d.ts +6 -0
- package/dist/src/agent/routes/package-updates.test.js +25 -0
- package/dist/src/agent/routes/package-updates.test.js.map +1 -0
- package/dist/src/agent/setup-state.integration.test.d.ts +6 -0
- package/dist/src/agent/setup-state.integration.test.js +182 -0
- package/dist/src/agent/setup-state.integration.test.js.map +1 -0
- package/dist/src/agent/snapshot-server.js +1 -0
- package/dist/src/agent/snapshot-server.js.map +1 -1
- package/dist/src/agent/states/executing.d.ts +6 -0
- package/dist/src/agent/states/executing.js +63 -27
- package/dist/src/agent/states/executing.js.map +1 -1
- package/dist/src/agent/states/streaming.js +18 -2
- package/dist/src/agent/states/streaming.js.map +1 -1
- package/dist/src/agent/tool-executor-local.js +11 -2
- package/dist/src/agent/tool-executor-local.js.map +1 -1
- package/dist/src/agent/validate-connection.integration.test.d.ts +6 -0
- package/dist/src/agent/validate-connection.integration.test.js +160 -0
- package/dist/src/agent/validate-connection.integration.test.js.map +1 -0
- package/dist/src/api/create-agent.js +1 -0
- package/dist/src/api/create-agent.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +9 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/intent/executor.d.ts +48 -0
- package/dist/src/intent/executor.js +420 -0
- package/dist/src/intent/executor.js.map +1 -0
- package/dist/src/intent/executor.test.d.ts +6 -0
- package/dist/src/intent/executor.test.js +543 -0
- package/dist/src/intent/executor.test.js.map +1 -0
- package/dist/src/intent/index.d.ts +10 -0
- package/dist/src/intent/index.js +9 -0
- package/dist/src/intent/index.js.map +1 -0
- package/dist/src/intent/loader.d.ts +16 -0
- package/dist/src/intent/loader.js +112 -0
- package/dist/src/intent/loader.js.map +1 -0
- package/dist/src/intent/loader.test.d.ts +6 -0
- package/dist/src/intent/loader.test.js +86 -0
- package/dist/src/intent/loader.test.js.map +1 -0
- package/dist/src/intent/matcher.d.ts +26 -0
- package/dist/src/intent/matcher.js +29 -0
- package/dist/src/intent/matcher.js.map +1 -0
- package/dist/src/intent/matcher.test.d.ts +6 -0
- package/dist/src/intent/matcher.test.js +53 -0
- package/dist/src/intent/matcher.test.js.map +1 -0
- package/dist/src/intent/onboarding.e2e.test.d.ts +6 -0
- package/dist/src/intent/onboarding.e2e.test.js +394 -0
- package/dist/src/intent/onboarding.e2e.test.js.map +1 -0
- package/dist/src/routes/ai-stream.js +96 -3
- package/dist/src/routes/ai-stream.js.map +1 -1
- package/dist/src/routes/session-resolver.js +16 -0
- package/dist/src/routes/session-resolver.js.map +1 -1
- package/dist/src/session/credential-scrubber.d.ts +35 -0
- package/dist/src/session/credential-scrubber.js +150 -0
- package/dist/src/session/credential-scrubber.js.map +1 -0
- package/dist/src/session/credential-scrubber.test.d.ts +6 -0
- package/dist/src/session/credential-scrubber.test.js +192 -0
- package/dist/src/session/credential-scrubber.test.js.map +1 -0
- package/dist/src/session/manager.intent.test.d.ts +6 -0
- package/dist/src/session/manager.intent.test.js +197 -0
- package/dist/src/session/manager.intent.test.js.map +1 -0
- package/dist/src/session/manager.js +114 -0
- package/dist/src/session/manager.js.map +1 -1
- package/dist/src/session/session-builder.d.ts +16 -1
- package/dist/src/session/session-builder.js +209 -41
- package/dist/src/session/session-builder.js.map +1 -1
- package/dist/src/session/store.js +48 -2
- package/dist/src/session/store.js.map +1 -1
- package/dist/src/session/tool-context-factory.js +12 -0
- package/dist/src/session/tool-context-factory.js.map +1 -1
- package/dist/src/session/types.d.ts +12 -1
- package/dist/src/setup/commit-setup.d.ts +94 -0
- package/dist/src/setup/commit-setup.js +154 -0
- package/dist/src/setup/commit-setup.js.map +1 -0
- package/dist/src/setup/commit-setup.test.d.ts +6 -0
- package/dist/src/setup/commit-setup.test.js +310 -0
- package/dist/src/setup/commit-setup.test.js.map +1 -0
- package/dist/src/tools/README.md +270 -0
- package/dist/src/tools/admin-tools.d.ts +27 -0
- package/dist/src/tools/admin-tools.js +734 -0
- package/dist/src/tools/admin-tools.js.map +1 -0
- package/dist/src/tools/agent-package-discovery.test.d.ts +6 -0
- package/dist/src/tools/agent-package-discovery.test.js +90 -0
- package/dist/src/tools/agent-package-discovery.test.js.map +1 -0
- package/dist/src/tools/builtin/ask-choice.d.ts +8 -0
- package/dist/src/tools/builtin/ask-choice.js +54 -0
- package/dist/src/tools/builtin/ask-choice.js.map +1 -0
- package/dist/src/tools/context.d.ts +154 -0
- package/dist/src/tools/context.js +30 -0
- package/dist/src/tools/context.js.map +1 -0
- package/dist/src/tools/custom-tool-adapter.d.ts +33 -2
- package/dist/src/tools/custom-tool-adapter.js +38 -1
- package/dist/src/tools/custom-tool-adapter.js.map +1 -1
- package/dist/src/tools/custom-tool-adapter.test.js +48 -0
- package/dist/src/tools/custom-tool-adapter.test.js.map +1 -1
- package/dist/src/tools/fetch-url-tool.js +2 -0
- package/dist/src/tools/fetch-url-tool.js.map +1 -1
- package/dist/src/tools/file-tools.js +16 -0
- package/dist/src/tools/file-tools.js.map +1 -1
- package/dist/src/tools/fs/local.test.d.ts +6 -0
- package/dist/src/tools/fs/local.test.js +126 -0
- package/dist/src/tools/fs/local.test.js.map +1 -0
- package/dist/src/tools/index.d.ts +35 -0
- package/dist/src/tools/index.js +11 -0
- package/dist/src/tools/index.js.map +1 -0
- package/dist/src/tools/mcp-tool-adapter.js +2 -0
- package/dist/src/tools/mcp-tool-adapter.js.map +1 -1
- package/dist/src/tools/memory-tool.js +23 -1
- package/dist/src/tools/memory-tool.js.map +1 -1
- package/dist/src/tools/permissions.d.ts +36 -0
- package/dist/src/tools/permissions.js +97 -0
- package/dist/src/tools/permissions.js.map +1 -0
- package/dist/src/tools/permissions.test.d.ts +6 -0
- package/dist/src/tools/permissions.test.js +62 -0
- package/dist/src/tools/permissions.test.js.map +1 -0
- package/dist/src/tools/request-tool.js +2 -0
- package/dist/src/tools/request-tool.js.map +1 -1
- package/dist/src/tools/sdk-context.d.ts +43 -0
- package/dist/src/tools/sdk-context.js +94 -0
- package/dist/src/tools/sdk-context.js.map +1 -0
- package/dist/src/tools/sdk-context.test.d.ts +6 -0
- package/dist/src/tools/sdk-context.test.js +134 -0
- package/dist/src/tools/sdk-context.test.js.map +1 -0
- package/dist/src/tools/store-tools.js +6 -0
- package/dist/src/tools/store-tools.js.map +1 -1
- package/dist/src/tools/types.d.ts +53 -14
- package/dist/src/tools/web-search-tool.js +2 -0
- package/dist/src/tools/web-search-tool.js.map +1 -1
- package/dist/src/types.d.ts +164 -28
- package/dist/src/types.js +9 -3
- package/dist/src/types.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -4
- package/dist/src/tools/agent-config-tool.d.ts +0 -7
- package/dist/src/tools/agent-config-tool.js +0 -78
- package/dist/src/tools/agent-config-tool.js.map +0 -1
- package/dist/src/tools/collect-secret-tool.d.ts +0 -7
- package/dist/src/tools/collect-secret-tool.js +0 -47
- package/dist/src/tools/collect-secret-tool.js.map +0 -1
- package/dist/src/tools/fs/http.d.ts +0 -37
- package/dist/src/tools/fs/http.js +0 -88
- package/dist/src/tools/fs/http.js.map +0 -1
- package/dist/src/tools/http-file-tools.d.ts +0 -13
- package/dist/src/tools/http-file-tools.js +0 -146
- package/dist/src/tools/http-file-tools.js.map +0 -1
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Phase E.2 integration test for `commitSetup`. Runs against a real
|
|
8
|
+
* Postgres + a real `LocalFsBackend` over a temp repo so the
|
|
9
|
+
* file-then-DB ordering, idempotency, and validation gating all get
|
|
10
|
+
* exercised end-to-end.
|
|
11
|
+
*
|
|
12
|
+
* Skips when DATABASE_URL is not set (same pattern as the Phase B
|
|
13
|
+
* setup-state.test.ts).
|
|
14
|
+
*/
|
|
15
|
+
import { randomUUID } from 'node:crypto';
|
|
16
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
17
|
+
import { tmpdir } from 'node:os';
|
|
18
|
+
import * as path from 'node:path';
|
|
19
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
20
|
+
import { closeDb, ensureSchema, getDb, upsertSetupState, } from '@amodalai/db';
|
|
21
|
+
import { LocalFsBackend } from '../tools/fs/local.js';
|
|
22
|
+
import { commitSetup, composeAmodalJson } from './commit-setup.js';
|
|
23
|
+
const HAS_DB = Boolean(process.env['DATABASE_URL']);
|
|
24
|
+
const describeWhenDb = HAS_DB ? describe : describe.skip;
|
|
25
|
+
function makePlan(overrides) {
|
|
26
|
+
return {
|
|
27
|
+
templatePackage: '@amodalai/test-template',
|
|
28
|
+
slots: [],
|
|
29
|
+
config: [],
|
|
30
|
+
completion: {
|
|
31
|
+
title: 'Test Agent',
|
|
32
|
+
suggestions: [],
|
|
33
|
+
automationTitle: null,
|
|
34
|
+
},
|
|
35
|
+
...overrides,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const slackSlot = {
|
|
39
|
+
label: 'Slack',
|
|
40
|
+
description: 'Where the digest gets posted.',
|
|
41
|
+
required: true,
|
|
42
|
+
multi: false,
|
|
43
|
+
options: [
|
|
44
|
+
{
|
|
45
|
+
packageName: '@amodalai/connection-slack',
|
|
46
|
+
displayName: 'Slack',
|
|
47
|
+
authType: 'bearer',
|
|
48
|
+
oauthScopes: [],
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
describeWhenDb('commitSetup (Phase E.2 integration)', () => {
|
|
53
|
+
let db;
|
|
54
|
+
let repoRoot;
|
|
55
|
+
let fs;
|
|
56
|
+
let agentId;
|
|
57
|
+
const scopeId = '';
|
|
58
|
+
beforeAll(async () => {
|
|
59
|
+
db = getDb();
|
|
60
|
+
await ensureSchema(db);
|
|
61
|
+
});
|
|
62
|
+
afterAll(async () => {
|
|
63
|
+
await closeDb();
|
|
64
|
+
});
|
|
65
|
+
beforeEach(async () => {
|
|
66
|
+
repoRoot = await mkdtemp(path.join(tmpdir(), 'commit-setup-'));
|
|
67
|
+
fs = new LocalFsBackend({ repoRoot });
|
|
68
|
+
agentId = `agent_${randomUUID()}`;
|
|
69
|
+
});
|
|
70
|
+
afterEach(async () => {
|
|
71
|
+
// Clean DB row + temp repo so reruns stay isolated.
|
|
72
|
+
try {
|
|
73
|
+
const { deleteSetupState } = await import('@amodalai/db');
|
|
74
|
+
await deleteSetupState(db, agentId, scopeId);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Ignore — best-effort cleanup.
|
|
78
|
+
}
|
|
79
|
+
await rm(repoRoot, { recursive: true, force: true });
|
|
80
|
+
});
|
|
81
|
+
it('returns no_state when no setup_state row exists', async () => {
|
|
82
|
+
const result = await commitSetup({ db, fs, agentId, scopeId });
|
|
83
|
+
expect(result).toMatchObject({ ok: false, reason: 'no_state' });
|
|
84
|
+
});
|
|
85
|
+
it('returns not_ready when a required slot is missing', async () => {
|
|
86
|
+
await upsertSetupState(db, agentId, scopeId, {
|
|
87
|
+
phase: 'configuring',
|
|
88
|
+
plan: makePlan({ slots: [slackSlot] }),
|
|
89
|
+
});
|
|
90
|
+
const result = await commitSetup({ db, fs, agentId, scopeId });
|
|
91
|
+
expect(result.ok).toBe(false);
|
|
92
|
+
if (result.ok)
|
|
93
|
+
throw new Error('unreachable');
|
|
94
|
+
expect(result.reason).toBe('not_ready');
|
|
95
|
+
if (result.reason !== 'not_ready')
|
|
96
|
+
throw new Error('unreachable');
|
|
97
|
+
expect(result.warnings[0]?.kind).toBe('missing_required_slot');
|
|
98
|
+
});
|
|
99
|
+
it('commits when required slots are satisfied — writes amodal.json + marks completed_at', async () => {
|
|
100
|
+
await upsertSetupState(db, agentId, scopeId, {
|
|
101
|
+
phase: 'configuring',
|
|
102
|
+
plan: makePlan({
|
|
103
|
+
slots: [slackSlot],
|
|
104
|
+
completion: { title: 'Marketing Digest', suggestions: [], automationTitle: 'Weekly digest' },
|
|
105
|
+
}),
|
|
106
|
+
appendCompleted: [
|
|
107
|
+
{
|
|
108
|
+
slotLabel: 'Slack',
|
|
109
|
+
packageName: '@amodalai/connection-slack',
|
|
110
|
+
connectedAt: new Date().toISOString(),
|
|
111
|
+
validatedAt: new Date().toISOString(),
|
|
112
|
+
validationFormatted: 'Found 12 channels',
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
const result = await commitSetup({ db, fs, agentId, scopeId });
|
|
117
|
+
expect(result.ok).toBe(true);
|
|
118
|
+
if (!result.ok)
|
|
119
|
+
throw new Error('unreachable');
|
|
120
|
+
expect(result.alreadyComplete).toBe(false);
|
|
121
|
+
expect(result.completedAt).toBeInstanceOf(Date);
|
|
122
|
+
// amodal.json was written with the right content.
|
|
123
|
+
const written = await readFile(path.join(repoRoot, 'amodal.json'), 'utf-8');
|
|
124
|
+
const parsed = JSON.parse(written);
|
|
125
|
+
expect(parsed.name).toBe('marketing-digest');
|
|
126
|
+
expect(parsed.version).toBe('1.0.0');
|
|
127
|
+
expect(parsed.packages).toContain('@amodalai/test-template');
|
|
128
|
+
expect(parsed.packages).toContain('@amodalai/connection-slack');
|
|
129
|
+
});
|
|
130
|
+
it('is idempotent — second commit returns alreadyComplete: true with the same timestamp', async () => {
|
|
131
|
+
await upsertSetupState(db, agentId, scopeId, {
|
|
132
|
+
phase: 'configuring',
|
|
133
|
+
plan: makePlan(),
|
|
134
|
+
});
|
|
135
|
+
const r1 = await commitSetup({ db, fs, agentId, scopeId });
|
|
136
|
+
expect(r1.ok).toBe(true);
|
|
137
|
+
if (!r1.ok)
|
|
138
|
+
throw new Error('unreachable');
|
|
139
|
+
const r2 = await commitSetup({ db, fs, agentId, scopeId });
|
|
140
|
+
expect(r2.ok).toBe(true);
|
|
141
|
+
if (!r2.ok)
|
|
142
|
+
throw new Error('unreachable');
|
|
143
|
+
expect(r2.alreadyComplete).toBe(true);
|
|
144
|
+
expect(r2.completedAt.getTime()).toBe(r1.completedAt.getTime());
|
|
145
|
+
});
|
|
146
|
+
it('force: true commits even with missing required slots', async () => {
|
|
147
|
+
await upsertSetupState(db, agentId, scopeId, {
|
|
148
|
+
phase: 'configuring',
|
|
149
|
+
plan: makePlan({ slots: [slackSlot] }),
|
|
150
|
+
});
|
|
151
|
+
const result = await commitSetup({ db, fs, agentId, scopeId, force: true });
|
|
152
|
+
expect(result.ok).toBe(true);
|
|
153
|
+
if (!result.ok)
|
|
154
|
+
throw new Error('unreachable');
|
|
155
|
+
// amodal.json was still written even without the Slack connection
|
|
156
|
+
// — this is the "user said skip Slack and finish anyway" path.
|
|
157
|
+
const written = await readFile(path.join(repoRoot, 'amodal.json'), 'utf-8');
|
|
158
|
+
expect(written.length).toBeGreaterThan(0);
|
|
159
|
+
});
|
|
160
|
+
it('returns not_ready with a clear warning when no Plan is attached', async () => {
|
|
161
|
+
// Edge case: row exists (from update_setup_state seeding planning
|
|
162
|
+
// phase) but no Plan ever got attached.
|
|
163
|
+
await upsertSetupState(db, agentId, scopeId, { phase: 'planning' });
|
|
164
|
+
const result = await commitSetup({ db, fs, agentId, scopeId });
|
|
165
|
+
expect(result.ok).toBe(false);
|
|
166
|
+
if (result.ok)
|
|
167
|
+
throw new Error('unreachable');
|
|
168
|
+
expect(result.reason).toBe('not_ready');
|
|
169
|
+
});
|
|
170
|
+
it('does not write amodal.json when readiness fails', async () => {
|
|
171
|
+
await upsertSetupState(db, agentId, scopeId, {
|
|
172
|
+
phase: 'configuring',
|
|
173
|
+
plan: makePlan({ slots: [slackSlot] }),
|
|
174
|
+
});
|
|
175
|
+
const result = await commitSetup({ db, fs, agentId, scopeId });
|
|
176
|
+
expect(result.ok).toBe(false);
|
|
177
|
+
// The temp repo is fresh — no amodal.json should have been
|
|
178
|
+
// written when readiness blocked the commit.
|
|
179
|
+
await expect(readFile(path.join(repoRoot, 'amodal.json'), 'utf-8')).rejects.toThrow();
|
|
180
|
+
});
|
|
181
|
+
it('survives a concurrent double-commit: first wins, second sees alreadyComplete', async () => {
|
|
182
|
+
// Phase E.2 risk from the build plan: "Agent fires
|
|
183
|
+
// request_complete_setup at the same instant the user clicks
|
|
184
|
+
// Finish setup. Both reach commit_setup; first acquires the
|
|
185
|
+
// row lock and writes; second sees completed_at IS NOT NULL
|
|
186
|
+
// and returns alreadyComplete: true. Test this explicitly
|
|
187
|
+
// with two parallel HTTP calls."
|
|
188
|
+
await upsertSetupState(db, agentId, scopeId, {
|
|
189
|
+
phase: 'configuring',
|
|
190
|
+
plan: makePlan(),
|
|
191
|
+
});
|
|
192
|
+
const [r1, r2] = await Promise.all([
|
|
193
|
+
commitSetup({ db, fs, agentId, scopeId }),
|
|
194
|
+
commitSetup({ db, fs, agentId, scopeId }),
|
|
195
|
+
]);
|
|
196
|
+
// Both succeed.
|
|
197
|
+
expect(r1.ok).toBe(true);
|
|
198
|
+
expect(r2.ok).toBe(true);
|
|
199
|
+
if (!r1.ok || !r2.ok)
|
|
200
|
+
throw new Error('unreachable');
|
|
201
|
+
// Exactly one was the canonical write; the other reports
|
|
202
|
+
// alreadyComplete: true. Both report the same timestamp
|
|
203
|
+
// (the second is reading the row the first wrote).
|
|
204
|
+
const canonical = r1.alreadyComplete ? r2 : r1;
|
|
205
|
+
const followUp = r1.alreadyComplete ? r1 : r2;
|
|
206
|
+
expect(canonical.alreadyComplete).toBe(false);
|
|
207
|
+
expect(followUp.alreadyComplete).toBe(true);
|
|
208
|
+
expect(canonical.completedAt.getTime()).toBe(followUp.completedAt.getTime());
|
|
209
|
+
// The amodal.json that landed is well-formed (only one writer
|
|
210
|
+
// — the second branch early-returned before writing).
|
|
211
|
+
const written = await readFile(path.join(repoRoot, 'amodal.json'), 'utf-8');
|
|
212
|
+
const parsed = JSON.parse(written);
|
|
213
|
+
expect(parsed.name).toBeTruthy();
|
|
214
|
+
expect(parsed.version).toBeTruthy();
|
|
215
|
+
});
|
|
216
|
+
it('concurrent commits with one force=true still resolve idempotently', async () => {
|
|
217
|
+
// Edge case: the agent calls request_complete_setup (force:
|
|
218
|
+
// false) at the same time the user clicks Finish anyway
|
|
219
|
+
// (force: true). Whichever lands first wins; the other
|
|
220
|
+
// returns alreadyComplete: true. Force flag is irrelevant
|
|
221
|
+
// post-completion since completedAt is already set.
|
|
222
|
+
await upsertSetupState(db, agentId, scopeId, {
|
|
223
|
+
phase: 'configuring',
|
|
224
|
+
plan: makePlan(),
|
|
225
|
+
});
|
|
226
|
+
const [r1, r2] = await Promise.all([
|
|
227
|
+
commitSetup({ db, fs, agentId, scopeId, force: false }),
|
|
228
|
+
commitSetup({ db, fs, agentId, scopeId, force: true }),
|
|
229
|
+
]);
|
|
230
|
+
expect(r1.ok).toBe(true);
|
|
231
|
+
expect(r2.ok).toBe(true);
|
|
232
|
+
if (!r1.ok || !r2.ok)
|
|
233
|
+
throw new Error('unreachable');
|
|
234
|
+
expect(r1.completedAt.getTime()).toBe(r2.completedAt.getTime());
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
describe('composeAmodalJson (unit)', () => {
|
|
238
|
+
it('builds a config from name + packages with template first', () => {
|
|
239
|
+
const config = composeAmodalJson({
|
|
240
|
+
phase: 'configuring',
|
|
241
|
+
currentStep: null,
|
|
242
|
+
completed: [
|
|
243
|
+
{
|
|
244
|
+
slotLabel: 'Slack',
|
|
245
|
+
packageName: '@amodalai/connection-slack',
|
|
246
|
+
connectedAt: '2026-04-30T10:00:00Z',
|
|
247
|
+
validatedAt: '2026-04-30T10:00:05Z',
|
|
248
|
+
validationFormatted: 'Found 12',
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
skipped: [],
|
|
252
|
+
configAnswers: {},
|
|
253
|
+
deferredRequests: [],
|
|
254
|
+
providedContext: {},
|
|
255
|
+
plan: makePlan({
|
|
256
|
+
templatePackage: '@amodalai/marketing-ops',
|
|
257
|
+
completion: { title: 'Marketing Ops', suggestions: [], automationTitle: null },
|
|
258
|
+
}),
|
|
259
|
+
}, makePlan({
|
|
260
|
+
templatePackage: '@amodalai/marketing-ops',
|
|
261
|
+
completion: { title: 'Marketing Ops', suggestions: [], automationTitle: null },
|
|
262
|
+
}));
|
|
263
|
+
expect(config.name).toBe('marketing-ops');
|
|
264
|
+
expect(config.packages).toEqual(['@amodalai/marketing-ops', '@amodalai/connection-slack']);
|
|
265
|
+
});
|
|
266
|
+
it('falls back to "agent" when the completion title is empty', () => {
|
|
267
|
+
const config = composeAmodalJson({
|
|
268
|
+
phase: 'configuring',
|
|
269
|
+
currentStep: null,
|
|
270
|
+
completed: [],
|
|
271
|
+
skipped: [],
|
|
272
|
+
configAnswers: {},
|
|
273
|
+
deferredRequests: [],
|
|
274
|
+
providedContext: {},
|
|
275
|
+
plan: null,
|
|
276
|
+
}, null);
|
|
277
|
+
expect(config.name).toBe('agent');
|
|
278
|
+
expect(config.packages).toBeUndefined();
|
|
279
|
+
});
|
|
280
|
+
it('dedupes packages across the template + completed list', () => {
|
|
281
|
+
const config = composeAmodalJson({
|
|
282
|
+
phase: 'configuring',
|
|
283
|
+
currentStep: null,
|
|
284
|
+
completed: [
|
|
285
|
+
{
|
|
286
|
+
slotLabel: 'Slack',
|
|
287
|
+
packageName: '@amodalai/connection-slack',
|
|
288
|
+
connectedAt: 'x',
|
|
289
|
+
validatedAt: null,
|
|
290
|
+
validationFormatted: null,
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
slotLabel: 'Slack DM',
|
|
294
|
+
packageName: '@amodalai/connection-slack',
|
|
295
|
+
connectedAt: 'x',
|
|
296
|
+
validatedAt: null,
|
|
297
|
+
validationFormatted: null,
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
skipped: [],
|
|
301
|
+
configAnswers: {},
|
|
302
|
+
deferredRequests: [],
|
|
303
|
+
providedContext: {},
|
|
304
|
+
plan: makePlan({ templatePackage: '@amodalai/connection-slack' }),
|
|
305
|
+
}, makePlan({ templatePackage: '@amodalai/connection-slack' }));
|
|
306
|
+
// Single occurrence even though it appeared 3 times across template + completed.
|
|
307
|
+
expect(config.packages).toEqual(['@amodalai/connection-slack']);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
//# sourceMappingURL=commit-setup.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit-setup.test.js","sourceRoot":"","sources":["../../../src/setup/commit-setup.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Copyright 2026 Amodal Labs, Inc.
|
|
3
|
+
SPDX-License-Identifier: MIT
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Custom-tool SDK
|
|
7
|
+
|
|
8
|
+
Public API for tools shipped inside agent packages — `agent-admin`,
|
|
9
|
+
`connection-*`, `template-*`, and any third-party agent the registry
|
|
10
|
+
points to. Every tool that wants to do more than return a string
|
|
11
|
+
(emit inline UI, read or write files, run a query, make an HTTP call)
|
|
12
|
+
goes through this surface.
|
|
13
|
+
|
|
14
|
+
The SDK is maintained as **additive-only** post-launch. Existing
|
|
15
|
+
fields and block types do not change shape — older tools keep working
|
|
16
|
+
when the runtime upgrades, and the registry doesn't have to track
|
|
17
|
+
per-package SDK versions.
|
|
18
|
+
|
|
19
|
+
## Where tools live
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
<agent-package>/
|
|
23
|
+
package.json ← amodal.permissions declares capabilities
|
|
24
|
+
tools/
|
|
25
|
+
my_tool/
|
|
26
|
+
tool.json ← description + JSON Schema parameters
|
|
27
|
+
handler.ts ← export default async (params, ctx) => …
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The runtime walks `<agent-package>/tools/*/{tool.json, handler.ts}`
|
|
31
|
+
on agent load. Every directory whose name matches `^[a-z][a-z0-9_]*$`
|
|
32
|
+
becomes a tool the LLM can call. There is no separate registration
|
|
33
|
+
step.
|
|
34
|
+
|
|
35
|
+
## Writing a handler
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// tools/show_preview/handler.ts
|
|
39
|
+
import type { ToolContext } from "@amodalai/runtime/tools";
|
|
40
|
+
|
|
41
|
+
export default async function showPreview(
|
|
42
|
+
params: { title: string; tagline: string },
|
|
43
|
+
ctx: ToolContext,
|
|
44
|
+
): Promise<{ ok: true }> {
|
|
45
|
+
ctx.emit({
|
|
46
|
+
type: "block",
|
|
47
|
+
block: {
|
|
48
|
+
type: "agent_card_preview",
|
|
49
|
+
card: {
|
|
50
|
+
title: params.title,
|
|
51
|
+
tagline: params.tagline,
|
|
52
|
+
platforms: [],
|
|
53
|
+
thumbnailConversation: [],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
return { ok: true };
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The first argument is the parameter object (validated against
|
|
62
|
+
`tool.json#parameters` before the handler runs). The second is a
|
|
63
|
+
[`ToolContext`](./context.ts) — the SDK surface this README documents.
|
|
64
|
+
|
|
65
|
+
Pure-JS handlers work too — agent packages with no build step can ship
|
|
66
|
+
a plain function and skip the `@amodalai/runtime` import. The runtime
|
|
67
|
+
compiles `handler.ts` with esbuild on first call regardless.
|
|
68
|
+
|
|
69
|
+
## `ToolContext` reference
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
interface ToolContext {
|
|
73
|
+
// Identity
|
|
74
|
+
agentId: string;
|
|
75
|
+
scopeId: string; // empty string = agent-level
|
|
76
|
+
scopeContext?: Record<string, string>;
|
|
77
|
+
sessionId: string;
|
|
78
|
+
|
|
79
|
+
// Side effects (always available)
|
|
80
|
+
emit(event: EmitEvent): void; // structured: text | block | error
|
|
81
|
+
log(message: string): void; // sugar for emit({type:'text', …})
|
|
82
|
+
|
|
83
|
+
// Capabilities (permission-gated)
|
|
84
|
+
fs: FsBackend;
|
|
85
|
+
db: ToolDbHandle;
|
|
86
|
+
fetch: typeof globalThis.fetch;
|
|
87
|
+
|
|
88
|
+
// Cancellation
|
|
89
|
+
signal: AbortSignal;
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### `ctx.emit(event)`
|
|
94
|
+
|
|
95
|
+
Three event variants:
|
|
96
|
+
|
|
97
|
+
- `{type: 'text', text}` — appended as agent prose alongside the LLM's stream.
|
|
98
|
+
- `{type: 'block', block}` — dispatched into the inline-block list and rendered
|
|
99
|
+
by the widget (or by Studio's `inlineBlockRenderers` for Studio-only block
|
|
100
|
+
types like `connection_panel`).
|
|
101
|
+
- `{type: 'error', message}` — surfaces an inline error notice and is logged
|
|
102
|
+
server-side.
|
|
103
|
+
|
|
104
|
+
Block types are typed in [`@amodalai/types/blocks`](../../../types/src/blocks.ts)
|
|
105
|
+
as a discriminated union — `text`, `ask_choice`, `agent_card_preview`,
|
|
106
|
+
`connection_panel`, `proposal`, `update_plan`. Add a new variant by
|
|
107
|
+
extending the union; the widget reducer keys off the `type` field.
|
|
108
|
+
|
|
109
|
+
### `ctx.fs` — repo file access
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
ctx.fs.readRepoFile("amodal.json");
|
|
113
|
+
ctx.fs.writeRepoFile("skills/digest/SKILL.md", body);
|
|
114
|
+
ctx.fs.readManyRepoFiles(["a.json", "b.json"]);
|
|
115
|
+
ctx.fs.listRepoFiles("skills/");
|
|
116
|
+
ctx.fs.deleteRepoFile("skills/old-skill/SKILL.md");
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
All paths are repo-relative. Absolute paths and `..` traversal are
|
|
120
|
+
rejected — the backend throws `FsSandboxError`. The same handler
|
|
121
|
+
works against either backend the runtime selects via
|
|
122
|
+
`AMODAL_REPO_MODE`:
|
|
123
|
+
|
|
124
|
+
- `local` (default, `amodal dev`) — direct `fs/promises`, sub-ms.
|
|
125
|
+
- `cloud` — proxies through `cloud-phase-4/platform-api`'s
|
|
126
|
+
`/api/repo/files/*` routes (Phase 0G); ~20-50ms per write.
|
|
127
|
+
|
|
128
|
+
Permission gates: `readRepoFile` / `readManyRepoFiles` / `listRepoFiles`
|
|
129
|
+
require `fs.read`. `writeRepoFile` / `deleteRepoFile` require `fs.write`.
|
|
130
|
+
|
|
131
|
+
### `ctx.db` — Drizzle handle
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const rows = await ctx.db.execute({
|
|
135
|
+
sql: "SELECT * FROM setup_state WHERE agent_id = $1",
|
|
136
|
+
params: [ctx.agentId],
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Scoped to the agent's session. Phase B introduces per-domain query
|
|
141
|
+
modules at `@amodalai/db/queries/<domain>.ts` (the Midday pattern); use
|
|
142
|
+
those over raw SQL when one exists.
|
|
143
|
+
|
|
144
|
+
Permission gate: any call to `ctx.db.execute` requires both `db.read`
|
|
145
|
+
and `db.write` — the SDK can't reliably parse SQL to know which is
|
|
146
|
+
which, so the conservative gate is "declare both if you touch the DB
|
|
147
|
+
at all." Phase B may split into `query()` / `mutate()` once we see how
|
|
148
|
+
db tools shake out in practice.
|
|
149
|
+
|
|
150
|
+
### `ctx.fetch` — outbound HTTP
|
|
151
|
+
|
|
152
|
+
Same signature as `globalThis.fetch`. The runtime injects `ctx.signal`
|
|
153
|
+
when the caller doesn't pass one, so outbound requests cancel with the
|
|
154
|
+
tool invocation.
|
|
155
|
+
|
|
156
|
+
Permission gate: `net.fetch`.
|
|
157
|
+
|
|
158
|
+
## Declaring permissions
|
|
159
|
+
|
|
160
|
+
In the agent package's `package.json`:
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"name": "@amodalai/agent-admin",
|
|
165
|
+
"amodal": {
|
|
166
|
+
"permissions": ["fs.read", "fs.write", "db.read", "db.write"]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Default-deny: a package with no `amodal.permissions` block gets an
|
|
172
|
+
empty list, and any tool inside it that reaches for a privileged
|
|
173
|
+
`ctx.*` capability throws `PermissionError` at the boundary, naming
|
|
174
|
+
the tool, the missing permission, and the package.
|
|
175
|
+
|
|
176
|
+
Available tiers:
|
|
177
|
+
|
|
178
|
+
- `fs.read` / `fs.write` — repo file access.
|
|
179
|
+
- `db.read` / `db.write` — Drizzle `ctx.db.execute`.
|
|
180
|
+
- `net.fetch` — outbound HTTP.
|
|
181
|
+
|
|
182
|
+
Notably **not present**: `secrets.*`. Credentials enter the system in
|
|
183
|
+
exactly two places — the Configure modal (`POST /api/secrets/:name`)
|
|
184
|
+
and the OAuth callback (`/api/oauth/callback`). The SDK has no
|
|
185
|
+
`ctx.saveSecret` and never will. Tools never see tokens in args, in
|
|
186
|
+
chat history, or in the LLM's context window. If your tool needs a
|
|
187
|
+
secret to talk to a third party, read it from `process.env` at runtime
|
|
188
|
+
through the connection package's auth surface — never accept it as a
|
|
189
|
+
parameter from the LLM.
|
|
190
|
+
|
|
191
|
+
## Common patterns
|
|
192
|
+
|
|
193
|
+
### Emit a block from a handler
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
ctx.emit({
|
|
197
|
+
type: "block",
|
|
198
|
+
block: {
|
|
199
|
+
type: "ask_choice",
|
|
200
|
+
askId: `choice_${ctx.sessionId}_${Date.now().toString(36)}`,
|
|
201
|
+
question: "When should the digest run?",
|
|
202
|
+
options: [
|
|
203
|
+
{ label: "Monday 8 AM", value: "monday-8am" },
|
|
204
|
+
{ label: "Friday 4 PM", value: "friday-4pm" },
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Read setup state from the DB (Phase B onward)
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
const rows = await ctx.db.execute({
|
|
214
|
+
sql: "SELECT * FROM setup_state WHERE agent_id = $1 AND scope_id = $2",
|
|
215
|
+
params: [ctx.agentId, ctx.scopeId],
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Write a config file
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
await ctx.fs.writeRepoFile(
|
|
223
|
+
"amodal.json",
|
|
224
|
+
JSON.stringify({ ...config, packages: [...config.packages, name] }, null, 2) +
|
|
225
|
+
"\n",
|
|
226
|
+
);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Make an outbound HTTP call
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
const res = await ctx.fetch("https://api.example.com/probe", {
|
|
233
|
+
headers: { Authorization: `Bearer ${process.env.MY_TOKEN}` },
|
|
234
|
+
});
|
|
235
|
+
const data = (await res.json()) as { count: number };
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Errors
|
|
239
|
+
|
|
240
|
+
- `PermissionError` — a `ctx.*` method was called without the matching
|
|
241
|
+
permission declared. Message names tool / package / permission so
|
|
242
|
+
the package author can fix the manifest in one place.
|
|
243
|
+
- `FsSandboxError` — a path resolved outside the repo root. Callers
|
|
244
|
+
should treat this as a bug, not a recoverable failure.
|
|
245
|
+
|
|
246
|
+
Both extend `Error` and are exported from `@amodalai/runtime/tools`.
|
|
247
|
+
|
|
248
|
+
## Versioning
|
|
249
|
+
|
|
250
|
+
The SDK ships in `@amodalai/runtime`. Subpath import:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
import type { ToolContext } from "@amodalai/runtime/tools";
|
|
254
|
+
import {
|
|
255
|
+
PermissionError,
|
|
256
|
+
FsSandboxError,
|
|
257
|
+
LocalFsBackend,
|
|
258
|
+
} from "@amodalai/runtime/tools";
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Treat the block-type union and the `ToolContext` interface as
|
|
262
|
+
**additive-only**. New phases extend; they do not rename or remove.
|
|
263
|
+
|
|
264
|
+
## Related
|
|
265
|
+
|
|
266
|
+
- **Connection validation probes** (Phase A) — the contract a connection
|
|
267
|
+
package's `validate.js` follows so the admin agent's
|
|
268
|
+
`validate_connection` tool can surface a real-data sanity check after
|
|
269
|
+
Connect. Documented in
|
|
270
|
+
[`@amodalai/core/cards/README.md`](../../../core/src/cards/README.md#connection-validation-probes).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import type { ToolRegistry } from './types.js';
|
|
7
|
+
import type { Logger } from '../logger.js';
|
|
8
|
+
export interface AdminToolsOptions {
|
|
9
|
+
/** Path to the agent repo — `install_package` writes amodal.json here. */
|
|
10
|
+
repoRoot: string;
|
|
11
|
+
/** npm registry base URL. Defaults to npmjs.org; tests override. */
|
|
12
|
+
registryUrl?: string;
|
|
13
|
+
logger: Logger;
|
|
14
|
+
}
|
|
15
|
+
export declare function registerAdminTools(registry: ToolRegistry, opts: AdminToolsOptions): void;
|
|
16
|
+
interface SearchHit {
|
|
17
|
+
name: string;
|
|
18
|
+
version: string;
|
|
19
|
+
description: string;
|
|
20
|
+
keywords: string[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Pull the relevant fields from npm's `/-/v1/search` response. The response
|
|
24
|
+
* shape is `{ objects: [{ package: { name, version, description, keywords } }] }`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function extractSearchHits(raw: unknown): SearchHit[];
|
|
27
|
+
export {};
|