archal 0.9.19 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -154
- package/agents/openclaw/.archal.json +8 -0
- package/agents/openclaw/Dockerfile +97 -0
- package/agents/openclaw/README.md +113 -0
- package/agents/openclaw/drive.mjs +301 -0
- package/agents/openclaw/extract-openclaw-response-text.mjs +49 -0
- package/agents/openclaw/package.json +9 -0
- package/agents/openclaw/scenarios/acceptance/github-escalation-and-cleanup.md +43 -0
- package/agents/openclaw/scenarios/acceptance/jira-linear-release-triage.md +39 -0
- package/agents/openclaw/scenarios/acceptance/multi-clone-customer-risk-review.md +45 -0
- package/agents/openclaw/scenarios/acceptance/slack-incident-handoff.md +42 -0
- package/agents/openclaw/scenarios/acceptance/stripe-dunning-remediation.md +40 -0
- package/agents/openclaw/scenarios/close-stale-issues.md +35 -0
- package/agents/openclaw/scenarios/github-issue-triage-read-only.md +44 -0
- package/agents/openclaw/workspace/AGENTS.md +23 -0
- package/agents/openclaw/workspace/IDENTITY.md +8 -0
- package/agents/openclaw/workspace/SOUL.md +14 -0
- package/agents/openclaw/workspace/TOOLS.md +54 -0
- package/clone-assets/apify/tools.json +256 -22
- package/clone-assets/calcom/tools.json +2062 -0
- package/clone-assets/clickup/tools.json +3245 -0
- package/clone-assets/customerio/tools.json +2350 -0
- package/clone-assets/datadog/tools.json +734 -0
- package/clone-assets/github/tools.json +307 -27
- package/clone-assets/gitlab/tools.json +10688 -0
- package/clone-assets/google-workspace/tools.json +18 -6
- package/clone-assets/hubspot/tools.json +1604 -0
- package/clone-assets/jira/fidelity.json +1 -1
- package/clone-assets/jira/tools.json +266 -543
- package/clone-assets/linear/tools.json +278 -40
- package/clone-assets/ownerrez/tools.json +548 -0
- package/clone-assets/pricelabs/tools.json +343 -0
- package/clone-assets/sentry/tools.json +2821 -0
- package/clone-assets/slack/tools.json +1 -2
- package/clone-assets/stripe/tools.json +299 -46
- package/clone-assets/supabase/tools.json +437 -0
- package/clone-assets/unipile/tools.json +408 -0
- package/clone-assets/webflow/tools.json +2185 -0
- package/dist/autoloop-worker-types-CHaclqtD.d.cts +160 -0
- package/dist/cli.cjs +127896 -84811
- package/dist/{vitest/chunk-L36NXAU6.js → commands/autoloop-hosted-control-plane-client.cjs} +16344 -18845
- package/dist/commands/autoloop-hosted-control-plane-client.d.cts +133 -0
- package/dist/commands/autoloop-pr-verification.cjs +4312 -0
- package/dist/commands/autoloop-pr-verification.d.cts +19 -0
- package/dist/commands/autoloop-result-parser.cjs +27058 -0
- package/dist/commands/autoloop-result-parser.d.cts +196 -0
- package/dist/commands/autoloop-worker.cjs +38488 -0
- package/dist/commands/autoloop-worker.d.cts +102 -0
- package/dist/eval-shim.mjs +216 -0
- package/dist/index.cjs +15 -2
- package/dist/index.d.cts +3 -1
- package/dist/layer-1-ziaLpfLr.d.cts +62 -0
- package/dist/metafile-cjs.json +1 -0
- package/dist/reporter-entry.js +3 -0
- package/dist/sdk/index.cjs +51630 -0
- package/dist/sdk/index.d.cts +626 -0
- package/dist/vitest/chunk-2PDHTPZC.js +4867 -0
- package/dist/vitest/chunk-7L4BBB6M.js +2613 -0
- package/dist/vitest/index.cjs +6152 -75718
- package/dist/vitest/index.d.ts +22 -86
- package/dist/vitest/index.js +45 -414
- package/dist/vitest/runtime/hosted-session-reaper.cjs +682 -34399
- package/dist/vitest/runtime/hosted-session-reaper.js +1 -1
- package/dist/vitest/runtime/setup-files.js +2 -2
- package/manifest.json +9 -0
- package/package.json +20 -14
- package/skills/archal-agent/SKILL.md +86 -0
- package/skills/autoloop/SKILL.md +391 -0
- package/skills/autoloop/references/hosted-sources.md +94 -0
- package/skills/autoloop/references/trace-schema-mapping.md +104 -0
- package/skills/eval/SKILL.md +17 -15
- package/skills/free-account/SKILL.md +103 -0
- package/skills/install-agent/SKILL.md +202 -0
- package/skills/onboard/SKILL.md +29 -18
- package/skills/scenario/SKILL.md +36 -18
- package/skills/seed/SKILL.md +238 -0
- package/skills/vitest/SKILL.md +3 -2
- package/dist/harness.cjs +0 -62
- package/dist/harness.d.cts +0 -20
- package/dist/seed/dynamic-generator.cjs +0 -45687
- package/dist/seed/dynamic-generator.d.cts +0 -106
- package/dist/vitest/chunk-WZ7SA4CK.js +0 -47369
- package/skills/attach/SKILL.md +0 -402
package/README.md
CHANGED
|
@@ -9,9 +9,9 @@ Ramp, Apify, Tavily, Supabase, and Google Workspace.
|
|
|
9
9
|
- `npm install -g archal` may print peer-dependency warnings from the
|
|
10
10
|
vendored CLI runtime. These are safe to ignore; all required modules
|
|
11
11
|
are bundled.
|
|
12
|
-
- The `vitest` integration under `archal/vitest`
|
|
13
|
-
Projects on
|
|
14
|
-
|
|
12
|
+
- The `vitest` integration under `archal/vitest` supports `vitest@>=2.1.0 <3`.
|
|
13
|
+
Projects on Vitest 3 should pin a workspace to Vitest 2 for Archal tests
|
|
14
|
+
until the integration supports Vitest 3's reporter/runtime APIs.
|
|
15
15
|
|
|
16
16
|
File issues at <https://github.com/Archal-Labs/archal/issues>.
|
|
17
17
|
|
|
@@ -37,6 +37,41 @@ cannot manage audit events or workspace API keys. Use an owner/admin user
|
|
|
37
37
|
credential, either `archal login` or a dashboard-issued user API key, for
|
|
38
38
|
workspace administration.
|
|
39
39
|
|
|
40
|
+
## SDK
|
|
41
|
+
|
|
42
|
+
The `archal` root import exposes the code-first SDK primitives: provision
|
|
43
|
+
clones, run your agent, collect an outcome, and judge it — all in one
|
|
44
|
+
package, no separate installs.
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
import { clones, criterion, judge, reportAgentMetrics, version } from 'archal';
|
|
48
|
+
|
|
49
|
+
const session = await clones(['github'], { seed: { github: 'small-project' } });
|
|
50
|
+
try {
|
|
51
|
+
// call your agent, pointing it at session.url('github')
|
|
52
|
+
const outcome = await session.outcome();
|
|
53
|
+
const judgment = await judge(outcome, [
|
|
54
|
+
criterion('The agent created an issue with the right title'),
|
|
55
|
+
]);
|
|
56
|
+
console.log(judgment.score, judgment.passed);
|
|
57
|
+
} finally {
|
|
58
|
+
await session.stop();
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Available exports from `archal`:
|
|
63
|
+
|
|
64
|
+
| Export | Type | Description |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `clones(services, opts?)` | function | Provision a hosted clone session |
|
|
67
|
+
| `criterion(text, opts?)` | function | Create an LLM-judged criterion |
|
|
68
|
+
| `judge(outcome, criteria, opts?)` | function | Grade an outcome against criteria |
|
|
69
|
+
| `reportAgentMetrics(metrics)` | function | Write token usage from a harness before exit |
|
|
70
|
+
| `version` | string | Package version |
|
|
71
|
+
|
|
72
|
+
The Vitest integration lives at `archal/vitest`. See
|
|
73
|
+
<https://docs.archal.ai/guides/vitest>.
|
|
74
|
+
|
|
40
75
|
## CLI
|
|
41
76
|
|
|
42
77
|
The CLI is the primary interface. `archal run` executes a
|
|
@@ -55,8 +90,8 @@ archal init
|
|
|
55
90
|
|
|
56
91
|
# 3. Edit .archal/harness.mjs to call your agent
|
|
57
92
|
|
|
58
|
-
# 4. Run the starter scenario from .archal.json
|
|
59
|
-
archal run
|
|
93
|
+
# 4. Run the starter scenario from .archal.json with controlled routing
|
|
94
|
+
archal run --docker
|
|
60
95
|
```
|
|
61
96
|
|
|
62
97
|
`archal init` creates `.archal.json`, `.archal/harness.mjs`, and
|
|
@@ -71,7 +106,10 @@ config looks like this:
|
|
|
71
106
|
{
|
|
72
107
|
"agent": {
|
|
73
108
|
"command": "node",
|
|
74
|
-
"args": [".archal/harness.mjs"]
|
|
109
|
+
"args": [".archal/harness.mjs"],
|
|
110
|
+
"env": {
|
|
111
|
+
"OPENAI_API_KEY": "${OPENAI_API_KEY}"
|
|
112
|
+
}
|
|
75
113
|
},
|
|
76
114
|
"scenarios": ["scenarios/first-run.md"],
|
|
77
115
|
"clones": ["github", "stripe"],
|
|
@@ -79,23 +117,12 @@ config looks like this:
|
|
|
79
117
|
}
|
|
80
118
|
```
|
|
81
119
|
|
|
82
|
-
|
|
120
|
+
The harness only receives variables declared in `agent.env`; use `${VAR}`
|
|
121
|
+
interpolation for shell secrets.
|
|
83
122
|
|
|
84
|
-
|
|
123
|
+
### Supported clones
|
|
85
124
|
|
|
86
|
-
|
|
87
|
-
|---|---|
|
|
88
|
-
| Discord | Guilds, channels, messages, members |
|
|
89
|
-
| GitHub | Repos, issues, PRs, labels, reviews |
|
|
90
|
-
| Slack | Channels, messages, users, reactions |
|
|
91
|
-
| Stripe | Customers, subscriptions, invoices, products |
|
|
92
|
-
| Linear | Teams, issues, projects, cycles |
|
|
93
|
-
| Jira | Projects, issues, workflows, components |
|
|
94
|
-
| Ramp | Cards, transactions, reimbursements, users |
|
|
95
|
-
| Apify | Actors, tasks, runs, datasets |
|
|
96
|
-
| Tavily | Search and extraction responses |
|
|
97
|
-
| Supabase | Auth, Postgres, storage, edge functions |
|
|
98
|
-
| Google Workspace | Gmail, Drive, Calendar, Docs, People |
|
|
125
|
+
Run `archal clone` for the current hosted catalog, and `archal clone list` for active clone sessions.
|
|
99
126
|
|
|
100
127
|
### Command reference
|
|
101
128
|
|
|
@@ -108,151 +135,32 @@ config looks like this:
|
|
|
108
135
|
| `archal clone status` | Inspect the active session |
|
|
109
136
|
| `archal clone stop` | Stop the active session |
|
|
110
137
|
| `archal clone list` | List all your active sessions |
|
|
111
|
-
| `archal clone attach <uuid>` | Reattach to a session by id |
|
|
112
138
|
| `archal clone renew <seconds>` | Extend the session lifetime |
|
|
113
139
|
| `archal clone reset` | Reset clone state without tearing down the session |
|
|
114
140
|
| `archal clone seed <clone> <name>` | Load a named seed into a running clone |
|
|
115
141
|
| `archal run [scenario]` | Run a scenario file (or use `--config` for `.archal.json`) |
|
|
142
|
+
| `archal workspace api-keys` | List workspace API keys with an owner/admin user session |
|
|
143
|
+
| `archal workspace api-key create <label>` | Create a workspace API key for CI/runtime auth |
|
|
144
|
+
| `archal workspace api-key revoke <key-id>` | Revoke a workspace API key |
|
|
145
|
+
| `archal autoloop [trace-dir] --repo <dir>` | Register a read-only trace source or start a local trace loop |
|
|
146
|
+
| `archal autoloop status [run-id]` | Show local trace job status or hosted run evidence |
|
|
147
|
+
| `archal autoloop detach <trace-dir>` | Stop a local file-backed autoloop loop |
|
|
116
148
|
| `archal scenario list` | Browse local and hosted scenarios |
|
|
117
149
|
| `archal seed list [clone]` | List prebuilt clone seeds |
|
|
118
|
-
| `archal
|
|
119
|
-
| `archal
|
|
150
|
+
| `archal traces list` | View recent scenario traces |
|
|
151
|
+
| `archal traces detail <name|id>` | View trace details for a run |
|
|
120
152
|
| `archal usage` | Check active workspace session-minutes and plan |
|
|
121
153
|
|
|
122
154
|
Run `archal <command> --help` for flag details.
|
|
123
155
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
You can also import `archal/vitest` to route SDK traffic from a vitest
|
|
127
|
-
suite through a hosted clone, with no code changes to your production
|
|
128
|
-
code. This is useful if you want to test the HTTP side of an integration
|
|
129
|
-
without hitting real provider APIs.
|
|
130
|
-
|
|
131
|
-
> The vitest helper supports the hosted route-mode clone catalog: Apify,
|
|
132
|
-
> Discord, GitHub, Google Workspace, Jira, Linear, Ramp, Slack, Stripe,
|
|
133
|
-
> Supabase, and Tavily.
|
|
134
|
-
> If you only need scenario-level evaluation, the CLI flow above is
|
|
135
|
-
> simpler to set up.
|
|
136
|
-
|
|
137
|
-
### Minimal config
|
|
138
|
-
|
|
139
|
-
```ts
|
|
140
|
-
import { defineConfig } from 'vitest/config';
|
|
141
|
-
import { withArchal } from 'archal/vitest';
|
|
142
|
-
|
|
143
|
-
export default defineConfig({
|
|
144
|
-
test: withArchal(
|
|
145
|
-
{
|
|
146
|
-
// everything you already had in test:, unchanged
|
|
147
|
-
globals: true,
|
|
148
|
-
coverage: { provider: 'v8' },
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
services: {
|
|
152
|
-
stripe: { mode: 'route', seed: 'small-business' },
|
|
153
|
-
},
|
|
154
|
-
},
|
|
155
|
-
),
|
|
156
|
-
});
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
`withArchal(existingTest, { services })` wraps an existing `vitest.config.ts`'s `test:` block, preserving every field you already had. Pass `{}` as the first argument if you're starting from scratch.
|
|
156
|
+
For terminal-first autonomous loops, see
|
|
157
|
+
<https://docs.archal.ai/guides/autoloop-production-traces>.
|
|
160
158
|
|
|
161
|
-
|
|
159
|
+
## Vitest integration
|
|
162
160
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
it('creates a customer', async () => {
|
|
168
|
-
const stripe = new Stripe('sk_test_fake'); // fake key is fine
|
|
169
|
-
const customer = await stripe.customers.create({ // goes to clone, not real Stripe
|
|
170
|
-
email: 'test@example.com',
|
|
171
|
-
});
|
|
172
|
-
expect(customer.id).toMatch(/^cus_/);
|
|
173
|
-
});
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### Per-test state isolation
|
|
177
|
-
|
|
178
|
-
```ts
|
|
179
|
-
import { beforeEach } from 'vitest';
|
|
180
|
-
import { resetArchalClones } from 'archal/vitest';
|
|
181
|
-
|
|
182
|
-
beforeEach(async () => {
|
|
183
|
-
await resetArchalClones();
|
|
184
|
-
});
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### Webhook testing
|
|
188
|
-
|
|
189
|
-
The hosted clone runs in AWS ECS, so it can't POST to your localhost.
|
|
190
|
-
Instead, your test pulls queued deliveries with `waitForArchalWebhook()`
|
|
191
|
-
and invokes your handler directly with the exact payload the clone would
|
|
192
|
-
have sent.
|
|
193
|
-
|
|
194
|
-
```ts
|
|
195
|
-
import { it, expect } from 'vitest';
|
|
196
|
-
import Stripe from 'stripe';
|
|
197
|
-
import { waitForArchalWebhook } from 'archal/vitest';
|
|
198
|
-
import { handleStripeWebhook } from './src/webhooks';
|
|
199
|
-
|
|
200
|
-
const stripe = new Stripe('sk_test_fake');
|
|
201
|
-
|
|
202
|
-
it('records a subscription when customer.subscription.created fires', async () => {
|
|
203
|
-
// 1. Register a webhook endpoint (the clone needs to know what events to queue)
|
|
204
|
-
await stripe.webhookEndpoints.create({
|
|
205
|
-
url: 'http://test.local/stripe-wh',
|
|
206
|
-
enabled_events: ['customer.subscription.created'],
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// 2. Trigger the event
|
|
210
|
-
const customer = await stripe.customers.create({ email: 'wh@x.com' });
|
|
211
|
-
const sub = await stripe.subscriptions.create({
|
|
212
|
-
customer: customer.id,
|
|
213
|
-
items: [{ price: 'price_existing_in_seed' }],
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
// 3. Pull the queued delivery
|
|
217
|
-
const event = await waitForArchalWebhook('stripe', 'customer.subscription.created');
|
|
218
|
-
expect(event.payload.data.object.id).toBe(sub.id);
|
|
219
|
-
|
|
220
|
-
// 4. Invoke your handler with the exact payload
|
|
221
|
-
await handleStripeWebhook(event.body, event.headers['Stripe-Signature'], process.env.STRIPE_WEBHOOK_SECRET);
|
|
222
|
-
});
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
**Webhook coverage**:
|
|
226
|
-
|
|
227
|
-
| Service | Support | Notes |
|
|
228
|
-
|---|---|---|
|
|
229
|
-
| Stripe | ✅ | |
|
|
230
|
-
| GitHub | ✅ | |
|
|
231
|
-
| Slack | ✅ | Receiver-side signature only |
|
|
232
|
-
| Jira | ✅ | Delivered via history buffer (also POSTed to registered URLs) |
|
|
233
|
-
| Linear | ✅ | Delivered via history buffer (also POSTed to registered URLs) |
|
|
234
|
-
| Supabase | ❌ | Database-triggered; test against real Postgres |
|
|
235
|
-
| Google Workspace | ❌ | GCP Pub/Sub push notifications, not webhooks |
|
|
236
|
-
|
|
237
|
-
**Parallel workers caveat**: `waitForArchalWebhook()` consumes deliveries
|
|
238
|
-
from a shared queue by default. When vitest runs test files in parallel
|
|
239
|
-
across workers, worker A can swallow worker B's event. If your tests
|
|
240
|
-
depend on webhook events, set `testIsolation: 'serial'` in your config.
|
|
241
|
-
|
|
242
|
-
### Test isolation across parallel workers
|
|
243
|
-
|
|
244
|
-
Each vitest worker is routed to its own per-worker state on the clone,
|
|
245
|
-
so parallel tests across workers don't see each other's writes. The
|
|
246
|
-
integration reads `VITEST_WORKER_ID` in each worker process and tags
|
|
247
|
-
every outbound SDK request with an `X-Archal-Worker-Id` header. The clone
|
|
248
|
-
maintains a separate state engine per worker id, seeded from the
|
|
249
|
-
baseline on first request.
|
|
250
|
-
|
|
251
|
-
**Isolation-enabled clones**: Stripe, GitHub, Slack, Jira, Linear.
|
|
252
|
-
|
|
253
|
-
**Clones without isolation** (Supabase, Google Workspace, Ramp) fall back
|
|
254
|
-
to shared state. If your tests depend on global assertions against those
|
|
255
|
-
clones, set `testIsolation: 'serial'`.
|
|
161
|
+
The package also exposes `archal/vitest` for routing SDK traffic from a Vitest
|
|
162
|
+
suite through hosted clones. The stable guide lives at
|
|
163
|
+
<https://docs.archal.ai/guides/vitest>.
|
|
256
164
|
|
|
257
165
|
## Authentication
|
|
258
166
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# OpenClaw agent harness — runs the real OpenClaw agent against Archal clones.
|
|
2
|
+
#
|
|
3
|
+
# This is the packaged-agent successor to the legacy `--sandbox` path. Instead of
|
|
4
|
+
# a fixed `archal/sandbox` image with a baked-in proxy + entrypoint, OpenClaw runs
|
|
5
|
+
# here as an ordinary packaged agent through the generic Docker-harness sidecar
|
|
6
|
+
# engine (the sidecar owns network interception).
|
|
7
|
+
#
|
|
8
|
+
# The SIDECAR — not this image — owns all network interception: DNS rewrites, the
|
|
9
|
+
# TLS MITM listener, the CA, and the agent-egress seal. This image therefore runs
|
|
10
|
+
# NO in-container proxy, NO `/etc/hosts` rewrite, and NO iptables. The agent keeps
|
|
11
|
+
# calling the real service domains (e.g. api.github.com); the sidecar transparently
|
|
12
|
+
# routes that traffic to the seeded clone, and `api.openai.com` /
|
|
13
|
+
# `api.anthropic.com` are forwarded to the real model with the host key injected by
|
|
14
|
+
# the proxy. The sidecar writes its CA to /agent-output/ca.crt and the harness sets
|
|
15
|
+
# NODE_EXTRA_CA_CERTS to it, so child processes trust the intercept automatically.
|
|
16
|
+
FROM node:22-bookworm-slim
|
|
17
|
+
|
|
18
|
+
# Pin the agent version. Override at build time:
|
|
19
|
+
# --build-arg OPENCLAW_VERSION=2026.6.6
|
|
20
|
+
# When no build-arg is passed, the pinned version is read from the colocated
|
|
21
|
+
# package.json `dependencies.openclaw`, which Dependabot's npm updater keeps
|
|
22
|
+
# current (see .github/dependabot.yml). Do NOT use a floating tag — the agent is
|
|
23
|
+
# the highest-blast-radius surface and `latest` is a supply-chain attack surface.
|
|
24
|
+
ARG OPENCLAW_VERSION=
|
|
25
|
+
|
|
26
|
+
ENV OPENCLAW_DISABLE_BONJOUR=1
|
|
27
|
+
|
|
28
|
+
# System tools OpenClaw's shell/exec tools expect:
|
|
29
|
+
# ca-certificates - lets `update-ca-certificates` consume the sidecar CA
|
|
30
|
+
# curl - gateway health checks + agent HTTP calls
|
|
31
|
+
# git - required by gh and common agent workflows
|
|
32
|
+
# jq - JSON shaping in agent shell steps
|
|
33
|
+
# ripgrep - fast source/search tool used by coding agents
|
|
34
|
+
# gh (GitHub CLI) is installed from the official apt repo below.
|
|
35
|
+
RUN apt-get update \
|
|
36
|
+
&& apt-get install -y --no-install-recommends \
|
|
37
|
+
ca-certificates \
|
|
38
|
+
curl \
|
|
39
|
+
git \
|
|
40
|
+
jq \
|
|
41
|
+
ripgrep \
|
|
42
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
43
|
+
|
|
44
|
+
# GitHub CLI from the official apt repo. OpenClaw reaches GitHub clones by
|
|
45
|
+
# shelling out to `gh` (and direct curl to api.github.com) — there is no GitHub
|
|
46
|
+
# MCP server in this harness.
|
|
47
|
+
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
|
|
48
|
+
| dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
|
|
49
|
+
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
|
|
50
|
+
> /etc/apt/sources.list.d/github-cli.list \
|
|
51
|
+
&& apt-get update \
|
|
52
|
+
&& apt-get install -y gh \
|
|
53
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
54
|
+
|
|
55
|
+
# Install the agent and run its bundled-plugin postinstall so the stock
|
|
56
|
+
# extensions (browser, etc.) materialize under dist/extensions. Fail the build
|
|
57
|
+
# loudly if the extension root is missing.
|
|
58
|
+
#
|
|
59
|
+
# Copy the version manifest to a temp path so it is available to the install RUN
|
|
60
|
+
# below; the RUN deletes it so it never lands in the shipped image.
|
|
61
|
+
COPY package.json /tmp/openclaw-pin/package.json
|
|
62
|
+
# Version source of truth: an explicit `--build-arg OPENCLAW_VERSION` wins; otherwise
|
|
63
|
+
# the version pinned in package.json (Dependabot-managed) is used. Docker expands the
|
|
64
|
+
# `${OPENCLAW_VERSION:-…}` default, and the `$(node -p …)` runs in the shell. This is a
|
|
65
|
+
# SINGLE reference on purpose: Docker textually substitutes every `${OPENCLAW_VERSION}`
|
|
66
|
+
# in a RUN with the build-arg value before the shell runs, so a second reference (or a
|
|
67
|
+
# reused shell var of the same name) would be clobbered. A missing/malformed pin yields
|
|
68
|
+
# `openclaw@undefined` / `openclaw@`, which npm rejects loudly — never a floating install.
|
|
69
|
+
RUN npm install -g "openclaw@${OPENCLAW_VERSION:-$(node -p "require('/tmp/openclaw-pin/package.json').dependencies.openclaw")}" \
|
|
70
|
+
&& node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs \
|
|
71
|
+
&& test -d /usr/local/lib/node_modules/openclaw/dist/extensions \
|
|
72
|
+
&& rm -rf /tmp/openclaw-pin
|
|
73
|
+
|
|
74
|
+
# Pre-configure the gh CLI with a format-valid dummy token. The sidecar replaces
|
|
75
|
+
# the Authorization header on every forwarded request with supervisor-owned
|
|
76
|
+
# credentials, so this token only needs to pass gh's local format check — a
|
|
77
|
+
# `gho_` prefix makes gh treat it as a valid OAuth token. DO NOT set GH_TOKEN:
|
|
78
|
+
# it takes precedence over hosts.yml and gh validates it with a direct API call
|
|
79
|
+
# that bypasses the proxy's header replacement.
|
|
80
|
+
RUN mkdir -p /root/.config/gh \
|
|
81
|
+
&& printf '%s\n' \
|
|
82
|
+
'github.com:' \
|
|
83
|
+
' oauth_token: gho_AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTt' \
|
|
84
|
+
' user: workflow-bot' \
|
|
85
|
+
' git_protocol: https' \
|
|
86
|
+
> /root/.config/gh/hosts.yml
|
|
87
|
+
|
|
88
|
+
WORKDIR /app
|
|
89
|
+
|
|
90
|
+
# The drive entrypoint + the agent's persona/workspace assets. The drive script
|
|
91
|
+
# copies the workspace into ~/.openclaw/workspace at boot.
|
|
92
|
+
COPY drive.mjs /app/drive.mjs
|
|
93
|
+
COPY extract-openclaw-response-text.mjs /app/extract-openclaw-response-text.mjs
|
|
94
|
+
COPY workspace/ /app/workspace/
|
|
95
|
+
|
|
96
|
+
# The .archal.json launch command overrides this; kept for standalone debugging.
|
|
97
|
+
CMD ["node", "/app/drive.mjs"]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# OpenClaw Agent Harness
|
|
2
|
+
|
|
3
|
+
This example runs the **real OpenClaw agent** against an Archal clone, packaged as
|
|
4
|
+
an ordinary agent and executed through the generic Docker-harness **sidecar**
|
|
5
|
+
engine.
|
|
6
|
+
|
|
7
|
+
It is the agent behind `archal run <scenario>.md --sandbox`. The `--sandbox` flag
|
|
8
|
+
no longer runs a bespoke in-container engine: the legacy path — a fixed
|
|
9
|
+
`archal/sandbox` image with an in-container TLS proxy, DNS rewrites, a baked
|
|
10
|
+
entrypoint, and the `runSandboxed` special case — has been removed. `--sandbox`
|
|
11
|
+
now launches this packaged agent through the generic Docker-harness **sidecar**,
|
|
12
|
+
which owns all network interception while this image just runs the agent.
|
|
13
|
+
|
|
14
|
+
> **Status: live.** `archal run <scenario>.md --sandbox` resolves to this bundled
|
|
15
|
+
> package. There is no separate `--agent` flag — the packaged-agent selector was
|
|
16
|
+
> removed; other packaged agents run via `--harness <dir> --dockerfile <dir>/Dockerfile`.
|
|
17
|
+
|
|
18
|
+
## What this demonstrates
|
|
19
|
+
|
|
20
|
+
- A full external agent (the OpenClaw gateway + its shell/exec tools) packaged
|
|
21
|
+
into one image, driven once per task and printing its answer to stdout.
|
|
22
|
+
- **Transparent interception by the sidecar**: the agent's `gh` / `curl` calls to
|
|
23
|
+
`api.github.com` are routed to the clone via the sidecar's DNS + TLS MITM — no
|
|
24
|
+
base-URL override, no code change. The sidecar writes its CA to
|
|
25
|
+
`/agent-output/ca.crt`; the harness sets `NODE_EXTRA_CA_CERTS` to it, so the CA
|
|
26
|
+
is trusted by the agent and its child processes.
|
|
27
|
+
- **No in-container network plumbing**: this image runs **no** proxy, **no**
|
|
28
|
+
`/etc/hosts` rewrite, and **no** iptables. That was the old single-container
|
|
29
|
+
model; the sidecar replaces it.
|
|
30
|
+
- **Real model, fake services**: provider domains (`api.openai.com`,
|
|
31
|
+
`api.anthropic.com`, …) are forwarded to the real model with the host key
|
|
32
|
+
injected by the proxy; only clone domains are intercepted.
|
|
33
|
+
- **Read-only behavior** scored from the clone trace plus the agent's answer text.
|
|
34
|
+
|
|
35
|
+
## Files
|
|
36
|
+
|
|
37
|
+
| File | Purpose |
|
|
38
|
+
|------|---------|
|
|
39
|
+
| `Dockerfile` | Packages `openclaw` (version pinned in `package.json`, overridable via `--build-arg OPENCLAW_VERSION`) + the `gh` CLI |
|
|
40
|
+
| `package.json` | Dependabot-watched version pin for `openclaw` (not a pnpm-workspace package) |
|
|
41
|
+
| `drive.mjs` | Entrypoint: reads `AGENT_TASK`, starts the local gateway, sends the task, prints the answer to stdout |
|
|
42
|
+
| `extract-openclaw-response-text.mjs` | Shared Responses-API answer parser (canonical copy in `packages/sandbox-runtime`) |
|
|
43
|
+
| `workspace/` | A **generic demo persona** (`IDENTITY.md`, `SOUL.md`, `AGENTS.md`, `TOOLS.md`) — swap or mount your agent's real persona to run it as itself |
|
|
44
|
+
| `.archal.json` | Declares the agent command + the `github` clone |
|
|
45
|
+
| `scenarios/` | GitHub triage and live-acceptance scenarios |
|
|
46
|
+
|
|
47
|
+
## How `drive.mjs` works
|
|
48
|
+
|
|
49
|
+
It reproduces the **agent-side** of the legacy sandbox entrypoint; the
|
|
50
|
+
**network-side** (proxy, CA install, DNS, iptables) is the sidecar's job and is
|
|
51
|
+
intentionally absent here. The drive script:
|
|
52
|
+
|
|
53
|
+
1. Stages the bundled `workspace/` into a writable `~/.openclaw/workspace` and
|
|
54
|
+
writes a minimal non-interactive `~/.openclaw/openclaw.json` (local gateway on
|
|
55
|
+
`:18789`, provider base URLs at their real defaults with `allowPrivateNetwork`,
|
|
56
|
+
shell/exec tools allowed).
|
|
57
|
+
2. Starts the gateway: `openclaw gateway run --port 18789 --bind loopback`, and
|
|
58
|
+
waits for the `[gateway] ready` marker.
|
|
59
|
+
3. Sends the task to that gateway:
|
|
60
|
+
`openclaw agent --agent main --session-id <id> --message "$AGENT_TASK" --timeout <s> --json`.
|
|
61
|
+
4. Parses the agent's final answer via `extract-openclaw-response-text.mjs` and
|
|
62
|
+
prints it to stdout for the evaluator.
|
|
63
|
+
|
|
64
|
+
## Syntax check
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
node --check drive.mjs
|
|
68
|
+
ARCHAL_PREFLIGHT=1 node drive.mjs # only meaningful inside the built image
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Run
|
|
72
|
+
|
|
73
|
+
`archal run <scenario>.md --sandbox` is the one-flag shortcut for this bundled
|
|
74
|
+
package. To run it explicitly as a packaged agent — the same path `--sandbox`
|
|
75
|
+
resolves to internally — point the generic harness flags at this directory:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
cd examples/agents/openclaw
|
|
79
|
+
# Set the key for the model's provider (see the env-var table below):
|
|
80
|
+
# OPENAI_API_KEY, ANTHROPIC_API_KEY, or GEMINI_API_KEY.
|
|
81
|
+
export OPENAI_API_KEY=...
|
|
82
|
+
archal run scenarios/github-issue-triage-read-only.md \
|
|
83
|
+
--harness . \
|
|
84
|
+
--dockerfile Dockerfile \
|
|
85
|
+
-n 1
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Docker mode is required so the sidecar can control DNS and TLS trust for
|
|
89
|
+
`api.github.com`.
|
|
90
|
+
|
|
91
|
+
## Build args
|
|
92
|
+
|
|
93
|
+
| Arg | Default | Notes |
|
|
94
|
+
|-----|---------|-------|
|
|
95
|
+
| `OPENCLAW_VERSION` | from `package.json` (`dependencies.openclaw`, Dependabot-managed) | Pin the agent version. Defaults to the pinned `package.json` version; override with `docker build --build-arg OPENCLAW_VERSION=2026.6.6 ...` or the equivalent harness option. Do not use a floating tag. |
|
|
96
|
+
|
|
97
|
+
## Environment variables
|
|
98
|
+
|
|
99
|
+
| Variable | Source | Notes |
|
|
100
|
+
|----------|--------|-------|
|
|
101
|
+
| `AGENT_TASK` | Injected by Archal | The scenario prompt |
|
|
102
|
+
| `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `GEMINI_API_KEY` | Host → proxy | Real key on the host; the sidecar injects the matching provider's native auth header. Each provider is pinned to its native wire API (`openai-responses` / `anthropic-messages` / `google-generative-ai`), so non-OpenAI models hit their native paths (`/v1/messages`, `:generateContent`) instead of OpenAI-compat `/chat/completions`. |
|
|
103
|
+
| `AGENT_MODEL` | Injected by Archal (optional) | Overrides the gateway's default model. Use a provider-prefixed id (e.g. `anthropic/claude-sonnet-4-6`, `google/gemini-2.5-flash`) |
|
|
104
|
+
| `AGENT_ID` | Optional | Selects the agent (default `main`) |
|
|
105
|
+
| `AGENT_DISABLE_PLUGINS` / `AGENT_EVAL_MODE=isolated` | Optional | Eval mode — runs with an isolated config and no business-tool plugins (GitHub is reached via the `gh` CLI, not a plugin) |
|
|
106
|
+
| `ARCHAL_TIMEOUT` | Optional | Per-task agent timeout in seconds (default `120`) |
|
|
107
|
+
|
|
108
|
+
## Read-only home / workspace
|
|
109
|
+
|
|
110
|
+
The demo persona is shipped read-only-friendly: `drive.mjs` copies the bundled
|
|
111
|
+
`workspace/` into a writable `~/.openclaw/workspace`, so the source assets can be
|
|
112
|
+
mounted **read-only**. Pass `--openclaw-home` to mount your own OpenClaw home at
|
|
113
|
+
`/openclaw-home` inside the container.
|