orizu 0.5.44 → 0.5.46
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/account-directory.d.ts.map +1 -1
- package/dist/account-directory.js +4 -11
- package/dist/hosted-boot.d.ts +3 -0
- package/dist/hosted-boot.d.ts.map +1 -1
- package/dist/hosted-boot.js +14 -0
- package/dist/hosted-project-folder-seed.d.ts +18 -0
- package/dist/hosted-project-folder-seed.d.ts.map +1 -0
- package/dist/hosted-project-folder-seed.js +78 -0
- package/dist/secret-redaction.d.ts +14 -0
- package/dist/secret-redaction.d.ts.map +1 -1
- package/dist/secret-redaction.js +25 -0
- package/dist/skilled-proposer-launch.d.ts +5 -0
- package/dist/skilled-proposer-launch.d.ts.map +1 -1
- package/dist/skilled-proposer-launch.js +104 -46
- package/dist/team-member-add-workflow.d.ts +15 -0
- package/dist/team-member-add-workflow.d.ts.map +1 -0
- package/dist/team-member-add-workflow.js +111 -0
- package/package.json +1 -1
- package/src/account-directory.ts +4 -11
- package/src/hosted-boot.ts +18 -0
- package/src/hosted-project-folder-seed.ts +95 -0
- package/src/secret-redaction.ts +30 -0
- package/src/skilled-proposer-launch.ts +78 -36
- package/src/team-member-add-workflow.ts +113 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +123 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account-directory.d.ts","sourceRoot":"","sources":["../src/account-directory.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"account-directory.d.ts","sourceRoot":"","sources":["../src/account-directory.ts"],"names":[],"mappings":"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"}
|
|
@@ -357,20 +357,13 @@ async function addTeamMember() {
|
|
|
357
357
|
if (!email) {
|
|
358
358
|
throw new Error('Usage: orizu teams members add --email <email> [--team <teamSlug>]');
|
|
359
359
|
}
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
headers: { 'Content-Type': 'application/json' },
|
|
363
|
-
body: JSON.stringify({ email }),
|
|
364
|
-
});
|
|
365
|
-
if (!response.ok) {
|
|
366
|
-
throw new Error(`Failed to add team member: ${await response.text()}`);
|
|
367
|
-
}
|
|
368
|
-
const data = await parseJsonResponse(response, 'Team member add');
|
|
360
|
+
const { addTeamMemberWithDeadline } = await import('./team-member-add-workflow.js');
|
|
361
|
+
const member = await addTeamMemberWithDeadline(teamSlug, email, authedFetch);
|
|
369
362
|
if (hasJsonFlag()) {
|
|
370
|
-
printJson({ member
|
|
363
|
+
printJson({ member });
|
|
371
364
|
return;
|
|
372
365
|
}
|
|
373
|
-
printLine(`Added team member ${sanitizeTerminalText(
|
|
366
|
+
printLine(`Added team member ${sanitizeTerminalText(member.email)} (${sanitizeTerminalText(member.id)})`);
|
|
374
367
|
}
|
|
375
368
|
async function removeTeamMember() {
|
|
376
369
|
const teamSlug = await resolveTeamSlug(getArg('--team'));
|
package/dist/hosted-boot.d.ts
CHANGED
|
@@ -160,6 +160,9 @@ export interface ResolvedSession {
|
|
|
160
160
|
agentSessionId: string | null;
|
|
161
161
|
seedOrigin: 'team' | 'session';
|
|
162
162
|
projectId: string | null;
|
|
163
|
+
/** The session's own project's slug and name (ORI-2609), when the server sends them. */
|
|
164
|
+
projectSlug: string | null;
|
|
165
|
+
projectName: string | null;
|
|
163
166
|
/** Session lifetime in minutes (from client_info), if the coordinator recorded
|
|
164
167
|
* it — used to derive the per-prompt max-duration cap (ALI-1061). */
|
|
165
168
|
durationMinutes: number | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hosted-boot.d.ts","sourceRoot":"","sources":["../src/hosted-boot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAmBH,OAAO,EAML,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EAEtB,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"hosted-boot.d.ts","sourceRoot":"","sources":["../src/hosted-boot.ts"],"names":[],"mappings":"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"}
|
package/dist/hosted-boot.js
CHANGED
|
@@ -39,6 +39,7 @@ import { INJECTED_ENV_VARS_ENV, closeHostedLoopSession, runHostedLoop, runHosted
|
|
|
39
39
|
import { DEFAULT_EGRESS_CANARY_HOST, DEFAULT_HOSTED_MODEL } from './hosted-loop-lifecycle.js';
|
|
40
40
|
import { composeHostedAnswerPrompt } from './hosted-question.js';
|
|
41
41
|
import { resumeRunEventSink } from './hosted-run-event-sink.js';
|
|
42
|
+
import { seedProjectFolder } from './hosted-project-folder-seed.js';
|
|
42
43
|
import { stageOrizuSkill } from './hosted-skill-staging.js';
|
|
43
44
|
import { openHostedLiveChannel } from './hosted-live-channel.js';
|
|
44
45
|
/** Non-secret placeholder model key (firewall brokers the real org key on
|
|
@@ -381,6 +382,8 @@ export async function resolveSession(opts) {
|
|
|
381
382
|
// A server with copying switched off omits seedOrigin; no source is bound.
|
|
382
383
|
seedOrigin: session.seedOrigin === 'session' ? 'session' : 'team',
|
|
383
384
|
projectId: asString(session.projectId),
|
|
385
|
+
projectSlug: asString(session.projectSlug),
|
|
386
|
+
projectName: asString(session.projectName),
|
|
384
387
|
};
|
|
385
388
|
}
|
|
386
389
|
export const HOSTED_SESSION_DEFAULT_POLL_MS = 5_000;
|
|
@@ -1052,6 +1055,17 @@ const c = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
|
1052
1055
|
exec('git', ['-C', workspaceDir, 'config', 'user.email', AGENT_GIT_IDENTITY.email]);
|
|
1053
1056
|
if (!wokenDisk && !sessionCopyFilled)
|
|
1054
1057
|
log(`cloned ${session.repoBranch}`);
|
|
1058
|
+
// 5a — ORI-2609: the team copy holds only the projects the team seed knew
|
|
1059
|
+
// about. Write this session's project folder if it is missing, before the
|
|
1060
|
+
// agent starts. Never fails the boot. A woken disk is the agent's own saved
|
|
1061
|
+
// state, so a folder it removed there is not put back.
|
|
1062
|
+
log(wokenDisk
|
|
1063
|
+
? 'project folder left as the woken disk has it'
|
|
1064
|
+
: seedProjectFolder({
|
|
1065
|
+
workTree: workspaceDir,
|
|
1066
|
+
project: { id: session.projectId, slug: session.projectSlug, name: session.projectName },
|
|
1067
|
+
exec: (cmd, args) => exec(cmd, args),
|
|
1068
|
+
}));
|
|
1055
1069
|
// 5b — Stage the orizu skill into the cloned repo so the agent discovers the
|
|
1056
1070
|
// Orizu workflows (ALI-1059). SHARED with the operator path via `stageOrizuSkill`
|
|
1057
1071
|
// (one resolution chain + the harvest-safe .git/info/exclude append). Non-fatal:
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ProjectFolderSeedProject {
|
|
2
|
+
id: string | null;
|
|
3
|
+
slug: string | null;
|
|
4
|
+
name: string | null;
|
|
5
|
+
}
|
|
6
|
+
type SeedExec = (cmd: string, args: string[]) => {
|
|
7
|
+
status: number;
|
|
8
|
+
stdout: string;
|
|
9
|
+
stderr: string;
|
|
10
|
+
};
|
|
11
|
+
/** Returns the one line the boot logs. */
|
|
12
|
+
export declare function seedProjectFolder(input: {
|
|
13
|
+
workTree: string;
|
|
14
|
+
project: ProjectFolderSeedProject;
|
|
15
|
+
exec: SeedExec;
|
|
16
|
+
}): string;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=hosted-project-folder-seed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hosted-project-folder-seed.d.ts","sourceRoot":"","sources":["../src/hosted-project-folder-seed.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IACjB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;AAED,KAAK,QAAQ,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAgBnG,0CAA0C;AAC1C,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,wBAAwB,CAAA;IACjC,IAAI,EAAE,QAAQ,CAAA;CACf,GAAG,MAAM,CA8CT"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ORI-2609: every project of an approved team shares the team's repository,
|
|
3
|
+
* but the team seed wrote only the first project's folder. A hosted session
|
|
4
|
+
* for another project writes that project's folder into its own checkout at
|
|
5
|
+
* boot, exactly as the team seed would have, and commits it locally. The
|
|
6
|
+
* session's normal save carries the commit; nothing is pushed here.
|
|
7
|
+
*
|
|
8
|
+
* A folder that is already there, whole or partial, is never touched, so a
|
|
9
|
+
* second session never duplicates or overwrites one. Never throws: a seed
|
|
10
|
+
* failure is reported for the boot to log, and the session still starts.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { dirname, join } from 'node:path';
|
|
14
|
+
import { buildSeedContractFiles, SEED_COMMIT_AUTHOR_EMAIL, SEED_COMMIT_AUTHOR_NAME, SEED_COMMIT_DATE, } from './workspace-bootstrap-seed.js';
|
|
15
|
+
// One plain folder name: no separators, no traversal, no leading dot or dash.
|
|
16
|
+
const SAFE_PROJECT_SLUG = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
17
|
+
function readTeamFacts(workTree) {
|
|
18
|
+
// The team manifest the team seed wrote is the source of the team facts, so
|
|
19
|
+
// the new project's manifest agrees with the repository it lands in.
|
|
20
|
+
const manifest = JSON.parse(readFileSync(join(workTree, 'orizu.team.json'), 'utf8'));
|
|
21
|
+
if (typeof manifest.slug !== 'string' || manifest.slug.length === 0) {
|
|
22
|
+
throw new Error('orizu.team.json carries no team slug');
|
|
23
|
+
}
|
|
24
|
+
// The team id does not appear in project files; it is passed for completeness.
|
|
25
|
+
return { id: typeof manifest.teamId === 'string' ? manifest.teamId : '', slug: manifest.slug };
|
|
26
|
+
}
|
|
27
|
+
/** Returns the one line the boot logs. */
|
|
28
|
+
export function seedProjectFolder(input) {
|
|
29
|
+
const { workTree, project, exec } = input;
|
|
30
|
+
if (!project.id)
|
|
31
|
+
return 'project folder skipped: the session names no project';
|
|
32
|
+
// Without the slug there is no folder name; the server leaves it out when it
|
|
33
|
+
// could not read the project.
|
|
34
|
+
if (!project.slug)
|
|
35
|
+
return 'project folder skipped: the server sent no project slug';
|
|
36
|
+
try {
|
|
37
|
+
const slug = project.slug;
|
|
38
|
+
if (!SAFE_PROJECT_SLUG.test(slug))
|
|
39
|
+
throw new Error('the project slug is not a plain folder name');
|
|
40
|
+
const folder = `projects/${slug}`;
|
|
41
|
+
const projectsDir = join(workTree, 'projects');
|
|
42
|
+
if (existsSync(projectsDir) && !lstatSync(projectsDir).isDirectory()) {
|
|
43
|
+
throw new Error('projects/ is not a folder');
|
|
44
|
+
}
|
|
45
|
+
if (existsSync(join(workTree, folder)))
|
|
46
|
+
return `project folder ${folder}/ already present`;
|
|
47
|
+
// Byte-for-byte the files the team seed writes for this project.
|
|
48
|
+
const files = buildSeedContractFiles({
|
|
49
|
+
team: readTeamFacts(workTree),
|
|
50
|
+
projects: [{ id: project.id, slug, name: project.name }],
|
|
51
|
+
}).filter(file => file.path.startsWith(`${folder}/`));
|
|
52
|
+
for (const file of files) {
|
|
53
|
+
const target = join(workTree, file.path);
|
|
54
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
55
|
+
writeFileSync(target, file.bytes, { mode: 0o644 });
|
|
56
|
+
}
|
|
57
|
+
// No hook the checkout's config names runs inside the boot (as in harvest).
|
|
58
|
+
// The exec seam takes no `env` option, so `env` pins the commit dates.
|
|
59
|
+
const git = (step, ...args) => {
|
|
60
|
+
const result = exec('env', [
|
|
61
|
+
`GIT_AUTHOR_DATE=${SEED_COMMIT_DATE}`, `GIT_COMMITTER_DATE=${SEED_COMMIT_DATE}`,
|
|
62
|
+
'git', '-C', workTree, '-c', 'core.hooksPath=/dev/null', '-c', 'core.fsmonitor=false', ...args,
|
|
63
|
+
]);
|
|
64
|
+
if (result.status !== 0)
|
|
65
|
+
throw new Error(`git ${step} failed: ${(result.stderr || result.stdout).trim()}`);
|
|
66
|
+
};
|
|
67
|
+
git('add', 'add', '--', folder);
|
|
68
|
+
// Paths after `--` commit only this folder, whatever else is staged. A fixed
|
|
69
|
+
// author, committer and date make every session that starts from the same
|
|
70
|
+
// team head produce the same commit, so two sessions' publishes share it
|
|
71
|
+
// rather than conflict on files neither of them touched.
|
|
72
|
+
git('commit', '-c', 'commit.gpgsign=false', '-c', `user.name=${SEED_COMMIT_AUTHOR_NAME}`, '-c', `user.email=${SEED_COMMIT_AUTHOR_EMAIL}`, 'commit', '--quiet', '--no-verify', '-m', `Add the ${slug} project folder`, '--', folder);
|
|
73
|
+
return `project folder ${folder}/ seeded`;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
return `project folder seed failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -28,6 +28,20 @@
|
|
|
28
28
|
* event payload can be passed through before it is sent to the RunAPI.
|
|
29
29
|
*/
|
|
30
30
|
export declare const REDACTION_PLACEHOLDER = "[redacted]";
|
|
31
|
+
/**
|
|
32
|
+
* The shape pass alone, for servers that hold no secret values: the web app
|
|
33
|
+
* and the session coordinator scrub agent text with it before it reaches a
|
|
34
|
+
* browser (ORI-1905). One list of shapes for the CLI and both servers.
|
|
35
|
+
*/
|
|
36
|
+
export declare function redactSecretShapes(input: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* For reply text shown as it is written: each snapshot is the reply so far, so
|
|
39
|
+
* one can end part-way through a token, before it is long enough for its shape
|
|
40
|
+
* to match. Such a tail is redacted too. The saved read shows a reply's last
|
|
41
|
+
* snapshot, so a finished reply that really ends in a bare `sk-` loses those
|
|
42
|
+
* characters there as well.
|
|
43
|
+
*/
|
|
44
|
+
export declare function redactStreamedTextShapes(input: string): string;
|
|
31
45
|
export interface RedactOptions {
|
|
32
46
|
/** Verbatim secret values to strip wherever they appear (bearer, tokens). */
|
|
33
47
|
secrets?: readonly string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-redaction.d.ts","sourceRoot":"","sources":["../src/secret-redaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,eAAO,MAAM,qBAAqB,eAAe,CAAA;AAgDjD,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,GAAE,aAAkB,GAAG,CAAC,CAGzE;AAmBD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAQjF"}
|
|
1
|
+
{"version":3,"file":"secret-redaction.d.ts","sourceRoot":"","sources":["../src/secret-redaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,eAAO,MAAM,qBAAqB,eAAe,CAAA;AAgDjD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAExD;AAUD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI9D;AAED,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,GAAE,aAAkB,GAAG,CAAC,CAGzE;AAmBD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAQjF"}
|
package/dist/secret-redaction.js
CHANGED
|
@@ -70,6 +70,31 @@ function redactString(input, secrets) {
|
|
|
70
70
|
}
|
|
71
71
|
return out;
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* The shape pass alone, for servers that hold no secret values: the web app
|
|
75
|
+
* and the session coordinator scrub agent text with it before it reaches a
|
|
76
|
+
* browser (ORI-1905). One list of shapes for the CLI and both servers.
|
|
77
|
+
*/
|
|
78
|
+
export function redactSecretShapes(input) {
|
|
79
|
+
return redactString(input, []);
|
|
80
|
+
}
|
|
81
|
+
// A known credential prefix at the end of the text, followed by fewer
|
|
82
|
+
// characters than its shape above needs: the start of a token still being
|
|
83
|
+
// written. Each run is bounded, so the scan stays linear in the text, and a
|
|
84
|
+
// longer run that is not a token (a path, a word) is left alone. `Bearer` only
|
|
85
|
+
// counts in an Authorization header, so prose like "a Bearer token" keeps its
|
|
86
|
+
// last word.
|
|
87
|
+
const OPEN_TOKEN_AT_END = /(?:\bgithub_pat_[A-Za-z0-9_]{0,19}|\bgh[posur]_[A-Za-z0-9_]{0,19}|\borizu_(?:agent|pat)_[A-Za-z0-9_-]{0,9}|\bsk-[A-Za-z0-9_-]{0,19}|\b(authorization:\s*bearer)\s+[A-Za-z0-9._~+/-]{0,15})$/i;
|
|
88
|
+
/**
|
|
89
|
+
* For reply text shown as it is written: each snapshot is the reply so far, so
|
|
90
|
+
* one can end part-way through a token, before it is long enough for its shape
|
|
91
|
+
* to match. Such a tail is redacted too. The saved read shows a reply's last
|
|
92
|
+
* snapshot, so a finished reply that really ends in a bare `sk-` loses those
|
|
93
|
+
* characters there as well.
|
|
94
|
+
*/
|
|
95
|
+
export function redactStreamedTextShapes(input) {
|
|
96
|
+
return redactSecretShapes(input).replace(OPEN_TOKEN_AT_END, (_match, header) => header ? `${header} ${REDACTION_PLACEHOLDER}` : REDACTION_PLACEHOLDER);
|
|
97
|
+
}
|
|
73
98
|
/**
|
|
74
99
|
* Deep-redact a JSON-ish value. Strings are scrubbed; arrays/objects are walked;
|
|
75
100
|
* everything else is returned as-is. Object KEYS are never treated as secrets
|
|
@@ -23,6 +23,11 @@ export interface SkilledProposerChildOutput {
|
|
|
23
23
|
stdout: (chunk: string) => void;
|
|
24
24
|
stderr: (chunk: string) => void;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Run the optimizer, passing a stop signal on to it. On a stop signal the CLI
|
|
28
|
+
* waits until the optimizer exits (capped at CHILD_STOP_WAIT_MS, then kills
|
|
29
|
+
* it) and then ends by that same signal.
|
|
30
|
+
*/
|
|
26
31
|
export declare function spawnSkilledProposerChild(command: string, args: string[], engine: GepaEngine, environment: NodeJS.ProcessEnv, onSpawn?: () => Promise<void>, output?: SkilledProposerChildOutput): Promise<{
|
|
27
32
|
status: number | null;
|
|
28
33
|
error?: Error;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skilled-proposer-launch.d.ts","sourceRoot":"","sources":["../src/skilled-proposer-launch.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAO3D,UAAU,yBAAyB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAC3B,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAClD,oBAAoB,EAAE,OAAO,CAAA;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IAC9B,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;CAClC;AACD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAC,UAAU,CAAA;IAC9B,MAAM,CAAC,EAAE,yBAAyB,CAAA;CACnC;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAChC;
|
|
1
|
+
{"version":3,"file":"skilled-proposer-launch.d.ts","sourceRoot":"","sources":["../src/skilled-proposer-launch.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAO3D,UAAU,yBAAyB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAC3B,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAClD,oBAAoB,EAAE,OAAO,CAAA;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IAC9B,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;CAClC;AACD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAC,UAAU,CAAA;IAC9B,MAAM,CAAC,EAAE,yBAAyB,CAAA;CACnC;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAChC;AAsBD;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,CAAC,UAAU,EACnF,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAC7B,MAAM,CAAC,EAAE,0BAA0B;YA0CX,MAAM,GAAG,IAAI;YAAU,KAAK;GAkDrD;AAwCD,UAAU,aAAa;IAAG,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE;AAQ9F,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,aAAa,EAAE,MAAM,UAAQ,GAAG,MAAM,CAepF;AAED,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,MAAM,CAAC,UAAU,GAC7B,qBAAqB,CAyBvB"}
|
|
@@ -23,70 +23,128 @@ function materializeConfigPayload(engine, environment) {
|
|
|
23
23
|
}
|
|
24
24
|
return () => rmSync(root, { recursive: true, force: true });
|
|
25
25
|
}
|
|
26
|
+
const STOP_SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGQUIT'];
|
|
27
|
+
// The optimizer marks its run stopped before exiting (at most 10 s for the
|
|
28
|
+
// status write); past this it is killed so nothing is left running (ORI-2402).
|
|
29
|
+
const CHILD_STOP_WAIT_MS = 15_000;
|
|
30
|
+
/**
|
|
31
|
+
* Run the optimizer, passing a stop signal on to it. On a stop signal the CLI
|
|
32
|
+
* waits until the optimizer exits (capped at CHILD_STOP_WAIT_MS, then kills
|
|
33
|
+
* it) and then ends by that same signal.
|
|
34
|
+
*/
|
|
26
35
|
export async function spawnSkilledProposerChild(command, args, engine, environment, onSpawn, output) {
|
|
27
36
|
const childEnvironment = { ...environment };
|
|
28
|
-
const shouldMaterialize = engine === 'official'
|
|
29
|
-
&& childEnvironment.ORIZU_CANDIDATE_PROPOSER === 'skilled-proposer'
|
|
30
|
-
&& childEnvironment.ORIZU_SKILLED_PROPOSER_CONFIG !== undefined;
|
|
31
|
-
if (!shouldMaterialize)
|
|
32
|
-
return spawnSync(command, args, { stdio: 'inherit', env: childEnvironment });
|
|
33
37
|
let child;
|
|
34
38
|
let cleanup = () => { };
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
let stopSignal;
|
|
40
|
+
// On the terminal path the optimizer leads its own process group, so a stop
|
|
41
|
+
// also reaches its runners, which may run in worker threads it can't stop.
|
|
42
|
+
let childGroup;
|
|
43
|
+
const forward = (signal) => {
|
|
44
|
+
if (childGroup) {
|
|
39
45
|
try {
|
|
40
|
-
|
|
46
|
+
process.kill(-childGroup, signal);
|
|
47
|
+
return;
|
|
41
48
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
catch { /* group already gone */ }
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
child?.kill(signal);
|
|
53
|
+
}
|
|
54
|
+
catch { /* already exited */ }
|
|
55
|
+
};
|
|
56
|
+
let giveUp;
|
|
57
|
+
const removeHandlers = () => { for (const entry of handlers)
|
|
58
|
+
process.off(entry.signal, entry.handle); };
|
|
59
|
+
const endBySignal = (signal) => {
|
|
60
|
+
if (giveUp)
|
|
61
|
+
clearTimeout(giveUp);
|
|
62
|
+
removeHandlers();
|
|
63
|
+
cleanup();
|
|
64
|
+
// With our handlers gone, the signal's default action ends the process.
|
|
65
|
+
process.kill(process.pid, signal);
|
|
66
|
+
return new Promise(() => { });
|
|
67
|
+
};
|
|
68
|
+
const handlers = STOP_SIGNALS.map(signal => ({
|
|
69
|
+
signal,
|
|
70
|
+
handle: () => {
|
|
71
|
+
if (!child) {
|
|
72
|
+
void endBySignal(signal);
|
|
73
|
+
return;
|
|
51
74
|
}
|
|
75
|
+
forward(signal);
|
|
76
|
+
if (stopSignal)
|
|
77
|
+
return;
|
|
78
|
+
stopSignal = signal;
|
|
79
|
+
// The child read the private payload at start and a stopping run
|
|
80
|
+
// proposes nothing, so it goes now rather than when the child exits.
|
|
81
|
+
cleanup();
|
|
82
|
+
giveUp = setTimeout(() => forward('SIGKILL'), CHILD_STOP_WAIT_MS);
|
|
52
83
|
},
|
|
53
84
|
}));
|
|
54
85
|
for (const entry of handlers)
|
|
55
|
-
process.
|
|
86
|
+
process.on(entry.signal, entry.handle);
|
|
56
87
|
try {
|
|
57
88
|
cleanup = materializeConfigPayload(engine, childEnvironment) ?? cleanup;
|
|
58
89
|
await new Promise(resolve => setImmediate(resolve));
|
|
90
|
+
let result;
|
|
59
91
|
if (output) {
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
const spawnedChild = spawn(command, args, {
|
|
63
|
-
stdio: 'inherit',
|
|
64
|
-
env: childEnvironment,
|
|
65
|
-
});
|
|
66
|
-
child = spawnedChild;
|
|
67
|
-
const spawned = new Promise((resolve, reject) => {
|
|
68
|
-
spawnedChild.once('spawn', resolve);
|
|
69
|
-
spawnedChild.once('error', reject);
|
|
70
|
-
});
|
|
71
|
-
const closed = new Promise(resolve => {
|
|
72
|
-
let error;
|
|
73
|
-
spawnedChild.once('error', cause => { error = cause; });
|
|
74
|
-
spawnedChild.once('close', status => resolve(error ? { status, error } : { status }));
|
|
75
|
-
});
|
|
76
|
-
await spawned;
|
|
77
|
-
try {
|
|
78
|
-
await onSpawn?.();
|
|
92
|
+
result = await runPipedChild(command, args, childEnvironment, async () => { await onSpawn?.(); }, output, spawnedChild => { child = spawnedChild; });
|
|
79
93
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
else {
|
|
95
|
+
// stdin is not passed on: a process outside the terminal's foreground
|
|
96
|
+
// group that read the terminal would be stopped by it.
|
|
97
|
+
// Outside the CLI's group, the optimizer would outlive a SIGKILL of the
|
|
98
|
+
// CLI; it watches for that and stops itself.
|
|
99
|
+
const spawnedChild = spawn(command, args, {
|
|
100
|
+
stdio: ['ignore', 'inherit', 'inherit'],
|
|
101
|
+
env: { ...childEnvironment, ORIZU_STOP_WITH_PARENT: '1' },
|
|
102
|
+
detached: true,
|
|
103
|
+
});
|
|
104
|
+
child = spawnedChild;
|
|
105
|
+
childGroup = spawnedChild.pid;
|
|
106
|
+
const spawned = new Promise((resolve, reject) => {
|
|
107
|
+
spawnedChild.once('spawn', resolve);
|
|
108
|
+
spawnedChild.once('error', reject);
|
|
109
|
+
});
|
|
110
|
+
const closed = new Promise(resolve => {
|
|
111
|
+
let error;
|
|
112
|
+
spawnedChild.once('error', cause => { error = cause; });
|
|
113
|
+
spawnedChild.once('close', status => resolve(error ? { status, error } : { status }));
|
|
114
|
+
});
|
|
115
|
+
// A launch failure (e.g. ENOENT) is returned, not thrown, so callers
|
|
116
|
+
// name the engine as they did with spawnSync.
|
|
117
|
+
try {
|
|
118
|
+
await spawned;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
return { status: null, error: error };
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
await onSpawn?.();
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
spawnedChild.kill('SIGTERM');
|
|
128
|
+
await closed;
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
result = await closed;
|
|
132
|
+
// A runner that ignored the stop must not outlive the optimizer.
|
|
133
|
+
if (stopSignal) {
|
|
134
|
+
try {
|
|
135
|
+
process.kill(-spawnedChild.pid, 'SIGKILL');
|
|
136
|
+
}
|
|
137
|
+
catch { /* group already gone */ }
|
|
138
|
+
}
|
|
84
139
|
}
|
|
85
|
-
|
|
140
|
+
if (stopSignal)
|
|
141
|
+
return await endBySignal(stopSignal);
|
|
142
|
+
return result;
|
|
86
143
|
}
|
|
87
144
|
finally {
|
|
88
|
-
|
|
89
|
-
|
|
145
|
+
if (giveUp)
|
|
146
|
+
clearTimeout(giveUp);
|
|
147
|
+
removeHandlers();
|
|
90
148
|
cleanup();
|
|
91
149
|
}
|
|
92
150
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Schema } from 'effect';
|
|
2
|
+
declare const AddReply: Schema.Struct<{
|
|
3
|
+
readonly member: Schema.Struct<{
|
|
4
|
+
readonly id: Schema.NonEmptyString;
|
|
5
|
+
readonly email: Schema.String;
|
|
6
|
+
}>;
|
|
7
|
+
}>;
|
|
8
|
+
/** The member as the server sent it; `--json` prints every field it has. */
|
|
9
|
+
export type AddedMember = typeof AddReply.Type['member'] & Readonly<Record<string, unknown>>;
|
|
10
|
+
/** Sends one request; calls `onRequestStart` just before anything goes out. */
|
|
11
|
+
export type TeamMemberAddRequest = (path: string, init: RequestInit, onRequestStart?: () => void) => Promise<Response>;
|
|
12
|
+
/** `orizu teams members add`: the invited member, or an error saying whether the invite may be saved. */
|
|
13
|
+
export declare function addTeamMemberWithDeadline(teamSlug: string, email: string, request: TeamMemberAddRequest): Promise<AddedMember>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=team-member-add-workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-member-add-workflow.d.ts","sourceRoot":"","sources":["../src/team-member-add-workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,MAAM,EAAE,MAAM,QAAQ,CAAA;AAmB5D,QAAA,MAAM,QAAQ;;;;;EAKZ,CAAA;AAEF,4EAA4E;AAC5E,MAAM,MAAM,WAAW,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAE5F,+EAA+E;AAC/E,MAAM,MAAM,oBAAoB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,CAAC,EAAE,MAAM,IAAI,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AA2EtH,yGAAyG;AACzG,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,CAMpI"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Result, Schema } from 'effect';
|
|
2
|
+
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js';
|
|
3
|
+
import { extractErrorMessage } from './error-response.js';
|
|
4
|
+
import { sanitizeTerminalText } from './json-response.js';
|
|
5
|
+
import { appErrorOf, unreachedCode } from './request-outcome.js';
|
|
6
|
+
// Loaded only through `await import(...)`, like the dataset workflows.
|
|
7
|
+
const FAILED = 'Failed to add team member';
|
|
8
|
+
// What the route says when it cannot tell whether the invite was saved (ORI-2383 slice A).
|
|
9
|
+
const SERVER_MAY_BE_SAVED = 'Invite may already be saved. Refresh the member list before inviting again.';
|
|
10
|
+
// The route saves the address trimmed and lowercased, so the check names that address.
|
|
11
|
+
const mayBeInvited = (teamSlug, email) => {
|
|
12
|
+
const saved = sanitizeTerminalText(email.trim().toLowerCase());
|
|
13
|
+
return `${saved} may already be invited: run \`orizu teams members list --team ${teamSlug}\` and look for ${saved}. If they are listed, they were invited; don't invite them again.`;
|
|
14
|
+
};
|
|
15
|
+
const AddReply = Schema.Struct({
|
|
16
|
+
member: Schema.Struct({
|
|
17
|
+
id: Schema.NonEmptyString,
|
|
18
|
+
email: Schema.String,
|
|
19
|
+
}),
|
|
20
|
+
});
|
|
21
|
+
// The invite may be saved when the server says so, when the reply was lost,
|
|
22
|
+
// late or unreadable after the request was sent, when a 5xx did not come from
|
|
23
|
+
// the app (a gateway giving up while the app kept working), or when the reply
|
|
24
|
+
// has no member. Any other error reply is a clear refusal.
|
|
25
|
+
class TeamMemberAddFailure extends Error {
|
|
26
|
+
mayBeSaved;
|
|
27
|
+
_tag = 'TeamMemberAddFailure';
|
|
28
|
+
constructor(message, mayBeSaved) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.mayBeSaved = mayBeSaved;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// One request, never retried: a retry after a saved invite is refused as
|
|
34
|
+
// "already a member", which hides that the first one worked.
|
|
35
|
+
function memberAdd(teamSlug, email, request, timeoutMs) {
|
|
36
|
+
return Effect.suspend(() => {
|
|
37
|
+
let sent = false;
|
|
38
|
+
let timedOut = false;
|
|
39
|
+
return Effect.tryPromise({
|
|
40
|
+
try: async (signal) => {
|
|
41
|
+
const controller = new AbortController();
|
|
42
|
+
const abort = () => controller.abort();
|
|
43
|
+
const timer = setTimeout(() => { timedOut = true; controller.abort(); }, timeoutMs);
|
|
44
|
+
if (signal.aborted)
|
|
45
|
+
abort();
|
|
46
|
+
else
|
|
47
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
48
|
+
try {
|
|
49
|
+
const response = await request(`/api/cli/teams/${encodeURIComponent(teamSlug)}/members`, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: { 'Content-Type': 'application/json' },
|
|
52
|
+
body: JSON.stringify({ email }),
|
|
53
|
+
signal: controller.signal,
|
|
54
|
+
}, () => { sent = true; });
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
const appError = await appErrorOf(response.clone());
|
|
57
|
+
const detail = await extractErrorMessage(response);
|
|
58
|
+
if (appError === SERVER_MAY_BE_SAVED)
|
|
59
|
+
throw new TeamMemberAddFailure(appError, true);
|
|
60
|
+
if (response.status < 400 || (response.status >= 500 && appError === null)) {
|
|
61
|
+
throw new TeamMemberAddFailure(`Member add got no clear answer (status ${response.status}).`, true);
|
|
62
|
+
}
|
|
63
|
+
// The route's own failures already say what failed.
|
|
64
|
+
throw new TeamMemberAddFailure(detail.startsWith('Failed to ') ? detail : `${FAILED}: ${detail}`, false);
|
|
65
|
+
}
|
|
66
|
+
// Reading the body can still fail or stall; that stays under the deadline.
|
|
67
|
+
const body = await response.text();
|
|
68
|
+
let data;
|
|
69
|
+
try {
|
|
70
|
+
data = JSON.parse(body);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
data = undefined;
|
|
74
|
+
}
|
|
75
|
+
if (Result.isFailure(Schema.decodeUnknownResult(AddReply)(data))) {
|
|
76
|
+
throw new TeamMemberAddFailure(`Member add returned an unexpected response (status ${response.status}).`, true);
|
|
77
|
+
}
|
|
78
|
+
// Decoding keeps only the named fields; the reply's member is returned whole.
|
|
79
|
+
return data.member;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
signal.removeEventListener('abort', abort);
|
|
84
|
+
controller.abort();
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
catch: cause => {
|
|
88
|
+
if (cause instanceof TeamMemberAddFailure)
|
|
89
|
+
return cause;
|
|
90
|
+
if (timedOut)
|
|
91
|
+
return new TeamMemberAddFailure('Member add exceeded its deadline.', sent);
|
|
92
|
+
const unreached = unreachedCode(cause);
|
|
93
|
+
if (unreached)
|
|
94
|
+
return new TeamMemberAddFailure(`${FAILED}: could not reach the server (${unreached}).`, false);
|
|
95
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
96
|
+
if (!sent || message.startsWith('ORIZU_AUTH_CONTEXT_CHANGED:'))
|
|
97
|
+
return new TeamMemberAddFailure(message, false);
|
|
98
|
+
// Also reached when a sign-in refresh fails after a 401: still told to
|
|
99
|
+
// check, so say only that the invite did not finish.
|
|
100
|
+
return new TeamMemberAddFailure(`Member add did not finish: ${message}`, true);
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/** `orizu teams members add`: the invited member, or an error saying whether the invite may be saved. */
|
|
106
|
+
export async function addTeamMemberWithDeadline(teamSlug, email, request) {
|
|
107
|
+
const exit = await Effect.runPromiseExit(memberAdd(teamSlug, email, request, datasetTransferTimeoutMs()).pipe(Effect.mapError(failure => new Error(failure.mayBeSaved ? `${failure.message}\n${mayBeInvited(teamSlug, email)}` : failure.message))));
|
|
108
|
+
if (Exit.isSuccess(exit))
|
|
109
|
+
return exit.value;
|
|
110
|
+
throw Cause.squash(exit.cause);
|
|
111
|
+
}
|
package/package.json
CHANGED
package/src/account-directory.ts
CHANGED
|
@@ -634,20 +634,13 @@ async function addTeamMember() {
|
|
|
634
634
|
throw new Error('Usage: orizu teams members add --email <email> [--team <teamSlug>]')
|
|
635
635
|
}
|
|
636
636
|
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
headers: { 'Content-Type': 'application/json' },
|
|
640
|
-
body: JSON.stringify({ email }),
|
|
641
|
-
})
|
|
642
|
-
if (!response.ok) {
|
|
643
|
-
throw new Error(`Failed to add team member: ${await response.text()}`)
|
|
644
|
-
}
|
|
645
|
-
const data = await parseJsonResponse<{ member: TeamMember }>(response, 'Team member add')
|
|
637
|
+
const { addTeamMemberWithDeadline } = await import('./team-member-add-workflow.js')
|
|
638
|
+
const member = await addTeamMemberWithDeadline(teamSlug, email, authedFetch)
|
|
646
639
|
if (hasJsonFlag()) {
|
|
647
|
-
printJson({ member
|
|
640
|
+
printJson({ member })
|
|
648
641
|
return
|
|
649
642
|
}
|
|
650
|
-
printLine(`Added team member ${sanitizeTerminalText(
|
|
643
|
+
printLine(`Added team member ${sanitizeTerminalText(member.email)} (${sanitizeTerminalText(member.id)})`)
|
|
651
644
|
}
|
|
652
645
|
|
|
653
646
|
async function removeTeamMember() {
|
package/src/hosted-boot.ts
CHANGED
|
@@ -60,6 +60,7 @@ import {
|
|
|
60
60
|
import { DEFAULT_EGRESS_CANARY_HOST, DEFAULT_HOSTED_MODEL } from './hosted-loop-lifecycle.js'
|
|
61
61
|
import { composeHostedAnswerPrompt } from './hosted-question.js'
|
|
62
62
|
import { resumeRunEventSink } from './hosted-run-event-sink.js'
|
|
63
|
+
import { seedProjectFolder } from './hosted-project-folder-seed.js'
|
|
63
64
|
import { stageOrizuSkill } from './hosted-skill-staging.js'
|
|
64
65
|
import { openHostedLiveChannel, type HostedLiveChannel } from './hosted-live-channel.js'
|
|
65
66
|
|
|
@@ -441,6 +442,9 @@ export interface ResolvedSession {
|
|
|
441
442
|
agentSessionId: string | null
|
|
442
443
|
seedOrigin: 'team' | 'session'
|
|
443
444
|
projectId: string | null
|
|
445
|
+
/** The session's own project's slug and name (ORI-2609), when the server sends them. */
|
|
446
|
+
projectSlug: string | null
|
|
447
|
+
projectName: string | null
|
|
444
448
|
/** Session lifetime in minutes (from client_info), if the coordinator recorded
|
|
445
449
|
* it — used to derive the per-prompt max-duration cap (ALI-1061). */
|
|
446
450
|
durationMinutes: number | null
|
|
@@ -547,6 +551,8 @@ export async function resolveSession(opts: {
|
|
|
547
551
|
// A server with copying switched off omits seedOrigin; no source is bound.
|
|
548
552
|
seedOrigin: session.seedOrigin === 'session' ? 'session' : 'team',
|
|
549
553
|
projectId: asString(session.projectId),
|
|
554
|
+
projectSlug: asString(session.projectSlug),
|
|
555
|
+
projectName: asString(session.projectName),
|
|
550
556
|
}
|
|
551
557
|
}
|
|
552
558
|
|
|
@@ -1342,6 +1348,18 @@ const c = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
|
1342
1348
|
exec('git', ['-C', workspaceDir, 'config', 'user.email', AGENT_GIT_IDENTITY.email])
|
|
1343
1349
|
if (!wokenDisk && !sessionCopyFilled) log(`cloned ${session.repoBranch}`)
|
|
1344
1350
|
|
|
1351
|
+
// 5a — ORI-2609: the team copy holds only the projects the team seed knew
|
|
1352
|
+
// about. Write this session's project folder if it is missing, before the
|
|
1353
|
+
// agent starts. Never fails the boot. A woken disk is the agent's own saved
|
|
1354
|
+
// state, so a folder it removed there is not put back.
|
|
1355
|
+
log(wokenDisk
|
|
1356
|
+
? 'project folder left as the woken disk has it'
|
|
1357
|
+
: seedProjectFolder({
|
|
1358
|
+
workTree: workspaceDir,
|
|
1359
|
+
project: { id: session.projectId, slug: session.projectSlug, name: session.projectName },
|
|
1360
|
+
exec: (cmd, args) => exec(cmd, args),
|
|
1361
|
+
}))
|
|
1362
|
+
|
|
1345
1363
|
// 5b — Stage the orizu skill into the cloned repo so the agent discovers the
|
|
1346
1364
|
// Orizu workflows (ALI-1059). SHARED with the operator path via `stageOrizuSkill`
|
|
1347
1365
|
// (one resolution chain + the harvest-safe .git/info/exclude append). Non-fatal:
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ORI-2609: every project of an approved team shares the team's repository,
|
|
3
|
+
* but the team seed wrote only the first project's folder. A hosted session
|
|
4
|
+
* for another project writes that project's folder into its own checkout at
|
|
5
|
+
* boot, exactly as the team seed would have, and commits it locally. The
|
|
6
|
+
* session's normal save carries the commit; nothing is pushed here.
|
|
7
|
+
*
|
|
8
|
+
* A folder that is already there, whole or partial, is never touched, so a
|
|
9
|
+
* second session never duplicates or overwrites one. Never throws: a seed
|
|
10
|
+
* failure is reported for the boot to log, and the session still starts.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
13
|
+
import { dirname, join } from 'node:path'
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
buildSeedContractFiles,
|
|
17
|
+
SEED_COMMIT_AUTHOR_EMAIL,
|
|
18
|
+
SEED_COMMIT_AUTHOR_NAME,
|
|
19
|
+
SEED_COMMIT_DATE,
|
|
20
|
+
} from './workspace-bootstrap-seed.js'
|
|
21
|
+
|
|
22
|
+
export interface ProjectFolderSeedProject {
|
|
23
|
+
id: string | null
|
|
24
|
+
slug: string | null
|
|
25
|
+
name: string | null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type SeedExec = (cmd: string, args: string[]) => { status: number; stdout: string; stderr: string }
|
|
29
|
+
|
|
30
|
+
// One plain folder name: no separators, no traversal, no leading dot or dash.
|
|
31
|
+
const SAFE_PROJECT_SLUG = /^[A-Za-z0-9][A-Za-z0-9._-]*$/
|
|
32
|
+
|
|
33
|
+
function readTeamFacts(workTree: string): { id: string; slug: string } {
|
|
34
|
+
// The team manifest the team seed wrote is the source of the team facts, so
|
|
35
|
+
// the new project's manifest agrees with the repository it lands in.
|
|
36
|
+
const manifest = JSON.parse(readFileSync(join(workTree, 'orizu.team.json'), 'utf8')) as Record<string, unknown>
|
|
37
|
+
if (typeof manifest.slug !== 'string' || manifest.slug.length === 0) {
|
|
38
|
+
throw new Error('orizu.team.json carries no team slug')
|
|
39
|
+
}
|
|
40
|
+
// The team id does not appear in project files; it is passed for completeness.
|
|
41
|
+
return { id: typeof manifest.teamId === 'string' ? manifest.teamId : '', slug: manifest.slug }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Returns the one line the boot logs. */
|
|
45
|
+
export function seedProjectFolder(input: {
|
|
46
|
+
workTree: string
|
|
47
|
+
project: ProjectFolderSeedProject
|
|
48
|
+
exec: SeedExec
|
|
49
|
+
}): string {
|
|
50
|
+
const { workTree, project, exec } = input
|
|
51
|
+
if (!project.id) return 'project folder skipped: the session names no project'
|
|
52
|
+
// Without the slug there is no folder name; the server leaves it out when it
|
|
53
|
+
// could not read the project.
|
|
54
|
+
if (!project.slug) return 'project folder skipped: the server sent no project slug'
|
|
55
|
+
try {
|
|
56
|
+
const slug = project.slug
|
|
57
|
+
if (!SAFE_PROJECT_SLUG.test(slug)) throw new Error('the project slug is not a plain folder name')
|
|
58
|
+
const folder = `projects/${slug}`
|
|
59
|
+
const projectsDir = join(workTree, 'projects')
|
|
60
|
+
if (existsSync(projectsDir) && !lstatSync(projectsDir).isDirectory()) {
|
|
61
|
+
throw new Error('projects/ is not a folder')
|
|
62
|
+
}
|
|
63
|
+
if (existsSync(join(workTree, folder))) return `project folder ${folder}/ already present`
|
|
64
|
+
|
|
65
|
+
// Byte-for-byte the files the team seed writes for this project.
|
|
66
|
+
const files = buildSeedContractFiles({
|
|
67
|
+
team: readTeamFacts(workTree),
|
|
68
|
+
projects: [{ id: project.id, slug, name: project.name }],
|
|
69
|
+
}).filter(file => file.path.startsWith(`${folder}/`))
|
|
70
|
+
for (const file of files) {
|
|
71
|
+
const target = join(workTree, file.path)
|
|
72
|
+
mkdirSync(dirname(target), { recursive: true })
|
|
73
|
+
writeFileSync(target, file.bytes, { mode: 0o644 })
|
|
74
|
+
}
|
|
75
|
+
// No hook the checkout's config names runs inside the boot (as in harvest).
|
|
76
|
+
// The exec seam takes no `env` option, so `env` pins the commit dates.
|
|
77
|
+
const git = (step: string, ...args: string[]) => {
|
|
78
|
+
const result = exec('env', [
|
|
79
|
+
`GIT_AUTHOR_DATE=${SEED_COMMIT_DATE}`, `GIT_COMMITTER_DATE=${SEED_COMMIT_DATE}`,
|
|
80
|
+
'git', '-C', workTree, '-c', 'core.hooksPath=/dev/null', '-c', 'core.fsmonitor=false', ...args,
|
|
81
|
+
])
|
|
82
|
+
if (result.status !== 0) throw new Error(`git ${step} failed: ${(result.stderr || result.stdout).trim()}`)
|
|
83
|
+
}
|
|
84
|
+
git('add', 'add', '--', folder)
|
|
85
|
+
// Paths after `--` commit only this folder, whatever else is staged. A fixed
|
|
86
|
+
// author, committer and date make every session that starts from the same
|
|
87
|
+
// team head produce the same commit, so two sessions' publishes share it
|
|
88
|
+
// rather than conflict on files neither of them touched.
|
|
89
|
+
git('commit', '-c', 'commit.gpgsign=false', '-c', `user.name=${SEED_COMMIT_AUTHOR_NAME}`, '-c', `user.email=${SEED_COMMIT_AUTHOR_EMAIL}`,
|
|
90
|
+
'commit', '--quiet', '--no-verify', '-m', `Add the ${slug} project folder`, '--', folder)
|
|
91
|
+
return `project folder ${folder}/ seeded`
|
|
92
|
+
} catch (error) {
|
|
93
|
+
return `project folder seed failed: ${error instanceof Error ? error.message : String(error)}`
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/secret-redaction.ts
CHANGED
|
@@ -76,6 +76,36 @@ function redactString(input: string, secrets: readonly string[]): string {
|
|
|
76
76
|
return out
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* The shape pass alone, for servers that hold no secret values: the web app
|
|
81
|
+
* and the session coordinator scrub agent text with it before it reaches a
|
|
82
|
+
* browser (ORI-1905). One list of shapes for the CLI and both servers.
|
|
83
|
+
*/
|
|
84
|
+
export function redactSecretShapes(input: string): string {
|
|
85
|
+
return redactString(input, [])
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// A known credential prefix at the end of the text, followed by fewer
|
|
89
|
+
// characters than its shape above needs: the start of a token still being
|
|
90
|
+
// written. Each run is bounded, so the scan stays linear in the text, and a
|
|
91
|
+
// longer run that is not a token (a path, a word) is left alone. `Bearer` only
|
|
92
|
+
// counts in an Authorization header, so prose like "a Bearer token" keeps its
|
|
93
|
+
// last word.
|
|
94
|
+
const OPEN_TOKEN_AT_END = /(?:\bgithub_pat_[A-Za-z0-9_]{0,19}|\bgh[posur]_[A-Za-z0-9_]{0,19}|\borizu_(?:agent|pat)_[A-Za-z0-9_-]{0,9}|\bsk-[A-Za-z0-9_-]{0,19}|\b(authorization:\s*bearer)\s+[A-Za-z0-9._~+/-]{0,15})$/i
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* For reply text shown as it is written: each snapshot is the reply so far, so
|
|
98
|
+
* one can end part-way through a token, before it is long enough for its shape
|
|
99
|
+
* to match. Such a tail is redacted too. The saved read shows a reply's last
|
|
100
|
+
* snapshot, so a finished reply that really ends in a bare `sk-` loses those
|
|
101
|
+
* characters there as well.
|
|
102
|
+
*/
|
|
103
|
+
export function redactStreamedTextShapes(input: string): string {
|
|
104
|
+
return redactSecretShapes(input).replace(OPEN_TOKEN_AT_END, (_match, header?: string) =>
|
|
105
|
+
header ? `${header} ${REDACTION_PLACEHOLDER}` : REDACTION_PLACEHOLDER
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
79
109
|
export interface RedactOptions {
|
|
80
110
|
/** Verbatim secret values to strip wherever they appear (bearer, tokens). */
|
|
81
111
|
secrets?: readonly string[]
|
|
@@ -49,39 +49,64 @@ function materializeConfigPayload(engine: GepaEngine, environment: NodeJS.Proces
|
|
|
49
49
|
return () => rmSync(root, { recursive: true, force: true })
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
const STOP_SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGQUIT'] as const
|
|
53
|
+
// The optimizer marks its run stopped before exiting (at most 10 s for the
|
|
54
|
+
// status write); past this it is killed so nothing is left running (ORI-2402).
|
|
55
|
+
const CHILD_STOP_WAIT_MS = 15_000
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Run the optimizer, passing a stop signal on to it. On a stop signal the CLI
|
|
59
|
+
* waits until the optimizer exits (capped at CHILD_STOP_WAIT_MS, then kills
|
|
60
|
+
* it) and then ends by that same signal.
|
|
61
|
+
*/
|
|
52
62
|
export async function spawnSkilledProposerChild(
|
|
53
63
|
command: string, args: string[], engine: GepaEngine, environment: NodeJS.ProcessEnv,
|
|
54
64
|
onSpawn?: () => Promise<void>,
|
|
55
65
|
output?: SkilledProposerChildOutput,
|
|
56
66
|
) {
|
|
57
67
|
const childEnvironment = { ...environment }
|
|
58
|
-
const shouldMaterialize = engine === 'official'
|
|
59
|
-
&& childEnvironment.ORIZU_CANDIDATE_PROPOSER === 'skilled-proposer'
|
|
60
|
-
&& childEnvironment.ORIZU_SKILLED_PROPOSER_CONFIG !== undefined
|
|
61
|
-
if (!shouldMaterialize) return spawnSync(command, args, { stdio: 'inherit', env: childEnvironment })
|
|
62
|
-
|
|
63
68
|
let child: ReturnType<typeof spawn> | PipedChild | undefined
|
|
64
69
|
let cleanup = () => {}
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
let stopSignal: NodeJS.Signals | undefined
|
|
71
|
+
// On the terminal path the optimizer leads its own process group, so a stop
|
|
72
|
+
// also reaches its runners, which may run in worker threads it can't stop.
|
|
73
|
+
let childGroup: number | undefined
|
|
74
|
+
const forward = (signal: NodeJS.Signals) => {
|
|
75
|
+
if (childGroup) {
|
|
76
|
+
try { process.kill(-childGroup, signal); return } catch { /* group already gone */ }
|
|
77
|
+
}
|
|
78
|
+
try { child?.kill(signal) } catch { /* already exited */ }
|
|
79
|
+
}
|
|
80
|
+
let giveUp: ReturnType<typeof setTimeout> | undefined
|
|
81
|
+
const removeHandlers = () => { for (const entry of handlers) process.off(entry.signal, entry.handle) }
|
|
82
|
+
const endBySignal = (signal: NodeJS.Signals): Promise<never> => {
|
|
83
|
+
if (giveUp) clearTimeout(giveUp)
|
|
84
|
+
removeHandlers()
|
|
85
|
+
cleanup()
|
|
86
|
+
// With our handlers gone, the signal's default action ends the process.
|
|
87
|
+
process.kill(process.pid, signal)
|
|
88
|
+
return new Promise<never>(() => {})
|
|
89
|
+
}
|
|
90
|
+
const handlers = STOP_SIGNALS.map(signal => ({
|
|
67
91
|
signal,
|
|
68
92
|
handle: () => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
93
|
+
if (!child) { void endBySignal(signal); return }
|
|
94
|
+
forward(signal)
|
|
95
|
+
if (stopSignal) return
|
|
96
|
+
stopSignal = signal
|
|
97
|
+
// The child read the private payload at start and a stopping run
|
|
98
|
+
// proposes nothing, so it goes now rather than when the child exits.
|
|
99
|
+
cleanup()
|
|
100
|
+
giveUp = setTimeout(() => forward('SIGKILL'), CHILD_STOP_WAIT_MS)
|
|
77
101
|
},
|
|
78
102
|
}))
|
|
79
|
-
for (const entry of handlers) process.
|
|
103
|
+
for (const entry of handlers) process.on(entry.signal, entry.handle)
|
|
80
104
|
try {
|
|
81
105
|
cleanup = materializeConfigPayload(engine, childEnvironment) ?? cleanup
|
|
82
106
|
await new Promise<void>(resolve => setImmediate(resolve))
|
|
107
|
+
let result: { status: number | null, error?: Error }
|
|
83
108
|
if (output) {
|
|
84
|
-
|
|
109
|
+
result = await runPipedChild(
|
|
85
110
|
command,
|
|
86
111
|
args,
|
|
87
112
|
childEnvironment,
|
|
@@ -89,27 +114,44 @@ export async function spawnSkilledProposerChild(
|
|
|
89
114
|
output,
|
|
90
115
|
spawnedChild => { child = spawnedChild }
|
|
91
116
|
)
|
|
117
|
+
} else {
|
|
118
|
+
// stdin is not passed on: a process outside the terminal's foreground
|
|
119
|
+
// group that read the terminal would be stopped by it.
|
|
120
|
+
// Outside the CLI's group, the optimizer would outlive a SIGKILL of the
|
|
121
|
+
// CLI; it watches for that and stops itself.
|
|
122
|
+
const spawnedChild = spawn(command, args, {
|
|
123
|
+
stdio: ['ignore', 'inherit', 'inherit'],
|
|
124
|
+
env: { ...childEnvironment, ORIZU_STOP_WITH_PARENT: '1' },
|
|
125
|
+
detached: true,
|
|
126
|
+
})
|
|
127
|
+
child = spawnedChild
|
|
128
|
+
childGroup = spawnedChild.pid
|
|
129
|
+
const spawned = new Promise<void>((resolve, reject) => {
|
|
130
|
+
spawnedChild.once('spawn', resolve)
|
|
131
|
+
spawnedChild.once('error', reject)
|
|
132
|
+
})
|
|
133
|
+
const closed = new Promise<{ status: number | null, error?: Error }>(resolve => {
|
|
134
|
+
let error: Error | undefined
|
|
135
|
+
spawnedChild.once('error', cause => { error = cause })
|
|
136
|
+
spawnedChild.once('close', status => resolve(error ? { status, error } : { status }))
|
|
137
|
+
})
|
|
138
|
+
// A launch failure (e.g. ENOENT) is returned, not thrown, so callers
|
|
139
|
+
// name the engine as they did with spawnSync.
|
|
140
|
+
try { await spawned }
|
|
141
|
+
catch (error) { return { status: null, error: error as Error } }
|
|
142
|
+
try { await onSpawn?.() }
|
|
143
|
+
catch (error) { spawnedChild.kill('SIGTERM'); await closed; throw error }
|
|
144
|
+
result = await closed
|
|
145
|
+
// A runner that ignored the stop must not outlive the optimizer.
|
|
146
|
+
if (stopSignal) {
|
|
147
|
+
try { process.kill(-spawnedChild.pid!, 'SIGKILL') } catch { /* group already gone */ }
|
|
148
|
+
}
|
|
92
149
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
env: childEnvironment,
|
|
96
|
-
})
|
|
97
|
-
child = spawnedChild
|
|
98
|
-
const spawned = new Promise<void>((resolve, reject) => {
|
|
99
|
-
spawnedChild.once('spawn', resolve)
|
|
100
|
-
spawnedChild.once('error', reject)
|
|
101
|
-
})
|
|
102
|
-
const closed = new Promise<{ status: number | null, error?: Error }>(resolve => {
|
|
103
|
-
let error: Error | undefined
|
|
104
|
-
spawnedChild.once('error', cause => { error = cause })
|
|
105
|
-
spawnedChild.once('close', status => resolve(error ? { status, error } : { status }))
|
|
106
|
-
})
|
|
107
|
-
await spawned
|
|
108
|
-
try { await onSpawn?.() }
|
|
109
|
-
catch (error) { spawnedChild.kill('SIGTERM'); await closed; throw error }
|
|
110
|
-
return await closed
|
|
150
|
+
if (stopSignal) return await endBySignal(stopSignal)
|
|
151
|
+
return result
|
|
111
152
|
} finally {
|
|
112
|
-
|
|
153
|
+
if (giveUp) clearTimeout(giveUp)
|
|
154
|
+
removeHandlers()
|
|
113
155
|
cleanup()
|
|
114
156
|
}
|
|
115
157
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Result, Schema } from 'effect'
|
|
2
|
+
|
|
3
|
+
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js'
|
|
4
|
+
import { extractErrorMessage } from './error-response.js'
|
|
5
|
+
import { sanitizeTerminalText } from './json-response.js'
|
|
6
|
+
import { appErrorOf, unreachedCode } from './request-outcome.js'
|
|
7
|
+
|
|
8
|
+
// Loaded only through `await import(...)`, like the dataset workflows.
|
|
9
|
+
|
|
10
|
+
const FAILED = 'Failed to add team member'
|
|
11
|
+
// What the route says when it cannot tell whether the invite was saved (ORI-2383 slice A).
|
|
12
|
+
const SERVER_MAY_BE_SAVED = 'Invite may already be saved. Refresh the member list before inviting again.'
|
|
13
|
+
|
|
14
|
+
// The route saves the address trimmed and lowercased, so the check names that address.
|
|
15
|
+
const mayBeInvited = (teamSlug: string, email: string) => {
|
|
16
|
+
const saved = sanitizeTerminalText(email.trim().toLowerCase())
|
|
17
|
+
return `${saved} may already be invited: run \`orizu teams members list --team ${teamSlug}\` and look for ${saved}. If they are listed, they were invited; don't invite them again.`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const AddReply = Schema.Struct({
|
|
21
|
+
member: Schema.Struct({
|
|
22
|
+
id: Schema.NonEmptyString,
|
|
23
|
+
email: Schema.String,
|
|
24
|
+
}),
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
/** The member as the server sent it; `--json` prints every field it has. */
|
|
28
|
+
export type AddedMember = typeof AddReply.Type['member'] & Readonly<Record<string, unknown>>
|
|
29
|
+
|
|
30
|
+
/** Sends one request; calls `onRequestStart` just before anything goes out. */
|
|
31
|
+
export type TeamMemberAddRequest = (path: string, init: RequestInit, onRequestStart?: () => void) => Promise<Response>
|
|
32
|
+
|
|
33
|
+
// The invite may be saved when the server says so, when the reply was lost,
|
|
34
|
+
// late or unreadable after the request was sent, when a 5xx did not come from
|
|
35
|
+
// the app (a gateway giving up while the app kept working), or when the reply
|
|
36
|
+
// has no member. Any other error reply is a clear refusal.
|
|
37
|
+
class TeamMemberAddFailure extends Error {
|
|
38
|
+
readonly _tag = 'TeamMemberAddFailure'
|
|
39
|
+
constructor(message: string, readonly mayBeSaved: boolean) { super(message) }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// One request, never retried: a retry after a saved invite is refused as
|
|
43
|
+
// "already a member", which hides that the first one worked.
|
|
44
|
+
function memberAdd(teamSlug: string, email: string, request: TeamMemberAddRequest, timeoutMs: number): Effect.Effect<AddedMember, TeamMemberAddFailure> {
|
|
45
|
+
return Effect.suspend(() => {
|
|
46
|
+
let sent = false
|
|
47
|
+
let timedOut = false
|
|
48
|
+
return Effect.tryPromise({
|
|
49
|
+
try: async signal => {
|
|
50
|
+
const controller = new AbortController()
|
|
51
|
+
const abort = () => controller.abort()
|
|
52
|
+
const timer = setTimeout(() => { timedOut = true; controller.abort() }, timeoutMs)
|
|
53
|
+
if (signal.aborted) abort()
|
|
54
|
+
else signal.addEventListener('abort', abort, { once: true })
|
|
55
|
+
try {
|
|
56
|
+
const response = await request(`/api/cli/teams/${encodeURIComponent(teamSlug)}/members`, {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: { 'Content-Type': 'application/json' },
|
|
59
|
+
body: JSON.stringify({ email }),
|
|
60
|
+
signal: controller.signal,
|
|
61
|
+
}, () => { sent = true })
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const appError = await appErrorOf(response.clone())
|
|
64
|
+
const detail = await extractErrorMessage(response)
|
|
65
|
+
if (appError === SERVER_MAY_BE_SAVED) throw new TeamMemberAddFailure(appError, true)
|
|
66
|
+
if (response.status < 400 || (response.status >= 500 && appError === null)) {
|
|
67
|
+
throw new TeamMemberAddFailure(`Member add got no clear answer (status ${response.status}).`, true)
|
|
68
|
+
}
|
|
69
|
+
// The route's own failures already say what failed.
|
|
70
|
+
throw new TeamMemberAddFailure(detail.startsWith('Failed to ') ? detail : `${FAILED}: ${detail}`, false)
|
|
71
|
+
}
|
|
72
|
+
// Reading the body can still fail or stall; that stays under the deadline.
|
|
73
|
+
const body = await response.text()
|
|
74
|
+
let data: unknown
|
|
75
|
+
try {
|
|
76
|
+
data = JSON.parse(body)
|
|
77
|
+
} catch {
|
|
78
|
+
data = undefined
|
|
79
|
+
}
|
|
80
|
+
if (Result.isFailure(Schema.decodeUnknownResult(AddReply)(data))) {
|
|
81
|
+
throw new TeamMemberAddFailure(`Member add returned an unexpected response (status ${response.status}).`, true)
|
|
82
|
+
}
|
|
83
|
+
// Decoding keeps only the named fields; the reply's member is returned whole.
|
|
84
|
+
return (data as { member: AddedMember }).member
|
|
85
|
+
} finally {
|
|
86
|
+
clearTimeout(timer)
|
|
87
|
+
signal.removeEventListener('abort', abort)
|
|
88
|
+
controller.abort()
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
catch: cause => {
|
|
92
|
+
if (cause instanceof TeamMemberAddFailure) return cause
|
|
93
|
+
if (timedOut) return new TeamMemberAddFailure('Member add exceeded its deadline.', sent)
|
|
94
|
+
const unreached = unreachedCode(cause)
|
|
95
|
+
if (unreached) return new TeamMemberAddFailure(`${FAILED}: could not reach the server (${unreached}).`, false)
|
|
96
|
+
const message = cause instanceof Error ? cause.message : String(cause)
|
|
97
|
+
if (!sent || message.startsWith('ORIZU_AUTH_CONTEXT_CHANGED:')) return new TeamMemberAddFailure(message, false)
|
|
98
|
+
// Also reached when a sign-in refresh fails after a 401: still told to
|
|
99
|
+
// check, so say only that the invite did not finish.
|
|
100
|
+
return new TeamMemberAddFailure(`Member add did not finish: ${message}`, true)
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** `orizu teams members add`: the invited member, or an error saying whether the invite may be saved. */
|
|
107
|
+
export async function addTeamMemberWithDeadline(teamSlug: string, email: string, request: TeamMemberAddRequest): Promise<AddedMember> {
|
|
108
|
+
const exit = await Effect.runPromiseExit(memberAdd(teamSlug, email, request, datasetTransferTimeoutMs()).pipe(
|
|
109
|
+
Effect.mapError(failure => new Error(failure.mayBeSaved ? `${failure.message}\n${mayBeInvited(teamSlug, email)}` : failure.message)),
|
|
110
|
+
))
|
|
111
|
+
if (Exit.isSuccess(exit)) return exit.value
|
|
112
|
+
throw Cause.squash(exit.cause)
|
|
113
|
+
}
|
|
@@ -4,6 +4,11 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
+
import signal
|
|
8
|
+
import sys
|
|
9
|
+
import threading
|
|
10
|
+
import time
|
|
11
|
+
from contextlib import contextmanager
|
|
7
12
|
from pathlib import Path
|
|
8
13
|
from typing import Any
|
|
9
14
|
from uuid import uuid4
|
|
@@ -431,7 +436,120 @@ def _result_summary(*, run_id: str, best_id: str, best_score: float, seed_score:
|
|
|
431
436
|
}
|
|
432
437
|
|
|
433
438
|
|
|
439
|
+
_STOP_SIGNALS = (signal.SIGINT, signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT)
|
|
440
|
+
# One Ctrl-C reaches the optimizer twice: from the terminal and again from the
|
|
441
|
+
# CLI that passes it on. A repeat this soon after the first is the same press.
|
|
442
|
+
_REPEAT_WINDOW_SECONDS = 1.0
|
|
443
|
+
_STATUS_WRITE_SECONDS = 10.0
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
class RunInterrupted(BaseException):
|
|
447
|
+
"""A stop signal (Ctrl-C, SIGTERM, a closed terminal) reached the run."""
|
|
448
|
+
|
|
449
|
+
def __init__(self, signal_name: str, signum: int) -> None:
|
|
450
|
+
super().__init__(signal_name)
|
|
451
|
+
self.signal_name = signal_name
|
|
452
|
+
self.signum = signum
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _end_by_signal(signum: int) -> None:
|
|
456
|
+
"""Exit the way an unhandled signal would, so callers see 128 + its number."""
|
|
457
|
+
sys.stdout.flush()
|
|
458
|
+
sys.stderr.flush()
|
|
459
|
+
signal.signal(signum, signal.SIG_DFL)
|
|
460
|
+
os.kill(os.getpid(), signum)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
@contextmanager
|
|
464
|
+
def interrupt_signals():
|
|
465
|
+
"""Turn the first stop signal into ``RunInterrupted``; a later one ends at once."""
|
|
466
|
+
first_at: list[float] = []
|
|
467
|
+
|
|
468
|
+
def handle(signum: int, _frame: Any) -> None:
|
|
469
|
+
if not first_at:
|
|
470
|
+
first_at.append(time.monotonic())
|
|
471
|
+
if os.getpgrp() == os.getpid():
|
|
472
|
+
# Runners may run in worker threads, which this interrupt does
|
|
473
|
+
# not reach; when we lead the process group, stop them too.
|
|
474
|
+
# Our own copy of the signal falls in the repeat window.
|
|
475
|
+
try:
|
|
476
|
+
os.killpg(os.getpgrp(), signum)
|
|
477
|
+
except OSError:
|
|
478
|
+
pass
|
|
479
|
+
raise RunInterrupted(signal.Signals(signum).name, signum)
|
|
480
|
+
if time.monotonic() - first_at[0] < _REPEAT_WINDOW_SECONDS:
|
|
481
|
+
return
|
|
482
|
+
_end_by_signal(signum)
|
|
483
|
+
|
|
484
|
+
previous = {signum: signal.getsignal(signum) for signum in _STOP_SIGNALS}
|
|
485
|
+
for signum in _STOP_SIGNALS:
|
|
486
|
+
signal.signal(signum, handle)
|
|
487
|
+
try:
|
|
488
|
+
yield
|
|
489
|
+
finally:
|
|
490
|
+
for signum, handler in previous.items():
|
|
491
|
+
signal.signal(signum, handler)
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def _stop_when_parent_exits() -> None:
|
|
495
|
+
"""Stop like SIGTERM once the CLI that started us is gone (e.g. SIGKILLed).
|
|
496
|
+
|
|
497
|
+
The CLI puts the optimizer in its own process group, so a signal sent to
|
|
498
|
+
the CLI's group no longer reaches it; without this it would keep running.
|
|
499
|
+
"""
|
|
500
|
+
parent = os.getppid()
|
|
501
|
+
|
|
502
|
+
def watch() -> None:
|
|
503
|
+
while os.getppid() == parent:
|
|
504
|
+
time.sleep(0.5)
|
|
505
|
+
os.kill(os.getpid(), signal.SIGTERM)
|
|
506
|
+
|
|
507
|
+
threading.Thread(target=watch, daemon=True).start()
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def _mark_interrupted_run(client: OrizuClient, run_id: str, signal_name: str) -> None:
|
|
511
|
+
"""Mark a stopped local run cancelled once, waiting at most 10 s; never retry."""
|
|
512
|
+
outcome: list[BaseException | None] = []
|
|
513
|
+
|
|
514
|
+
def write() -> None:
|
|
515
|
+
try:
|
|
516
|
+
client.update_run(run_id, status="cancelled",
|
|
517
|
+
metadata={"stop_reason": "interrupted", "signal": signal_name})
|
|
518
|
+
outcome.append(None)
|
|
519
|
+
except BaseException as error: # reported below; the run still stops
|
|
520
|
+
outcome.append(error)
|
|
521
|
+
|
|
522
|
+
writer = threading.Thread(target=write, daemon=True)
|
|
523
|
+
writer.start()
|
|
524
|
+
writer.join(_STATUS_WRITE_SECONDS)
|
|
525
|
+
if not outcome or outcome[0] is not None:
|
|
526
|
+
print(
|
|
527
|
+
f"Run {run_id} may still show as running: run "
|
|
528
|
+
f"`orizu optimizations cancel {run_id}` to mark it stopped.",
|
|
529
|
+
file=sys.stderr, flush=True,
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
|
|
434
533
|
def run_from_environment() -> dict[str, Any]:
|
|
534
|
+
"""Run official GEPA; a stopped local run is marked cancelled (ORI-2402).
|
|
535
|
+
|
|
536
|
+
A hosted run's final status belongs to its coordinator, which records it
|
|
537
|
+
before stopping the sandbox, so a hosted run writes no status here.
|
|
538
|
+
"""
|
|
539
|
+
started: dict[str, Any] = {}
|
|
540
|
+
with interrupt_signals():
|
|
541
|
+
if os.environ.get("ORIZU_STOP_WITH_PARENT") == "1":
|
|
542
|
+
_stop_when_parent_exits()
|
|
543
|
+
try:
|
|
544
|
+
return _run_from_environment(started)
|
|
545
|
+
except RunInterrupted as interrupt:
|
|
546
|
+
if started.get("run_id") and not started.get("hosted"):
|
|
547
|
+
_mark_interrupted_run(started["client"], started["run_id"], interrupt.signal_name)
|
|
548
|
+
_end_by_signal(interrupt.signum)
|
|
549
|
+
raise
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def _run_from_environment(started: dict[str, Any]) -> dict[str, Any]:
|
|
435
553
|
"""Run official GEPA over the verified legacy runner subprocess contract.
|
|
436
554
|
|
|
437
555
|
ALI-1502 supplies these variables after verifying runner bytes. Scorer
|
|
@@ -542,6 +660,7 @@ def run_from_environment() -> dict[str, Any]:
|
|
|
542
660
|
model_config_settings_version_id=instruction_set.get("model_config_settings_version_id") if isinstance(instruction_set, dict) else None,
|
|
543
661
|
runner_version_id=prompt_context.runner_version_id,
|
|
544
662
|
)
|
|
663
|
+
started.update(client=client, run_id=run_id, hosted=hosted_run_id is not None)
|
|
545
664
|
print(json.dumps({"optimization_run_id": run_id}), flush=True)
|
|
546
665
|
# The frozen sink treats ``None`` as no local artifact writer. Keep that
|
|
547
666
|
# legacy behavior rather than maintaining a connector-local no-op logger.
|
|
@@ -728,6 +847,9 @@ def run_from_environment() -> dict[str, Any]:
|
|
|
728
847
|
total_tokens_out=int(getattr(reflection_lm, "total_tokens_out", 0)),
|
|
729
848
|
total_tokens=int(getattr(reflection_lm, "total_tokens", 0)),
|
|
730
849
|
)
|
|
850
|
+
# From here the run has its own final status; a stop must not
|
|
851
|
+
# overwrite it with "cancelled".
|
|
852
|
+
started.pop("run_id", None)
|
|
731
853
|
if budget_exhausted:
|
|
732
854
|
if hosted_run_id is None:
|
|
733
855
|
client.update_run(
|
|
@@ -764,6 +886,7 @@ def run_from_environment() -> dict[str, Any]:
|
|
|
764
886
|
temporary_result_path.replace(result_path)
|
|
765
887
|
return summary
|
|
766
888
|
except Exception as error:
|
|
889
|
+
started.pop("run_id", None)
|
|
767
890
|
if sink.failed:
|
|
768
891
|
sink.retry_failed_status()
|
|
769
892
|
elif hosted_run_id is None:
|