razzou-harness 0.4.2 → 0.4.4
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/CHANGELOG.md +28 -0
- package/README.md +14 -4
- package/dist/daemon/architects.js +1 -0
- package/dist/daemon/manager.js +2 -0
- package/dist/daemon/orchestrator.js +20 -0
- package/dist/daemon/tokens.js +49 -0
- package/dist/setup/doctor.js +1 -1
- package/dist/setup/setup.js +48 -0
- package/dist/setup/terraform.js +86 -0
- package/dist/state/state.js +14 -0
- package/dist/web/assets/{index-CLq7s9N9.js → index-B26a4Goq.js} +2 -2
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,34 @@
|
|
|
3
3
|
The notable changes in each release of razzou-harness. Before 1.0, a new minor version
|
|
4
4
|
may change how a project is set up, and its notes say how.
|
|
5
5
|
|
|
6
|
+
## 0.4.4 (2026-11-08)
|
|
7
|
+
|
|
8
|
+
**Upgrading from 0.4.3:** install the new version, then rebuild the agents' image with
|
|
9
|
+
`harness doctor --build-images`: its tag follows the harness's version.
|
|
10
|
+
|
|
11
|
+
- **Claude's tokens, in the dashboard.** A meter beside the agents' hours counts every
|
|
12
|
+
token Claude used for the project, written and read (the prompt cache included), and
|
|
13
|
+
splits it by role. A Developer's and a reviewer's come from Claude Code's report when
|
|
14
|
+
their session ends. A Manager's and an Architect's, which run on without one, are read
|
|
15
|
+
every five minutes from the conversations Claude Code saves in their sandbox, and once
|
|
16
|
+
more before a Manager's sandbox goes; a sandbox made again counts from zero, added to
|
|
17
|
+
the total so far.
|
|
18
|
+
|
|
19
|
+
## 0.4.3 (2026-11-08)
|
|
20
|
+
|
|
21
|
+
**Upgrading from 0.4.2:** install the new version, then rebuild the agents' image with
|
|
22
|
+
`harness doctor --build-images`: its tag follows the harness's version.
|
|
23
|
+
|
|
24
|
+
- **`harness setup` writes the Terraform scaffold** your bootstrap expects, when you
|
|
25
|
+
answer its AWS questions, and on any later run: a partial `backend "s3" {}` block in
|
|
26
|
+
`iac/versions.tf`, `iac/backend-configs/staging.hcl` with the state bucket and the
|
|
27
|
+
project's key, read from the GitHub variables the bootstrap set, `iac/env/staging.tfvars`
|
|
28
|
+
with the account, the region and the repository's ids, and the variables and provider
|
|
29
|
+
they feed. Only missing files are written, a backend block of the project's own is
|
|
30
|
+
kept, and the files are committed with setup's others. The backend config sets no
|
|
31
|
+
encryption, so the bucket's default, its KMS key, applies. Until now the doctor
|
|
32
|
+
required these files and nothing wrote them.
|
|
33
|
+
|
|
6
34
|
## 0.4.2 (2026-11-08)
|
|
7
35
|
|
|
8
36
|
**Upgrading from 0.4.1:** upgrade OpenShell to 0.1.7 first, from its Debian package
|
package/README.md
CHANGED
|
@@ -140,7 +140,8 @@ gateway key. In order, it logs you in to `gh`; creates
|
|
|
140
140
|
the repository on GitHub if the project has no remote; creates the App from its
|
|
141
141
|
manifest when this machine has none, and waits for you to install it; writes the
|
|
142
142
|
harness's files; asks the questions and writes `harness/config.json`, with the
|
|
143
|
-
`staging` environment and the variables on GitHub for AWS
|
|
143
|
+
`staging` environment and the variables on GitHub for AWS, and the Terraform scaffold in
|
|
144
|
+
`iac/` that your bootstrap expects; commits the files it wrote,
|
|
144
145
|
and only those, with your git once you agree, and pushes them when you are on `main`;
|
|
145
146
|
runs `harness doctor` and fixes what it can (starts the OpenShell gateway, builds the
|
|
146
147
|
agents' image, imports the Claude profile and creates the provider with your token,
|
|
@@ -260,7 +261,8 @@ behind a token. Its views:
|
|
|
260
261
|
- **Needs you:** questions from the Manager, proposals to accept, pull requests to merge,
|
|
261
262
|
escalations, owner actions, a paused pipeline. Each has its button or form.
|
|
262
263
|
- **Project:** modules, proposals, plans and features, with attempts, findings, CI,
|
|
263
|
-
deploys and `progress.txt
|
|
264
|
+
deploys and `progress.txt`; and the budgets: your hours, the agents' hours, and the
|
|
265
|
+
tokens Claude used, written and read, by role.
|
|
264
266
|
- **Agents:** the Architect's terminal, the Manager's and the Developers' live
|
|
265
267
|
transcripts, a message box for the Manager, and a shell in any agent's sandbox. An
|
|
266
268
|
attempt's card keeps the network requests its sandbox's policy refused.
|
|
@@ -433,7 +435,7 @@ through an `Arch:` pull request, or a commit of your own on `main`.
|
|
|
433
435
|
|
|
434
436
|
```json
|
|
435
437
|
{
|
|
436
|
-
"harnessVersion": "0.4.
|
|
438
|
+
"harnessVersion": "0.4.4",
|
|
437
439
|
"project": { "name": "shop", "repo": "<org>/shop", "bot": "<org>-harness[bot]" },
|
|
438
440
|
"roles": {
|
|
439
441
|
"architect": { "model": "claude-opus-5-5", "effort": "max" },
|
|
@@ -554,7 +556,8 @@ and Terraform applies only in CI, after you merge.
|
|
|
554
556
|
`harness-*` roles and to the gateway user, so your project's own Terraform can't widen
|
|
555
557
|
the agents' access.
|
|
556
558
|
|
|
557
|
-
**Then, in the project
|
|
559
|
+
**Then, in the project** (`harness setup` does steps 1 to 3 when you answer its AWS
|
|
560
|
+
questions):
|
|
558
561
|
1. In `harness/config.json`, add `aws` and `terraform`:
|
|
559
562
|
|
|
560
563
|
```json
|
|
@@ -570,6 +573,13 @@ and Terraform applies only in CI, after you merge.
|
|
|
570
573
|
([NVIDIA/OpenShell#5375](https://github.com/NVIDIA/OpenShell/issues/5375)). Keep the `terraform` paths
|
|
571
574
|
as they are: the CI workflow uses the same ones. `harness github-setup` then requires
|
|
572
575
|
`tf-plan` as well.
|
|
576
|
+
|
|
577
|
+
In `iac/`, the Terraform scaffold: a partial `backend "s3" {}` block, the backend
|
|
578
|
+
config with the state bucket, the project's key, the region and `use_lockfile = true`,
|
|
579
|
+
and the variables file. `harness setup` writes whatever of it is missing, from the
|
|
580
|
+
variables your bootstrap set on GitHub. Its backend config sets no encryption, so the
|
|
581
|
+
bucket's default, your KMS key, applies: `encrypt = true` without `kms_key_id` would
|
|
582
|
+
replace it with SSE-S3.
|
|
573
583
|
2. Set the repository variables the workflow reads: `AWS_REGION_STAGING`,
|
|
574
584
|
`AWS_ROLE_PLAN_ARN_STAGING` and `AWS_ROLE_APPLY_ARN_STAGING`.
|
|
575
585
|
3. `harness doctor` creates the OpenShell AWS provider: it asks for the view role's ARN,
|
|
@@ -66,6 +66,7 @@ export class Architects {
|
|
|
66
66
|
}
|
|
67
67
|
this.attach(module);
|
|
68
68
|
await this.sendNotes(module);
|
|
69
|
+
await o.noteTokens(`architect:${module.module}`, module.architect.sandbox);
|
|
69
70
|
if (module.submission && !this.busy.has(module.module))
|
|
70
71
|
await this.process(module);
|
|
71
72
|
if (module.status === 'in_review' && module.design?.pr)
|
package/dist/daemon/manager.js
CHANGED
|
@@ -76,6 +76,7 @@ export class Managers {
|
|
|
76
76
|
else if (manager.status === 'running' && (await this.alive(manager))) {
|
|
77
77
|
this.attach(manager);
|
|
78
78
|
await this.deliver(manager);
|
|
79
|
+
await o.noteTokens(`manager:${key}`, manager.sandbox);
|
|
79
80
|
}
|
|
80
81
|
else {
|
|
81
82
|
await this.relaunch(o.state.managers[key]);
|
|
@@ -269,6 +270,7 @@ export class Managers {
|
|
|
269
270
|
this.detachOne(managerKey(manager));
|
|
270
271
|
const backup = join(this.localDir(manager), `transcript-${manager.sessionId}.jsonl`);
|
|
271
272
|
await o.driver.download(manager.sandbox, this.paths(manager).output, backup).catch(() => { });
|
|
273
|
+
await o.noteTokens(`manager:${managerKey(manager)}`, manager.sandbox, true);
|
|
272
274
|
await o.driver.remove(manager.sandbox);
|
|
273
275
|
o.record({ type: 'MANAGER_STOPPED', payload: { ...identity(manager), reason } });
|
|
274
276
|
}
|
|
@@ -27,6 +27,7 @@ import { parseCostEstimate } from '../review/cost-estimate.js';
|
|
|
27
27
|
import { activeReview, reviewStep, stepReview } from './review.js';
|
|
28
28
|
import { Managers } from './manager.js';
|
|
29
29
|
import { sweepLeftOvers } from './sandboxes.js';
|
|
30
|
+
import { readTokens } from './tokens.js';
|
|
30
31
|
import { attemptRefName, preMerge, probeScript, refOf, sandboxName, waitForFile } from './refs.js';
|
|
31
32
|
import { handleRequest } from './requests.js';
|
|
32
33
|
import { buildTask } from './task.js';
|
|
@@ -75,6 +76,8 @@ export class Orchestrator {
|
|
|
75
76
|
stepErrors = { key: '', count: 0 };
|
|
76
77
|
/** When the left-over sandboxes are next deleted: at the first pass, then every hour. */
|
|
77
78
|
nextSweep = 0;
|
|
79
|
+
/** When each long-lived agent's tokens were last read, by agent. */
|
|
80
|
+
tokensReadAt = new Map();
|
|
78
81
|
/** The gateway's providers, read at most once a minute, for the keys of the project's APIs. */
|
|
79
82
|
providerNames;
|
|
80
83
|
/** Errors in a row of each plan's step, and when it is tried again: one plan that keeps failing never stops the others. */
|
|
@@ -1528,6 +1531,23 @@ export class Orchestrator {
|
|
|
1528
1531
|
if (requests.length > 0)
|
|
1529
1532
|
this.record({ type: 'NETWORK_REFUSED', ref: refOf(state, attempt), payload: { requests } });
|
|
1530
1533
|
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Records what a long-lived agent, a Manager (`manager:<key>`) or an Architect
|
|
1536
|
+
* (`architect:<module>`), has used of Claude, read from its sandbox at most every five
|
|
1537
|
+
* minutes unless `now`: before a sandbox goes. Only a reading that changed is recorded.
|
|
1538
|
+
*/
|
|
1539
|
+
async noteTokens(agent, sandbox, now = false) {
|
|
1540
|
+
if (!now && this.now() - (this.tokensReadAt.get(agent) ?? -Infinity) < 5 * 60_000)
|
|
1541
|
+
return;
|
|
1542
|
+
this.tokensReadAt.set(agent, this.now());
|
|
1543
|
+
const reading = await readTokens(this, sandbox);
|
|
1544
|
+
if (!reading)
|
|
1545
|
+
return;
|
|
1546
|
+
const last = this.state.agentTokens?.[agent]?.reading ?? { inputTokens: 0, outputTokens: 0 };
|
|
1547
|
+
if (last.inputTokens === reading.inputTokens && last.outputTokens === reading.outputTokens)
|
|
1548
|
+
return;
|
|
1549
|
+
this.record({ type: 'AGENT_TOKENS', payload: { agent, reading } });
|
|
1550
|
+
}
|
|
1531
1551
|
/** An error outside any attempt: logged, and the loop goes on. */
|
|
1532
1552
|
async onStepError(error) {
|
|
1533
1553
|
this.stepErrors = { key: 'idle', count: this.stepErrors.key === 'idle' ? this.stepErrors.count + 1 : 1 };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { sh } from '../util.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sums, in the sandbox, the tokens of every conversation Claude Code saved under
|
|
4
|
+
* `$HOME/.claude/projects`: its subagents' and a relaunch's included. A reply is written
|
|
5
|
+
* over several lines that share its id, so each id counts once, with its final count;
|
|
6
|
+
* a line that doesn't parse, like the one being written, is skipped. Input counts the
|
|
7
|
+
* prompt cache's tokens, written and read, as `toUsage` does for a Developer.
|
|
8
|
+
*/
|
|
9
|
+
const COUNT = `
|
|
10
|
+
const fs = require('fs'), path = require('path');
|
|
11
|
+
const replies = new Map();
|
|
12
|
+
const read = (file) => {
|
|
13
|
+
for (const line of fs.readFileSync(file, 'utf8').split('\\n')) {
|
|
14
|
+
let entry;
|
|
15
|
+
try { entry = JSON.parse(line); } catch { continue; }
|
|
16
|
+
const message = entry && entry.type === 'assistant' ? entry.message : undefined;
|
|
17
|
+
if (!message || !message.id || !message.usage) continue;
|
|
18
|
+
const u = message.usage;
|
|
19
|
+
const input = (u.input_tokens || 0) + (u.cache_creation_input_tokens || 0) + (u.cache_read_input_tokens || 0);
|
|
20
|
+
const output = u.output_tokens || 0;
|
|
21
|
+
const seen = replies.get(message.id);
|
|
22
|
+
if (!seen || output > seen[1]) replies.set(message.id, [input, output]);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const walk = (dir) => {
|
|
26
|
+
for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
27
|
+
const next = path.join(dir, item.name);
|
|
28
|
+
if (item.isDirectory()) walk(next);
|
|
29
|
+
else if (item.name.endsWith('.jsonl')) read(next);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
try { walk(path.join(process.env.HOME, '.claude', 'projects')); } catch {}
|
|
33
|
+
let inputTokens = 0, outputTokens = 0;
|
|
34
|
+
for (const [input, output] of replies.values()) { inputTokens += input; outputTokens += output; }
|
|
35
|
+
process.stdout.write(JSON.stringify({ inputTokens, outputTokens }));
|
|
36
|
+
`;
|
|
37
|
+
/** What a long-lived agent has used of Claude so far, from its sandbox; undefined when it can't be read. */
|
|
38
|
+
export async function readTokens(o, sandbox) {
|
|
39
|
+
const result = await o.exec(sandbox, `node -e ${sh(COUNT)}`).catch(() => undefined);
|
|
40
|
+
if (result?.code !== 0)
|
|
41
|
+
return undefined;
|
|
42
|
+
try {
|
|
43
|
+
const usage = JSON.parse(result.stdout);
|
|
44
|
+
return Number.isFinite(usage.inputTokens) && Number.isFinite(usage.outputTokens) ? { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens } : undefined;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
package/dist/setup/doctor.js
CHANGED
|
@@ -218,7 +218,7 @@ async function project(options, config, report) {
|
|
|
218
218
|
if (config.terraform) {
|
|
219
219
|
const tf = config.terraform;
|
|
220
220
|
for (const file of [tf.backendConfig, tf.varFile]) {
|
|
221
|
-
report(existsSync(join(options.projectDir, tf.dir, file)) ? ok('Terraform', `${tf.dir}/${file}`) : fail('Terraform', `${tf.dir}/${file} is missing`, 'see
|
|
221
|
+
report(existsSync(join(options.projectDir, tf.dir, file)) ? ok('Terraform', `${tf.dir}/${file}`) : fail('Terraform', `${tf.dir}/${file} is missing`, '`harness setup` writes the missing Terraform scaffold, or see the README\'s AWS section'));
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
}
|
package/dist/setup/setup.js
CHANGED
|
@@ -7,6 +7,7 @@ import { applyRepositoryRules, checkApp, createApp, installCi } from '../github/
|
|
|
7
7
|
import { run, sha256 } from '../util.js';
|
|
8
8
|
import { doctor, granted } from './doctor.js';
|
|
9
9
|
import { appBot, githubRepo, init } from './init.js';
|
|
10
|
+
import { writeTerraformScaffold } from './terraform.js';
|
|
10
11
|
export function fixFor(check) {
|
|
11
12
|
if (check.status === 'ok' || check.status === 'skip')
|
|
12
13
|
return undefined;
|
|
@@ -214,6 +215,7 @@ async function askSettings(options) {
|
|
|
214
215
|
const written = [];
|
|
215
216
|
let changed = false;
|
|
216
217
|
let viewRole;
|
|
218
|
+
let account;
|
|
217
219
|
if (!config.aws && /^y/i.test(await ask('Does this project deploy to AWS with Terraform? [y/N] '))) {
|
|
218
220
|
say('The roles come from your bootstrap (see the README\'s AWS section); the key is asked for later, at a hidden prompt.');
|
|
219
221
|
const region = (await ask('AWS region (like eu-west-1): ')).trim();
|
|
@@ -231,6 +233,28 @@ async function askSettings(options) {
|
|
|
231
233
|
const repo = config.project?.repo;
|
|
232
234
|
if (repo)
|
|
233
235
|
await configureGitHubForAws(options, repo, { region, plan, apply });
|
|
236
|
+
account = /^arn:aws:iam::(\d{12}):/.exec(view)[1];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// The Terraform scaffold the bootstrap expects, which the doctor checks: whatever of it is
|
|
240
|
+
// missing, on this run or a later one. A later run reads the account from the plan role
|
|
241
|
+
// this one stored on GitHub.
|
|
242
|
+
if (config.aws?.region && config.terraform) {
|
|
243
|
+
const repo = config.project?.repo;
|
|
244
|
+
account ??= /^arn:aws:iam::(\d{12}):/.exec((await githubVariable(repo, 'AWS_ROLE_PLAN_ARN_STAGING')) ?? '')?.[1];
|
|
245
|
+
if (account) {
|
|
246
|
+
const layout = { dir: 'iac', backendConfig: 'backend-configs/staging.hcl', varFile: 'env/staging.tfvars', ...config.terraform };
|
|
247
|
+
// As the schema reads it: `iac`, `./iac` and `iac/` are one folder. Slashes are trimmed by hand: a regex would take quadratic time on a long run of them.
|
|
248
|
+
let dir = layout.dir;
|
|
249
|
+
while (dir.startsWith('./') || dir.startsWith('/'))
|
|
250
|
+
dir = dir.slice(dir.startsWith('./') ? 2 : 1);
|
|
251
|
+
while (dir.endsWith('/'))
|
|
252
|
+
dir = dir.slice(0, -1);
|
|
253
|
+
layout.dir = dir || 'iac';
|
|
254
|
+
const scaffold = writeTerraformScaffold(projectDir, layout, await terraformFacts(repo, String(config.project?.name ?? 'project'), String(config.aws.region), account));
|
|
255
|
+
if (scaffold.length > 0)
|
|
256
|
+
say(`Terraform: wrote ${scaffold.join(', ')}.`);
|
|
257
|
+
written.push(...scaffold);
|
|
234
258
|
}
|
|
235
259
|
}
|
|
236
260
|
if (changed) {
|
|
@@ -254,6 +278,30 @@ async function askSettings(options) {
|
|
|
254
278
|
}
|
|
255
279
|
return { written, ...(viewRole ? { viewRole } : {}) };
|
|
256
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* Where the project's state lives, and its repository's numeric ids: read from GitHub, where
|
|
283
|
+
* the bootstrap's configure-github.sh set TF_STATE_BUCKET_STAGING and TF_STATE_KEY_STAGING,
|
|
284
|
+
* or else by the bootstrap's naming, `tf-state-staging-<account>` and
|
|
285
|
+
* `staging/<repository>/terraform.tfstate`.
|
|
286
|
+
*/
|
|
287
|
+
async function terraformFacts(repo, name, region, account) {
|
|
288
|
+
const bucket = await githubVariable(repo, 'TF_STATE_BUCKET_STAGING');
|
|
289
|
+
const key = await githubVariable(repo, 'TF_STATE_KEY_STAGING');
|
|
290
|
+
const ids = (await ghApi(repo, `repos/${repo}`, '"\\(.owner.id) \\(.id)"'))?.split(' ');
|
|
291
|
+
return {
|
|
292
|
+
region,
|
|
293
|
+
account,
|
|
294
|
+
bucket: bucket ?? `tf-state-staging-${account}`,
|
|
295
|
+
key: key ?? `staging/${repo ? repo.split('/')[1] : name}/terraform.tfstate`,
|
|
296
|
+
...(repo && ids?.length === 2 ? { github: { repo, ownerId: ids[0], repoId: ids[1] } } : {}),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
/** What the owner's gh reads at `path`, through `jq`; undefined without a repository, or on any failure. */
|
|
300
|
+
async function ghApi(repo, path, jq) {
|
|
301
|
+
const result = repo ? await run('gh', ['api', path, '--jq', jq]).catch(() => undefined) : undefined;
|
|
302
|
+
return result?.code === 0 && result.stdout.trim() ? result.stdout.trim() : undefined;
|
|
303
|
+
}
|
|
304
|
+
const githubVariable = (repo, name) => ghApi(repo, `repos/${repo}/actions/variables/${name}`, '.value');
|
|
257
305
|
/** The staging environment, restricted to main, and the variables the workflow reads, with the owner's gh. */
|
|
258
306
|
async function configureGitHubForAws(options, repo, aws) {
|
|
259
307
|
const gh = async (args, body) => {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Writes the Terraform scaffold the bootstrap expects in the layout's folder: a partial
|
|
5
|
+
* `backend "s3"` block in `versions.tf`, the backend config and the variables file, and the
|
|
6
|
+
* variables and provider those values feed. Only what is missing is written: an existing
|
|
7
|
+
* file, or a backend block in any of the folder's `.tf` files, is the project's own. The
|
|
8
|
+
* backend config sets no encryption, so the bucket's default, the bootstrap's KMS key,
|
|
9
|
+
* applies: `encrypt = true` without `kms_key_id` would replace it with SSE-S3. Every file
|
|
10
|
+
* is as `terraform fmt` writes it, which CI checks. Returns the paths written, from the
|
|
11
|
+
* project's root.
|
|
12
|
+
*/
|
|
13
|
+
export function writeTerraformScaffold(projectDir, layout, values) {
|
|
14
|
+
const { dir } = layout;
|
|
15
|
+
const written = [];
|
|
16
|
+
const write = (path, content) => {
|
|
17
|
+
const file = join(projectDir, dir, path);
|
|
18
|
+
if (existsSync(file))
|
|
19
|
+
return false;
|
|
20
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
21
|
+
writeFileSync(file, content);
|
|
22
|
+
written.push(`${dir}/${path}`);
|
|
23
|
+
return true;
|
|
24
|
+
};
|
|
25
|
+
const folder = join(projectDir, dir);
|
|
26
|
+
const hasBackend = existsSync(folder)
|
|
27
|
+
&& readdirSync(folder).filter((f) => f.endsWith('.tf')).some((f) => /^\s*backend\s+"/m.test(readFileSync(join(folder, f), 'utf8')));
|
|
28
|
+
if (!hasBackend) {
|
|
29
|
+
write('versions.tf', [
|
|
30
|
+
'terraform {',
|
|
31
|
+
' required_version = ">= 1.10"',
|
|
32
|
+
'',
|
|
33
|
+
' required_providers {',
|
|
34
|
+
' aws = {',
|
|
35
|
+
' source = "hashicorp/aws"',
|
|
36
|
+
' version = "~> 6.65"',
|
|
37
|
+
' }',
|
|
38
|
+
' }',
|
|
39
|
+
'',
|
|
40
|
+
` # Partial configuration: the values are in ${layout.backendConfig}, for`,
|
|
41
|
+
` # terraform init -backend-config=${layout.backendConfig}`,
|
|
42
|
+
' backend "s3" {}',
|
|
43
|
+
'}',
|
|
44
|
+
'',
|
|
45
|
+
].join('\n'));
|
|
46
|
+
}
|
|
47
|
+
write(layout.backendConfig, [
|
|
48
|
+
...assignments([['bucket', values.bucket], ['key', values.key], ['region', values.region]]),
|
|
49
|
+
'',
|
|
50
|
+
"# No encrypt or kms_key_id: the bucket's default encryption, its KMS key, applies.",
|
|
51
|
+
'# encrypt = true without kms_key_id would replace it with SSE-S3.',
|
|
52
|
+
'use_lockfile = true',
|
|
53
|
+
'',
|
|
54
|
+
].join('\n'));
|
|
55
|
+
const github = values.github;
|
|
56
|
+
const variables = [
|
|
57
|
+
['env', 'staging', 'The environment.'],
|
|
58
|
+
['aws_account_id', values.account, 'The AWS account the environment is in.'],
|
|
59
|
+
['aws_region', values.region, 'The AWS region the environment is in.'],
|
|
60
|
+
...(github ? [
|
|
61
|
+
['github_repo', github.repo, 'The GitHub repository, as owner/name.'],
|
|
62
|
+
['github_owner_id', github.ownerId, "The repository owner's numeric id, which OIDC trust policies pin."],
|
|
63
|
+
['github_repo_id', github.repoId, "The repository's numeric id, which OIDC trust policies pin."],
|
|
64
|
+
] : []),
|
|
65
|
+
];
|
|
66
|
+
const tfvars = [
|
|
67
|
+
...assignments(variables.slice(0, 3).map(([name, value]) => [name, value])),
|
|
68
|
+
...(github ? ['', ...assignments(variables.slice(3).map(([name, value]) => [name, value]))] : []),
|
|
69
|
+
'',
|
|
70
|
+
].join('\n');
|
|
71
|
+
// The variables follow the values: a tfvars file of the project's own sets its own.
|
|
72
|
+
if (write(layout.varFile, tfvars)) {
|
|
73
|
+
write('variables.tf', variables.map(([name, , description]) => [
|
|
74
|
+
`variable "${name}" {`,
|
|
75
|
+
...assignments([['description', description], ['type', 'string']], ['type']).map((line) => ` ${line}`),
|
|
76
|
+
'}',
|
|
77
|
+
].join('\n')).join('\n\n') + '\n');
|
|
78
|
+
write('providers.tf', ['provider "aws" {', ' region = var.aws_region', '}', ''].join('\n'));
|
|
79
|
+
}
|
|
80
|
+
return written;
|
|
81
|
+
}
|
|
82
|
+
/** `name = "value"` lines with their equals signs aligned, as `terraform fmt` aligns them; `bare` names take an expression, unquoted. */
|
|
83
|
+
function assignments(pairs, bare = []) {
|
|
84
|
+
const width = Math.max(...pairs.map(([name]) => name.length));
|
|
85
|
+
return pairs.map(([name, value]) => `${name.padEnd(width)} = ${bare.includes(name) ? value : JSON.stringify(value)}`);
|
|
86
|
+
}
|
package/dist/state/state.js
CHANGED
|
@@ -119,6 +119,20 @@ export function apply(state, event) {
|
|
|
119
119
|
else
|
|
120
120
|
state.budgets.agentHours = event.payload.hours;
|
|
121
121
|
break;
|
|
122
|
+
case 'AGENT_TOKENS': {
|
|
123
|
+
const { agent, reading } = event.payload;
|
|
124
|
+
const tokens = (state.agentTokens ??= {});
|
|
125
|
+
const known = tokens[agent];
|
|
126
|
+
// A reading below the last is a new sandbox, whose count starts from zero.
|
|
127
|
+
const grew = known !== undefined && reading.inputTokens >= known.reading.inputTokens && reading.outputTokens >= known.reading.outputTokens;
|
|
128
|
+
const base = grew ? known.reading : { inputTokens: 0, outputTokens: 0 };
|
|
129
|
+
const total = known?.total ?? { inputTokens: 0, outputTokens: 0 };
|
|
130
|
+
tokens[agent] = {
|
|
131
|
+
total: { inputTokens: total.inputTokens + reading.inputTokens - base.inputTokens, outputTokens: total.outputTokens + reading.outputTokens - base.outputTokens },
|
|
132
|
+
reading: { inputTokens: reading.inputTokens, outputTokens: reading.outputTokens },
|
|
133
|
+
};
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
122
136
|
case 'SANDBOX_LIMITS_SET': {
|
|
123
137
|
const { cpu, memoryGiB } = event.payload;
|
|
124
138
|
if (cpu === null && memoryGiB === null)
|
|
@@ -92,5 +92,5 @@ ${e}</tr>
|
|
|
92
92
|
Please report this to https://github.com/markedjs/marked.`,e){let e=`<p>An error occurred:</p><pre>`+Ju(n.message+``,!0)+`</pre>`;return t?Promise.resolve(e):e}if(t)return Promise.reject(n);throw n}}};function Q(e,t){return cd.parse(e,t)}Q.options=Q.setOptions=function(e){return cd.setOptions(e),Q.defaults=cd.defaults,Dl(Q.defaults),Q},Q.getDefaults=Tl,Q.defaults=El;function ld(...e){return cd.use(...e),Q.defaults=cd.defaults,Dl(Q.defaults),Q}Q.use=ld,Q.walkTokens=function(e,t){return cd.walkTokens(e,t)},Q.parseInline=cd.parseInline,Q.Parser=od,Q.parser=od.parse,Q.Renderer=id,Q.TextRenderer=ad,Q.Lexer=rd,Q.lexer=rd.lex,Q.Tokenizer=nd,Q.Hooks=sd,Q.parse=Q,Q.options,Q.setOptions,Q.walkTokens,Q.parseInline,od.parse,rd.lex,wl.addHook(`afterSanitizeAttributes`,e=>{e.tagName===`A`&&(e.setAttribute(`target`,`_blank`),e.setAttribute(`rel`,`noopener noreferrer`))});function ud({text:e}){let t=(0,l.useMemo)(()=>wl.sanitize(Q.parse(e,{async:!1,gfm:!0}),{FORBID_TAGS:[`style`],FORBID_ATTR:[`style`]}),[e]);return(0,x.jsx)(`div`,{className:`markdown`,dangerouslySetInnerHTML:{__html:t}})}function dd({agent:e}){let[t,n]=(0,l.useState)([]),r=(0,l.useRef)(null),i=(0,l.useRef)(!0);(0,l.useEffect)(()=>{n([]),h(`/api/transcript?agent=${encodeURIComponent(e)}`).then(e=>n(e??[]))},[e]),d({transcript:(t,r)=>t===e&&n(e=>[...e.slice(-999),r])}),(0,l.useLayoutEffect)(()=>{i.current&&r.current&&(r.current.scrollTop=r.current.scrollHeight)},[t]);let a=gd(t);return(0,x.jsxs)(`div`,{className:`transcript-wrap`,children:[(0,x.jsxs)(`div`,{className:`transcript`,ref:r,onScroll:e=>{let t=e.currentTarget;i.current=t.scrollHeight-t.scrollTop-t.clientHeight<40},children:[t.length===0&&(0,x.jsx)(`div`,{className:`muted`,children:`Nothing yet.`}),md(t).map((e,t)=>(0,x.jsx)(fd,{item:e},t))]}),(0,x.jsxs)(`div`,{className:`muted small`,children:[a.turns,` model calls · `,a.written&&`${a.written} · `,`context `,a.context.toLocaleString(),` tokens`]})]})}function fd({item:e}){let t=e.message;switch(e.type){case`system`:return e.subtype===`init`?(0,x.jsxs)(`div`,{className:`t-meta`,children:[`session started · `,String(e.model??``)]}):e.subtype===`api_retry`?(0,x.jsxs)(`div`,{className:`t-meta warn`,children:[`a model call failed (`,String(e.error_status??e.error??`unknown`),`): retry `,String(e.attempt??`?`),` of `,String(e.max_retries??`?`)]}):null;case`assistant`:return(0,x.jsx)(x.Fragment,{children:(Array.isArray(t?.content)?t.content:[]).map((e,t)=>e.type===`text`&&e.text?(0,x.jsx)(`div`,{className:`t-text`,children:(0,x.jsx)(ud,{text:e.text})},t):e.type===`tool_use`?(0,x.jsx)(pd,{name:e.name??`?`,input:e.input??{}},t):null)});case`user`:return typeof t?.content==`string`?(0,x.jsxs)(`div`,{className:`t-input`,children:[`→ `,t.content]}):(0,x.jsx)(x.Fragment,{children:(t?.content??[]).filter(e=>e.type===`tool_result`).map((e,t)=>{let n=hd(e.content);return(0,x.jsxs)(`details`,{className:e.is_error?`t-result bad`:`t-result`,children:[(0,x.jsxs)(`summary`,{children:[e.is_error?`error: `:`↳ `,n.split(`
|
|
93
93
|
`)[0].slice(0,160)||`(empty)`]}),(0,x.jsx)(`pre`,{children:n})]},t)})});case`result`:return(0,x.jsxs)(`div`,{className:e.is_error?`t-meta bad`:`t-meta`,children:[`finished: `,String(e.subtype??``),` · `,String(e.num_turns??`?`),` turns`,typeof e.duration_ms==`number`?` · ${v(e.duration_ms)}`:``,typeof e.total_cost_usd==`number`?` · $${e.total_cost_usd.toFixed(2)}`:``]});default:return null}}function pd({name:e,input:t}){let n=[`command`,`file_path`,`pattern`,`url`,`description`,`path`].map(e=>t[e]).find(e=>typeof e==`string`);return(0,x.jsxs)(`details`,{className:`t-tool`,children:[(0,x.jsxs)(`summary`,{children:[(0,x.jsx)(`b`,{children:e.replace(/^mcp__harness__/,``)}),` `,(0,x.jsx)(`span`,{className:`mono`,children:String(n??``).split(`
|
|
94
94
|
`)[0].slice(0,200)})]}),(0,x.jsx)(`pre`,{children:JSON.stringify(t,null,2)})]})}function md(e){let t=new Set;return e.filter(e=>e.type!==`system`||e.subtype!==`init`||!t.has(e.session_id)&&(t.add(e.session_id),!0))}function hd(e){return(typeof e==`string`?e:Array.isArray(e)?e.map(e=>typeof e?.text==`string`?e.text:``).join(`
|
|
95
|
-
`):``).replace(/\x1b\[[0-9;?]*[A-Za-z]/g,``)}function gd(e){let t=new Map,n=0,r=!1;for(let i of e){let e=i.message;i.type===`assistant`&&e?.usage&&(t.set(e.id??String(t.size),e.usage),r=!0),i.type===`result`&&(n+=i.usage?.output_tokens??0,r=!1)}let i=[...t.values()].at(-1);return{turns:t.size,written:n===0&&r?void 0:`${n.toLocaleString()} tokens written${r?` before this turn`:``}`,context:i?(i.input_tokens??0)+(i.cache_read_input_tokens??0)+(i.cache_creation_input_tokens??0):0}}var _d=[`starting`,`running`,`collecting`,`checking`,`reviewing`];function vd({data:e,selected:t,go:n}){let r=Cd(),i=bd(e),a=i.find(e=>e.id===t)??i[0],[o,s]=(0,l.useState)(!1);return(0,l.useEffect)(()=>s(!1),[a?.id]),a?(0,x.jsxs)(`div`,{className:`agents`,children:[(0,x.jsx)(`div`,{className:`cards`,children:i.map(e=>(0,x.jsxs)(`button`,{className:e.id===a.id?`card selected`:`card`,onClick:()=>n(`agents`,e.id),children:[(0,x.jsxs)(`div`,{children:[(0,x.jsx)(`b`,{children:e.role}),` · `,e.title]}),(0,x.jsxs)(`div`,{children:[(0,x.jsx)(ie,{status:e.status}),` `,(0,x.jsx)(`span`,{className:`muted small`,children:e.detail})]}),e.clock&&(0,x.jsx)(Sd,{now:r,...e.clock})]},e.id))}),(0,x.jsxs)(`div`,{className:`panel`,children:[(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsxs)(`h2`,{children:[a.role,` · `,a.title]}),a.sandbox&&e.terminals&&(0,x.jsx)(`button`,{onClick:()=>s(!o),children:o?`Back`:`Shell in ${a.sandbox}`}),a.sandbox&&(0,x.jsx)(`button`,{onClick:()=>n(`sandboxes`,a.sandbox),children:`Its sandbox`})]}),o&&a.sandbox?(0,x.jsx)(Js,{sandbox:a.sandbox}):(0,x.jsx)(yd,{card:a,data:e})]})]}):(0,x.jsx)(`p`,{className:`muted`,children:`No agent yet: start a module in the Project view.`})}function yd({card:e,data:t}){if(e.role===`Architect`){let n=e.id.slice(10);return t.terminals?(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(Js,{module:n}),(0,x.jsxs)(`p`,{className:`muted small`,children:[`The Architect's own terminal, shared with `,(0,x.jsxs)(`code`,{children:[`harness attach `,n]}),`. It keeps running when you leave.`]})]}):(0,x.jsxs)(`p`,{children:[`The dashboard has no terminals here: node-pty didn't install. Use `,(0,x.jsxs)(`code`,{children:[`harness attach `,n]}),`.`]})}if(e.role===`Manager`){let t=e.id.slice(8);return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(dd,{agent:e.id}),(0,x.jsx)(ae,{label:`Send to the Manager`,placeholder:`A message for the Manager`,onSend:e=>p(`/api/message`,{text:e,manager:t})})]})}return(0,x.jsxs)(x.Fragment,{children:[e.refused&&e.refused.length>0&&(0,x.jsxs)(`details`,{className:`refused`,children:[(0,x.jsxs)(`summary`,{children:[`Its sandbox's network policy refused `,xd(e.refused)]}),(0,x.jsx)(se,{denials:e.refused})]}),(0,x.jsx)(dd,{agent:e.id})]})}function bd({state:e}){let t=[];for(let n of Object.values(e.modules))t.push({id:`architect:${n.module}`,role:`Architect`,title:n.module,status:n.architect.status,detail:`module ${n.status.replace(/_/g,` `)}`,sandbox:n.architect.sandbox});for(let n of Object.values(e.managers)){let e=n.plan?`${n.module}/${n.plan}`:n.module;t.push({id:`manager:${e}`,role:`Manager`,title:e,status:n.status,detail:n.launchedAt?`launch ${n.launch}, at ${_(n.launchedAt)}`:``,...n.status===`stopped`?{}:{sandbox:n.sandbox}})}let n=Object.values(e.plans).flatMap(e=>(e.reviews??[]).map(t=>({plan:e,review:t}))).sort((e,t)=>Date.parse(t.review.startedAt)-Date.parse(e.review.startedAt)).slice(0,6);for(let{plan:e,review:r}of n){let n=r.status===`starting`||r.status===`running`,i=r.findings?.filter(e=>e.severity===`blocking`).length??0;t.push({id:`${r.role}:${r.head.slice(0,8)}`,role:`Reviewer`,title:`${e.module}/${e.plan} · ${r.role} · ${r.head.slice(0,8)}`,status:r.status===`reported`?r.verdict??`reported`:r.status,detail:r.status===`reported`?`${i} blocking`:r.status===`failed`?r.reason??``:``,...n?{sandbox:r.sandbox,clock:{start:Date.parse(r.startedAt),deadline:Date.parse(r.deadline)}}:{}})}let r=Object.values(e.features).flatMap(e=>e.attempts.map(t=>({feature:e,attempt:t}))).sort((e,t)=>Date.parse(t.attempt.startedAt)-Date.parse(e.attempt.startedAt)).slice(0,8);for(let{feature:e,attempt:n}of r){let r=_d.includes(n.status),i=n.usage?` · ${n.usage.outputTokens.toLocaleString()} tokens written`:``,a=n.refused?.length?` · refused ${xd(n.refused)}`:``;t.push({id:`developer:${e.module}/${e.plan}/${e.feature}/a${n.n}`,role:`Developer`,title:`${e.module}/${e.plan}/${e.feature} #${n.n}`,status:n.status,detail:r?a.slice(3):`${n.outcome??``}${i}${a}`,...r?{sandbox:n.sandbox,clock:{start:Date.parse(n.startedAt),deadline:Date.parse(n.deadline)}}:{},...n.refused?{refused:n.refused}:{}})}return t}function xd(e){let t=[...new Set(e.map(e=>e.target))];return t.length>1?`${t[0]} and ${t.length-1} more`:t[0]??``}function Sd({now:e,start:t,deadline:n}){let r=Math.min(1,(e-t)/Math.max(1,n-t));return(0,x.jsxs)(`div`,{className:`deadline`,title:`deadline ${new Date(n).toLocaleTimeString()}`,children:[(0,x.jsx)(`div`,{style:{width:`${r*100}%`},className:r>.8?`late`:void 0}),(0,x.jsxs)(`span`,{className:`small muted`,children:[v(e-t),` of `,v(n-t)]})]})}function Cd(){let[e,t]=(0,l.useState)(Date.now());return(0,l.useEffect)(()=>{let e=window.setInterval(()=>t(Date.now()),1e3);return()=>window.clearInterval(e)},[]),e}function wd(){let[e,t]=(0,l.useState)([]),[n,r]=(0,l.useState)(``),[i,a]=(0,l.useState)(!1),[o,s]=(0,l.useState)();(0,l.useEffect)(()=>{h(`/api/log?limit=1000`).then(e=>t(e??[]))},[]),d({event:e=>t(t=>t.some(t=>t.id===e.id)?t:[...t.slice(-1999),e])});let c=n.toLowerCase().split(/\s+/).filter(Boolean),u=e.filter(e=>!i||e.kind===`message`).filter(e=>{let t=`${e.type} ${g(e.ref)} ${e.from??``} ${e.to??``} ${JSON.stringify(e.payload)}`.toLowerCase();return c.every(e=>t.includes(e))}).reverse();return(0,x.jsxs)(`div`,{className:`stack`,children:[(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsx)(`input`,{value:n,placeholder:`Filter: a type, a module, any word`,size:40,onChange:e=>r(e.target.value)}),(0,x.jsxs)(`label`,{children:[(0,x.jsx)(`input`,{type:`checkbox`,checked:i,onChange:e=>a(e.target.checked)}),` messages only`]}),(0,x.jsxs)(`span`,{className:`muted small`,children:[u.length,` of `,e.length]})]}),(0,x.jsx)(`table`,{className:`events`,children:(0,x.jsx)(`tbody`,{children:u.slice(0,500).map(e=>(0,x.jsxs)(`tr`,{onClick:()=>s(o===e.id?void 0:e.id),children:[(0,x.jsx)(`td`,{className:`mono muted`,children:_(e.ts)}),(0,x.jsx)(`td`,{className:e.kind===`message`?`mono run`:`mono`,children:e.type}),(0,x.jsx)(`td`,{className:`mono`,children:g(e.ref)}),(0,x.jsx)(`td`,{className:`muted`,children:e.from?`${e.from}${e.to?` → ${e.to}`:``}`:``}),(0,x.jsxs)(`td`,{children:[te(e),o===e.id&&(0,x.jsx)(`pre`,{children:JSON.stringify(e.payload,null,2)})]})]},e.id))})})]})}function Td({state:e,design:t},n){let r=[],i=e.pipeline.paused;i&&r.push({key:`paused`,title:`The pipeline is paused`,body:(0,x.jsxs)(x.Fragment,{children:[(0,x.jsxs)(`p`,{children:[i.reason,i.until?` (until ${_(i.until)})`:``]}),(0,x.jsx)(oe,{label:`Resume`,onClick:()=>p(`/api/resume`)})]})});for(let t of Object.values(e.ownerRequests).filter(e=>e.answer===void 0))r.push({key:`request:${t.requestId}`,title:`The Manager of ${t.module} asks you`,body:(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(ud,{text:t.question}),t.blocking&&(0,x.jsx)(`p`,{className:`muted small`,children:`No new attempt starts until you answer.`}),(0,x.jsx)(ae,{label:`Answer`,placeholder:`Your answer`,onSend:e=>p(`/api/answer`,{id:t.requestId,text:e})})]})});for(let t of Object.values(e.modules)){let e=t.proposals.at(-1);t.status===`proposed`&&e&&!e.acceptedAt&&r.push({key:`proposal:${t.module}`,title:`The Architect of ${t.module} proposes v${e.version}`,body:(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(Od,{module:t.module,version:e.version}),(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsx)(oe,{label:`Accept v${e.version}`,onClick:()=>p(`/api/accept`,{module:t.module,version:e.version})}),(0,x.jsx)(`button`,{onClick:()=>n(`agents`,`architect:${t.module}`),children:`Ask for changes in its terminal`})]})]})}),t.status===`in_review`&&t.design?.pr&&r.push({key:`design:${t.module}`,title:t.frozenCommit?`A change to the design of ${t.module} is ready for review`:`The design of ${t.module} is ready for review`,body:(0,x.jsxs)(x.Fragment,{children:[t.frozenCommit&&(0,x.jsx)(`p`,{children:t.design.summary}),(0,x.jsxs)(`p`,{children:[`Review and merge `,(0,x.jsxs)(S,{url:t.design.pr.url,children:[`Arch PR #`,t.design.pr.number]}),` on GitHub, with a merge commit. Comments go to the Architect, in its terminal.`,t.frozenCommit&&` No new attempt starts in this module until you merge or close it.`]}),t.design.suspect.length>0&&(0,x.jsxs)(`p`,{className:`warn small`,children:[`These checks already pass before anything is built, so they may test nothing: `,t.design.suspect.join(`, `)]})]})});for(let e of t.escalations.filter(e=>e.decision===void 0))r.push({key:`escalation:${e.id}`,title:`The Manager asks the Architect of ${t.module}${e.feature?` about ${e.feature}`:``}`,body:(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(ud,{text:e.question}),(0,x.jsx)(`p`,{className:`muted small`,children:`The Architect decides. You can weigh in at its terminal.`}),(0,x.jsx)(`button`,{onClick:()=>n(`agents`,`architect:${t.module}`),children:`Open its terminal`})]})})}for(let t of Object.values(e.plans).filter(e=>e.pr&&(e.status===`ready_to_merge`||e.status===`pr_open`)))t.leftToOwner?r.push({key:`left:${t.module}/${t.plan}`,title:`PR #${t.pr.number} of ${t.module}/${t.plan} waits for you`,body:(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(`p`,{children:t.leftToOwner.reason}),(0,x.jsxs)(`p`,{children:[`Review and merge `,(0,x.jsxs)(S,{url:t.pr.url,children:[`PR #`,t.pr.number]}),` on GitHub, with a merge commit.`]})]})}):t.status===`ready_to_merge`&&r.push({key:`merge:${t.module}/${t.plan}`,title:`${t.module}/${t.plan} passed CI`,body:(0,x.jsxs)(`p`,{children:[`Review and merge `,(0,x.jsxs)(S,{url:t.pr.url,children:[`PR #`,t.pr.number]}),` on GitHub, with a merge commit.`]})});for(let t of Object.values(e.features).filter(e=>e.status===`escalated`))r.push({key:`escalated:${kd(t)}`,title:`${kd(t)} is escalated`,body:(0,x.jsx)(Dd,{feature:t})});for(let n of t.modules)for(let t of n.actions){let i=e.actions[`${n.id}/${t.id}`];i?.status!==`done`&&r.push({key:`action:${n.id}/${t.id}`,title:`${t.kind===`gate`?`Gate`:`Task`} ${n.id}/${t.id}: ${t.title}`,body:(0,x.jsxs)(x.Fragment,{children:[t.blocks.length>0&&(0,x.jsxs)(`p`,{className:`muted small`,children:[`Holds back `,t.blocks.join(`, `),` until it's done.`]}),(0,x.jsx)(ud,{text:t.instructions}),i?.status===`verifying`&&(0,x.jsx)(`p`,{className:`run small`,children:`Its check is running.`}),i?.status===`refused`&&(0,x.jsxs)(`p`,{className:`bad small`,children:[`Its check failed: `,i.reason]}),(0,x.jsx)(ae,{label:t.verify?`Done: run its check`:`Mark done`,placeholder:`A note (optional)`,optional:!0,onSend:e=>p(`/api/actions/done`,{module:n.id,action:t.id,...e?{note:e}:{}})})]})})}return r}function Ed({data:e,go:t}){let n=Td(e,t);return n.length===0?(0,x.jsx)(`p`,{className:`muted`,children:`Nothing needs you.`}):(0,x.jsx)(`div`,{className:`stack`,children:n.map(e=>(0,x.jsxs)(`section`,{className:`need`,children:[(0,x.jsx)(`h3`,{children:e.title}),e.body]},e.key))})}function Dd({feature:e}){let[t,n]=(0,l.useState)(``);return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(`p`,{children:e.escalation}),(0,x.jsx)(`textarea`,{rows:2,value:t,placeholder:`Guidance for one more attempt, or a note for the Architect`,onChange:e=>n(e.target.value)}),(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsx)(oe,{label:`One more attempt`,onClick:()=>p(`/api/retry`,{feature:kd(e),...t.trim()?{note:t}:{}})}),(0,x.jsx)(oe,{label:`Send it to the Architect`,onClick:()=>p(`/api/architect`,{module:e.module,text:`[owner] ${kd(e)} is escalated: ${e.escalation??``} ${t.trim()?`My note: ${t.trim()}`:``} Should the design change?`})}),(0,x.jsx)(oe,{label:`Pause the pipeline`,onClick:()=>p(`/api/pause`)})]})]})}function Od({module:e,version:t}){let[n,r]=(0,l.useState)();return(0,l.useEffect)(()=>{m(`/api/proposal?module=${encodeURIComponent(e)}&version=${t}`).then(r)},[e,t]),(0,x.jsx)(`div`,{className:`document`,children:n===void 0?(0,x.jsx)(`p`,{className:`muted`,children:`Loading…`}):(0,x.jsx)(ud,{text:n})})}var kd=e=>`${e.module}/${e.plan}/${e.feature}`,Ad=230,jd=28,Md=34,Nd=10,Pd=42,Fd=26,Id=[`starting`,`running`,`collecting`,`checking`,`reviewing`];function Ld({module:e,state:t}){let n=e.plans.map((e,t)=>({plan:e,x:t*258})),r=Pd+Math.max(1,...e.plans.map(e=>e.features.length))*Md+Nd,i=Math.max(1,n.length*258-jd),a=new Map;for(let{plan:e,x:t}of n)e.features.forEach((n,r)=>a.set(`${e.id}/${n.id}`,{x:t+Nd,y:Pd+r*Md}));let o=e.plans.flatMap(e=>e.features.flatMap(t=>t.dependsOn.map(n=>({from:n.includes(`/`)?n:`${e.id}/${n}`,to:`${e.id}/${t.id}`}))));return(0,x.jsx)(`div`,{className:`graph-wrap`,children:(0,x.jsxs)(`svg`,{className:`graph`,width:i,height:r,viewBox:`0 0 ${i} ${r}`,role:`img`,"aria-label":`The features of ${e.id} and their dependencies`,children:[(0,x.jsx)(`defs`,{children:(0,x.jsx)(`marker`,{id:`arrow`,viewBox:`0 0 8 8`,refX:`7`,refY:`4`,markerWidth:`7`,markerHeight:`7`,orient:`auto-start-reverse`,children:(0,x.jsx)(`path`,{d:`M 0 0 L 8 4 L 0 8 z`})})}),n.map(({plan:n,x:i})=>{let o=t.plans[`${e.id}/${n.id}`];return(0,x.jsxs)(`g`,{className:`plan-box ${Bd(o)}`,children:[(0,x.jsx)(`rect`,{x:i,y:0,width:Ad,height:r,rx:8}),(0,x.jsx)(`text`,{x:i+Nd,y:17,className:`plan-title`,children:Hd(`${n.id} · ${n.title}`,30)}),(0,x.jsx)(`text`,{x:i+Nd,y:32,className:`plan-status`,children:Vd(o,n.dependsOn)}),n.features.map(r=>{let i=a.get(`${n.id}/${r.id}`),o=t.features[`${e.id}/${n.id}/${r.id}`];return(0,x.jsxs)(`g`,{className:`node ${Rd(o)}`,children:[(0,x.jsx)(`title`,{children:`${r.id} ${r.title}: ${zd(o)}`}),(0,x.jsx)(`rect`,{x:i.x,y:i.y,width:210,height:Fd,rx:5}),(0,x.jsx)(`text`,{x:i.x+8,y:i.y+17,children:Hd(`${r.id} ${r.title}`,28)})]},r.id)})]},n.id)}),o.map(({from:e,to:t})=>{let n=a.get(e),r=a.get(t);if(!n||!r)return null;let i=n.x===r.x?`M ${n.x+210} ${n.y+Fd/2} C ${n.x+210+16} ${n.y+Fd/2}, ${r.x+210+16} ${r.y+Fd/2}, ${r.x+210} ${r.y+Fd/2}`:`M ${n.x+210} ${n.y+Fd/2} C ${n.x+210+jd} ${n.y+Fd/2}, ${r.x-jd} ${r.y+Fd/2}, ${r.x} ${r.y+Fd/2}`;return(0,x.jsx)(`path`,{d:i,className:`edge`,markerEnd:`url(#arrow)`},`${e}>${t}`)})]})})}function Rd(e){if(!e)return`muted`;if(e.status===`accepted`)return`ok`;if(e.status===`escalated`)return`bad`;let t=e.attempts.at(-1);return t&&Id.includes(t.status)?`run`:e.attempts.length>0?`warn`:`muted`}function zd(e){if(!e)return`not started`;let t=e.attempts.at(-1);return`${e.status.replace(/_/g,` `)}${t?`, attempt ${t.n} ${t.status}`:``}`}function Bd(e){return e?e.status===`completed`||e.mergeCommit?`ok`:e.leftToOwner||e.status===`ready_to_merge`||e.status===`deploy_failed`||e.status===`verify_failed`?`warn`:`run`:`muted`}function Vd(e,t){return e?`${e.status===`completed`?`complete`:e.mergeCommit?`merged, ${e.status}`:e.status.replace(/_/g,` `)}${e.pr?` · PR #${e.pr.number}`:``}${e.mergedBy===`daemon`?` · by the daemon`:``}`:t.length>0?`after ${t.join(`, `)}`:`not started`}var Hd=(e,t)=>e.length>t?`${e.slice(0,t-1)}…`:e;function Ud({data:e}){let{state:t,design:n,progress:r}=e,[i,a]=(0,l.useState)(),o=[...new Set([...Object.keys(t.modules),...n.modules.map(e=>e.id)])];return(0,x.jsxs)(`div`,{className:`stack`,children:[(0,x.jsx)(Kd,{state:t,ceiling:n.swarm?.usageCeilingPercent??null,...e.ownerTime?{ownerTime:e.ownerTime}:{},...e.usage?{usage:e.usage}:{}}),(0,x.jsx)(Jd,{}),o.length===0&&(0,x.jsx)(`p`,{className:`muted`,children:`No module yet.`}),o.map(r=>{let o=t.modules[r],s=n.modules.find(e=>e.id===r),c=s?.plans??[];return(0,x.jsxs)(`section`,{children:[(0,x.jsxs)(`h2`,{children:[r,` `,o&&(0,x.jsxs)(`span`,{className:`muted`,children:[`· `,o.title]}),` `,o&&(0,x.jsx)(ie,{status:o.status}),o?.design?.pr&&(0,x.jsxs)(x.Fragment,{children:[` · `,(0,x.jsxs)(S,{url:o.design.pr.url,children:[`Arch PR #`,o.design.pr.number]})]})]}),o&&o.proposals.length>0&&(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsx)(`span`,{className:`muted`,children:`Proposals:`}),o.proposals.map(e=>(0,x.jsxs)(`button`,{className:i?.module===r&&i.version===e.version?`selected`:void 0,onClick:()=>a(i?.module===r&&i.version===e.version?void 0:{module:r,version:e.version}),children:[`v`,e.version,e.acceptedAt?` ✓`:``]},e.version))]}),i?.module===r&&(0,x.jsx)(Od,{module:r,version:i.version}),s&&c.length>0&&(0,x.jsx)(Ld,{module:s,state:t}),c.map(t=>(0,x.jsx)(Yd,{data:e,module:r,plan:t},t.id))]},r)}),(0,x.jsxs)(`section`,{children:[(0,x.jsx)(`h2`,{children:`progress.txt`}),(0,x.jsx)(`pre`,{children:r.join(`
|
|
96
|
-
`)||`(nothing accepted yet)`})]})]})}function Wd(e,t=6e4){let[n,r]=(0,l.useState)(()=>Date.now());return(0,l.useEffect)(()=>{if(r(Date.now()),!e)return;let n=window.setInterval(()=>r(Date.now()),t);return()=>window.clearInterval(n)},[e,t]),n}function Gd(e,t){let n=0;for(let r of Object.values(e.features))for(let e of r.attempts){let r=Date.parse(e.startedAt),i=e.endedAt?Date.parse(e.endedAt):t;Number.isFinite(r)&&Number.isFinite(i)&&(n+=Math.max(0,i-r))}return n}function Kd({state:e,ownerTime:t,usage:n,ceiling:r}){let i=e.budgets??{},a=t!==void 0&&t.committedHours>t.plannableHours,o=t!==void 0&&t.usedHours>t.hours,s=Gd(e,Wd(Object.values(e.features).some(e=>e.attempts.some(e=>!e.endedAt))))/36e5,c=i.agentHours!==void 0&&s>i.agentHours;return(0,x.jsxs)(`section`,{children:[(0,x.jsx)(`h2`,{children:`Budgets`}),(0,x.jsxs)(`div`,{className:`meters`,children:[(0,x.jsxs)(`div`,{className:`meter`,children:[(0,x.jsx)(`h3`,{children:`Your time`}),(0,x.jsx)(`p`,{children:i.ownerHours===void 0?(0,x.jsx)(`span`,{className:`muted`,children:`Not set: the Architect designs without a limit on your time.`}):(0,x.jsxs)(x.Fragment,{children:[(0,x.jsxs)(`b`,{children:[i.ownerHours,` h`]}),` for the whole project`,i.ownerHoursSetAt&&(0,x.jsxs)(`span`,{className:`muted`,children:[`, set `,new Date(i.ownerHoursSetAt).toLocaleDateString()]})]})}),t&&(0,x.jsxs)(`p`,{children:[(0,x.jsxs)(`b`,{className:o?`bad`:void 0,children:[t.usedHours,` h used`]}),` · `,(0,x.jsxs)(`span`,{className:a?`bad`:void 0,children:[t.committedHours,` h planned by the designs`]}),` · `,(0,x.jsxs)(`span`,{className:`muted`,children:[t.plannableHours,` h plannable after the `,t.reservePercent,`% reserve`]})]}),(0,x.jsx)(qd,{field:`ownerHours`,value:i.ownerHours,placeholder:`hours`}),(0,x.jsx)(`p`,{className:`muted small`,children:t?`Planned: the estimates of the owner actions in submitted designs, gates and tasks. Used: the actions you marked done. What you read, proposals and pull requests, doesn't count.`:`Once set, the Architect must estimate every owner action and design within your hours; a design over them is refused.`})]}),(0,x.jsxs)(`div`,{className:`meter`,children:[(0,x.jsx)(`h3`,{children:`Agent hours`}),(0,x.jsxs)(`p`,{children:[(0,x.jsxs)(`b`,{className:c?`bad`:void 0,children:[s.toFixed(1),` h`]}),i.agentHours===void 0?(0,x.jsx)(`span`,{className:`muted`,children:` · no threshold`}):(0,x.jsxs)(x.Fragment,{children:[` of `,i.agentHours,` h`]}),c&&(0,x.jsx)(`span`,{className:`bad small`,children:` · over the threshold`})]}),(0,x.jsx)(qd,{field:`agentHours`,value:i.agentHours,placeholder:`threshold`}),(0,x.jsx)(`p`,{className:`muted small`,children:`The Developers' attempt clocks, summed. Over the threshold the number turns red; nothing stops.`})]}),n&&(0,x.jsxs)(`div`,{className:`meter`,children:[(0,x.jsx)(`h3`,{children:`Subscription`}),(0,x.jsx)(`p`,{children:n.usage.limits.map((e,t)=>(0,x.jsxs)(`span`,{children:[t>0&&` · `,(0,x.jsxs)(`b`,{className:r!==null&&e.percent>=r?`bad`:void 0,children:[e.percent,`%`]}),` `,e.kind.replace(/_/g,` `),`, resets `,new Date(e.resetsAt).toLocaleString()]},e.kind))}),(0,x.jsxs)(`p`,{className:`muted small`,children:[`Read `,new Date(n.at).toLocaleTimeString(),`. In Swarm no attempt starts at or above `,r===null?`the ceiling, which is off`:`${r}%`,`; the pipeline waits for the window to reset.`]})]})]})]})}function qd({field:e,value:t,placeholder:n}){let[r,i]=(0,l.useState)(``),a=Number(r),o=r.trim()!==``&&Number.isFinite(a)&&a>0;return(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsx)(`input`,{type:`number`,min:1,step:1,value:r,placeholder:n,onChange:e=>i(e.target.value)}),(0,x.jsx)(oe,{label:`Save`,onClick:async()=>{if(!o)return{done:!1,error:`a positive number of hours`};let t=await p(`/api/budgets`,{[e]:a});return t.done&&i(``),t}}),t!==void 0&&(0,x.jsx)(oe,{label:`Clear`,onClick:()=>p(`/api/budgets`,{[e]:null})})]})}function Jd(){let[e,t]=(0,l.useState)(``),[n,r]=(0,l.useState)(``),[i,a]=(0,l.useState)();return(0,x.jsxs)(`section`,{children:[(0,x.jsx)(`h2`,{children:`Start a module`}),(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsx)(`input`,{value:e,placeholder:`id, like orders`,size:14,onChange:e=>t(e.target.value)}),(0,x.jsx)(`input`,{value:n,placeholder:`what it is for`,size:36,onChange:e=>r(e.target.value)}),(0,x.jsx)(`button`,{disabled:!e.trim(),onClick:async()=>{let i=await p(`/api/modules`,{id:e.trim(),title:n});a(i.done?void 0:i.error),i.done&&(t(``),r(``))},children:`Start`}),i&&(0,x.jsx)(`span`,{className:`bad small`,children:i})]}),(0,x.jsx)(`p`,{className:`muted small`,children:`An Architect starts in its own sandbox; talk to it in the Agents view.`})]})}function Yd({data:e,module:t,plan:n}){let{state:r,design:i}=e,a=`${t}/${n.id}`,o=r.plans[a],s=Object.values(r.features).filter(e=>e.fix&&`${e.module}/${e.plan}`===a),c=[...n.features.map(e=>({id:e.id,title:e.title,kind:e.kind,state:r.features[`${a}/${e.id}`]})),...s.map(e=>({id:e.feature,title:`Fix after ${e.fix.kind===`ci`?`CI`:`the deploy`}, round ${e.fix.round}`,kind:`fix`,state:e}))];return(0,x.jsxs)(`div`,{className:`plan`,children:[(0,x.jsxs)(`h3`,{children:[n.id,` · `,n.title,` `,(0,x.jsx)(ie,{status:o?.status??`waiting`}),o?.pr&&(0,x.jsxs)(x.Fragment,{children:[` `,(0,x.jsxs)(S,{url:o.pr.url,children:[`PR #`,o.pr.number]})]}),o?.mergedBy===`daemon`&&(0,x.jsxs)(x.Fragment,{children:[` `,(0,x.jsx)(`span`,{className:`muted small`,children:`merged by the daemon`})]}),o?.ci&&(0,x.jsxs)(x.Fragment,{children:[` `,(0,x.jsx)(ie,{status:o.ci.status,label:`CI ${o.ci.status}`})]})]}),o&&(0,x.jsx)(Xd,{plan:o}),(0,x.jsxs)(`table`,{children:[(0,x.jsx)(`thead`,{children:(0,x.jsxs)(`tr`,{children:[(0,x.jsx)(`th`,{children:`Feature`}),(0,x.jsx)(`th`,{children:`Status`}),(0,x.jsx)(`th`,{children:`Attempts`}),(0,x.jsx)(`th`,{children:`Last attempt`}),(0,x.jsx)(`th`,{children:`Commit`})]})}),(0,x.jsx)(`tbody`,{children:c.map(e=>(0,x.jsx)(Zd,{id:e.id,title:e.title,kind:e.kind,feature:e.state,max:i.maxAttempts},e.id))})]})]})}function Xd({plan:e}){let t=e.ci?.jobs?.filter(e=>e.conclusion!==`success`)??[];return(0,x.jsxs)(`div`,{className:`small`,children:[t.map(e=>(0,x.jsxs)(`details`,{children:[(0,x.jsxs)(`summary`,{className:`bad`,children:[`CI job `,e.name,`: `,e.conclusion]}),(0,x.jsx)(`pre`,{children:e.log??``})]},e.name)),e.reviews?.filter(t=>t.head===e.pushed).map(e=>(0,x.jsxs)(`p`,{children:[e.role===`security`?`Security`:`Cost`,` review of `,e.head.slice(0,8),`:`,` `,e.status===`reported`?(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(ie,{status:e.verdict===`pass`?`passed`:`failed`,label:e.verdict}),` `,e.findings?.filter(e=>e.severity===`blocking`).length??0,` blocking, `,e.findings?.filter(e=>e.severity===`advisory`).length??0,` advisory`,e.posted?.problem?(0,x.jsxs)(`span`,{className:`bad`,children:[` · `,e.posted.problem]}):null]}):e.status===`failed`?(0,x.jsxs)(`span`,{className:`bad`,children:[`failed: `,e.reason]}):(0,x.jsx)(ie,{status:e.status})]},`${e.role}-${e.head}`)),e.deploy&&(0,x.jsxs)(`p`,{children:[`Deploy of `,e.deploy.commit.slice(0,8),`: `,(0,x.jsx)(ie,{status:e.deploy.status}),e.deploy.jobs?.filter(e=>e.conclusion!==`success`).map(e=>(0,x.jsxs)(S,{url:e.url,children:[` `,e.name]},e.name))]}),e.postDeploy&&(0,x.jsxs)(`details`,{children:[(0,x.jsxs)(`summary`,{children:[`Post-deploy checks: `,(0,x.jsx)(ie,{status:e.postDeploy.status})]}),e.postDeploy.results.map(e=>(0,x.jsxs)(`div`,{children:[(0,x.jsx)(`b`,{className:e.exit===0?`ok`:`bad`,children:e.id}),` `,(0,x.jsx)(`span`,{className:`muted`,children:v(e.durationMs)}),(0,x.jsx)(`pre`,{children:e.output})]},e.id))]})]})}function Zd({id:e,title:t,kind:n,feature:r,max:i}){let a=r?.attempts.at(-1),o=a?r.attempts.filter(e=>e.definitionHash===a.definitionHash&&e.counts).length:0,s=a?.endedAt?Date.parse(a.endedAt):Date.now();return(0,x.jsxs)(`tr`,{children:[(0,x.jsxs)(`td`,{children:[(0,x.jsx)(`b`,{children:e}),` `,t,` `,n===`infra`&&(0,x.jsx)(`span`,{className:`muted small`,children:`infra`})]}),(0,x.jsxs)(`td`,{children:[(0,x.jsx)(ie,{status:r?.status??`pending`}),r?.awaitingGuidanceSince&&(0,x.jsx)(`div`,{className:`muted small`,children:`waiting for the Manager's guidance`})]}),(0,x.jsx)(`td`,{className:`mono`,children:r?`${r.attempts.length}${o?` (${o} of ${i+r.extraAttempts} counted)`:``}`:`0`}),(0,x.jsx)(`td`,{children:a&&(0,x.jsxs)(x.Fragment,{children:[a.status,a.outcome?` · ${a.outcome}`:``,` · `,v(s-Date.parse(a.startedAt)),a.reason&&(0,x.jsx)(`div`,{className:`small`,children:a.reason}),a.finding&&(0,x.jsxs)(`details`,{className:`small`,children:[(0,x.jsxs)(`summary`,{children:[`Finding: `,a.finding.category]}),(0,x.jsx)(`p`,{children:a.finding.summary}),a.finding.needs&&(0,x.jsxs)(`p`,{children:[`Needs: `,a.finding.needs]})]}),a.questions?.map(e=>(0,x.jsxs)(`div`,{className:`small muted`,children:[`Q: `,e.text,e.answer?` — A: ${e.answer}`:``]},e.requestId))]})}),(0,x.jsx)(`td`,{className:`mono`,children:r?.commit?.slice(0,8)??``})]})}function Qd({go:e,selected:t}){let[n,r]=(0,l.useState)(),[i,a]=(0,l.useState)(!1),o=$d(),s=async()=>{let e=await h(`/api/sandboxes`).catch(()=>void 0);a(e===void 0),e&&r(e)};if((0,l.useEffect)(()=>{s();let e=window.setInterval(()=>void s(),1e4);return()=>window.clearInterval(e)},[]),!n)return(0,x.jsx)(`p`,{className:`muted`,children:i?`The daemon could not read the sandboxes.`:`Reading the sandboxes from OpenShell…`});let{gateway:c,machine:u}=n,d=n.sandboxes.filter(e=>e.use!==`other`),f=n.sandboxes.filter(e=>e.use===`other`),m=d.find(e=>e.name===t),g=d.filter(e=>e.use===`left over`).length,_=e=>o?.sandboxes[e.name],v=async e=>{if(!window.confirm(`Delete ${e.name}? Nothing of this project uses it any more.`))return{done:!0};let t=await p(`/api/sandboxes/remove`,{name:e.name});return await s(),t};return(0,x.jsxs)(`div`,{className:`stack`,children:[(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsx)(`b`,{children:`OpenShell`}),(0,x.jsx)(ie,{status:c.status===`connected`?`running`:`failed`,label:c.status}),c.version&&(0,x.jsx)(`span`,{className:`muted small`,children:c.version}),(0,x.jsxs)(`span`,{className:`muted small`,children:[n.sandboxes.length,` sandbox`,n.sandboxes.length===1?``:`es`,` on the gateway, `,d.length,` of this project`,g>0?`, ${g} left over`:``]}),(0,x.jsx)(`button`,{onClick:()=>void s(),children:`Refresh`})]}),c.problem&&(0,x.jsx)(`p`,{className:`bad small`,children:c.problem}),n.problem&&(0,x.jsx)(`p`,{className:`bad small`,children:n.problem}),(0,x.jsx)(`p`,{className:`small`,children:o?(0,x.jsxs)(x.Fragment,{children:[`Every OpenShell sandbox on this machine uses `,(0,x.jsx)(`b`,{children:b(o.total.cpu)}),` of its `,u.cpus,` CPUs and `,(0,x.jsx)(`b`,{children:y(o.total.memoryBytes)}),` of its `,u.memoryGiB,` GiB`,(0,x.jsx)(`span`,{className:`muted`,children:`, OpenShell's supervisors included · docker stats, every 5 s`})]}):(0,x.jsx)(`span`,{className:`muted`,children:`Reading what the sandboxes use from Docker…`})}),o?.problem&&(0,x.jsx)(`p`,{className:`bad small`,children:o.problem}),(0,x.jsx)(tf,{limits:n.limits,machine:u,onSaved:s},`${n.limits.cpu??``}/${n.limits.memoryGiB??``}`),(0,x.jsxs)(`section`,{children:[(0,x.jsx)(`h2`,{children:`This project's`}),d.length===0?(0,x.jsx)(`p`,{className:`muted`,children:`None on the gateway now.`}):(0,x.jsxs)(`table`,{children:[(0,x.jsx)(`thead`,{children:(0,x.jsxs)(`tr`,{children:[(0,x.jsx)(`th`,{children:`Sandbox`}),(0,x.jsx)(`th`,{children:`For`}),(0,x.jsx)(`th`,{children:`OpenShell`}),(0,x.jsx)(`th`,{children:`Age`}),(0,x.jsx)(`th`,{children:`CPU`}),(0,x.jsx)(`th`,{children:`Memory`}),(0,x.jsx)(`th`,{children:`Use`}),(0,x.jsx)(`th`,{})]})}),(0,x.jsx)(`tbody`,{children:d.map(t=>(0,x.jsxs)(`tr`,{className:t.name===m?.name?`clickable selected`:`clickable`,onClick:()=>e(`sandboxes`,t.name),children:[(0,x.jsx)(`td`,{className:`mono`,children:t.name}),(0,x.jsxs)(`td`,{children:[t.role??`unknown`,t.title?` · ${t.title}`:``]}),(0,x.jsx)(`td`,{children:(0,x.jsx)(ie,{status:t.phase===`Ready`?`running`:t.phase.toLowerCase(),label:t.phase})}),(0,x.jsx)(`td`,{className:`muted`,children:rf(t)}),(0,x.jsx)(ef,{usage:_(t)}),(0,x.jsx)(`td`,{children:(0,x.jsx)(ie,{status:t.use===`in use`?`running`:`waiting`,label:t.use})}),(0,x.jsxs)(`td`,{onClick:e=>e.stopPropagation(),children:[t.agent&&(0,x.jsx)(`button`,{onClick:()=>e(`agents`,t.agent),children:`Agent`}),t.use===`left over`&&(0,x.jsx)(oe,{label:`Delete`,danger:!0,onClick:()=>v(t)})]})]},t.name))})]}),g>0&&(0,x.jsx)(`p`,{className:`muted small`,children:`A left-over sandbox is this project's, but nothing uses it any more: a crash or a failed delete left it. The daemon deletes them when it starts and every hour, except an Architect's, which holds your conversation with it; Delete removes one now.`})]}),m&&(0,x.jsx)(nf,{sandbox:m}),f.length>0&&(0,x.jsxs)(`section`,{children:[(0,x.jsx)(`h2`,{children:`Other sandboxes on this gateway`}),(0,x.jsxs)(`table`,{children:[(0,x.jsx)(`thead`,{children:(0,x.jsxs)(`tr`,{children:[(0,x.jsx)(`th`,{children:`Sandbox`}),(0,x.jsx)(`th`,{children:`Project`}),(0,x.jsx)(`th`,{children:`OpenShell`}),(0,x.jsx)(`th`,{children:`Age`}),(0,x.jsx)(`th`,{children:`CPU`}),(0,x.jsx)(`th`,{children:`Memory`})]})}),(0,x.jsx)(`tbody`,{children:f.map(e=>(0,x.jsxs)(`tr`,{children:[(0,x.jsx)(`td`,{className:`mono`,children:e.name}),(0,x.jsx)(`td`,{className:`muted`,children:e.project??`no label`}),(0,x.jsx)(`td`,{children:(0,x.jsx)(ie,{status:e.phase===`Ready`?`running`:e.phase.toLowerCase(),label:e.phase})}),(0,x.jsx)(`td`,{className:`muted`,children:rf(e)}),(0,x.jsx)(ef,{usage:_(e)})]},e.name))})]}),(0,x.jsxs)(`p`,{className:`muted small`,children:[`Another project's, a probe of `,(0,x.jsx)(`code`,{children:`harness doctor`}),`, or one made by 0.3.0 or earlier, which didn't label sandboxes. The dashboard deletes none of them; in a terminal: `,(0,x.jsx)(C,{text:`openshell sandbox delete ${f[0].name}`})]})]})]})}function $d(){let[e,t]=(0,l.useState)();return(0,l.useEffect)(()=>{let e=!1,n,r=async()=>{let i=await h(`/api/sandboxes/usage`).catch(()=>void 0);e||(i&&t(i),n=window.setTimeout(()=>void r(),5e3))};return r(),()=>{e=!0,window.clearTimeout(n)}},[]),e}function ef({usage:e}){if(!e)return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(`td`,{}),(0,x.jsx)(`td`,{})]});let t=(e,t)=>t!==void 0&&e>=.9*t?`bad`:void 0;return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsxs)(`td`,{className:t(e.cpu,e.cpuLimit),children:[b(e.cpu),e.cpuLimit!==void 0&&(0,x.jsxs)(`span`,{className:`muted`,children:[` of `,e.cpuLimit]})]}),(0,x.jsxs)(`td`,{className:t(e.memoryBytes,e.memoryLimitBytes),children:[y(e.memoryBytes),e.memoryLimitBytes!==void 0&&(0,x.jsxs)(`span`,{className:`muted`,children:[` of `,y(e.memoryLimitBytes)]})]})]})}function tf({limits:e,machine:t,onSaved:n}){let[r,i]=(0,l.useState)(e.cpu?.toString()??``),[a,o]=(0,l.useState)(e.memoryGiB?.toString()??``),s=e=>e.trim()===``?null:Number(e),c=e.cpu!==void 0||e.memoryGiB!==void 0,u=[e.cpu===void 0?`any CPUs`:`${e.cpu} CPU${e.cpu===1?``:`s`}`,e.memoryGiB===void 0?`any memory`:`${e.memoryGiB} GiB`].join(` and `);return(0,x.jsxs)(`section`,{children:[(0,x.jsx)(`h2`,{children:`Caps on each new sandbox`}),(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsxs)(`label`,{children:[`CPUs `,(0,x.jsx)(`input`,{type:`number`,min:.5,max:t.cpus,step:.5,value:r,placeholder:`no cap`,onChange:e=>i(e.target.value)})]}),(0,x.jsxs)(`label`,{children:[`Memory `,(0,x.jsx)(`input`,{type:`number`,min:1,max:Math.floor(t.memoryGiB),step:1,value:a,placeholder:`no cap`,onChange:e=>o(e.target.value)}),` GiB`]}),(0,x.jsx)(oe,{label:`Save`,onClick:async()=>{let e=await p(`/api/sandboxes/limits`,{cpu:s(r),memoryGiB:s(a)});return e.done&&await n(),e}})]}),(0,x.jsxs)(`p`,{className:`muted small`,children:[c?`Now ${u} for each sandbox. `:`No cap now: each sandbox may use all of this machine's ${t.cpus} CPUs and ${t.memoryGiB} GiB. `,`The caps hold for the sandboxes made from now on, for each attempt, check run, reviewer and Manager; those already there, like an Architect's, keep theirs. Past its memory cap, a sandbox may swap up to as much again; then the process that asks for more is killed, with exit code 137, and the sandbox stays.`]})]})}function nf({sandbox:e}){let[t,n]=(0,l.useState)();return(0,l.useEffect)(()=>{n(void 0);let t=!0,r=()=>void h(`/api/sandboxes/denials?name=${encodeURIComponent(e.name)}`).then(e=>t&&n(e??[]),()=>t&&n([]));r();let i=window.setInterval(r,15e3);return()=>{t=!1,window.clearInterval(i)}},[e.name]),(0,x.jsxs)(`section`,{children:[(0,x.jsxs)(`h2`,{children:[`Refused by its network policy · `,e.name]}),t===void 0?(0,x.jsx)(`p`,{className:`muted`,children:`Reading its log…`}):t.length===0?(0,x.jsx)(`p`,{className:`muted`,children:`Nothing refused in its recent log.`}):(0,x.jsx)(se,{denials:t})]})}function rf(e){return e.createdAt?v(Date.now()-Date.parse(e.createdAt)):``}var af=[[`needs`,`Needs you`],[`project`,`Project`],[`agents`,`Agents`],[`sandboxes`,`Sandboxes`],[`events`,`Events`]];function of(e){let[t,...n]=e.replace(/^#/,``).split(`/`),r=af.some(([e])=>e===t)?t:`needs`;return n.length?{view:r,detail:decodeURIComponent(n.join(`/`))}:{view:r}}function sf({repo:e,setup:t}){let n=t?.rules,r=[...!n||!n.ok?[(0,x.jsxs)(`li`,{children:[`Apply the mode's rules on GitHub, with your own `,(0,x.jsx)(`code`,{children:`gh`}),`: `,(0,x.jsx)(C,{text:`harness github-setup --repo ${e}`}),n&&n.problems.length>0&&(0,x.jsxs)(`span`,{className:`muted`,children:[` · now: `,n.problems.join(`; `)]})]},`rules`)]:[],...t?.statusesWritable===!1||!t?[(0,x.jsxs)(`li`,{children:[`Give the App `,(0,x.jsx)(`b`,{children:`Commit statuses: write`}),` on GitHub, and accept the new permission on its installation.`,t?` The daemon could only get a read token.`:` An App created with this version has it already.`,` Without it the reviewers' verdicts can't be posted, and pull requests wait for you with the reason.`]},`statuses`)]:[],...t?.tokenStored?[]:[(0,x.jsxs)(`li`,{children:[`Store the token the usage probe reads, in a terminal: `,(0,x.jsx)(C,{text:`harness doctor`}),` asks for the one from `,(0,x.jsx)(`code`,{children:`claude setup-token`}),`. Without it the daemon learns of a limit only when an attempt hits it.`]},`token`)]];return r.length===0?null:(0,x.jsxs)(`div`,{className:`note`,children:[(0,x.jsx)(`b`,{children:`Before you use Swarm`}),t?`, ${r.length===1?`one step`:`${r.length} steps`} left`:`, three steps, once`,`. Swarm merges only once GitHub's rules match the mode; `,(0,x.jsx)(`code`,{children:`harness doctor`}),` checks all of it.`,(0,x.jsx)(`ol`,{children:r})]})}function cf(){let{data:e,problem:t,connected:n}=f(),[r,i]=(0,l.useState)(()=>of(location.hash));(0,l.useEffect)(()=>{let e=()=>i(of(location.hash));return window.addEventListener(`hashchange`,e),()=>window.removeEventListener(`hashchange`,e)},[]);let a=(e,t)=>{location.hash=t?`${e}/${encodeURIComponent(t)}`:e};if(!e)return(0,x.jsx)(`div`,{className:`boot`,children:t??`Connecting to the daemon…`});let o=Td(e,a).length,s=e.state.pipeline.paused,c=(e.state.mode??`manual`)===`swarm`,u=(e.state.infraMerge??`ceiling`)===`daemon`;return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsxs)(`header`,{children:[(0,x.jsxs)(`h1`,{children:[`harness · `,e.design.project]}),(0,x.jsx)(ie,{status:s?`paused`:`running`}),s?(0,x.jsx)(oe,{label:`Resume`,onClick:()=>p(`/api/resume`)}):(0,x.jsx)(oe,{label:`Pause`,onClick:()=>p(`/api/pause`)}),(0,x.jsx)(ie,{status:c?`swarm`:`manual`,label:c?`Swarm`:`Manual`}),(0,x.jsx)(oe,{label:c?`Switch to Manual`:`Switch to Swarm`,onClick:()=>{let e=c?`In Manual you review and merge every pull request yourself. Switch to Manual?`:`In Swarm the daemon merges plans that pass CI and the reviewers, without your review. Switch to Swarm?`;return window.confirm(e)?p(`/api/mode`,{mode:c?`manual`:`swarm`}):Promise.resolve({done:!0})}}),c&&(0,x.jsx)(ie,{status:u?`swarm`:`waiting`,label:u?`Infra: the daemon merges`:`Infra: yours`}),c&&(0,x.jsx)(oe,{label:u?`Keep infra merges for me`:`Let the daemon merge infra`,onClick:()=>{let e=u?`INFRA and MIXED plans will wait for you again, unless the cost job's estimate is under the ceiling. Keep their merges for you?`:`INFRA and MIXED plans will merge themselves once CI, the security reviewer and the cost reviewer pass, like CODE plans. An estimate over the ceiling still waits for you. Let the daemon merge infra?`;return window.confirm(e)?p(`/api/infra-merge`,{by:u?`ceiling`:`daemon`}):Promise.resolve({done:!0})}}),(0,x.jsx)(`span`,{className:`muted small`,children:n?e.state.daemon?`daemon ${e.state.daemon.version}, pid ${e.state.daemon.pid}`:`daemon stopped`:`reconnecting…`}),(0,x.jsx)(`nav`,{children:af.map(([e,t])=>(0,x.jsxs)(`a`,{href:`#${e}`,className:r.view===e?`active`:void 0,children:[t,e===`needs`&&o>0&&(0,x.jsx)(`span`,{className:`count`,children:o})]},e))})]}),t&&(0,x.jsx)(`div`,{className:`banner`,children:t}),c&&(e.design.repo?(0,x.jsx)(sf,{repo:e.design.repo,...e.setup?{setup:e.setup}:{}}):(0,x.jsx)(`div`,{className:`note`,children:`Swarm: this project has no GitHub repository, so there is nothing to merge; plans complete locally.`})),(0,x.jsxs)(`main`,{children:[r.view===`needs`&&(0,x.jsx)(Ed,{data:e,go:a}),r.view===`project`&&(0,x.jsx)(Ud,{data:e}),r.view===`agents`&&(0,x.jsx)(vd,{data:e,go:a,...r.detail?{selected:r.detail}:{}}),r.view===`sandboxes`&&(0,x.jsx)(Qd,{go:a,...r.detail?{selected:r.detail}:{}}),r.view===`events`&&(0,x.jsx)(wd,{})]})]})}(0,c.createRoot)(document.getElementById(`root`)).render((0,x.jsx)(l.StrictMode,{children:(0,x.jsx)(cf,{})}));
|
|
95
|
+
`):``).replace(/\x1b\[[0-9;?]*[A-Za-z]/g,``)}function gd(e){let t=new Map,n=0,r=!1;for(let i of e){let e=i.message;i.type===`assistant`&&e?.usage&&(t.set(e.id??String(t.size),e.usage),r=!0),i.type===`result`&&(n+=i.usage?.output_tokens??0,r=!1)}let i=[...t.values()].at(-1);return{turns:t.size,written:n===0&&r?void 0:`${n.toLocaleString()} tokens written${r?` before this turn`:``}`,context:i?(i.input_tokens??0)+(i.cache_read_input_tokens??0)+(i.cache_creation_input_tokens??0):0}}var _d=[`starting`,`running`,`collecting`,`checking`,`reviewing`];function vd({data:e,selected:t,go:n}){let r=Cd(),i=bd(e),a=i.find(e=>e.id===t)??i[0],[o,s]=(0,l.useState)(!1);return(0,l.useEffect)(()=>s(!1),[a?.id]),a?(0,x.jsxs)(`div`,{className:`agents`,children:[(0,x.jsx)(`div`,{className:`cards`,children:i.map(e=>(0,x.jsxs)(`button`,{className:e.id===a.id?`card selected`:`card`,onClick:()=>n(`agents`,e.id),children:[(0,x.jsxs)(`div`,{children:[(0,x.jsx)(`b`,{children:e.role}),` · `,e.title]}),(0,x.jsxs)(`div`,{children:[(0,x.jsx)(ie,{status:e.status}),` `,(0,x.jsx)(`span`,{className:`muted small`,children:e.detail})]}),e.clock&&(0,x.jsx)(Sd,{now:r,...e.clock})]},e.id))}),(0,x.jsxs)(`div`,{className:`panel`,children:[(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsxs)(`h2`,{children:[a.role,` · `,a.title]}),a.sandbox&&e.terminals&&(0,x.jsx)(`button`,{onClick:()=>s(!o),children:o?`Back`:`Shell in ${a.sandbox}`}),a.sandbox&&(0,x.jsx)(`button`,{onClick:()=>n(`sandboxes`,a.sandbox),children:`Its sandbox`})]}),o&&a.sandbox?(0,x.jsx)(Js,{sandbox:a.sandbox}):(0,x.jsx)(yd,{card:a,data:e})]})]}):(0,x.jsx)(`p`,{className:`muted`,children:`No agent yet: start a module in the Project view.`})}function yd({card:e,data:t}){if(e.role===`Architect`){let n=e.id.slice(10);return t.terminals?(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(Js,{module:n}),(0,x.jsxs)(`p`,{className:`muted small`,children:[`The Architect's own terminal, shared with `,(0,x.jsxs)(`code`,{children:[`harness attach `,n]}),`. It keeps running when you leave.`]})]}):(0,x.jsxs)(`p`,{children:[`The dashboard has no terminals here: node-pty didn't install. Use `,(0,x.jsxs)(`code`,{children:[`harness attach `,n]}),`.`]})}if(e.role===`Manager`){let t=e.id.slice(8);return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(dd,{agent:e.id}),(0,x.jsx)(ae,{label:`Send to the Manager`,placeholder:`A message for the Manager`,onSend:e=>p(`/api/message`,{text:e,manager:t})})]})}return(0,x.jsxs)(x.Fragment,{children:[e.refused&&e.refused.length>0&&(0,x.jsxs)(`details`,{className:`refused`,children:[(0,x.jsxs)(`summary`,{children:[`Its sandbox's network policy refused `,xd(e.refused)]}),(0,x.jsx)(se,{denials:e.refused})]}),(0,x.jsx)(dd,{agent:e.id})]})}function bd({state:e}){let t=[];for(let n of Object.values(e.modules))t.push({id:`architect:${n.module}`,role:`Architect`,title:n.module,status:n.architect.status,detail:`module ${n.status.replace(/_/g,` `)}`,sandbox:n.architect.sandbox});for(let n of Object.values(e.managers)){let e=n.plan?`${n.module}/${n.plan}`:n.module;t.push({id:`manager:${e}`,role:`Manager`,title:e,status:n.status,detail:n.launchedAt?`launch ${n.launch}, at ${_(n.launchedAt)}`:``,...n.status===`stopped`?{}:{sandbox:n.sandbox}})}let n=Object.values(e.plans).flatMap(e=>(e.reviews??[]).map(t=>({plan:e,review:t}))).sort((e,t)=>Date.parse(t.review.startedAt)-Date.parse(e.review.startedAt)).slice(0,6);for(let{plan:e,review:r}of n){let n=r.status===`starting`||r.status===`running`,i=r.findings?.filter(e=>e.severity===`blocking`).length??0;t.push({id:`${r.role}:${r.head.slice(0,8)}`,role:`Reviewer`,title:`${e.module}/${e.plan} · ${r.role} · ${r.head.slice(0,8)}`,status:r.status===`reported`?r.verdict??`reported`:r.status,detail:r.status===`reported`?`${i} blocking`:r.status===`failed`?r.reason??``:``,...n?{sandbox:r.sandbox,clock:{start:Date.parse(r.startedAt),deadline:Date.parse(r.deadline)}}:{}})}let r=Object.values(e.features).flatMap(e=>e.attempts.map(t=>({feature:e,attempt:t}))).sort((e,t)=>Date.parse(t.attempt.startedAt)-Date.parse(e.attempt.startedAt)).slice(0,8);for(let{feature:e,attempt:n}of r){let r=_d.includes(n.status),i=n.usage?` · ${n.usage.outputTokens.toLocaleString()} tokens written`:``,a=n.refused?.length?` · refused ${xd(n.refused)}`:``;t.push({id:`developer:${e.module}/${e.plan}/${e.feature}/a${n.n}`,role:`Developer`,title:`${e.module}/${e.plan}/${e.feature} #${n.n}`,status:n.status,detail:r?a.slice(3):`${n.outcome??``}${i}${a}`,...r?{sandbox:n.sandbox,clock:{start:Date.parse(n.startedAt),deadline:Date.parse(n.deadline)}}:{},...n.refused?{refused:n.refused}:{}})}return t}function xd(e){let t=[...new Set(e.map(e=>e.target))];return t.length>1?`${t[0]} and ${t.length-1} more`:t[0]??``}function Sd({now:e,start:t,deadline:n}){let r=Math.min(1,(e-t)/Math.max(1,n-t));return(0,x.jsxs)(`div`,{className:`deadline`,title:`deadline ${new Date(n).toLocaleTimeString()}`,children:[(0,x.jsx)(`div`,{style:{width:`${r*100}%`},className:r>.8?`late`:void 0}),(0,x.jsxs)(`span`,{className:`small muted`,children:[v(e-t),` of `,v(n-t)]})]})}function Cd(){let[e,t]=(0,l.useState)(Date.now());return(0,l.useEffect)(()=>{let e=window.setInterval(()=>t(Date.now()),1e3);return()=>window.clearInterval(e)},[]),e}function wd(){let[e,t]=(0,l.useState)([]),[n,r]=(0,l.useState)(``),[i,a]=(0,l.useState)(!1),[o,s]=(0,l.useState)();(0,l.useEffect)(()=>{h(`/api/log?limit=1000`).then(e=>t(e??[]))},[]),d({event:e=>t(t=>t.some(t=>t.id===e.id)?t:[...t.slice(-1999),e])});let c=n.toLowerCase().split(/\s+/).filter(Boolean),u=e.filter(e=>!i||e.kind===`message`).filter(e=>{let t=`${e.type} ${g(e.ref)} ${e.from??``} ${e.to??``} ${JSON.stringify(e.payload)}`.toLowerCase();return c.every(e=>t.includes(e))}).reverse();return(0,x.jsxs)(`div`,{className:`stack`,children:[(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsx)(`input`,{value:n,placeholder:`Filter: a type, a module, any word`,size:40,onChange:e=>r(e.target.value)}),(0,x.jsxs)(`label`,{children:[(0,x.jsx)(`input`,{type:`checkbox`,checked:i,onChange:e=>a(e.target.checked)}),` messages only`]}),(0,x.jsxs)(`span`,{className:`muted small`,children:[u.length,` of `,e.length]})]}),(0,x.jsx)(`table`,{className:`events`,children:(0,x.jsx)(`tbody`,{children:u.slice(0,500).map(e=>(0,x.jsxs)(`tr`,{onClick:()=>s(o===e.id?void 0:e.id),children:[(0,x.jsx)(`td`,{className:`mono muted`,children:_(e.ts)}),(0,x.jsx)(`td`,{className:e.kind===`message`?`mono run`:`mono`,children:e.type}),(0,x.jsx)(`td`,{className:`mono`,children:g(e.ref)}),(0,x.jsx)(`td`,{className:`muted`,children:e.from?`${e.from}${e.to?` → ${e.to}`:``}`:``}),(0,x.jsxs)(`td`,{children:[te(e),o===e.id&&(0,x.jsx)(`pre`,{children:JSON.stringify(e.payload,null,2)})]})]},e.id))})})]})}function Td({state:e,design:t},n){let r=[],i=e.pipeline.paused;i&&r.push({key:`paused`,title:`The pipeline is paused`,body:(0,x.jsxs)(x.Fragment,{children:[(0,x.jsxs)(`p`,{children:[i.reason,i.until?` (until ${_(i.until)})`:``]}),(0,x.jsx)(oe,{label:`Resume`,onClick:()=>p(`/api/resume`)})]})});for(let t of Object.values(e.ownerRequests).filter(e=>e.answer===void 0))r.push({key:`request:${t.requestId}`,title:`The Manager of ${t.module} asks you`,body:(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(ud,{text:t.question}),t.blocking&&(0,x.jsx)(`p`,{className:`muted small`,children:`No new attempt starts until you answer.`}),(0,x.jsx)(ae,{label:`Answer`,placeholder:`Your answer`,onSend:e=>p(`/api/answer`,{id:t.requestId,text:e})})]})});for(let t of Object.values(e.modules)){let e=t.proposals.at(-1);t.status===`proposed`&&e&&!e.acceptedAt&&r.push({key:`proposal:${t.module}`,title:`The Architect of ${t.module} proposes v${e.version}`,body:(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(Od,{module:t.module,version:e.version}),(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsx)(oe,{label:`Accept v${e.version}`,onClick:()=>p(`/api/accept`,{module:t.module,version:e.version})}),(0,x.jsx)(`button`,{onClick:()=>n(`agents`,`architect:${t.module}`),children:`Ask for changes in its terminal`})]})]})}),t.status===`in_review`&&t.design?.pr&&r.push({key:`design:${t.module}`,title:t.frozenCommit?`A change to the design of ${t.module} is ready for review`:`The design of ${t.module} is ready for review`,body:(0,x.jsxs)(x.Fragment,{children:[t.frozenCommit&&(0,x.jsx)(`p`,{children:t.design.summary}),(0,x.jsxs)(`p`,{children:[`Review and merge `,(0,x.jsxs)(S,{url:t.design.pr.url,children:[`Arch PR #`,t.design.pr.number]}),` on GitHub, with a merge commit. Comments go to the Architect, in its terminal.`,t.frozenCommit&&` No new attempt starts in this module until you merge or close it.`]}),t.design.suspect.length>0&&(0,x.jsxs)(`p`,{className:`warn small`,children:[`These checks already pass before anything is built, so they may test nothing: `,t.design.suspect.join(`, `)]})]})});for(let e of t.escalations.filter(e=>e.decision===void 0))r.push({key:`escalation:${e.id}`,title:`The Manager asks the Architect of ${t.module}${e.feature?` about ${e.feature}`:``}`,body:(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(ud,{text:e.question}),(0,x.jsx)(`p`,{className:`muted small`,children:`The Architect decides. You can weigh in at its terminal.`}),(0,x.jsx)(`button`,{onClick:()=>n(`agents`,`architect:${t.module}`),children:`Open its terminal`})]})})}for(let t of Object.values(e.plans).filter(e=>e.pr&&(e.status===`ready_to_merge`||e.status===`pr_open`)))t.leftToOwner?r.push({key:`left:${t.module}/${t.plan}`,title:`PR #${t.pr.number} of ${t.module}/${t.plan} waits for you`,body:(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(`p`,{children:t.leftToOwner.reason}),(0,x.jsxs)(`p`,{children:[`Review and merge `,(0,x.jsxs)(S,{url:t.pr.url,children:[`PR #`,t.pr.number]}),` on GitHub, with a merge commit.`]})]})}):t.status===`ready_to_merge`&&r.push({key:`merge:${t.module}/${t.plan}`,title:`${t.module}/${t.plan} passed CI`,body:(0,x.jsxs)(`p`,{children:[`Review and merge `,(0,x.jsxs)(S,{url:t.pr.url,children:[`PR #`,t.pr.number]}),` on GitHub, with a merge commit.`]})});for(let t of Object.values(e.features).filter(e=>e.status===`escalated`))r.push({key:`escalated:${kd(t)}`,title:`${kd(t)} is escalated`,body:(0,x.jsx)(Dd,{feature:t})});for(let n of t.modules)for(let t of n.actions){let i=e.actions[`${n.id}/${t.id}`];i?.status!==`done`&&r.push({key:`action:${n.id}/${t.id}`,title:`${t.kind===`gate`?`Gate`:`Task`} ${n.id}/${t.id}: ${t.title}`,body:(0,x.jsxs)(x.Fragment,{children:[t.blocks.length>0&&(0,x.jsxs)(`p`,{className:`muted small`,children:[`Holds back `,t.blocks.join(`, `),` until it's done.`]}),(0,x.jsx)(ud,{text:t.instructions}),i?.status===`verifying`&&(0,x.jsx)(`p`,{className:`run small`,children:`Its check is running.`}),i?.status===`refused`&&(0,x.jsxs)(`p`,{className:`bad small`,children:[`Its check failed: `,i.reason]}),(0,x.jsx)(ae,{label:t.verify?`Done: run its check`:`Mark done`,placeholder:`A note (optional)`,optional:!0,onSend:e=>p(`/api/actions/done`,{module:n.id,action:t.id,...e?{note:e}:{}})})]})})}return r}function Ed({data:e,go:t}){let n=Td(e,t);return n.length===0?(0,x.jsx)(`p`,{className:`muted`,children:`Nothing needs you.`}):(0,x.jsx)(`div`,{className:`stack`,children:n.map(e=>(0,x.jsxs)(`section`,{className:`need`,children:[(0,x.jsx)(`h3`,{children:e.title}),e.body]},e.key))})}function Dd({feature:e}){let[t,n]=(0,l.useState)(``);return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(`p`,{children:e.escalation}),(0,x.jsx)(`textarea`,{rows:2,value:t,placeholder:`Guidance for one more attempt, or a note for the Architect`,onChange:e=>n(e.target.value)}),(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsx)(oe,{label:`One more attempt`,onClick:()=>p(`/api/retry`,{feature:kd(e),...t.trim()?{note:t}:{}})}),(0,x.jsx)(oe,{label:`Send it to the Architect`,onClick:()=>p(`/api/architect`,{module:e.module,text:`[owner] ${kd(e)} is escalated: ${e.escalation??``} ${t.trim()?`My note: ${t.trim()}`:``} Should the design change?`})}),(0,x.jsx)(oe,{label:`Pause the pipeline`,onClick:()=>p(`/api/pause`)})]})]})}function Od({module:e,version:t}){let[n,r]=(0,l.useState)();return(0,l.useEffect)(()=>{m(`/api/proposal?module=${encodeURIComponent(e)}&version=${t}`).then(r)},[e,t]),(0,x.jsx)(`div`,{className:`document`,children:n===void 0?(0,x.jsx)(`p`,{className:`muted`,children:`Loading…`}):(0,x.jsx)(ud,{text:n})})}var kd=e=>`${e.module}/${e.plan}/${e.feature}`,Ad=230,jd=28,Md=34,Nd=10,Pd=42,Fd=26,Id=[`starting`,`running`,`collecting`,`checking`,`reviewing`];function Ld({module:e,state:t}){let n=e.plans.map((e,t)=>({plan:e,x:t*258})),r=Pd+Math.max(1,...e.plans.map(e=>e.features.length))*Md+Nd,i=Math.max(1,n.length*258-jd),a=new Map;for(let{plan:e,x:t}of n)e.features.forEach((n,r)=>a.set(`${e.id}/${n.id}`,{x:t+Nd,y:Pd+r*Md}));let o=e.plans.flatMap(e=>e.features.flatMap(t=>t.dependsOn.map(n=>({from:n.includes(`/`)?n:`${e.id}/${n}`,to:`${e.id}/${t.id}`}))));return(0,x.jsx)(`div`,{className:`graph-wrap`,children:(0,x.jsxs)(`svg`,{className:`graph`,width:i,height:r,viewBox:`0 0 ${i} ${r}`,role:`img`,"aria-label":`The features of ${e.id} and their dependencies`,children:[(0,x.jsx)(`defs`,{children:(0,x.jsx)(`marker`,{id:`arrow`,viewBox:`0 0 8 8`,refX:`7`,refY:`4`,markerWidth:`7`,markerHeight:`7`,orient:`auto-start-reverse`,children:(0,x.jsx)(`path`,{d:`M 0 0 L 8 4 L 0 8 z`})})}),n.map(({plan:n,x:i})=>{let o=t.plans[`${e.id}/${n.id}`];return(0,x.jsxs)(`g`,{className:`plan-box ${Bd(o)}`,children:[(0,x.jsx)(`rect`,{x:i,y:0,width:Ad,height:r,rx:8}),(0,x.jsx)(`text`,{x:i+Nd,y:17,className:`plan-title`,children:Hd(`${n.id} · ${n.title}`,30)}),(0,x.jsx)(`text`,{x:i+Nd,y:32,className:`plan-status`,children:Vd(o,n.dependsOn)}),n.features.map(r=>{let i=a.get(`${n.id}/${r.id}`),o=t.features[`${e.id}/${n.id}/${r.id}`];return(0,x.jsxs)(`g`,{className:`node ${Rd(o)}`,children:[(0,x.jsx)(`title`,{children:`${r.id} ${r.title}: ${zd(o)}`}),(0,x.jsx)(`rect`,{x:i.x,y:i.y,width:210,height:Fd,rx:5}),(0,x.jsx)(`text`,{x:i.x+8,y:i.y+17,children:Hd(`${r.id} ${r.title}`,28)})]},r.id)})]},n.id)}),o.map(({from:e,to:t})=>{let n=a.get(e),r=a.get(t);if(!n||!r)return null;let i=n.x===r.x?`M ${n.x+210} ${n.y+Fd/2} C ${n.x+210+16} ${n.y+Fd/2}, ${r.x+210+16} ${r.y+Fd/2}, ${r.x+210} ${r.y+Fd/2}`:`M ${n.x+210} ${n.y+Fd/2} C ${n.x+210+jd} ${n.y+Fd/2}, ${r.x-jd} ${r.y+Fd/2}, ${r.x} ${r.y+Fd/2}`;return(0,x.jsx)(`path`,{d:i,className:`edge`,markerEnd:`url(#arrow)`},`${e}>${t}`)})]})})}function Rd(e){if(!e)return`muted`;if(e.status===`accepted`)return`ok`;if(e.status===`escalated`)return`bad`;let t=e.attempts.at(-1);return t&&Id.includes(t.status)?`run`:e.attempts.length>0?`warn`:`muted`}function zd(e){if(!e)return`not started`;let t=e.attempts.at(-1);return`${e.status.replace(/_/g,` `)}${t?`, attempt ${t.n} ${t.status}`:``}`}function Bd(e){return e?e.status===`completed`||e.mergeCommit?`ok`:e.leftToOwner||e.status===`ready_to_merge`||e.status===`deploy_failed`||e.status===`verify_failed`?`warn`:`run`:`muted`}function Vd(e,t){return e?`${e.status===`completed`?`complete`:e.mergeCommit?`merged, ${e.status}`:e.status.replace(/_/g,` `)}${e.pr?` · PR #${e.pr.number}`:``}${e.mergedBy===`daemon`?` · by the daemon`:``}`:t.length>0?`after ${t.join(`, `)}`:`not started`}var Hd=(e,t)=>e.length>t?`${e.slice(0,t-1)}…`:e;function Ud({data:e}){let{state:t,design:n,progress:r}=e,[i,a]=(0,l.useState)(),o=[...new Set([...Object.keys(t.modules),...n.modules.map(e=>e.id)])];return(0,x.jsxs)(`div`,{className:`stack`,children:[(0,x.jsx)(Jd,{state:t,ceiling:n.swarm?.usageCeilingPercent??null,...e.ownerTime?{ownerTime:e.ownerTime}:{},...e.usage?{usage:e.usage}:{}}),(0,x.jsx)(Zd,{}),o.length===0&&(0,x.jsx)(`p`,{className:`muted`,children:`No module yet.`}),o.map(r=>{let o=t.modules[r],s=n.modules.find(e=>e.id===r),c=s?.plans??[];return(0,x.jsxs)(`section`,{children:[(0,x.jsxs)(`h2`,{children:[r,` `,o&&(0,x.jsxs)(`span`,{className:`muted`,children:[`· `,o.title]}),` `,o&&(0,x.jsx)(ie,{status:o.status}),o?.design?.pr&&(0,x.jsxs)(x.Fragment,{children:[` · `,(0,x.jsxs)(S,{url:o.design.pr.url,children:[`Arch PR #`,o.design.pr.number]})]})]}),o&&o.proposals.length>0&&(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsx)(`span`,{className:`muted`,children:`Proposals:`}),o.proposals.map(e=>(0,x.jsxs)(`button`,{className:i?.module===r&&i.version===e.version?`selected`:void 0,onClick:()=>a(i?.module===r&&i.version===e.version?void 0:{module:r,version:e.version}),children:[`v`,e.version,e.acceptedAt?` ✓`:``]},e.version))]}),i?.module===r&&(0,x.jsx)(Od,{module:r,version:i.version}),s&&c.length>0&&(0,x.jsx)(Ld,{module:s,state:t}),c.map(t=>(0,x.jsx)(Qd,{data:e,module:r,plan:t},t.id))]},r)}),(0,x.jsxs)(`section`,{children:[(0,x.jsx)(`h2`,{children:`progress.txt`}),(0,x.jsx)(`pre`,{children:r.join(`
|
|
96
|
+
`)||`(nothing accepted yet)`})]})]})}function Wd(e,t=6e4){let[n,r]=(0,l.useState)(()=>Date.now());return(0,l.useEffect)(()=>{if(r(Date.now()),!e)return;let n=window.setInterval(()=>r(Date.now()),t);return()=>window.clearInterval(n)},[e,t]),n}function Gd(e,t){let n=0;for(let r of Object.values(e.features))for(let e of r.attempts){let r=Date.parse(e.startedAt),i=e.endedAt?Date.parse(e.endedAt):t;Number.isFinite(r)&&Number.isFinite(i)&&(n+=Math.max(0,i-r))}return n}function Kd(e){let t={Developers:{input:0,output:0},Managers:{input:0,output:0},Architects:{input:0,output:0},reviewers:{input:0,output:0}},n=(e,n)=>{n&&(t[e].input+=n.inputTokens,t[e].output+=n.outputTokens)};for(let t of Object.values(e.features))for(let e of t.attempts)n(`Developers`,e.usage);for(let t of Object.values(e.plans))for(let e of t.reviews??[])n(`reviewers`,e.usage);for(let[t,r]of Object.entries(e.agentTokens??{}))n(t.startsWith(`manager:`)?`Managers`:t.startsWith(`architect:`)?`Architects`:`Developers`,r.total);return Object.entries(t)}function qd(e){return e>=1e6?`${(e/1e6).toFixed(1)} M`:e>=1e3?`${Math.round(e/1e3)} k`:String(e)}function Jd({state:e,ownerTime:t,usage:n,ceiling:r}){let i=e.budgets??{},a=t!==void 0&&t.committedHours>t.plannableHours,o=t!==void 0&&t.usedHours>t.hours,s=Gd(e,Wd(Object.values(e.features).some(e=>e.attempts.some(e=>!e.endedAt))))/36e5,c=i.agentHours!==void 0&&s>i.agentHours;return(0,x.jsxs)(`section`,{children:[(0,x.jsx)(`h2`,{children:`Budgets`}),(0,x.jsxs)(`div`,{className:`meters`,children:[(0,x.jsxs)(`div`,{className:`meter`,children:[(0,x.jsx)(`h3`,{children:`Your time`}),(0,x.jsx)(`p`,{children:i.ownerHours===void 0?(0,x.jsx)(`span`,{className:`muted`,children:`Not set: the Architect designs without a limit on your time.`}):(0,x.jsxs)(x.Fragment,{children:[(0,x.jsxs)(`b`,{children:[i.ownerHours,` h`]}),` for the whole project`,i.ownerHoursSetAt&&(0,x.jsxs)(`span`,{className:`muted`,children:[`, set `,new Date(i.ownerHoursSetAt).toLocaleDateString()]})]})}),t&&(0,x.jsxs)(`p`,{children:[(0,x.jsxs)(`b`,{className:o?`bad`:void 0,children:[t.usedHours,` h used`]}),` · `,(0,x.jsxs)(`span`,{className:a?`bad`:void 0,children:[t.committedHours,` h planned by the designs`]}),` · `,(0,x.jsxs)(`span`,{className:`muted`,children:[t.plannableHours,` h plannable after the `,t.reservePercent,`% reserve`]})]}),(0,x.jsx)(Xd,{field:`ownerHours`,value:i.ownerHours,placeholder:`hours`}),(0,x.jsx)(`p`,{className:`muted small`,children:t?`Planned: the estimates of the owner actions in submitted designs, gates and tasks. Used: the actions you marked done. What you read, proposals and pull requests, doesn't count.`:`Once set, the Architect must estimate every owner action and design within your hours; a design over them is refused.`})]}),(0,x.jsxs)(`div`,{className:`meter`,children:[(0,x.jsx)(`h3`,{children:`Agent hours`}),(0,x.jsxs)(`p`,{children:[(0,x.jsxs)(`b`,{className:c?`bad`:void 0,children:[s.toFixed(1),` h`]}),i.agentHours===void 0?(0,x.jsx)(`span`,{className:`muted`,children:` · no threshold`}):(0,x.jsxs)(x.Fragment,{children:[` of `,i.agentHours,` h`]}),c&&(0,x.jsx)(`span`,{className:`bad small`,children:` · over the threshold`})]}),(0,x.jsx)(Xd,{field:`agentHours`,value:i.agentHours,placeholder:`threshold`}),(0,x.jsx)(`p`,{className:`muted small`,children:`The Developers' attempt clocks, summed. Over the threshold the number turns red; nothing stops.`})]}),(0,x.jsx)(Yd,{state:e}),n&&(0,x.jsxs)(`div`,{className:`meter`,children:[(0,x.jsx)(`h3`,{children:`Subscription`}),(0,x.jsx)(`p`,{children:n.usage.limits.map((e,t)=>(0,x.jsxs)(`span`,{children:[t>0&&` · `,(0,x.jsxs)(`b`,{className:r!==null&&e.percent>=r?`bad`:void 0,children:[e.percent,`%`]}),` `,e.kind.replace(/_/g,` `),`, resets `,new Date(e.resetsAt).toLocaleString()]},e.kind))}),(0,x.jsxs)(`p`,{className:`muted small`,children:[`Read `,new Date(n.at).toLocaleTimeString(),`. In Swarm no attempt starts at or above `,r===null?`the ceiling, which is off`:`${r}%`,`; the pipeline waits for the window to reset.`]})]})]})]})}function Yd({state:e}){let t=Kd(e),n=t.reduce((e,[,t])=>({input:e.input+t.input,output:e.output+t.output}),{input:0,output:0});return(0,x.jsxs)(`div`,{className:`meter`,children:[(0,x.jsx)(`h3`,{children:`Claude tokens`}),(0,x.jsxs)(`p`,{children:[(0,x.jsx)(`b`,{children:qd(n.input+n.output)}),(0,x.jsxs)(`span`,{className:`muted`,children:[` · `,qd(n.output),` written, `,qd(n.input),` read, cache included`]})]}),(0,x.jsx)(`p`,{className:`small`,children:t.map(([e,t])=>`${e} ${qd(t.input+t.output)}`).join(` · `)}),(0,x.jsx)(`p`,{className:`muted small`,children:`Claude Code's own counts: an attempt's and a review's when it ends, a Manager's and an Architect's every five minutes. Most of what is read comes from the prompt cache.`})]})}function Xd({field:e,value:t,placeholder:n}){let[r,i]=(0,l.useState)(``),a=Number(r),o=r.trim()!==``&&Number.isFinite(a)&&a>0;return(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsx)(`input`,{type:`number`,min:1,step:1,value:r,placeholder:n,onChange:e=>i(e.target.value)}),(0,x.jsx)(oe,{label:`Save`,onClick:async()=>{if(!o)return{done:!1,error:`a positive number of hours`};let t=await p(`/api/budgets`,{[e]:a});return t.done&&i(``),t}}),t!==void 0&&(0,x.jsx)(oe,{label:`Clear`,onClick:()=>p(`/api/budgets`,{[e]:null})})]})}function Zd(){let[e,t]=(0,l.useState)(``),[n,r]=(0,l.useState)(``),[i,a]=(0,l.useState)();return(0,x.jsxs)(`section`,{children:[(0,x.jsx)(`h2`,{children:`Start a module`}),(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsx)(`input`,{value:e,placeholder:`id, like orders`,size:14,onChange:e=>t(e.target.value)}),(0,x.jsx)(`input`,{value:n,placeholder:`what it is for`,size:36,onChange:e=>r(e.target.value)}),(0,x.jsx)(`button`,{disabled:!e.trim(),onClick:async()=>{let i=await p(`/api/modules`,{id:e.trim(),title:n});a(i.done?void 0:i.error),i.done&&(t(``),r(``))},children:`Start`}),i&&(0,x.jsx)(`span`,{className:`bad small`,children:i})]}),(0,x.jsx)(`p`,{className:`muted small`,children:`An Architect starts in its own sandbox; talk to it in the Agents view.`})]})}function Qd({data:e,module:t,plan:n}){let{state:r,design:i}=e,a=`${t}/${n.id}`,o=r.plans[a],s=Object.values(r.features).filter(e=>e.fix&&`${e.module}/${e.plan}`===a),c=[...n.features.map(e=>({id:e.id,title:e.title,kind:e.kind,state:r.features[`${a}/${e.id}`]})),...s.map(e=>({id:e.feature,title:`Fix after ${e.fix.kind===`ci`?`CI`:`the deploy`}, round ${e.fix.round}`,kind:`fix`,state:e}))];return(0,x.jsxs)(`div`,{className:`plan`,children:[(0,x.jsxs)(`h3`,{children:[n.id,` · `,n.title,` `,(0,x.jsx)(ie,{status:o?.status??`waiting`}),o?.pr&&(0,x.jsxs)(x.Fragment,{children:[` `,(0,x.jsxs)(S,{url:o.pr.url,children:[`PR #`,o.pr.number]})]}),o?.mergedBy===`daemon`&&(0,x.jsxs)(x.Fragment,{children:[` `,(0,x.jsx)(`span`,{className:`muted small`,children:`merged by the daemon`})]}),o?.ci&&(0,x.jsxs)(x.Fragment,{children:[` `,(0,x.jsx)(ie,{status:o.ci.status,label:`CI ${o.ci.status}`})]})]}),o&&(0,x.jsx)($d,{plan:o}),(0,x.jsxs)(`table`,{children:[(0,x.jsx)(`thead`,{children:(0,x.jsxs)(`tr`,{children:[(0,x.jsx)(`th`,{children:`Feature`}),(0,x.jsx)(`th`,{children:`Status`}),(0,x.jsx)(`th`,{children:`Attempts`}),(0,x.jsx)(`th`,{children:`Last attempt`}),(0,x.jsx)(`th`,{children:`Commit`})]})}),(0,x.jsx)(`tbody`,{children:c.map(e=>(0,x.jsx)(ef,{id:e.id,title:e.title,kind:e.kind,feature:e.state,max:i.maxAttempts},e.id))})]})]})}function $d({plan:e}){let t=e.ci?.jobs?.filter(e=>e.conclusion!==`success`)??[];return(0,x.jsxs)(`div`,{className:`small`,children:[t.map(e=>(0,x.jsxs)(`details`,{children:[(0,x.jsxs)(`summary`,{className:`bad`,children:[`CI job `,e.name,`: `,e.conclusion]}),(0,x.jsx)(`pre`,{children:e.log??``})]},e.name)),e.reviews?.filter(t=>t.head===e.pushed).map(e=>(0,x.jsxs)(`p`,{children:[e.role===`security`?`Security`:`Cost`,` review of `,e.head.slice(0,8),`:`,` `,e.status===`reported`?(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(ie,{status:e.verdict===`pass`?`passed`:`failed`,label:e.verdict}),` `,e.findings?.filter(e=>e.severity===`blocking`).length??0,` blocking, `,e.findings?.filter(e=>e.severity===`advisory`).length??0,` advisory`,e.posted?.problem?(0,x.jsxs)(`span`,{className:`bad`,children:[` · `,e.posted.problem]}):null]}):e.status===`failed`?(0,x.jsxs)(`span`,{className:`bad`,children:[`failed: `,e.reason]}):(0,x.jsx)(ie,{status:e.status})]},`${e.role}-${e.head}`)),e.deploy&&(0,x.jsxs)(`p`,{children:[`Deploy of `,e.deploy.commit.slice(0,8),`: `,(0,x.jsx)(ie,{status:e.deploy.status}),e.deploy.jobs?.filter(e=>e.conclusion!==`success`).map(e=>(0,x.jsxs)(S,{url:e.url,children:[` `,e.name]},e.name))]}),e.postDeploy&&(0,x.jsxs)(`details`,{children:[(0,x.jsxs)(`summary`,{children:[`Post-deploy checks: `,(0,x.jsx)(ie,{status:e.postDeploy.status})]}),e.postDeploy.results.map(e=>(0,x.jsxs)(`div`,{children:[(0,x.jsx)(`b`,{className:e.exit===0?`ok`:`bad`,children:e.id}),` `,(0,x.jsx)(`span`,{className:`muted`,children:v(e.durationMs)}),(0,x.jsx)(`pre`,{children:e.output})]},e.id))]})]})}function ef({id:e,title:t,kind:n,feature:r,max:i}){let a=r?.attempts.at(-1),o=a?r.attempts.filter(e=>e.definitionHash===a.definitionHash&&e.counts).length:0,s=a?.endedAt?Date.parse(a.endedAt):Date.now();return(0,x.jsxs)(`tr`,{children:[(0,x.jsxs)(`td`,{children:[(0,x.jsx)(`b`,{children:e}),` `,t,` `,n===`infra`&&(0,x.jsx)(`span`,{className:`muted small`,children:`infra`})]}),(0,x.jsxs)(`td`,{children:[(0,x.jsx)(ie,{status:r?.status??`pending`}),r?.awaitingGuidanceSince&&(0,x.jsx)(`div`,{className:`muted small`,children:`waiting for the Manager's guidance`})]}),(0,x.jsx)(`td`,{className:`mono`,children:r?`${r.attempts.length}${o?` (${o} of ${i+r.extraAttempts} counted)`:``}`:`0`}),(0,x.jsx)(`td`,{children:a&&(0,x.jsxs)(x.Fragment,{children:[a.status,a.outcome?` · ${a.outcome}`:``,` · `,v(s-Date.parse(a.startedAt)),a.reason&&(0,x.jsx)(`div`,{className:`small`,children:a.reason}),a.finding&&(0,x.jsxs)(`details`,{className:`small`,children:[(0,x.jsxs)(`summary`,{children:[`Finding: `,a.finding.category]}),(0,x.jsx)(`p`,{children:a.finding.summary}),a.finding.needs&&(0,x.jsxs)(`p`,{children:[`Needs: `,a.finding.needs]})]}),a.questions?.map(e=>(0,x.jsxs)(`div`,{className:`small muted`,children:[`Q: `,e.text,e.answer?` — A: ${e.answer}`:``]},e.requestId))]})}),(0,x.jsx)(`td`,{className:`mono`,children:r?.commit?.slice(0,8)??``})]})}function tf({go:e,selected:t}){let[n,r]=(0,l.useState)(),[i,a]=(0,l.useState)(!1),o=nf(),s=async()=>{let e=await h(`/api/sandboxes`).catch(()=>void 0);a(e===void 0),e&&r(e)};if((0,l.useEffect)(()=>{s();let e=window.setInterval(()=>void s(),1e4);return()=>window.clearInterval(e)},[]),!n)return(0,x.jsx)(`p`,{className:`muted`,children:i?`The daemon could not read the sandboxes.`:`Reading the sandboxes from OpenShell…`});let{gateway:c,machine:u}=n,d=n.sandboxes.filter(e=>e.use!==`other`),f=n.sandboxes.filter(e=>e.use===`other`),m=d.find(e=>e.name===t),g=d.filter(e=>e.use===`left over`).length,_=e=>o?.sandboxes[e.name],v=async e=>{if(!window.confirm(`Delete ${e.name}? Nothing of this project uses it any more.`))return{done:!0};let t=await p(`/api/sandboxes/remove`,{name:e.name});return await s(),t};return(0,x.jsxs)(`div`,{className:`stack`,children:[(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsx)(`b`,{children:`OpenShell`}),(0,x.jsx)(ie,{status:c.status===`connected`?`running`:`failed`,label:c.status}),c.version&&(0,x.jsx)(`span`,{className:`muted small`,children:c.version}),(0,x.jsxs)(`span`,{className:`muted small`,children:[n.sandboxes.length,` sandbox`,n.sandboxes.length===1?``:`es`,` on the gateway, `,d.length,` of this project`,g>0?`, ${g} left over`:``]}),(0,x.jsx)(`button`,{onClick:()=>void s(),children:`Refresh`})]}),c.problem&&(0,x.jsx)(`p`,{className:`bad small`,children:c.problem}),n.problem&&(0,x.jsx)(`p`,{className:`bad small`,children:n.problem}),(0,x.jsx)(`p`,{className:`small`,children:o?(0,x.jsxs)(x.Fragment,{children:[`Every OpenShell sandbox on this machine uses `,(0,x.jsx)(`b`,{children:b(o.total.cpu)}),` of its `,u.cpus,` CPUs and `,(0,x.jsx)(`b`,{children:y(o.total.memoryBytes)}),` of its `,u.memoryGiB,` GiB`,(0,x.jsx)(`span`,{className:`muted`,children:`, OpenShell's supervisors included · docker stats, every 5 s`})]}):(0,x.jsx)(`span`,{className:`muted`,children:`Reading what the sandboxes use from Docker…`})}),o?.problem&&(0,x.jsx)(`p`,{className:`bad small`,children:o.problem}),(0,x.jsx)(af,{limits:n.limits,machine:u,onSaved:s},`${n.limits.cpu??``}/${n.limits.memoryGiB??``}`),(0,x.jsxs)(`section`,{children:[(0,x.jsx)(`h2`,{children:`This project's`}),d.length===0?(0,x.jsx)(`p`,{className:`muted`,children:`None on the gateway now.`}):(0,x.jsxs)(`table`,{children:[(0,x.jsx)(`thead`,{children:(0,x.jsxs)(`tr`,{children:[(0,x.jsx)(`th`,{children:`Sandbox`}),(0,x.jsx)(`th`,{children:`For`}),(0,x.jsx)(`th`,{children:`OpenShell`}),(0,x.jsx)(`th`,{children:`Age`}),(0,x.jsx)(`th`,{children:`CPU`}),(0,x.jsx)(`th`,{children:`Memory`}),(0,x.jsx)(`th`,{children:`Use`}),(0,x.jsx)(`th`,{})]})}),(0,x.jsx)(`tbody`,{children:d.map(t=>(0,x.jsxs)(`tr`,{className:t.name===m?.name?`clickable selected`:`clickable`,onClick:()=>e(`sandboxes`,t.name),children:[(0,x.jsx)(`td`,{className:`mono`,children:t.name}),(0,x.jsxs)(`td`,{children:[t.role??`unknown`,t.title?` · ${t.title}`:``]}),(0,x.jsx)(`td`,{children:(0,x.jsx)(ie,{status:t.phase===`Ready`?`running`:t.phase.toLowerCase(),label:t.phase})}),(0,x.jsx)(`td`,{className:`muted`,children:sf(t)}),(0,x.jsx)(rf,{usage:_(t)}),(0,x.jsx)(`td`,{children:(0,x.jsx)(ie,{status:t.use===`in use`?`running`:`waiting`,label:t.use})}),(0,x.jsxs)(`td`,{onClick:e=>e.stopPropagation(),children:[t.agent&&(0,x.jsx)(`button`,{onClick:()=>e(`agents`,t.agent),children:`Agent`}),t.use===`left over`&&(0,x.jsx)(oe,{label:`Delete`,danger:!0,onClick:()=>v(t)})]})]},t.name))})]}),g>0&&(0,x.jsx)(`p`,{className:`muted small`,children:`A left-over sandbox is this project's, but nothing uses it any more: a crash or a failed delete left it. The daemon deletes them when it starts and every hour, except an Architect's, which holds your conversation with it; Delete removes one now.`})]}),m&&(0,x.jsx)(of,{sandbox:m}),f.length>0&&(0,x.jsxs)(`section`,{children:[(0,x.jsx)(`h2`,{children:`Other sandboxes on this gateway`}),(0,x.jsxs)(`table`,{children:[(0,x.jsx)(`thead`,{children:(0,x.jsxs)(`tr`,{children:[(0,x.jsx)(`th`,{children:`Sandbox`}),(0,x.jsx)(`th`,{children:`Project`}),(0,x.jsx)(`th`,{children:`OpenShell`}),(0,x.jsx)(`th`,{children:`Age`}),(0,x.jsx)(`th`,{children:`CPU`}),(0,x.jsx)(`th`,{children:`Memory`})]})}),(0,x.jsx)(`tbody`,{children:f.map(e=>(0,x.jsxs)(`tr`,{children:[(0,x.jsx)(`td`,{className:`mono`,children:e.name}),(0,x.jsx)(`td`,{className:`muted`,children:e.project??`no label`}),(0,x.jsx)(`td`,{children:(0,x.jsx)(ie,{status:e.phase===`Ready`?`running`:e.phase.toLowerCase(),label:e.phase})}),(0,x.jsx)(`td`,{className:`muted`,children:sf(e)}),(0,x.jsx)(rf,{usage:_(e)})]},e.name))})]}),(0,x.jsxs)(`p`,{className:`muted small`,children:[`Another project's, a probe of `,(0,x.jsx)(`code`,{children:`harness doctor`}),`, or one made by 0.3.0 or earlier, which didn't label sandboxes. The dashboard deletes none of them; in a terminal: `,(0,x.jsx)(C,{text:`openshell sandbox delete ${f[0].name}`})]})]})]})}function nf(){let[e,t]=(0,l.useState)();return(0,l.useEffect)(()=>{let e=!1,n,r=async()=>{let i=await h(`/api/sandboxes/usage`).catch(()=>void 0);e||(i&&t(i),n=window.setTimeout(()=>void r(),5e3))};return r(),()=>{e=!0,window.clearTimeout(n)}},[]),e}function rf({usage:e}){if(!e)return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(`td`,{}),(0,x.jsx)(`td`,{})]});let t=(e,t)=>t!==void 0&&e>=.9*t?`bad`:void 0;return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsxs)(`td`,{className:t(e.cpu,e.cpuLimit),children:[b(e.cpu),e.cpuLimit!==void 0&&(0,x.jsxs)(`span`,{className:`muted`,children:[` of `,e.cpuLimit]})]}),(0,x.jsxs)(`td`,{className:t(e.memoryBytes,e.memoryLimitBytes),children:[y(e.memoryBytes),e.memoryLimitBytes!==void 0&&(0,x.jsxs)(`span`,{className:`muted`,children:[` of `,y(e.memoryLimitBytes)]})]})]})}function af({limits:e,machine:t,onSaved:n}){let[r,i]=(0,l.useState)(e.cpu?.toString()??``),[a,o]=(0,l.useState)(e.memoryGiB?.toString()??``),s=e=>e.trim()===``?null:Number(e),c=e.cpu!==void 0||e.memoryGiB!==void 0,u=[e.cpu===void 0?`any CPUs`:`${e.cpu} CPU${e.cpu===1?``:`s`}`,e.memoryGiB===void 0?`any memory`:`${e.memoryGiB} GiB`].join(` and `);return(0,x.jsxs)(`section`,{children:[(0,x.jsx)(`h2`,{children:`Caps on each new sandbox`}),(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsxs)(`label`,{children:[`CPUs `,(0,x.jsx)(`input`,{type:`number`,min:.5,max:t.cpus,step:.5,value:r,placeholder:`no cap`,onChange:e=>i(e.target.value)})]}),(0,x.jsxs)(`label`,{children:[`Memory `,(0,x.jsx)(`input`,{type:`number`,min:1,max:Math.floor(t.memoryGiB),step:1,value:a,placeholder:`no cap`,onChange:e=>o(e.target.value)}),` GiB`]}),(0,x.jsx)(oe,{label:`Save`,onClick:async()=>{let e=await p(`/api/sandboxes/limits`,{cpu:s(r),memoryGiB:s(a)});return e.done&&await n(),e}})]}),(0,x.jsxs)(`p`,{className:`muted small`,children:[c?`Now ${u} for each sandbox. `:`No cap now: each sandbox may use all of this machine's ${t.cpus} CPUs and ${t.memoryGiB} GiB. `,`The caps hold for the sandboxes made from now on, for each attempt, check run, reviewer and Manager; those already there, like an Architect's, keep theirs. Past its memory cap, a sandbox may swap up to as much again; then the process that asks for more is killed, with exit code 137, and the sandbox stays.`]})]})}function of({sandbox:e}){let[t,n]=(0,l.useState)();return(0,l.useEffect)(()=>{n(void 0);let t=!0,r=()=>void h(`/api/sandboxes/denials?name=${encodeURIComponent(e.name)}`).then(e=>t&&n(e??[]),()=>t&&n([]));r();let i=window.setInterval(r,15e3);return()=>{t=!1,window.clearInterval(i)}},[e.name]),(0,x.jsxs)(`section`,{children:[(0,x.jsxs)(`h2`,{children:[`Refused by its network policy · `,e.name]}),t===void 0?(0,x.jsx)(`p`,{className:`muted`,children:`Reading its log…`}):t.length===0?(0,x.jsx)(`p`,{className:`muted`,children:`Nothing refused in its recent log.`}):(0,x.jsx)(se,{denials:t})]})}function sf(e){return e.createdAt?v(Date.now()-Date.parse(e.createdAt)):``}var cf=[[`needs`,`Needs you`],[`project`,`Project`],[`agents`,`Agents`],[`sandboxes`,`Sandboxes`],[`events`,`Events`]];function lf(e){let[t,...n]=e.replace(/^#/,``).split(`/`),r=cf.some(([e])=>e===t)?t:`needs`;return n.length?{view:r,detail:decodeURIComponent(n.join(`/`))}:{view:r}}function uf({repo:e,setup:t}){let n=t?.rules,r=[...!n||!n.ok?[(0,x.jsxs)(`li`,{children:[`Apply the mode's rules on GitHub, with your own `,(0,x.jsx)(`code`,{children:`gh`}),`: `,(0,x.jsx)(C,{text:`harness github-setup --repo ${e}`}),n&&n.problems.length>0&&(0,x.jsxs)(`span`,{className:`muted`,children:[` · now: `,n.problems.join(`; `)]})]},`rules`)]:[],...t?.statusesWritable===!1||!t?[(0,x.jsxs)(`li`,{children:[`Give the App `,(0,x.jsx)(`b`,{children:`Commit statuses: write`}),` on GitHub, and accept the new permission on its installation.`,t?` The daemon could only get a read token.`:` An App created with this version has it already.`,` Without it the reviewers' verdicts can't be posted, and pull requests wait for you with the reason.`]},`statuses`)]:[],...t?.tokenStored?[]:[(0,x.jsxs)(`li`,{children:[`Store the token the usage probe reads, in a terminal: `,(0,x.jsx)(C,{text:`harness doctor`}),` asks for the one from `,(0,x.jsx)(`code`,{children:`claude setup-token`}),`. Without it the daemon learns of a limit only when an attempt hits it.`]},`token`)]];return r.length===0?null:(0,x.jsxs)(`div`,{className:`note`,children:[(0,x.jsx)(`b`,{children:`Before you use Swarm`}),t?`, ${r.length===1?`one step`:`${r.length} steps`} left`:`, three steps, once`,`. Swarm merges only once GitHub's rules match the mode; `,(0,x.jsx)(`code`,{children:`harness doctor`}),` checks all of it.`,(0,x.jsx)(`ol`,{children:r})]})}function df(){let{data:e,problem:t,connected:n}=f(),[r,i]=(0,l.useState)(()=>lf(location.hash));(0,l.useEffect)(()=>{let e=()=>i(lf(location.hash));return window.addEventListener(`hashchange`,e),()=>window.removeEventListener(`hashchange`,e)},[]);let a=(e,t)=>{location.hash=t?`${e}/${encodeURIComponent(t)}`:e};if(!e)return(0,x.jsx)(`div`,{className:`boot`,children:t??`Connecting to the daemon…`});let o=Td(e,a).length,s=e.state.pipeline.paused,c=(e.state.mode??`manual`)===`swarm`,u=(e.state.infraMerge??`ceiling`)===`daemon`;return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsxs)(`header`,{children:[(0,x.jsxs)(`h1`,{children:[`harness · `,e.design.project]}),(0,x.jsx)(ie,{status:s?`paused`:`running`}),s?(0,x.jsx)(oe,{label:`Resume`,onClick:()=>p(`/api/resume`)}):(0,x.jsx)(oe,{label:`Pause`,onClick:()=>p(`/api/pause`)}),(0,x.jsx)(ie,{status:c?`swarm`:`manual`,label:c?`Swarm`:`Manual`}),(0,x.jsx)(oe,{label:c?`Switch to Manual`:`Switch to Swarm`,onClick:()=>{let e=c?`In Manual you review and merge every pull request yourself. Switch to Manual?`:`In Swarm the daemon merges plans that pass CI and the reviewers, without your review. Switch to Swarm?`;return window.confirm(e)?p(`/api/mode`,{mode:c?`manual`:`swarm`}):Promise.resolve({done:!0})}}),c&&(0,x.jsx)(ie,{status:u?`swarm`:`waiting`,label:u?`Infra: the daemon merges`:`Infra: yours`}),c&&(0,x.jsx)(oe,{label:u?`Keep infra merges for me`:`Let the daemon merge infra`,onClick:()=>{let e=u?`INFRA and MIXED plans will wait for you again, unless the cost job's estimate is under the ceiling. Keep their merges for you?`:`INFRA and MIXED plans will merge themselves once CI, the security reviewer and the cost reviewer pass, like CODE plans. An estimate over the ceiling still waits for you. Let the daemon merge infra?`;return window.confirm(e)?p(`/api/infra-merge`,{by:u?`ceiling`:`daemon`}):Promise.resolve({done:!0})}}),(0,x.jsx)(`span`,{className:`muted small`,children:n?e.state.daemon?`daemon ${e.state.daemon.version}, pid ${e.state.daemon.pid}`:`daemon stopped`:`reconnecting…`}),(0,x.jsx)(`nav`,{children:cf.map(([e,t])=>(0,x.jsxs)(`a`,{href:`#${e}`,className:r.view===e?`active`:void 0,children:[t,e===`needs`&&o>0&&(0,x.jsx)(`span`,{className:`count`,children:o})]},e))})]}),t&&(0,x.jsx)(`div`,{className:`banner`,children:t}),c&&(e.design.repo?(0,x.jsx)(uf,{repo:e.design.repo,...e.setup?{setup:e.setup}:{}}):(0,x.jsx)(`div`,{className:`note`,children:`Swarm: this project has no GitHub repository, so there is nothing to merge; plans complete locally.`})),(0,x.jsxs)(`main`,{children:[r.view===`needs`&&(0,x.jsx)(Ed,{data:e,go:a}),r.view===`project`&&(0,x.jsx)(Ud,{data:e}),r.view===`agents`&&(0,x.jsx)(vd,{data:e,go:a,...r.detail?{selected:r.detail}:{}}),r.view===`sandboxes`&&(0,x.jsx)(tf,{go:a,...r.detail?{selected:r.detail}:{}}),r.view===`events`&&(0,x.jsx)(wd,{})]})]})}(0,c.createRoot)(document.getElementById(`root`)).render((0,x.jsx)(l.StrictMode,{children:(0,x.jsx)(df,{})}));
|
package/dist/web/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>harness</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-B26a4Goq.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-CRYKs8kt.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/package.json
CHANGED