orizu 0.5.47 → 0.5.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-publish-workspace.d.ts +6 -0
- package/dist/app-publish-workspace.d.ts.map +1 -1
- package/dist/app-publish-workspace.js +54 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +12 -1
- package/dist/hosted-boot.d.ts +7 -0
- package/dist/hosted-boot.d.ts.map +1 -1
- package/dist/hosted-boot.js +31 -2
- package/dist/hosted-optimization.d.ts.map +1 -1
- package/dist/hosted-optimization.js +40 -21
- package/dist/hosted-runtime-assets.d.ts +9 -0
- package/dist/hosted-runtime-assets.d.ts.map +1 -1
- package/dist/hosted-runtime-assets.js +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +83 -68
- package/dist/runner-folders.d.ts +61 -0
- package/dist/runner-folders.d.ts.map +1 -0
- package/dist/runner-folders.js +222 -0
- package/dist/runner-version-materialization.d.ts +1 -1
- package/dist/runner-version-materialization.d.ts.map +1 -1
- package/dist/runner-version-materialization.js +20 -38
- package/dist/session-publish.d.ts +8 -0
- package/dist/session-publish.d.ts.map +1 -0
- package/dist/session-publish.js +118 -0
- package/dist/skilled-proposer-launch.d.ts +11 -1
- package/dist/skilled-proposer-launch.d.ts.map +1 -1
- package/dist/skilled-proposer-launch.js +35 -4
- package/dist/workbench-cli.d.ts.map +1 -1
- package/dist/workbench-cli.js +14 -1
- package/package.json +1 -1
- package/scripts/build-cli-dist.mjs +46 -8
- package/scripts/vendor-orizu-skill.mjs +52 -7
- package/src/app-publish-workspace.ts +57 -1
- package/src/help.ts +13 -1
- package/src/hosted-boot.ts +40 -2
- package/src/hosted-optimization.ts +49 -31
- package/src/hosted-runtime-assets.ts +10 -9
- package/src/index.ts +79 -87
- package/src/runner-folders.ts +223 -0
- package/src/runner-version-materialization.ts +21 -34
- package/src/session-publish.ts +127 -0
- package/src/skilled-proposer-launch.ts +41 -3
- package/src/workbench-cli.ts +14 -1
- package/vendor/skills/orizu/references/authority-map.md +1 -1
- package/vendor/skills/orizu/references/building-apps.md +1 -1
- package/vendor/skills/orizu/references/cli-reference.md +16 -0
package/src/hosted-boot.ts
CHANGED
|
@@ -197,6 +197,8 @@ export async function postBootStatus(opts: {
|
|
|
197
197
|
status: 'ready' | 'failed' | 'complete' | 'turn_failed'
|
|
198
198
|
runId: string | null
|
|
199
199
|
reason?: string | null
|
|
200
|
+
/** ORI-2512: how long each boot step took, sent with `ready`. */
|
|
201
|
+
bootSteps?: readonly BootStep[]
|
|
200
202
|
fetchImpl: BootFetch
|
|
201
203
|
log?: (line: string) => void
|
|
202
204
|
}): Promise<void> {
|
|
@@ -208,6 +210,7 @@ export async function postBootStatus(opts: {
|
|
|
208
210
|
status: opts.status,
|
|
209
211
|
...(opts.runId ? { runId: opts.runId } : {}),
|
|
210
212
|
...(opts.reason ? { reason: opts.reason } : {}),
|
|
213
|
+
...(opts.bootSteps?.length ? { bootSteps: opts.bootSteps } : {}),
|
|
211
214
|
}),
|
|
212
215
|
})
|
|
213
216
|
opts.log?.(`boot-status ${opts.status} reported (${res.status})`)
|
|
@@ -216,9 +219,31 @@ export async function postBootStatus(opts: {
|
|
|
216
219
|
}
|
|
217
220
|
}
|
|
218
221
|
|
|
222
|
+
/** ORI-2512: one step of the in-sandbox boot and how long it took. */
|
|
223
|
+
interface BootStep {
|
|
224
|
+
step: string
|
|
225
|
+
ms: number
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Times the boot's steps, each from the end of the one before. */
|
|
229
|
+
function bootStepClock(now: () => number): { mark: (step: string) => void; steps: BootStep[] } {
|
|
230
|
+
const steps: BootStep[] = []
|
|
231
|
+
let last = now()
|
|
232
|
+
return {
|
|
233
|
+
steps,
|
|
234
|
+
mark: step => {
|
|
235
|
+
const at = now()
|
|
236
|
+
// Timing only describes the boot: a clock that goes back skips a step.
|
|
237
|
+
if (at >= last) steps.push({ step, ms: Math.round(at - last) })
|
|
238
|
+
last = at
|
|
239
|
+
},
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
219
243
|
async function postRequiredIdleReady(opts: {
|
|
220
244
|
bootStatusUrl: string
|
|
221
245
|
bootSecret: string
|
|
246
|
+
bootSteps: readonly BootStep[]
|
|
222
247
|
fetchImpl: BootFetch
|
|
223
248
|
sleep: (ms: number) => Promise<void>
|
|
224
249
|
attemptTimeoutMs?: number
|
|
@@ -229,7 +254,7 @@ async function postRequiredIdleReady(opts: {
|
|
|
229
254
|
const response = await opts.fetchImpl(opts.bootStatusUrl, {
|
|
230
255
|
method: 'POST',
|
|
231
256
|
headers: { Authorization: `Bearer ${opts.bootSecret}`, 'Content-Type': 'application/json' },
|
|
232
|
-
body: JSON.stringify({ status: 'ready' }),
|
|
257
|
+
body: JSON.stringify({ status: 'ready', ...(opts.bootSteps.length ? { bootSteps: opts.bootSteps } : {}) }),
|
|
233
258
|
signal: AbortSignal.timeout(opts.attemptTimeoutMs ?? TURN_STATUS_ATTEMPT_TIMEOUT_MS),
|
|
234
259
|
})
|
|
235
260
|
if (response.ok) return
|
|
@@ -1015,6 +1040,7 @@ async function runHostedBootWithLive(
|
|
|
1015
1040
|
delete processEnv.ORIZU_BOOT_SECRET
|
|
1016
1041
|
processEnv.ORIZU_NO_UPDATE_CHECK = '1'
|
|
1017
1042
|
if (env.runId) assertSafeRunId(env.runId)
|
|
1043
|
+
const bootClock = bootStepClock(now)
|
|
1018
1044
|
|
|
1019
1045
|
// 1 — Pull the agent bearer (retry/backoff — the DO may still be arming).
|
|
1020
1046
|
let bearer = await pullAgentBearer({
|
|
@@ -1027,24 +1053,29 @@ async function runHostedBootWithLive(
|
|
|
1027
1053
|
log,
|
|
1028
1054
|
})
|
|
1029
1055
|
log('agent bearer pulled')
|
|
1056
|
+
bootClock.mark('boot_agent_token')
|
|
1030
1057
|
|
|
1031
1058
|
// 2 — Pull the connector env bundle and export it (+ register redaction).
|
|
1032
1059
|
const bundle = await fetchEnvBundle({ envBundleUrl: env.envBundleUrl, bearer: bearer.token, fetchImpl })
|
|
1033
1060
|
const redacted = applyEnvBundle(bundle, processEnv)
|
|
1034
1061
|
log(`env bundle applied (${bundle.connectors.length} connectors, ${redacted.length} redacted vars)`)
|
|
1062
|
+
bootClock.mark('boot_env_bundle')
|
|
1035
1063
|
|
|
1036
1064
|
// 3 — Resolve the session (workspace/branch/task) + run + repo from the plane.
|
|
1037
1065
|
let session = await resolveSession({ baseUrl: env.baseUrl, sessionId: env.sessionId, bearer: bearer.token, fetchImpl })
|
|
1038
1066
|
let currentNonTerminalRunId = session.runStatus === 'pending' || session.runStatus === 'running'
|
|
1039
1067
|
? session.runId
|
|
1040
1068
|
: null
|
|
1069
|
+
bootClock.mark('boot_session')
|
|
1041
1070
|
// Production restarts with no live run are between turns. Prove this boot is
|
|
1042
1071
|
// live before waiting so the coordinator's readiness timeout cannot reap an
|
|
1043
1072
|
// intentionally idle sandbox before the longer hosted idle policy applies.
|
|
1044
|
-
|
|
1073
|
+
const bootsIdle = !opts.runLoop && !currentNonTerminalRunId
|
|
1074
|
+
if (bootsIdle) {
|
|
1045
1075
|
await postRequiredIdleReady({
|
|
1046
1076
|
bootStatusUrl: env.bootStatusUrl,
|
|
1047
1077
|
bootSecret: env.bootSecret,
|
|
1078
|
+
bootSteps: bootClock.steps,
|
|
1048
1079
|
fetchImpl,
|
|
1049
1080
|
sleep,
|
|
1050
1081
|
attemptTimeoutMs: turnStatusAttemptTimeoutMs,
|
|
@@ -1144,6 +1175,7 @@ async function runHostedBootWithLive(
|
|
|
1144
1175
|
const repo = await resolveRepo({ baseUrl: env.baseUrl, workspaceId, sessionId: env.sessionId, bearer: bearer.token, fetchImpl })
|
|
1145
1176
|
const repositoryHost = new URL(repo.cloneUrl).host.toLowerCase()
|
|
1146
1177
|
log(`resolved run ${runId} on ${repo.repoFullName}@${session.repoBranch}`)
|
|
1178
|
+
bootClock.mark('boot_repo')
|
|
1147
1179
|
|
|
1148
1180
|
// 4 — Write the run-scoped assets: boot secret + initial bearer (0600), the
|
|
1149
1181
|
// PULL-MODE credential helper + its boot context. The bearer file feeds the
|
|
@@ -1347,6 +1379,7 @@ const c = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
|
1347
1379
|
exec('git', ['-C', workspaceDir, 'config', 'user.name', AGENT_GIT_IDENTITY.name])
|
|
1348
1380
|
exec('git', ['-C', workspaceDir, 'config', 'user.email', AGENT_GIT_IDENTITY.email])
|
|
1349
1381
|
if (!wokenDisk && !sessionCopyFilled) log(`cloned ${session.repoBranch}`)
|
|
1382
|
+
bootClock.mark('boot_clone')
|
|
1350
1383
|
|
|
1351
1384
|
// 5a — ORI-2609: the team copy holds only the projects the team seed knew
|
|
1352
1385
|
// about. Write this session's project folder if it is missing, before the
|
|
@@ -1359,6 +1392,7 @@ const c = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
|
1359
1392
|
project: { id: session.projectId, slug: session.projectSlug, name: session.projectName },
|
|
1360
1393
|
exec: (cmd, args) => exec(cmd, args),
|
|
1361
1394
|
}))
|
|
1395
|
+
bootClock.mark('boot_project_folder')
|
|
1362
1396
|
|
|
1363
1397
|
// 5b — Stage the orizu skill into the cloned repo so the agent discovers the
|
|
1364
1398
|
// Orizu workflows (ALI-1059). SHARED with the operator path via `stageOrizuSkill`
|
|
@@ -1377,6 +1411,7 @@ const c = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
|
1377
1411
|
} catch (error) {
|
|
1378
1412
|
log(`orizu skill staging failed: ${error instanceof Error ? error.message : String(error)}`)
|
|
1379
1413
|
}
|
|
1414
|
+
bootClock.mark('boot_skill_stage')
|
|
1380
1415
|
|
|
1381
1416
|
// ALI-1060: the agent is now genuinely LIVE — bearer minted, connectors
|
|
1382
1417
|
// applied, session/run resolved, repo cloned, skill staged. Signal the
|
|
@@ -1388,6 +1423,9 @@ const c = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
|
1388
1423
|
bootSecret: env.bootSecret,
|
|
1389
1424
|
status: 'ready',
|
|
1390
1425
|
runId,
|
|
1426
|
+
// An idle boot sent its steps before it waited for work; the wait is not
|
|
1427
|
+
// a boot step.
|
|
1428
|
+
...(bootsIdle ? {} : { bootSteps: bootClock.steps }),
|
|
1391
1429
|
fetchImpl,
|
|
1392
1430
|
log,
|
|
1393
1431
|
})
|
|
@@ -3,13 +3,12 @@ import { existsSync, readFileSync, rmSync } from 'node:fs'
|
|
|
3
3
|
import { createServer } from 'node:http'
|
|
4
4
|
import { tmpdir } from 'node:os'
|
|
5
5
|
import { delimiter, join } from 'node:path'
|
|
6
|
-
import { runPipedChild } from './child-output-tee.js'
|
|
7
6
|
import { getGepaPythonPathEntries } from './gepa-python-paths.js'
|
|
8
7
|
import { deliveredProviderLookup, HOSTED_PROVIDER_FAILURE, INJECTED_ENV_VARS_ENV, PROVIDER_REGISTRY_EXTRA_ENV } from './hosted-environment.js'
|
|
9
8
|
import { credentialEnvFor, parseModelIdentity } from './provider-registry.js'
|
|
10
9
|
import type { ProviderLookup } from './provider-lookup.js'
|
|
11
|
-
import { materializeRunnerVersion, cleanupMaterializedRunners } from './runner-version-materialization.js'
|
|
12
10
|
import { prepareSkilledProposerLaunch, spawnSkilledProposerChild } from './skilled-proposer-launch.js'
|
|
11
|
+
import type { SkilledProposerStopOptions } from './skilled-proposer-launch.js'
|
|
13
12
|
import { hostedProviderFromModel } from './hosted-provider-settings.js'
|
|
14
13
|
import { validNormalizedSkilledProposerConfig } from './skilled-proposer-wire.js'
|
|
15
14
|
import { REDACTION_PLACEHOLDER, redactSecrets } from './secret-redaction.js'
|
|
@@ -358,7 +357,8 @@ async function startTokenBroker(
|
|
|
358
357
|
}
|
|
359
358
|
return {
|
|
360
359
|
url: `http://127.0.0.1:${address.port}/${nonce}`,
|
|
361
|
-
|
|
360
|
+
// Also drop open keep-alive connections, so no refresh succeeds after stop.
|
|
361
|
+
stop: () => { server.close(); server.closeAllConnections() },
|
|
362
362
|
token: () => activeToken,
|
|
363
363
|
secrets: () => [...tokens],
|
|
364
364
|
}
|
|
@@ -367,30 +367,23 @@ async function spawnOfficial(
|
|
|
367
367
|
python: string,
|
|
368
368
|
environment: NodeJS.ProcessEnv,
|
|
369
369
|
ready: () => Promise<void>,
|
|
370
|
-
output: ProcessOutputCapture
|
|
370
|
+
output: ProcessOutputCapture,
|
|
371
|
+
stopOptions: SkilledProposerStopOptions,
|
|
371
372
|
): Promise<{ status: number | null; error?: Error }> {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
'official',
|
|
377
|
-
environment,
|
|
378
|
-
ready,
|
|
379
|
-
{
|
|
380
|
-
stdout: chunk => output.stdout.append(chunk),
|
|
381
|
-
stderr: chunk => output.stderr.append(chunk),
|
|
382
|
-
}
|
|
383
|
-
)
|
|
384
|
-
}
|
|
385
|
-
return runPipedChild(
|
|
373
|
+
// ORI-2429: both proposers start through the launcher (ORI-2402), so a stop
|
|
374
|
+
// signal reaches the optimizer, which then ends instead of outliving the
|
|
375
|
+
// CLI, and the CLI cleans up before it ends by that signal.
|
|
376
|
+
return spawnSkilledProposerChild(
|
|
386
377
|
python,
|
|
387
378
|
['-m', 'orizu_gepa_connector'],
|
|
379
|
+
'official',
|
|
388
380
|
environment,
|
|
389
381
|
ready,
|
|
390
382
|
{
|
|
391
383
|
stdout: chunk => output.stdout.append(chunk),
|
|
392
384
|
stderr: chunk => output.stderr.append(chunk),
|
|
393
|
-
}
|
|
385
|
+
},
|
|
386
|
+
stopOptions,
|
|
394
387
|
)
|
|
395
388
|
}
|
|
396
389
|
async function ensureFailedRun(
|
|
@@ -430,7 +423,14 @@ export async function hostedOptimizationCommand(io: HostedOptimizationIo): Promi
|
|
|
430
423
|
}
|
|
431
424
|
delete process.env.ORIZU_BOOT_SECRET
|
|
432
425
|
let reason = 'hosted_optimization_mint_refused'
|
|
433
|
-
|
|
426
|
+
// ORI-2429: one Effect scope owns the runner copies, the result file and the
|
|
427
|
+
// token broker, so a stop signal removes them too (see runner-folders.ts).
|
|
428
|
+
const { Effect, Scope } = await import('effect')
|
|
429
|
+
const { downloadedRunner, removeOnClose, RemovalLedger, stopClosesScope } = await import('./runner-folders.js')
|
|
430
|
+
const ledger = new RemovalLedger()
|
|
431
|
+
const { scope, close, stop, listen, removeHandlers } = stopClosesScope()
|
|
432
|
+
const inScope = <A>(effect: import('effect').Effect.Effect<A, unknown, import('effect').Scope.Scope>) =>
|
|
433
|
+
Effect.runPromise(effect.pipe(Effect.provideService(Scope.Scope, scope)))
|
|
434
434
|
let broker: TokenBroker | undefined
|
|
435
435
|
const resultFile = join(tmpdir(), `orizu-hosted-optimization-result-${boot.runId}-${randomUUID()}.json`)
|
|
436
436
|
const diagnosticSecrets = () => injectedSecrets(boot, broker)
|
|
@@ -438,6 +438,7 @@ export async function hostedOptimizationCommand(io: HostedOptimizationIo): Promi
|
|
|
438
438
|
const bootOutput = new BoundedTextCapture(diagnosticSecrets)
|
|
439
439
|
let failureDiagnostics: FailureDiagnostics | undefined
|
|
440
440
|
try {
|
|
441
|
+
listen()
|
|
441
442
|
const first = await mint(boot)
|
|
442
443
|
if (first.response.status === 422) reason = 'hosted_optimization_spec_missing'
|
|
443
444
|
if (!first.response.ok) {
|
|
@@ -462,18 +463,28 @@ export async function hostedOptimizationCommand(io: HostedOptimizationIo): Promi
|
|
|
462
463
|
process.env.ORIZU_BASE_URL = boot.baseUrl.origin
|
|
463
464
|
process.env.ORIZU_TOKEN = minted.token
|
|
464
465
|
reason = 'hosted_optimization_materialization_failed'
|
|
465
|
-
const candidate = await
|
|
466
|
-
const scorer = await
|
|
466
|
+
const candidate = await inScope(downloadedRunner(String(minted.jobSpec.runnerVersionId), ledger))
|
|
467
|
+
const scorer = await inScope(downloadedRunner(String(minted.jobSpec.scorerRunnerVersionId), ledger))
|
|
468
|
+
await inScope(removeOnClose(ledger, 'other', () => rmSync(resultFile, { force: true })))
|
|
467
469
|
broker = await startTokenBroker(boot, minted)
|
|
470
|
+
const startedBroker = broker
|
|
471
|
+
await inScope(removeOnClose(ledger, 'other', () => startedBroker.stop()))
|
|
468
472
|
const environment = childEnvironment(boot, minted, candidate.runnerDir, scorer.runnerDir, broker.url, resultFile)
|
|
469
473
|
const launch = prepareSkilledProposerLaunch(process.env.PYTHON || 'python3', 'official', environment)
|
|
470
474
|
reason = 'hosted_optimization_process_failed'
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
475
|
+
stop.launcherOwnsStop()
|
|
476
|
+
let result: Awaited<ReturnType<typeof spawnOfficial>>
|
|
477
|
+
try {
|
|
478
|
+
result = await spawnOfficial(
|
|
479
|
+
launch.python,
|
|
480
|
+
launch.environment,
|
|
481
|
+
() => postBoot(boot, 'ready', { status: 'ready' }),
|
|
482
|
+
processOutput,
|
|
483
|
+
{ beforeSignalExit: stop.beforeSignalExit },
|
|
484
|
+
)
|
|
485
|
+
} finally {
|
|
486
|
+
stop.launcherDone()
|
|
487
|
+
}
|
|
477
488
|
if (result.error || result.status !== 0) {
|
|
478
489
|
const processFailure = result.error?.message || `optimizer exited with status ${result.status}`
|
|
479
490
|
const secrets = injectedSecrets(boot, broker)
|
|
@@ -506,8 +517,15 @@ export async function hostedOptimizationCommand(io: HostedOptimizationIo): Promi
|
|
|
506
517
|
await io.printErr?.(reason)
|
|
507
518
|
return 1
|
|
508
519
|
} finally {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
520
|
+
removeHandlers()
|
|
521
|
+
// Closing stops the broker, deletes the result file and removes the copies.
|
|
522
|
+
close()
|
|
523
|
+
// As before: a broker or result-file failure is thrown first, then runner removals.
|
|
524
|
+
const otherFailure = ledger.failures.find(failure => failure.group === 'other')
|
|
525
|
+
if (otherFailure) throw otherFailure.error
|
|
526
|
+
const runnerFailures = ledger.failures.filter(failure => failure.group === 'downloads').map(failure => failure.error)
|
|
527
|
+
if (runnerFailures.length > 0) {
|
|
528
|
+
throw new AggregateError(runnerFailures, 'Failed to clean materialized runner directories')
|
|
529
|
+
}
|
|
512
530
|
}
|
|
513
531
|
}
|
|
@@ -51,15 +51,6 @@ export const AGENT_GIT_IDENTITY = { name: 'Orizu Agent', email: 'agent@orizu.ai'
|
|
|
51
51
|
|
|
52
52
|
// -- Hosted task prompt scaffolding (ALI-1036) -------------------------------
|
|
53
53
|
|
|
54
|
-
/**
|
|
55
|
-
* Origin-specific standing preambles wrap hosted task prompts. Web sessions get
|
|
56
|
-
* `HOSTED_TASK_PREAMBLE`, which directs the agent to use the question tool and
|
|
57
|
-
* wait for the human. CLI-origin sessions get `HOSTED_TASK_PREAMBLE_CLI`, where
|
|
58
|
-
* questions remain auto-answered for unattended operation. Undefined or unknown
|
|
59
|
-
* origins fail safely to the CLI preamble. Both variants tell the agent to
|
|
60
|
-
* commit as it goes; end-of-run auto-harvest (ALI-1036, `harvestWorkspace`)
|
|
61
|
-
* preserves anything left uncommitted.
|
|
62
|
-
*/
|
|
63
54
|
/** ORI-2653: where a push lands, shared by both preambles. A web session's
|
|
64
55
|
* `origin` is its own copy of the team repository; a legacy GitHub session's
|
|
65
56
|
* current branch is its session branch. Only an Orizu publish puts work on the
|
|
@@ -69,6 +60,15 @@ const HOSTED_PUSH_SCOPE =
|
|
|
69
60
|
"not on the team's main. Never say work is published or on the team's main unless an Orizu " +
|
|
70
61
|
'publish command reported that it merged or was already there. '
|
|
71
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Origin-specific standing preambles wrap hosted task prompts. Web sessions get
|
|
65
|
+
* `HOSTED_TASK_PREAMBLE`, which directs the agent to use the question tool and
|
|
66
|
+
* wait for the human. CLI-origin sessions get `HOSTED_TASK_PREAMBLE_CLI`, where
|
|
67
|
+
* questions remain auto-answered for unattended operation. Undefined or unknown
|
|
68
|
+
* origins fail safely to the CLI preamble. Both variants tell the agent to
|
|
69
|
+
* commit as it goes; end-of-run auto-harvest (ALI-1036, `harvestWorkspace`)
|
|
70
|
+
* preserves anything left uncommitted.
|
|
71
|
+
*/
|
|
72
72
|
export const HOSTED_TASK_PREAMBLE =
|
|
73
73
|
'You are in a hosted sandbox. Work autonomously. When blocked on human input, call the built-in ' +
|
|
74
74
|
'question tool with exactly one clear question and at most eight bounded options, then wait for ' +
|
|
@@ -76,6 +76,7 @@ export const HOSTED_TASK_PREAMBLE =
|
|
|
76
76
|
'branch in logical units as you go (an automatic ' +
|
|
77
77
|
'checkpoint will also save anything uncommitted at the end). Never switch branches. ' +
|
|
78
78
|
HOSTED_PUSH_SCOPE +
|
|
79
|
+
"To put work on the team's main, run `orizu session publish`. " +
|
|
79
80
|
'Production/default pointers and run metadata live in Orizu, not this repo.'
|
|
80
81
|
|
|
81
82
|
/** Pre-ALI-1758 unattended behavior retained for CLI-originated sessions. */
|
package/src/index.ts
CHANGED
|
@@ -100,7 +100,6 @@ import {
|
|
|
100
100
|
getTaskReport,
|
|
101
101
|
updateTaskStatus,
|
|
102
102
|
} from './task-commands.js'
|
|
103
|
-
import { cleanupAll, throwWithCleanup } from './cleanup.js'
|
|
104
103
|
import {
|
|
105
104
|
extractFlagValue,
|
|
106
105
|
verifyGepaRunnerDirsFromArgs,
|
|
@@ -177,7 +176,6 @@ import { inspectExistingWorkspaceTeam, getWorkspaceRoot } from './workspace.js'
|
|
|
177
176
|
import { writeLastErrorRecord, extractLastErrorCode } from './last-error-record.js'
|
|
178
177
|
import type { CliLengthStats, CliLengthMeasurementUnavailableReason } from './prompt-length-wire.js'
|
|
179
178
|
import type { RunnerInstructionSet } from './runner-instruction-set-contract.js'
|
|
180
|
-
import type { GepaVerifiedArgs } from './runner-dir-verify.js'
|
|
181
179
|
import type { ReportCommentsCliContext } from './report-comments-cli.js'
|
|
182
180
|
|
|
183
181
|
|
|
@@ -1293,101 +1291,94 @@ const GEPA_RUNNER_MATERIALIZATION_PAIRS = [
|
|
|
1293
1291
|
{ versionFlag: '--scorer-runner-version-id', dirFlag: '--scorer-runner-dir' },
|
|
1294
1292
|
] as const
|
|
1295
1293
|
|
|
1296
|
-
async function materializeMissingGepaRunnerDirs(args: string[]): Promise<{
|
|
1297
|
-
args: string[]
|
|
1298
|
-
cleanup: () => void
|
|
1299
|
-
}> {
|
|
1300
|
-
const rewritten = [...args]
|
|
1301
|
-
const cleanups: Array<() => void> = []
|
|
1302
|
-
const cleanup = () => cleanupAll(
|
|
1303
|
-
cleanups,
|
|
1304
|
-
'Failed to clean materialized runner directories'
|
|
1305
|
-
)
|
|
1306
|
-
|
|
1307
|
-
try {
|
|
1308
|
-
for (const pair of GEPA_RUNNER_MATERIALIZATION_PAIRS) {
|
|
1309
|
-
const runnerVersionId = extractFlagValue(rewritten, pair.versionFlag)
|
|
1310
|
-
const runnerDir = extractFlagValue(rewritten, pair.dirFlag)
|
|
1311
|
-
if (!runnerVersionId || runnerDir) continue
|
|
1312
|
-
|
|
1313
|
-
const materialized = await materializeRunnerVersion(runnerVersionId)
|
|
1314
|
-
cleanups.push(materialized.cleanup)
|
|
1315
|
-
rewritten.push(pair.dirFlag, materialized.runnerDir)
|
|
1316
|
-
}
|
|
1317
|
-
} catch (error) {
|
|
1318
|
-
throwWithCleanup(
|
|
1319
|
-
error,
|
|
1320
|
-
cleanups,
|
|
1321
|
-
'Runner materialization failed and cleanup also failed'
|
|
1322
|
-
)
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
return { args: rewritten, cleanup }
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
1294
|
async function runGepaOptimization() {
|
|
1329
1295
|
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1330
1296
|
const baseUrl = getBaseUrl()
|
|
1331
1297
|
const runGepa = getGepaPythonCommand(cliArgs.slice(2), process.env)
|
|
1332
1298
|
const python = runGepa.python
|
|
1333
|
-
|
|
1334
|
-
//
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
//
|
|
1339
|
-
|
|
1340
|
-
const materialized = await materializeMissingGepaRunnerDirs(forwardedArgs)
|
|
1341
|
-
let verified: GepaVerifiedArgs | undefined
|
|
1299
|
+
// ORI-2429: one Effect scope owns every runner copy this run writes to the
|
|
1300
|
+
// temp folder; it removes them on success, failure and stop signals.
|
|
1301
|
+
const { Effect } = await import('effect')
|
|
1302
|
+
const { downloadedRunner, heldUntilClose, RemovalLedger, runStoppable } = await import('./runner-folders.js')
|
|
1303
|
+
const ledger = new RemovalLedger()
|
|
1304
|
+
// Which step failed decides the message when a removal also failed, as before.
|
|
1305
|
+
let phase: 'materialize' | 'launch' = 'materialize'
|
|
1342
1306
|
let selectedEngine = 'official'
|
|
1307
|
+
let failure: { error: unknown } | undefined
|
|
1343
1308
|
try {
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1309
|
+
await runStoppable(stop => Effect.gen(function* () {
|
|
1310
|
+
// Download registered runner versions passed without a local folder; the
|
|
1311
|
+
// verification below then checks those bytes like any local folder.
|
|
1312
|
+
const materializedArgs = [...runGepa.args]
|
|
1313
|
+
for (const pair of GEPA_RUNNER_MATERIALIZATION_PAIRS) {
|
|
1314
|
+
const runnerVersionId = extractFlagValue(materializedArgs, pair.versionFlag)
|
|
1315
|
+
const runnerDir = extractFlagValue(materializedArgs, pair.dirFlag)
|
|
1316
|
+
if (!runnerVersionId || runnerDir) continue
|
|
1317
|
+
const materialized = yield* downloadedRunner(runnerVersionId, ledger, false)
|
|
1318
|
+
materializedArgs.push(pair.dirFlag, materialized.runnerDir)
|
|
1319
|
+
}
|
|
1320
|
+
phase = 'launch'
|
|
1321
|
+
// ALI-1159 (ADR-007): GEPA executes ad-hoc local runner dirs while
|
|
1322
|
+
// attributing records to registered runner version ids — verify the bytes
|
|
1323
|
+
// ARE those versions before the optimizer spawns. Scans the forwarded argv
|
|
1324
|
+
// itself (both argparse flag forms), fail-closed on half-populated pairs,
|
|
1325
|
+
// and REWRITES the dir flags to verified snapshots (the exact hashed bytes)
|
|
1326
|
+
// so a post-hash mutation of the original dirs cannot change what runs.
|
|
1327
|
+
const verified = yield* heldUntilClose(ledger, 'snapshots', () => verifyGepaRunnerDirsFromArgs(materializedArgs))
|
|
1328
|
+
let forwardedArgs = verified.args
|
|
1329
|
+
// Uniform resolution (ALI-1090): honors ORIZU_TOKEN / ORIZU_TOKEN_FILE before
|
|
1330
|
+
// credentials.json so hosted sandboxes can run optimizations. Read at spawn
|
|
1331
|
+
// time — AFTER the verification awaits above, so a hosted token-file
|
|
1332
|
+
// rotation crossing the hash + lookup round-trips cannot hand the child a
|
|
1333
|
+
// stale bearer.
|
|
1334
|
+
const token = resolveAuthTokenForBaseUrl(baseUrl)
|
|
1335
|
+
if (!forwardedArgs.includes('--project')) {
|
|
1336
|
+
forwardedArgs = ['--project', project, ...forwardedArgs]
|
|
1337
|
+
}
|
|
1338
|
+
const dispatch = applyHostedInstructionSetProfileOverride(dispatchGepaEngine(forwardedArgs, project, {
|
|
1339
|
+
...process.env,
|
|
1340
|
+
ORIZU_API_URL: baseUrl,
|
|
1341
|
+
ORIZU_TOKEN: token,
|
|
1342
|
+
ORIZU_PROJECT: project,
|
|
1343
|
+
ORIZU_VERIFIED_RUNNER_DIRS: JSON.stringify(verified.verifiedDirs),
|
|
1344
|
+
PYTHONPATH: getGepaPythonPathEntries(process.env.PYTHONPATH).join(delimiter),
|
|
1345
|
+
PYTHONUNBUFFERED: process.env.PYTHONUNBUFFERED || '1',
|
|
1346
|
+
}))
|
|
1347
|
+
const instructionSetProfileVersionId = yield* Effect.tryPromise({
|
|
1348
|
+
try: () => resolveGepaSeedFromEnvironment(dispatch.environment, project, 'production'),
|
|
1349
|
+
catch: error => error,
|
|
1350
|
+
})
|
|
1351
|
+
if (instructionSetProfileVersionId) dispatch.environment.ORIZU_INSTRUCTION_SET_PROFILE_VERSION_ID = instructionSetProfileVersionId
|
|
1352
|
+
selectedEngine = dispatch.engine
|
|
1353
|
+
const launch = prepareSkilledProposerLaunch(python, dispatch.engine, dispatch.environment)
|
|
1354
|
+
// While the optimizer runs, the launcher owns stop signals (ORI-2402) and
|
|
1355
|
+
// closes this scope just before the CLI ends by the signal. Taken in the
|
|
1356
|
+
// same step as the launch, so no stop can fall between the two.
|
|
1357
|
+
const result = yield* Effect.tryPromise({
|
|
1358
|
+
try: () => (stop.launcherOwnsStop(), spawnSkilledProposerChild(
|
|
1359
|
+
launch.python, ['-m', dispatch.module, ...dispatch.args], dispatch.engine, launch.environment,
|
|
1360
|
+
undefined, undefined, { beforeSignalExit: stop.beforeSignalExit },
|
|
1361
|
+
)),
|
|
1362
|
+
catch: error => error,
|
|
1363
|
+
}).pipe(Effect.ensuring(Effect.sync(stop.launcherDone)))
|
|
1364
|
+
if (result.error) {
|
|
1365
|
+
return yield* Effect.fail(new Error(`${selectedEngine} GEPA engine failed: ${result.error.message}`, { cause: result.error }))
|
|
1366
|
+
}
|
|
1367
|
+
if (result.status !== 0) {
|
|
1368
|
+
return yield* Effect.fail(new Error(`${selectedEngine} GEPA engine failed with exit code ${result.status}`))
|
|
1369
|
+
}
|
|
1363
1370
|
}))
|
|
1364
|
-
const instructionSetProfileVersionId = await resolveGepaSeedFromEnvironment(dispatch.environment, project, 'production')
|
|
1365
|
-
if (instructionSetProfileVersionId) dispatch.environment.ORIZU_INSTRUCTION_SET_PROFILE_VERSION_ID = instructionSetProfileVersionId
|
|
1366
|
-
selectedEngine = dispatch.engine
|
|
1367
|
-
const launch = prepareSkilledProposerLaunch(python, dispatch.engine, dispatch.environment)
|
|
1368
|
-
const result = await spawnSkilledProposerChild(
|
|
1369
|
-
launch.python, ['-m', dispatch.module, ...dispatch.args], dispatch.engine, launch.environment,
|
|
1370
|
-
)
|
|
1371
|
-
if (result.error) {
|
|
1372
|
-
throw new Error(`${selectedEngine} GEPA engine failed: ${result.error.message}`, { cause: result.error })
|
|
1373
|
-
}
|
|
1374
|
-
if (result.status !== 0) {
|
|
1375
|
-
throw new Error(`${selectedEngine} GEPA engine failed with exit code ${result.status}`)
|
|
1376
|
-
}
|
|
1377
1371
|
} catch (error) {
|
|
1378
|
-
|
|
1379
|
-
error,
|
|
1380
|
-
[
|
|
1381
|
-
...(verified ? [verified.cleanup] : []),
|
|
1382
|
-
materialized.cleanup,
|
|
1383
|
-
],
|
|
1384
|
-
'GEPA launch failed and runner cleanup also failed'
|
|
1385
|
-
)
|
|
1372
|
+
failure = { error }
|
|
1386
1373
|
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
'Failed to clean GEPA runner directories'
|
|
1390
|
-
|
|
1374
|
+
// The scope has closed here; a removal failure decides the message, as before.
|
|
1375
|
+
if (ledger.has()) {
|
|
1376
|
+
if (!failure) throw new AggregateError(ledger.errors(), 'Failed to clean GEPA runner directories')
|
|
1377
|
+
throw new AggregateError([failure.error, ...ledger.errors()], phase === 'materialize'
|
|
1378
|
+
? 'Runner materialization failed and cleanup also failed'
|
|
1379
|
+
: 'GEPA launch failed and runner cleanup also failed')
|
|
1380
|
+
}
|
|
1381
|
+
if (failure) throw failure.error
|
|
1391
1382
|
if (hasJsonFlag()) {
|
|
1392
1383
|
printJson({ status: 'completed', exitCode: 0 })
|
|
1393
1384
|
}
|
|
@@ -2256,6 +2247,7 @@ function isCliEntrypoint(): boolean {
|
|
|
2256
2247
|
if (isCliEntrypoint()) {
|
|
2257
2248
|
const rawArgs = process.argv.slice(2)
|
|
2258
2249
|
withPassiveUpdateNotice(rawArgs, getCliVersion, printError, args =>
|
|
2250
|
+
// ORI-2429: the process-boundary adapter stays a Promise (ADR-038 guide); the record write is Effect-owned and never replaces this error.
|
|
2259
2251
|
main(args).catch(async error => {
|
|
2260
2252
|
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
2261
2253
|
// The reporter email is unknown on the failure path, so the record masks every address.
|