@codeyam-editor/codeyam-editor 0.1.16-staging.ge120017 → 0.1.17-staging.gdcd54cf
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/LICENSE +21 -0
- package/README.md +72 -0
- package/npm/disk-pressure.js +48 -20
- package/npm/fleet-add-vm-steps.js +210 -0
- package/npm/fleet-build-data.js +11 -3
- package/npm/fleet-dashboard-data.js +284 -0
- package/npm/fleet-reset.js +6 -2
- package/npm/fleet-stuck-position.js +88 -0
- package/npm/fleet-stuck-status.js +16 -2
- package/npm/fleet-vm-card.js +320 -0
- package/npm/fleet-vm-poll.js +418 -0
- package/npm/ingress-rewire-retry.js +43 -0
- package/npm/launch-config-steps.js +513 -0
- package/npm/launch-config.js +21 -482
- package/npm/scenario-check-run.js +666 -0
- package/npm/scenario-check.js +352 -650
- package/npm/scenario-launch.js +100 -7
- package/npm/scenario-metrics.js +74 -9
- package/npm/scenario-playwright.js +89 -4
- package/npm/wasm-function-attribution.js +242 -0
- package/npm/wasm-name-section.js +121 -0
- package/npm/wasm-source-attribution.js +107 -0
- package/npm/wasm-test-module.js +31 -0
- package/package.json +5 -5
- package/ui/dist/assets/{ContainerInspector-Cig2PDjT.js → ContainerInspector-Dxc9Mueo.js} +1 -1
- package/ui/dist/assets/EditorShell-DB26RdeS.css +1 -0
- package/ui/dist/assets/EditorShell-icRqflQd.js +381 -0
- package/ui/dist/assets/ProjectLauncherScreen-iCf8wHB0.js +1 -0
- package/ui/dist/assets/{ScenarioDataPanel-BRUYhl1o.js → ScenarioDataPanel-C5kgmZMZ.js} +10 -10
- package/ui/dist/assets/{ScenarioDataPanelFullPage-DjwRM8HJ.js → ScenarioDataPanelFullPage-DoUNWztV.js} +1 -1
- package/ui/dist/assets/ScenarioRenderer-tS-1fxyi.js +26 -0
- package/ui/dist/assets/{index-Bkrhkuf5.js → index-B-G6cmd9.js} +8 -8
- package/ui/dist/assets/index-BdJK2jA3.css +1 -0
- package/ui/dist/assets/useEvents-CjsZBYAw.js +1 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/EditorShell-DOuUJNSm.js +0 -368
- package/ui/dist/assets/EditorShell-DTyhLLc8.css +0 -1
- package/ui/dist/assets/ProjectLauncherScreen-DP25uSQm.js +0 -1
- package/ui/dist/assets/ScenarioRenderer-CcbccxCW.js +0 -26
- package/ui/dist/assets/index-Dw4EB88N.css +0 -1
- package/ui/dist/assets/useEvents-DNb1olaj.js +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CodeYam
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# CodeYam Editor
|
|
2
|
+
|
|
3
|
+
A step-driven AI build editor with an isolated Live Preview, scenario capture, verification commands, and a Rust control API.
|
|
4
|
+
|
|
5
|
+
CodeYam Editor runs beside your project and drives an AI build agent through a planned, step-by-step workflow. Every feature is built together with the *scenarios* (the data states) that demonstrate it, shown in a live preview as you go and captured so they can be re-verified later. It works with any stack: web apps, backend services, CLIs, libraries.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
**Recommended: the one-line installer.** It installs through npm and puts `codeyam-editor` on your `PATH`:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
curl -fsSL https://codeyam.com/install.sh | sh
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
It checks the prerequisites (Node 20 or newer, and on Linux a recent enough glibc), runs the global npm install, and adds npm's global `bin` directory to your shell profile, so a new shell finds `codeyam-editor`. It prints exactly what it changed.
|
|
16
|
+
|
|
17
|
+
**Plain npm** (you manage `PATH` yourself):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @codeyam-editor/codeyam-editor
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
> If you use a custom npm prefix (for example `npm config set prefix ~/.npm-global` to avoid `sudo`), add its `bin` directory to `PATH` yourself. Otherwise the install succeeds but the shell reports `codeyam-editor: command not found`. The one-line installer does this for you.
|
|
24
|
+
|
|
25
|
+
**No install: a one-off run** through npx:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx @codeyam-editor/codeyam-editor
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Requirements
|
|
32
|
+
|
|
33
|
+
- Node.js 20 or newer.
|
|
34
|
+
- One of the supported platforms. npm installs the matching native binary automatically:
|
|
35
|
+
- macOS on Apple silicon (arm64) or Intel (x64)
|
|
36
|
+
- Linux x64 with glibc 2.28 or newer: Ubuntu 20.04+, Debian 10+, RHEL/Rocky/Alma 8+, Fedora 29+, Amazon Linux 2023. Older distros such as CentOS 7 and Amazon Linux 2 are not supported.
|
|
37
|
+
- Windows x64
|
|
38
|
+
- Playwright's Chromium, for scenario screenshots: `npx playwright install chromium`.
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
In your project directory:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
codeyam-editor init # write .codeyam/editor.json
|
|
46
|
+
codeyam-editor start # start the editor and open it in your browser
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`start` prints the editor's local URL and opens it. Pass `--no-open` to skip the browser, or `--port <PORT>` to pick the port. `init --provider <claude|codex|gemini|opencode>` chooses the build agent (Claude by default).
|
|
50
|
+
|
|
51
|
+
## Commands
|
|
52
|
+
|
|
53
|
+
| Command | What it does |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `codeyam-editor start` | Start the editor (control API and your app's process). |
|
|
56
|
+
| `codeyam-editor init` | Initialize a new `.codeyam/editor.json` config. |
|
|
57
|
+
| `codeyam-editor mcp` | Run an MCP (Model Context Protocol) server over stdio, for Claude Desktop and other MCP clients. |
|
|
58
|
+
| `codeyam-editor editor` | The workflow subcommands the build agent uses: scenarios, glossary, audit, and more. |
|
|
59
|
+
|
|
60
|
+
Run `codeyam-editor <command> --help` for a command's options, and `codeyam-editor editor --help` for the full list of workflow subcommands.
|
|
61
|
+
|
|
62
|
+
## Use with Claude
|
|
63
|
+
|
|
64
|
+
The editor works with Claude Code out of the box: `codeyam-editor init` sets up the project for it. For Claude Desktop or any other MCP client, register `codeyam-editor mcp` as a stdio MCP server. See [codeyam.com](https://codeyam.com/) for the other ways to install it in Claude.
|
|
65
|
+
|
|
66
|
+
## Links
|
|
67
|
+
|
|
68
|
+
- Homepage: <https://codeyam.com/>
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT. See `LICENSE`.
|
package/npm/disk-pressure.js
CHANGED
|
@@ -55,10 +55,11 @@ const PRUNABLE_CACHE_DIRS = [
|
|
|
55
55
|
// keyed on this known mount path, NOT folded into the generic allowlist, so a
|
|
56
56
|
// client project's own Rust target/ is never touched.
|
|
57
57
|
//
|
|
58
|
-
// Stack assumption:
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
58
|
+
// Stack assumption: every cargo build in the container writes to this volume —
|
|
59
|
+
// the editor's own on a self-host VM, and a Rust client project's on a client
|
|
60
|
+
// VM (60+ GB on the rapid-cad VMs, 2026-10-23). A VM whose project never runs
|
|
61
|
+
// cargo (Next.js, Vite, Python, …) carries an empty or absent volume, so
|
|
62
|
+
// cargoTargetReclaimPlan prunes nothing inside it there.
|
|
62
63
|
const CARGO_TARGET_DIR = '/codeyam-build-target';
|
|
63
64
|
|
|
64
65
|
// Sub-dirs of CARGO_TARGET_DIR that are ALWAYS safe to delete: pure coverage
|
|
@@ -73,33 +74,59 @@ const CARGO_TARGET_ALWAYS_PRUNE = ['llvm-cov-target'];
|
|
|
73
74
|
// in-flight compile to free space — the failure mode this tiering avoids).
|
|
74
75
|
const CARGO_TARGET_CRITICAL_CLEAN = ['debug'];
|
|
75
76
|
|
|
77
|
+
// Targeted tiers, in the order the guard runs them, at WARN and CRITICAL alike
|
|
78
|
+
// and regardless of buildInFlight: each removes only state no build will read
|
|
79
|
+
// again (scripts/cloud-disk-guard.sh::reclaim_targeted_tiers). Before them WARN
|
|
80
|
+
// freed nothing and CRITICAL could only wipe debug/ whole, so long-running Rust
|
|
81
|
+
// client VMs drifted to 100% and every reclaim cost a cold rebuild.
|
|
82
|
+
// - stale-incremental: superseded `incremental/<crate>-<hash>/s-*` state under
|
|
83
|
+
// every profile and target triple of CARGO_TARGET_DIR (needs the volume)
|
|
84
|
+
// - orphan-workspace-target: a project-local target/ while CARGO_TARGET_DIR
|
|
85
|
+
// points elsewhere
|
|
86
|
+
// - idle-base-run-targets: test-on-base scratch caches no live run holds
|
|
87
|
+
// - idle-scratch-targets: cargo targets left in agent scratch space
|
|
88
|
+
// The last three live outside the volume, so they apply on any VM.
|
|
89
|
+
const CARGO_TARGETED_TIERS = [
|
|
90
|
+
'stale-incremental',
|
|
91
|
+
'orphan-workspace-target',
|
|
92
|
+
'idle-base-run-targets',
|
|
93
|
+
'idle-scratch-targets',
|
|
94
|
+
];
|
|
95
|
+
const TIERS_NEEDING_CARGO_TARGET = ['stale-incremental'];
|
|
96
|
+
|
|
76
97
|
// Pure decision function for tiered cargo-target reclamation. Given the current
|
|
77
98
|
// pressure `level`, whether a build is in flight, and whether this VM actually
|
|
78
|
-
// has the cargo-target volume, return
|
|
79
|
-
// heavy `debug` clean was deferred (so the caller can
|
|
99
|
+
// has the cargo-target volume, return the targeted tiers to run, which sub-dirs
|
|
100
|
+
// to prune, and whether the heavy `debug` clean was deferred (so the caller can
|
|
101
|
+
// log it — no silent cap).
|
|
80
102
|
//
|
|
81
103
|
// Tiers (mirrored by the bash guard + watchdog):
|
|
82
|
-
// -
|
|
104
|
+
// - warn / critical → targeted tiers first, always
|
|
105
|
+
// - no cargo-target (guest/client VM) → prune nothing in the volume
|
|
83
106
|
// - warn → ALWAYS_PRUNE only (llvm-cov)
|
|
84
107
|
// - critical & idle → ALWAYS_PRUNE + CRITICAL_CLEAN
|
|
85
108
|
// - critical & build in flight → ALWAYS_PRUNE only, defer heavy
|
|
86
|
-
// - ok / unknown level → fail-safe:
|
|
109
|
+
// - ok / unknown level → fail-safe: run nothing
|
|
87
110
|
function cargoTargetReclaimPlan({ level, buildInFlight, hasCargoTarget } = {}) {
|
|
88
|
-
if (
|
|
111
|
+
if (level !== 'warn' && level !== 'critical') {
|
|
112
|
+
// 'ok', undefined, or any unknown level → do nothing (fail safe).
|
|
113
|
+
return { targeted: [], prune: [], deferredHeavyClean: false };
|
|
114
|
+
}
|
|
115
|
+
const targeted = CARGO_TARGETED_TIERS.filter(
|
|
116
|
+
(tier) => hasCargoTarget || !TIERS_NEEDING_CARGO_TARGET.includes(tier)
|
|
117
|
+
);
|
|
118
|
+
if (!hasCargoTarget) return { targeted, prune: [], deferredHeavyClean: false };
|
|
89
119
|
if (level === 'warn') {
|
|
90
|
-
return { prune: [...CARGO_TARGET_ALWAYS_PRUNE], deferredHeavyClean: false };
|
|
120
|
+
return { targeted, prune: [...CARGO_TARGET_ALWAYS_PRUNE], deferredHeavyClean: false };
|
|
91
121
|
}
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
return { prune: [...CARGO_TARGET_ALWAYS_PRUNE], deferredHeavyClean: true };
|
|
95
|
-
}
|
|
96
|
-
return {
|
|
97
|
-
prune: [...CARGO_TARGET_ALWAYS_PRUNE, ...CARGO_TARGET_CRITICAL_CLEAN],
|
|
98
|
-
deferredHeavyClean: false,
|
|
99
|
-
};
|
|
122
|
+
if (buildInFlight) {
|
|
123
|
+
return { targeted, prune: [...CARGO_TARGET_ALWAYS_PRUNE], deferredHeavyClean: true };
|
|
100
124
|
}
|
|
101
|
-
|
|
102
|
-
|
|
125
|
+
return {
|
|
126
|
+
targeted,
|
|
127
|
+
prune: [...CARGO_TARGET_ALWAYS_PRUNE, ...CARGO_TARGET_CRITICAL_CLEAN],
|
|
128
|
+
deferredHeavyClean: false,
|
|
129
|
+
};
|
|
103
130
|
}
|
|
104
131
|
|
|
105
132
|
// Classify an integer disk-usage percent into a pressure level. A non-numeric,
|
|
@@ -159,6 +186,7 @@ module.exports = {
|
|
|
159
186
|
CARGO_TARGET_DIR,
|
|
160
187
|
CARGO_TARGET_ALWAYS_PRUNE,
|
|
161
188
|
CARGO_TARGET_CRITICAL_CLEAN,
|
|
189
|
+
CARGO_TARGETED_TIERS,
|
|
162
190
|
cargoTargetReclaimPlan,
|
|
163
191
|
classifyDiskPressure,
|
|
164
192
|
parseDfUsedPct,
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const lc = require('./launch-config');
|
|
4
|
+
const fa = require('./fleet-auth');
|
|
5
|
+
const fer = require('./fleet-early-roster');
|
|
6
|
+
|
|
7
|
+
// npm/fleet-add-vm-steps.js — what an Add (fleet-dashboard server.js `addVm`)
|
|
8
|
+
// runs, and in what order, decided before anything is spawned.
|
|
9
|
+
//
|
|
10
|
+
// addVm keeps every side effect: the pending-job sidecar, the exit-marker
|
|
11
|
+
// clear, startJob and the roster writes. These two steps only DECIDE:
|
|
12
|
+
//
|
|
13
|
+
// resolveAddCreds — which reachable VM donates claude/git credentials (or
|
|
14
|
+
// which donor-less fallback applies) and the seeding
|
|
15
|
+
// shell steps that follow from it;
|
|
16
|
+
// buildAddVmScript — the provision script itself: cloud:up, the container
|
|
17
|
+
// gate, the early-roster marker, creds, tunnels and the
|
|
18
|
+
// awaited ingress wiring, as one newline-joined string.
|
|
19
|
+
//
|
|
20
|
+
// The server's constants arrive under their server names, and `shq` is the
|
|
21
|
+
// server's single-quote shell escaper, so both read as they did inline.
|
|
22
|
+
|
|
23
|
+
// `vms` is the server's state.vms; `requestedAccount` is the account the add
|
|
24
|
+
// should seed (a per-add override, else the fleet's chosen one), already
|
|
25
|
+
// resolved by the server because choosing it reads the dashboard's settings.
|
|
26
|
+
function resolveAddCreds({
|
|
27
|
+
n, VMS, vms, requestedAccount, inst, INSTANCE_PREFIX, CLAUDE_CREDS_SRC, GITHUB_TOKEN,
|
|
28
|
+
CONTAINER, GCLOUD, ZONE, shq,
|
|
29
|
+
}) {
|
|
30
|
+
// Creds source: prefer a *reachable* donor (a VM that actually answered the
|
|
31
|
+
// last poll — VMS[0] may be a destroyed-but-still-rostered orphan, #24), else
|
|
32
|
+
// fall back to the operator-configured CLAUDE_CREDS_SRC / GITHUB_TOKEN. A full
|
|
33
|
+
// teardown empties VMS, so there's no donor at all — the fallback is what lets
|
|
34
|
+
// "destroy all → Add count=N" produce a working fleet numbered 1-N (improve34).
|
|
35
|
+
// A reachable donor whose credential failed the integrity probe is dropped
|
|
36
|
+
// HERE, before resolveCredsSource's first-donor fallback could pick it: the
|
|
37
|
+
// copy sink would refuse its credential only after the add had committed to
|
|
38
|
+
// it (VM-11 → VM-2, 2026-09-17). Each dropped donor is named in the add log.
|
|
39
|
+
const donorSplit = fa.partitionDonorsByIntegrity(
|
|
40
|
+
VMS.filter((x) => x !== n && vms[x] && vms[x].reachable),
|
|
41
|
+
vms,
|
|
42
|
+
);
|
|
43
|
+
const reachableDonors = donorSplit.usable;
|
|
44
|
+
// The donor is the reachable VM holding the requested account, so the account
|
|
45
|
+
// the Add dialog showed is the account actually seeded. When no reachable VM
|
|
46
|
+
// holds it, fall back to the usual donor and raise a CY_DEGRADED reason naming
|
|
47
|
+
// both (credsSteps below).
|
|
48
|
+
const preferredDonor = fa.selectDonorForAccount(reachableDonors, vms, requestedAccount);
|
|
49
|
+
const creds = lc.resolveCredsSource({
|
|
50
|
+
reachableDonors,
|
|
51
|
+
preferredDonor,
|
|
52
|
+
requestedAccount,
|
|
53
|
+
claudeCredsSrc: CLAUDE_CREDS_SRC,
|
|
54
|
+
githubToken: GITHUB_TOKEN,
|
|
55
|
+
});
|
|
56
|
+
const refInst = creds.donor != null ? INSTANCE_PREFIX + creds.donor : null; // claude/git source when present
|
|
57
|
+
const donorAuth = creds.donor != null && vms[creds.donor] && vms[creds.donor].providerAuth;
|
|
58
|
+
const seededAccount = (donorAuth && donorAuth.account && donorAuth.account.email) || '';
|
|
59
|
+
// Build the creds-seeding steps for the resolved mode. The "donor" path is the
|
|
60
|
+
// unchanged default (container→container claude + host-file git from a live
|
|
61
|
+
// VM); host-stash / token / none are the donor-less fallback. `add-degraded`
|
|
62
|
+
// (modes token/none — no claude auth source) is flagged after startJob below.
|
|
63
|
+
// The shell-string builder lives in npm/launch-config.js so it's unit-testable
|
|
64
|
+
// (see launch-config.test.js::buildCredsSteps).
|
|
65
|
+
const credsSteps = lc.buildCredsSteps({
|
|
66
|
+
mode: creds.mode,
|
|
67
|
+
refInst,
|
|
68
|
+
inst,
|
|
69
|
+
container: CONTAINER,
|
|
70
|
+
gcloud: GCLOUD,
|
|
71
|
+
zone: ZONE,
|
|
72
|
+
n,
|
|
73
|
+
claudeCredsSrc: CLAUDE_CREDS_SRC,
|
|
74
|
+
account: seededAccount,
|
|
75
|
+
});
|
|
76
|
+
// Fall back loudly, never silently: the operator asked for an account no
|
|
77
|
+
// reachable VM holds, so say which account was requested and what was seeded.
|
|
78
|
+
if (creds.requestedAccountUnavailable) {
|
|
79
|
+
const reason = lc.accountFallbackMessage({
|
|
80
|
+
requested: requestedAccount,
|
|
81
|
+
seeded: seededAccount,
|
|
82
|
+
mode: creds.mode,
|
|
83
|
+
donor: creds.donor,
|
|
84
|
+
inst,
|
|
85
|
+
});
|
|
86
|
+
credsSteps.unshift(`echo ${shq(reason)}`);
|
|
87
|
+
}
|
|
88
|
+
credsSteps.unshift(...donorSplit.skipped.map((d) => lc.donorSkippedLine(d)));
|
|
89
|
+
return { creds, credsSteps };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// The provision script startJob runs for an add. `exitMarker` / `exitMarkerTmp`
|
|
93
|
+
// are the durable add-<n>.exit paths, which the server clears before calling.
|
|
94
|
+
function buildAddVmScript(opts) {
|
|
95
|
+
const {
|
|
96
|
+
n, cfg, inst, port, previewPort, appPort, upArgs, proj, tunnelCmd, credsSteps,
|
|
97
|
+
exitMarker, exitMarkerTmp, resolveProjectName, REPO_ROOT, GCLOUD, ZONE, CONTAINER,
|
|
98
|
+
WIRING_AWAIT_SEC, GIT_RESEED_BODY, shq,
|
|
99
|
+
} = opts;
|
|
100
|
+
// Make git creds live in the running container. Both the donor host-file copy
|
|
101
|
+
// and cloud:up's token write land on the read-only STAGING mount but DON'T
|
|
102
|
+
// touch the in-memory `cache` daemon (seeded from the empty placeholder at
|
|
103
|
+
// container boot) — so git auth stays inert until re-seeded. Mirror the
|
|
104
|
+
// entrypoint seed (scripts/cloud-editor-entrypoint.sh) live; base64-framed so
|
|
105
|
+
// the multi-line snippet survives the gcloud-ssh + docker-exec quoting layers.
|
|
106
|
+
// `-s` self-skips when STAGING is still the empty placeholder (mode none).
|
|
107
|
+
const gitReseedB64 = Buffer.from(GIT_RESEED_BODY, 'utf8').toString('base64');
|
|
108
|
+
const gitReseedStep = `echo ${gitReseedB64} | ${GCLOUD} compute ssh ${inst} --tunnel-through-iap --zone=${ZONE} --command='docker exec -i ${CONTAINER} bash -c "base64 -d | bash"' 2>/dev/null || echo "WARN git cache re-seed exec failed"`;
|
|
109
|
+
const script = [
|
|
110
|
+
`CY_ADD_EXIT_MARKER=${shq(exitMarker)}`,
|
|
111
|
+
`CY_ADD_EXIT_MARKER_TMP=${shq(exitMarkerTmp)}`,
|
|
112
|
+
`trap 'cy_code=$?; printf "%s\\n%s\\n" "$cy_code" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$CY_ADD_EXIT_MARKER_TMP" 2>/dev/null && mv -f "$CY_ADD_EXIT_MARKER_TMP" "$CY_ADD_EXIT_MARKER" 2>/dev/null; exit $cy_code' EXIT`,
|
|
113
|
+
`cd ${shq(REPO_ROOT)}`,
|
|
114
|
+
`echo "provisioning ${inst} — GCP project ${resolveProjectName()} — project ${proj}, editor ${cfg.editorVariant}${cfg.editorBranch ? '@' + cfg.editorBranch : ''} (cloud:up — image build can take ~15-25 min under fleet load)..."`,
|
|
115
|
+
// Background cloud:up so we can poll the container on our own cadence while
|
|
116
|
+
// the image build runs (~15-25 min). Under --no-tunnel it exits cleanly the
|
|
117
|
+
// instant the container is up — the wait below treats that exit as normal.
|
|
118
|
+
`npm run cloud:up -- ${upArgs} > /tmp/dash-add-vm-${n}.log 2>&1 &`,
|
|
119
|
+
`UP_PID=$!`,
|
|
120
|
+
// Wait up to ~28 min of real time for the editor container — the cold image
|
|
121
|
+
// build is the long pole and gets slower as the fleet grows. The gate (pure,
|
|
122
|
+
// tested: lc.containerGateCmd) treats ANY single `Up` as terminal success
|
|
123
|
+
// (mirrors lc.classifyContainerWait); a clean cloud:up exit is the NORMAL
|
|
124
|
+
// --no-tunnel success path (fragility #33) while a NON-ZERO exit fails fast
|
|
125
|
+
// after one re-check; a transient gcloud-ssh blip is retried; and failure
|
|
126
|
+
// past the deadline tells a vanished instance from a stuck build, then reaps
|
|
127
|
+
// the `node cloud.js up` grandchild $UP_PID doesn't cover (anchored at
|
|
128
|
+
// `node`, so it can't match THIS script's own `sh -c` wrapper).
|
|
129
|
+
//
|
|
130
|
+
// It probes on a WIDENING cadence (10s → 30s → 60s), not every 10s: each
|
|
131
|
+
// probe is a `gcloud compute ssh`, i.e. an oslogin read against the
|
|
132
|
+
// fleet-wide 60/min per-user quota, and the flat 10s loop spent 6/min per
|
|
133
|
+
// in-flight add on that question alone — invisible to hostOpLimiter, which
|
|
134
|
+
// only sees the dashboard's own calls. A quota refusal on a probe prints a
|
|
135
|
+
// CY_API_QUOTA line startJob's output tap journals.
|
|
136
|
+
lc.containerGateCmd({ gcloud: GCLOUD, inst, zone: ZONE, probeCmd: tunnelCmd, logPath: `/tmp/dash-add-vm-${n}.log` }),
|
|
137
|
+
// Container gate passed: the instance exists and the editor container is Up.
|
|
138
|
+
// Announce it so the dashboard rosters this VM NOW rather than waiting for
|
|
139
|
+
// the whole script to exit 0. Rostering here is what breaks the deadlock
|
|
140
|
+
// that stranded VM-4..7 on 2026-07-20: everything below (creds, tunnels, the
|
|
141
|
+
// awaited LB wiring) can still fail, but a rostered VM stays VISIBLE to
|
|
142
|
+
// `reconcile-ingress`, which skips unrostered numbers — so the periodic
|
|
143
|
+
// sweep can finish the wiring instead of the VM becoming invisible and
|
|
144
|
+
// burning its bootstrap-recovery budget on a probe that can never pass
|
|
145
|
+
// without ingress. See npm/fleet-early-roster.js.
|
|
146
|
+
`echo ${shq(fer.rosterMarkerLine(n))}`,
|
|
147
|
+
// Container is up — but cloud:up is NOT done. wireVmIngressOnAdd runs as a
|
|
148
|
+
// TAIL step of cloud:up (the slow EXTERNAL_MANAGED backend-services create +
|
|
149
|
+
// NEG attach + url-map host-rule), AFTER the container gate. The old code
|
|
150
|
+
// reaped cloud:up here (`kill $UP_PID`) 15s after the container was Up, on the
|
|
151
|
+
// false premise that `--no-tunnel` had "already exited" — that SIGTERM'd
|
|
152
|
+
// `backend-services create` mid-flight and stranded the VM backendless at 503
|
|
153
|
+
// until the self-heal sweep recovered it. So DON'T kill cloud:up here: just
|
|
154
|
+
// settle for in-flight seeds + sweep any stray :14199 tunnel before we open
|
|
155
|
+
// ours. The wiring is AWAITED (lc.awaitWiringCmd) at the end of the script,
|
|
156
|
+
// after the creds/tunnel steps — which don't depend on it — have run.
|
|
157
|
+
// The sweep is anchored at the ssh executable (lc.editorForwardSweepCmd).
|
|
158
|
+
// Bare, `pkill -f "14199:127.0.0.1:14199"` matched this script's OWN
|
|
159
|
+
// `sh -c '<add script>'` wrapper — the script text contains this line — and
|
|
160
|
+
// every concurrent sibling add wrapper, SIGTERMing them 15s after the
|
|
161
|
+
// container gate passed. That killed the EXIT trap that writes
|
|
162
|
+
// add-<n>.exit, orphaned the detached cloud:up, and painted a red
|
|
163
|
+
// "Add failed" on healthy VMs (2026-07-31, VM-3..8).
|
|
164
|
+
`sleep 15; ${lc.editorForwardSweepCmd({ localPort: 14199 })}`,
|
|
165
|
+
// Seed claude + git auth: donor copy (default) or the donor-less fallback.
|
|
166
|
+
...credsSteps,
|
|
167
|
+
// Activate git creds in the running container (host-file/STAGING write alone
|
|
168
|
+
// is inert against the booted cache daemon).
|
|
169
|
+
gitReseedStep,
|
|
170
|
+
// open the localhost tunnels (nohup; watchdog maintains them) as TWO
|
|
171
|
+
// INDEPENDENT ssh processes: the editor reachability forward (14299+N ->
|
|
172
|
+
// VM:14199) and a sibling Live Preview forward (15172+N -> VM:<appPort>)
|
|
173
|
+
// that the cloudflared-monitor fronts with a public "<N>-preview" tunnel.
|
|
174
|
+
// Splitting them is load-bearing — a preview-port re-wire then restarts ONLY
|
|
175
|
+
// the preview process, so the editor :14199 forward (the dashboard's
|
|
176
|
+
// reachability signal) stays up and the probe can't blip a false
|
|
177
|
+
// "unreachable". Separate log files keep each tunnel independently inspectable.
|
|
178
|
+
lc.editorTunnelCmd({ gcloud: GCLOUD, inst, zone: ZONE, editorPort: port, n }),
|
|
179
|
+
lc.previewTunnelCmd({ gcloud: GCLOUD, inst, zone: ZONE, previewPort, appPort, n }),
|
|
180
|
+
// Tell the editor's process-global provider-auth cache to forget the
|
|
181
|
+
// cold-boot probe's NeedsLogin verdict now that credsSteps has dropped a
|
|
182
|
+
// fresh .credentials.json onto the VM's /root/.claude/. Without this hop,
|
|
183
|
+
// /api/config keeps serving needs-login (UI shows ProviderAuthGate, Build
|
|
184
|
+
// tab locked) until the editor server is restarted — even though the
|
|
185
|
+
// creds are valid. Fail-soft: a few retries cover the nohup tunnel still
|
|
186
|
+
// settling; the manual restart-server workaround remains the fallback.
|
|
187
|
+
`echo "invalidating providerAuth cache on VM-${n}..."`,
|
|
188
|
+
`for i in 1 2 3 4 5; do if curl -sS -m 3 -X POST http://localhost:${port}/api/auth/refresh-cache >/dev/null 2>&1; then echo "providerAuth cache invalidated on VM-${n}"; break; fi; sleep 2; if [ "$i" = "5" ]; then echo "WARN providerAuth cache refresh failed on VM-${n} — restart editor if Build tab shows Connect"; fi; done`,
|
|
189
|
+
// Await cloud:up's slow wiring tail (started AFTER the container gate) rather
|
|
190
|
+
// than reaping it the instant the container was Up — the mid-`backend-services
|
|
191
|
+
// create` SIGTERM is the add-time 503 strand this closes. Waits for cloud:up to
|
|
192
|
+
// exit on its own (wiring done, success OR loud-fail) up to WIRING_AWAIT_SEC,
|
|
193
|
+
// force-reaping only past the deadline (the dashboard self-heal is the backstop).
|
|
194
|
+
// Placed here, after the creds/tunnel steps that don't depend on wiring, so the
|
|
195
|
+
// CY_DEGRADED grep below reads the FINAL wiring outcome — not a mid-flight log.
|
|
196
|
+
lc.awaitWiringCmd({ inst, deadlineSec: WIRING_AWAIT_SEC }),
|
|
197
|
+
// Republish any CY_DEGRADED marker cloud:up emitted (LB ingress wiring failed
|
|
198
|
+
// — the VM7-10 case) from its private log onto THIS job's stdout, where the
|
|
199
|
+
// completion scanner (finish()) reads markers from `buf`. Without this hop the
|
|
200
|
+
// marker stays buried in /tmp/dash-add-vm-N.log and the card never goes amber.
|
|
201
|
+
`grep -h '^CY_DEGRADED ' /tmp/dash-add-vm-${n}.log 2>/dev/null || true`,
|
|
202
|
+
// "+ wired" must be EARNED, not asserted: the unwired-but-green VM7-10 log
|
|
203
|
+
// came from claiming "+ wired" unconditionally here. State the provisioned
|
|
204
|
+
// fact only; the ingress-not-wired marker above carries the wiring state.
|
|
205
|
+
`echo "VM-${n} provisioned on localhost:${port} (preview localhost:${previewPort})"`,
|
|
206
|
+
].join('\n');
|
|
207
|
+
return script;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
module.exports = { resolveAddCreds, buildAddVmScript };
|
package/npm/fleet-build-data.js
CHANGED
|
@@ -158,7 +158,15 @@ function buildVmEnvelope(v, ctx) {
|
|
|
158
158
|
// decides manually whether to release it).
|
|
159
159
|
headHolderUnreachable: !!ctx.headHolderUnreachable,
|
|
160
160
|
queuePos: ctx.queuePos != null ? ctx.queuePos : null,
|
|
161
|
-
|
|
161
|
+
// The fleet queue's length when this VM is in it, else its own branch
|
|
162
|
+
// queue's length, else null (npm/fleet-stuck-position.js::resolveQueueLen).
|
|
163
|
+
queueLen: ctx.queueLen != null ? ctx.queueLen : null,
|
|
164
|
+
// This VM's OWN branch commit queue, read from its /api/branch-queue/status:
|
|
165
|
+
// { branch, position, length, inQueue, holdsHead, blocked, headMachine,
|
|
166
|
+
// headSuspect }, or null when the VM did not answer. Distinct from
|
|
167
|
+
// queuePos/isHead, which describe the fleet's pinned-branch queue only, so a
|
|
168
|
+
// VM on a client branch can say "position 4 of 5 on rapid-cad-7".
|
|
169
|
+
branchQueue: v.branchQueue || null,
|
|
162
170
|
job: ctx.job || null,
|
|
163
171
|
config: ctx.config || null,
|
|
164
172
|
project: ctx.project != null ? ctx.project : null,
|
|
@@ -193,7 +201,7 @@ function buildVmEnvelope(v, ctx) {
|
|
|
193
201
|
startedAt: v.startedAt || null,
|
|
194
202
|
featureStartedAt: v.featureStartedAt || null,
|
|
195
203
|
// The server-computed stuck / needs-you verdict (npm/fleet-stuck-status.js):
|
|
196
|
-
// { stepElapsedMs, level: 'ok'|'slow'|'stuck', needsYou, needsYouElapsedMs }.
|
|
204
|
+
// { stepElapsedMs, level: 'ok'|'slow'|'stuck'|'queued', needsYou, needsYouElapsedMs }.
|
|
197
205
|
// The authoritative level only changes on poll; the card colors the step
|
|
198
206
|
// tenure badge by it. null when buildData couldn't compute one.
|
|
199
207
|
attention: ctx.attention || null,
|
|
@@ -240,7 +248,7 @@ const REQUIRED_CARD_FIELDS = [
|
|
|
240
248
|
'sessionIdleSeconds', 'sessionActivity', 'sessionWedged', 'brokerLive', 'brokerStatus',
|
|
241
249
|
'awaitingInput', 'needsCleanup', 'feature', 'selectedPlan', 'step', 'mode', 'label', 'cycleClosed', 'status', 'orphan',
|
|
242
250
|
'degradedReasons', 'setupSteps', 'attentionReasons',
|
|
243
|
-
'isHead', 'headHolderUnreachable', 'queuePos', 'queueLen',
|
|
251
|
+
'isHead', 'headHolderUnreachable', 'queuePos', 'queueLen', 'branchQueue',
|
|
244
252
|
'job', 'config', 'project', 'variant', 'cardIdentity', 'brokerHistory',
|
|
245
253
|
'brokerDeath', 'providerAuth', 'previewMisconfigured', 'previewEndpoint', 'datastore', 'effects', 'currentProvider',
|
|
246
254
|
'readiness', 'backendHealth', 'vmAccess',
|