orizu 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -13
- package/dist/artifact-archive.js +152 -0
- package/dist/connectors-cli.js +80 -0
- package/dist/credentials.js +4 -1
- package/dist/daytona-slice-rehearsal.js +255 -0
- package/dist/daytona-slice.js +441 -0
- package/dist/egress-allowlist-cli.js +132 -0
- package/dist/egress-policy.js +159 -0
- package/dist/git-credential.js +131 -0
- package/dist/github-setup.js +226 -0
- package/dist/help.js +276 -21
- package/dist/hosted-bootstrap-rehearsal.js +235 -0
- package/dist/hosted-bootstrap.js +365 -0
- package/dist/hosted-broker-server.js +220 -0
- package/dist/hosted-git-http-server.js +136 -0
- package/dist/hosted-harness-claude.js +493 -0
- package/dist/hosted-harness-opencode.js +729 -0
- package/dist/hosted-harness.js +60 -0
- package/dist/hosted-loop.js +477 -0
- package/dist/hosted-run-event-sink.js +499 -0
- package/dist/hosted-runtime-assets.js +216 -0
- package/dist/hosted-session-cli.js +642 -0
- package/dist/index.js +945 -258
- package/dist/kill-switch-cli.js +89 -0
- package/dist/manifests-cli.js +143 -0
- package/dist/preview-runtime.js +4 -1
- package/dist/sandbox-provider.js +238 -0
- package/dist/secret-redaction.js +112 -0
- package/dist/session-timeline.js +103 -0
- package/dist/setup-prompt.js +4 -2
- package/dist/skill-installer.js +9 -1
- package/dist/smoke.js +250 -0
- package/dist/task-assignment-manifest.js +96 -0
- package/dist/vercel-sandbox-provider.js +260 -0
- package/dist/workbench-cli.js +493 -0
- package/dist/workspace-sync.js +924 -0
- package/dist/workspace.js +868 -57
- package/package.json +4 -1
- package/src/artifact-archive.ts +182 -0
- package/src/connectors-cli.ts +115 -0
- package/src/credentials.ts +6 -2
- package/src/daytona-slice-rehearsal.ts +320 -0
- package/src/daytona-slice.ts +598 -0
- package/src/egress-allowlist-cli.ts +147 -0
- package/src/egress-policy.ts +188 -0
- package/src/git-credential.ts +155 -0
- package/src/github-setup.ts +366 -0
- package/src/help.ts +277 -21
- package/src/hosted-bootstrap-rehearsal.ts +312 -0
- package/src/hosted-bootstrap.ts +571 -0
- package/src/hosted-broker-server.ts +278 -0
- package/src/hosted-git-http-server.ts +164 -0
- package/src/hosted-harness-claude.ts +621 -0
- package/src/hosted-harness-opencode.ts +823 -0
- package/src/hosted-harness.ts +124 -0
- package/src/hosted-loop.ts +680 -0
- package/src/hosted-run-event-sink.ts +632 -0
- package/src/hosted-runtime-assets.ts +272 -0
- package/src/hosted-session-cli.ts +844 -0
- package/src/index.ts +1102 -317
- package/src/kill-switch-cli.ts +113 -0
- package/src/manifests-cli.ts +168 -0
- package/src/preview-runtime.ts +14 -9
- package/src/sandbox-provider.ts +440 -0
- package/src/secret-redaction.ts +124 -0
- package/src/session-timeline.ts +152 -0
- package/src/setup-prompt.ts +4 -2
- package/src/skill-installer.ts +13 -1
- package/src/smoke.ts +342 -0
- package/src/task-assignment-manifest.ts +164 -0
- package/src/vercel-sandbox-provider.ts +388 -0
- package/src/workbench-cli.ts +701 -0
- package/src/workspace-sync.ts +1148 -0
- package/src/workspace.ts +1051 -64
- package/vendor/skills/orizu-cli/SKILL.md +8 -4
- package/vendor/skills/orizu-cli/references/cli-reference.md +12 -4
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-sandbox runtime assets for the hosted bootstrap (ALI-925, per ADR-005).
|
|
3
|
+
*
|
|
4
|
+
* Ports the DESIGN — not the code — of OpenInspect's
|
|
5
|
+
* `sandbox_runtime/credentials/git_credential_helper.py` into a self-contained
|
|
6
|
+
* script asset that Orizu writes into the sandbox and registers as a git
|
|
7
|
+
* `credential.helper`. On every git op git spawns the script; the script mints a
|
|
8
|
+
* fresh, downscoped, audited token from Orizu's per-repo broker
|
|
9
|
+
* (`POST /api/cli/workspaces/{id}/repo-token`) and hands it to git, caching to a
|
|
10
|
+
* 0600 run-scoped file with an expiry-refresh buffer. It NEVER echoes the token
|
|
11
|
+
* and reads the bearer only from a run-scoped 0600 file (G3): the bearer is
|
|
12
|
+
* never baked into git config, the clone URL, or any long-lived file.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately self-contained (no `orizu` package import) so it works even when
|
|
15
|
+
* the CLI-install step fails — the credential path must not depend on it. The
|
|
16
|
+
* script is emitted as CommonJS (`.cjs`) so it runs unchanged under both `node`
|
|
17
|
+
* (>=18, for global `fetch`) and `bun`, whichever the sandbox image ships.
|
|
18
|
+
*
|
|
19
|
+
* The boot context is split so no secret lands in a value git persists:
|
|
20
|
+
* - `boot.json` — non-secret config + the PATHS of the bearer/cache files;
|
|
21
|
+
* - `<bearer>` — the Orizu bearer, 0600, run-scoped, reaped at teardown;
|
|
22
|
+
* - `<cache>` — the minted repo token, 0600, written by the script itself.
|
|
23
|
+
* git config holds only `credential.helper = !<runtime> <script> <boot.json>`.
|
|
24
|
+
*/
|
|
25
|
+
export const HELPER_SCRIPT_BASENAME = 'orizu-git-credential-helper.cjs';
|
|
26
|
+
export const BOOT_CONTEXT_BASENAME = 'boot.json';
|
|
27
|
+
export const BEARER_BASENAME = 'bearer';
|
|
28
|
+
export const REPO_CRED_CACHE_BASENAME = 'repo-cred.json';
|
|
29
|
+
/** Refresh a cached token this long before its stated expiry (5 minutes). */
|
|
30
|
+
export const DEFAULT_CACHE_REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
31
|
+
/** Customer setup hook path, relative to the repo root (mirrors OpenInspect's
|
|
32
|
+
* `.openinspect/setup.sh` — fresh-boot only, non-fatal, output captured). */
|
|
33
|
+
export const SETUP_HOOK_RELATIVE_PATH = '.orizu/setup.sh';
|
|
34
|
+
// -- Pre-baked runtime marker (ALI-1017) -------------------------------------
|
|
35
|
+
/**
|
|
36
|
+
* Absolute path of the marker the pre-baked hosted-runtime image writes at build
|
|
37
|
+
* time (see packages/cli/hosted-runtime-image/Dockerfile). Its presence — plus
|
|
38
|
+
* the boot-context `prebaked` flag — tells the runtime the CLI + OpenCode are
|
|
39
|
+
* already on PATH, so the from-scratch install steps must be SKIPPED (they would
|
|
40
|
+
* otherwise fail under G5 default-deny egress).
|
|
41
|
+
*/
|
|
42
|
+
export const PREBAKED_MARKER_PATH = '/opt/orizu/prebaked.json';
|
|
43
|
+
/**
|
|
44
|
+
* Parse + validate a pre-baked marker file's contents. Returns the typed marker
|
|
45
|
+
* when the JSON is well-formed AND carries the expected `builtFor` guard with
|
|
46
|
+
* string version fields; returns null otherwise (missing file → caller passes an
|
|
47
|
+
* empty/whitespace string; malformed JSON; wrong shape). Never throws, so a
|
|
48
|
+
* belt-and-braces filesystem probe can call it on best-effort file reads.
|
|
49
|
+
*/
|
|
50
|
+
export function parsePrebakedMarker(raw) {
|
|
51
|
+
let parsed;
|
|
52
|
+
try {
|
|
53
|
+
parsed = JSON.parse(raw);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
if (!parsed || typeof parsed !== 'object')
|
|
59
|
+
return null;
|
|
60
|
+
const rec = parsed;
|
|
61
|
+
if (rec.builtFor !== 'vercel-sandbox')
|
|
62
|
+
return null;
|
|
63
|
+
const { cliVersion, opencodeVersion, claudeSdkVersion } = rec;
|
|
64
|
+
if (typeof cliVersion !== 'string' ||
|
|
65
|
+
typeof opencodeVersion !== 'string' ||
|
|
66
|
+
typeof claudeSdkVersion !== 'string') {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return { cliVersion, opencodeVersion, claudeSdkVersion, builtFor: 'vercel-sandbox' };
|
|
70
|
+
}
|
|
71
|
+
export function serializeBootContext(ctx) {
|
|
72
|
+
return `${JSON.stringify(ctx, null, 2)}\n`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The git credential-helper script, as a self-contained CommonJS source string.
|
|
76
|
+
* Implements the `get` action (mint/emit); `store`/`erase` are no-ops because
|
|
77
|
+
* the broker owns credential truth and the script never persists what git hands
|
|
78
|
+
* back. Diagnostics go to stderr only — stdout is reserved for git's protocol.
|
|
79
|
+
*
|
|
80
|
+
* NOTE: this is a rendered ARTIFACT, exercised end-to-end as a subprocess in
|
|
81
|
+
* `test/cli/hosted-credential-helper.test.ts`; that subprocess test is the
|
|
82
|
+
* source of truth for its behavior.
|
|
83
|
+
*/
|
|
84
|
+
export function renderCredentialHelperScript() {
|
|
85
|
+
return `'use strict'
|
|
86
|
+
// Orizu in-sandbox git credential helper (ALI-925). Self-contained CommonJS.
|
|
87
|
+
// Usage (git invokes it): node ${HELPER_SCRIPT_BASENAME} <boot.json> <get|store|erase>
|
|
88
|
+
const fs = require('node:fs')
|
|
89
|
+
|
|
90
|
+
function log(message) {
|
|
91
|
+
try { process.stderr.write('[orizu-cred] ' + message + '\\n') } catch (_) { /* ignore */ }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readStdin() {
|
|
95
|
+
try { return fs.readFileSync(0, 'utf8') } catch (_) { return '' }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseProtocolInput(text) {
|
|
99
|
+
const out = {}
|
|
100
|
+
for (const raw of text.split('\\n')) {
|
|
101
|
+
const line = raw.replace(/\\r$/, '')
|
|
102
|
+
if (!line) continue
|
|
103
|
+
const eq = line.indexOf('=')
|
|
104
|
+
if (eq <= 0) continue
|
|
105
|
+
out[line.slice(0, eq)] = line.slice(eq + 1)
|
|
106
|
+
}
|
|
107
|
+
return out
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function readCache(file, nowMs, bufferMs) {
|
|
111
|
+
let parsed
|
|
112
|
+
try { parsed = JSON.parse(fs.readFileSync(file, 'utf8')) } catch (_) { return null }
|
|
113
|
+
if (!parsed || typeof parsed !== 'object') return null
|
|
114
|
+
if (!parsed.username || !parsed.password) return null
|
|
115
|
+
if (typeof parsed.expiresAtMs !== 'number') return null
|
|
116
|
+
if (parsed.expiresAtMs - nowMs <= bufferMs) return null
|
|
117
|
+
return parsed
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function writeCache(file, cred) {
|
|
121
|
+
try {
|
|
122
|
+
const fd = fs.openSync(file, 'w', 0o600)
|
|
123
|
+
try { fs.writeSync(fd, JSON.stringify(cred)) } finally { fs.closeSync(fd) }
|
|
124
|
+
} catch (e) { log('cache write failed: ' + (e && e.message)) }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function requestToken(url, bearer, purpose, sessionId) {
|
|
128
|
+
const res = await fetch(url, {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + bearer },
|
|
131
|
+
body: JSON.stringify({ purpose: purpose, sessionId: sessionId }),
|
|
132
|
+
})
|
|
133
|
+
if (res.ok) {
|
|
134
|
+
const data = await res.json()
|
|
135
|
+
if (!data || !data.token) throw new Error('broker returned no token')
|
|
136
|
+
const parsedExpiry = Date.parse(data.expiresAt)
|
|
137
|
+
const expiresAtMs = Number.isFinite(parsedExpiry) ? parsedExpiry : Date.now() + 60 * 60 * 1000
|
|
138
|
+
return { cred: { username: 'x-access-token', password: String(data.token), expiresAtMs: expiresAtMs }, status: res.status }
|
|
139
|
+
}
|
|
140
|
+
return { cred: null, status: res.status }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function mint(ctx) {
|
|
144
|
+
const bearer = fs.readFileSync(ctx.bearerFile, 'utf8').trim()
|
|
145
|
+
if (!bearer) throw new Error('empty bearer file')
|
|
146
|
+
const base = String(ctx.apiBaseUrl || '').replace(/\\/$/, '')
|
|
147
|
+
const url = base + '/api/cli/workspaces/' + encodeURIComponent(ctx.workspaceId) + '/repo-token'
|
|
148
|
+
const purposes = ctx.tokenPurposes || {}
|
|
149
|
+
const primary = String(purposes.primary || 'write')
|
|
150
|
+
const fallback = String(purposes.fallback || 'read')
|
|
151
|
+
|
|
152
|
+
// Try the primary purpose first. A 403 means "unauthorized for this purpose"
|
|
153
|
+
// (e.g. a read-only caller asking to write) — the ONLY legitimate fallback
|
|
154
|
+
// trigger. A 400 means the broker does not recognise the purpose at all
|
|
155
|
+
// (vocabulary drift): fail loudly rather than silently downgrade.
|
|
156
|
+
const first = await requestToken(url, bearer, primary, ctx.sessionId)
|
|
157
|
+
if (first.cred) return first.cred
|
|
158
|
+
if (first.status === 400) {
|
|
159
|
+
throw new Error('broker rejected purpose "' + primary + '" as unknown (400) — vocabulary drift')
|
|
160
|
+
}
|
|
161
|
+
if (first.status !== 403) throw new Error('broker responded ' + first.status)
|
|
162
|
+
|
|
163
|
+
const second = await requestToken(url, bearer, fallback, ctx.sessionId)
|
|
164
|
+
if (second.cred) return second.cred
|
|
165
|
+
if (second.status === 400) {
|
|
166
|
+
throw new Error('broker rejected fallback purpose "' + fallback + '" as unknown (400) — vocabulary drift')
|
|
167
|
+
}
|
|
168
|
+
throw new Error('broker denied purposes "' + primary + '"/"' + fallback + '" (last status ' + second.status + ')')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function main() {
|
|
172
|
+
const contextPath = process.argv[2]
|
|
173
|
+
const action = process.argv[3] || 'get'
|
|
174
|
+
// store/erase: nothing to persist — drain stdin so git sees no SIGPIPE.
|
|
175
|
+
if (action !== 'get') { readStdin(); return 0 }
|
|
176
|
+
if (!contextPath) { log('missing boot context path'); return 1 }
|
|
177
|
+
|
|
178
|
+
let ctx
|
|
179
|
+
try { ctx = JSON.parse(fs.readFileSync(contextPath, 'utf8')) } catch (e) {
|
|
180
|
+
log('unreadable boot context: ' + (e && e.message)); return 1
|
|
181
|
+
}
|
|
182
|
+
const input = parseProtocolInput(readStdin())
|
|
183
|
+
|
|
184
|
+
// Scope: https only (except explicitly allow-listed loopback hosts, used by the
|
|
185
|
+
// local-sim rehearsal), on the configured host. Empty response (exit 0) lets
|
|
186
|
+
// git fall through cleanly without emitting a token to the wrong remote.
|
|
187
|
+
const protocol = String(input.protocol || '').toLowerCase()
|
|
188
|
+
const host = String(input.host || '').toLowerCase()
|
|
189
|
+
const insecureHosts = Array.isArray(ctx.insecureHttpHosts)
|
|
190
|
+
? ctx.insecureHttpHosts.map(function (h) { return String(h).toLowerCase() })
|
|
191
|
+
: []
|
|
192
|
+
const httpAllowed = protocol === 'http' && insecureHosts.indexOf(host) !== -1
|
|
193
|
+
if (protocol && protocol !== 'https' && !httpAllowed) { log('refusing non-https protocol'); return 0 }
|
|
194
|
+
const expectedHost = String(ctx.host || 'github.com').toLowerCase()
|
|
195
|
+
if (host && host !== expectedHost) { log('refusing host ' + host); return 0 }
|
|
196
|
+
|
|
197
|
+
const bufferMs = typeof ctx.cacheBufferMs === 'number' ? ctx.cacheBufferMs : ${DEFAULT_CACHE_REFRESH_BUFFER_MS}
|
|
198
|
+
let cred = readCache(ctx.cacheFile, Date.now(), bufferMs)
|
|
199
|
+
if (!cred) {
|
|
200
|
+
try { cred = await mint(ctx) } catch (e) { log('mint failed: ' + (e && e.message)); return 1 }
|
|
201
|
+
writeCache(ctx.cacheFile, cred)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
for (const key of Object.keys(input)) {
|
|
205
|
+
if (key === 'username' || key === 'password') continue
|
|
206
|
+
process.stdout.write(key + '=' + input[key] + '\\n')
|
|
207
|
+
}
|
|
208
|
+
process.stdout.write('username=' + cred.username + '\\n')
|
|
209
|
+
process.stdout.write('password=' + cred.password + '\\n')
|
|
210
|
+
process.stdout.write('\\n')
|
|
211
|
+
return 0
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
main().then(code => process.exit(code)).catch(e => { log('fatal: ' + (e && e.message)); process.exit(1) })
|
|
215
|
+
`;
|
|
216
|
+
}
|