orizu 0.5.51 → 0.5.53

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.
Files changed (109) hide show
  1. package/dist/app-commands.d.ts.map +1 -1
  2. package/dist/app-commands.js +4 -12
  3. package/dist/auth-commands.d.ts.map +1 -1
  4. package/dist/auth-commands.js +5 -108
  5. package/dist/cli-console-output.d.ts +7 -1
  6. package/dist/cli-console-output.d.ts.map +1 -1
  7. package/dist/cli-console-output.js +16 -3
  8. package/dist/cli-stops.d.ts +14 -0
  9. package/dist/cli-stops.d.ts.map +1 -0
  10. package/dist/cli-stops.js +22 -0
  11. package/dist/cli-write-commands.d.ts +143 -0
  12. package/dist/cli-write-commands.d.ts.map +1 -0
  13. package/dist/cli-write-commands.js +123 -0
  14. package/dist/egress-allowlist-cli.d.ts +29 -2
  15. package/dist/egress-allowlist-cli.d.ts.map +1 -1
  16. package/dist/egress-allowlist-cli.js +16 -89
  17. package/dist/egress-allowlist-workflow.d.ts +17 -0
  18. package/dist/egress-allowlist-workflow.d.ts.map +1 -0
  19. package/dist/egress-allowlist-workflow.js +183 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +44 -50
  22. package/dist/instruction-delivery.d.ts +4 -4
  23. package/dist/instruction-delivery.d.ts.map +1 -1
  24. package/dist/instruction-delivery.js +32 -46
  25. package/dist/instruction-prune.d.ts +24 -0
  26. package/dist/instruction-prune.d.ts.map +1 -0
  27. package/dist/instruction-prune.js +33 -0
  28. package/dist/instruction-set-sync/index.d.ts +0 -2
  29. package/dist/instruction-set-sync/index.d.ts.map +1 -1
  30. package/dist/instruction-set-sync/index.js +3 -36
  31. package/dist/instruction-set-update/index.d.ts +41 -3
  32. package/dist/instruction-set-update/index.d.ts.map +1 -1
  33. package/dist/instruction-set-update/index.js +156 -132
  34. package/dist/instruction-sets-cli.d.ts.map +1 -1
  35. package/dist/instruction-sets-cli.js +21 -2
  36. package/dist/instruction-sync-lock.d.ts +9 -0
  37. package/dist/instruction-sync-lock.d.ts.map +1 -0
  38. package/dist/instruction-sync-lock.js +24 -0
  39. package/dist/json-output-scrub.d.ts +3 -0
  40. package/dist/json-output-scrub.d.ts.map +1 -0
  41. package/dist/json-output-scrub.js +70 -0
  42. package/dist/last-error-record.d.ts +7 -0
  43. package/dist/last-error-record.d.ts.map +1 -1
  44. package/dist/last-error-record.js +14 -2
  45. package/dist/login-flow.d.ts +37 -0
  46. package/dist/login-flow.d.ts.map +1 -0
  47. package/dist/login-flow.js +295 -0
  48. package/dist/logout-diagnostic.d.ts.map +1 -1
  49. package/dist/logout-diagnostic.js +2 -1
  50. package/dist/manifests-cli.d.ts.map +1 -1
  51. package/dist/manifests-cli.js +36 -22
  52. package/dist/one-request-write.d.ts +59 -0
  53. package/dist/one-request-write.d.ts.map +1 -0
  54. package/dist/one-request-write.js +150 -0
  55. package/dist/provider-write-workflow.d.ts +12 -0
  56. package/dist/provider-write-workflow.d.ts.map +1 -0
  57. package/dist/provider-write-workflow.js +110 -0
  58. package/dist/providers-cli.d.ts.map +1 -1
  59. package/dist/providers-cli.js +27 -23
  60. package/dist/scores-accept-cli.d.ts +1 -1
  61. package/dist/scores-accept-cli.d.ts.map +1 -1
  62. package/dist/scores-accept-cli.js +4 -28
  63. package/dist/task-commands.d.ts.map +1 -1
  64. package/dist/task-commands.js +26 -62
  65. package/dist/workbench-cli.d.ts +2 -1
  66. package/dist/workbench-cli.d.ts.map +1 -1
  67. package/dist/workbench-cli.js +79 -593
  68. package/dist/workbench-workflows.d.ts +56 -0
  69. package/dist/workbench-workflows.d.ts.map +1 -0
  70. package/dist/workbench-workflows.js +665 -0
  71. package/dist/workspace-sync-workflow.d.ts +115 -0
  72. package/dist/workspace-sync-workflow.d.ts.map +1 -0
  73. package/dist/workspace-sync-workflow.js +642 -0
  74. package/dist/workspace-sync.d.ts +45 -97
  75. package/dist/workspace-sync.d.ts.map +1 -1
  76. package/dist/workspace-sync.js +53 -494
  77. package/package.json +1 -1
  78. package/src/app-commands.ts +3 -18
  79. package/src/auth-commands.ts +8 -127
  80. package/src/cli-console-output.ts +16 -2
  81. package/src/cli-stops.ts +27 -0
  82. package/src/cli-write-commands.ts +143 -0
  83. package/src/egress-allowlist-cli.ts +18 -90
  84. package/src/egress-allowlist-workflow.ts +198 -0
  85. package/src/index.ts +23 -57
  86. package/src/instruction-delivery.ts +31 -47
  87. package/src/instruction-prune.ts +59 -0
  88. package/src/instruction-set-sync/index.ts +3 -43
  89. package/src/instruction-set-update/index.ts +173 -122
  90. package/src/instruction-sets-cli.ts +23 -2
  91. package/src/instruction-sync-lock.ts +27 -0
  92. package/src/json-output-scrub.ts +73 -0
  93. package/src/last-error-record.ts +16 -2
  94. package/src/login-flow.ts +368 -0
  95. package/src/logout-diagnostic.ts +2 -1
  96. package/src/manifests-cli.ts +36 -23
  97. package/src/one-request-write.ts +184 -0
  98. package/src/provider-write-workflow.ts +117 -0
  99. package/src/providers-cli.ts +24 -17
  100. package/src/scores-accept-cli.ts +4 -46
  101. package/src/task-commands.ts +22 -115
  102. package/src/workbench-cli.ts +83 -684
  103. package/src/workbench-workflows.ts +815 -0
  104. package/src/workspace-sync-workflow.ts +838 -0
  105. package/src/workspace-sync.ts +63 -630
  106. package/dist/headless-login.d.ts +0 -19
  107. package/dist/headless-login.d.ts.map +0 -1
  108. package/dist/headless-login.js +0 -108
  109. package/src/headless-login.ts +0 -192
@@ -9,19 +9,16 @@
9
9
  * The BASE allowlist (Orizu API + model provider + git host) is code-owned and
10
10
  * NOT shown/managed here — this surface only widens a team's egress with domains
11
11
  * a customer workflow needs. Human ADMIN only: the server rejects agent bearers
12
- * and non-admins (the definer RPC gates on is_team_admin). Pure command logic
13
- * with an injected fetcher.
12
+ * and non-admins (the definer RPC gates on is_team_admin). The arguments and
13
+ * output live here; the requests run on Effect in egress-allowlist-workflow.ts
14
+ * (ORI-2764), loaded only when this command runs.
14
15
  */
15
- import { authedFetch } from './http.js';
16
16
  /** Mirrors MAX_TEAM_EGRESS_DOMAINS in the shared read-side validator
17
17
  * (workers/session-coordinator/src/egress-domains.ts). The coordinator only
18
18
  * applies the first this-many UNIQUE valid domains at policy compose; the
19
19
  * CLI is a standalone package and cannot import the worker module, so the
20
20
  * cap constant is duplicated here (kept in sync via that file's tests). */
21
21
  const MAX_ADDITIVE_EGRESS_DOMAINS = 100;
22
- function fetcherFrom(io) {
23
- return io.fetcher ?? authedFetch;
24
- }
25
22
  function argValue(args, flag) {
26
23
  const index = args.indexOf(flag);
27
24
  if (index === -1 || index + 1 >= args.length || args[index + 1].startsWith('--')) {
@@ -45,23 +42,13 @@ function printErr(io, message) {
45
42
  ;
46
43
  (io.printErr ?? io.print)(message);
47
44
  }
48
- async function responseMessage(response) {
49
- try {
50
- const data = (await response.clone().json());
51
- const error = typeof data.error === 'string' ? data.error : null;
52
- return error || response.statusText || String(response.status);
53
- }
54
- catch {
55
- return response.statusText || String(response.status);
56
- }
57
- }
58
- function splitDomains(value) {
45
+ export function splitDomains(value) {
59
46
  return value
60
47
  .split(',')
61
48
  .map(d => d.trim())
62
49
  .filter(d => d.length > 0);
63
50
  }
64
- function stringArray(value) {
51
+ export function stringArray(value) {
65
52
  return Array.isArray(value) ? value.filter((d) => typeof d === 'string') : [];
66
53
  }
67
54
  /**
@@ -75,7 +62,7 @@ function stringArray(value) {
75
62
  * shrink-by-one on an oversized legacy row leaves the returned set still
76
63
  * over-cap and the server does not send cappedDomains back.
77
64
  */
78
- function deriveCapped(stored, invalid) {
65
+ export function deriveCapped(stored, invalid) {
79
66
  const invalidSet = new Set(invalid);
80
67
  const capped = [];
81
68
  let validCount = 0;
@@ -88,19 +75,7 @@ function deriveCapped(stored, invalid) {
88
75
  }
89
76
  return capped;
90
77
  }
91
- async function fetchCurrent(fetcher, path) {
92
- const response = await fetcher(path, { method: 'GET' });
93
- if (!response.ok) {
94
- throw new Error(`show failed (${response.status}): ${await responseMessage(response)}`);
95
- }
96
- const data = (await response.json());
97
- return {
98
- domains: stringArray(data.extraDomains),
99
- invalid: stringArray(data.invalidDomains),
100
- capped: stringArray(data.cappedDomains),
101
- };
102
- }
103
- function emit(io, teamSlug, current) {
78
+ export function emit(io, teamSlug, current) {
104
79
  if (io.json) {
105
80
  io.print(JSON.stringify({
106
81
  teamSlug,
@@ -134,61 +109,13 @@ export async function egressAllowlistCommand(args, io) {
134
109
  printErr(io, 'Usage: orizu team egress-allowlist <team> [--add <domain>|--remove <domain>|--set a.com,b.com|--clear] [--json]');
135
110
  return 1;
136
111
  }
137
- const path = `/api/cli/teams/${encodeURIComponent(teamSlug)}/egress-allowlist`;
138
- const fetcher = fetcherFrom(io);
139
- const setValue = argValue(args, '--set');
140
- const addValue = argValue(args, '--add');
141
- const removeValue = argValue(args, '--remove');
142
- const clear = args.includes('--clear');
143
- const isMutation = setValue !== null || addValue !== null || removeValue !== null || clear;
144
- try {
145
- if (!isMutation) {
146
- emit(io, teamSlug, await fetchCurrent(fetcher, path));
147
- return 0;
148
- }
149
- // Compute the next set. --set/--clear replace; --add/--remove mutate current.
150
- let next;
151
- if (setValue !== null) {
152
- next = splitDomains(setValue);
153
- }
154
- else if (clear) {
155
- next = [];
156
- }
157
- else {
158
- const current = await fetchCurrent(fetcher, path);
159
- const set = new Set(current.domains);
160
- if (addValue)
161
- for (const d of splitDomains(addValue))
162
- set.add(d);
163
- if (removeValue)
164
- for (const d of splitDomains(removeValue))
165
- set.delete(d);
166
- next = [...set];
167
- }
168
- const response = await fetcher(path, {
169
- method: 'PUT',
170
- headers: { 'Content-Type': 'application/json' },
171
- body: JSON.stringify({ domains: next }),
172
- });
173
- if (!response.ok) {
174
- printErr(io, `egress-allowlist update failed (${response.status}): ${await responseMessage(response)}`);
175
- return 1;
176
- }
177
- const data = (await response.json());
178
- const stored = Array.isArray(data.extraDomains) ? stringArray(data.extraDomains) : next;
179
- const invalid = stringArray(data.invalidDomains);
180
- // Grandfathered retention: a successful PUT may still STORE legacy
181
- // invalid entries the admin retained — the server flags them and we show
182
- // it. Round 5 (P2): a >100-entry legacy row can only shrink one entry per
183
- // PUT (the write RPC's GREATEST(100, stored-1) bound), so the returned
184
- // set can still be OVER the cap. Re-derive the cap annotations from the
185
- // returned set — same positional logic GET uses — instead of assuming
186
- // the RPC left it at/under 100.
187
- emit(io, teamSlug, { domains: stored, invalid, capped: deriveCapped(stored, invalid) });
188
- return 0;
189
- }
190
- catch (error) {
191
- printErr(io, error instanceof Error ? error.message : String(error));
192
- return 1;
193
- }
112
+ const { runEgressAllowlist } = await import('./egress-allowlist-workflow.js');
113
+ return await runEgressAllowlist({
114
+ teamSlug,
115
+ path: `/api/cli/teams/${encodeURIComponent(teamSlug)}/egress-allowlist`,
116
+ setValue: argValue(args, '--set'),
117
+ addValue: argValue(args, '--add'),
118
+ removeValue: argValue(args, '--remove'),
119
+ clear: args.includes('--clear'),
120
+ }, io);
194
121
  }
@@ -0,0 +1,17 @@
1
+ import { type EgressAllowlistCommandIo } from './egress-allowlist-cli.js';
2
+ /** What one run does: show the list, or change it. */
3
+ export interface EgressAllowlistRun {
4
+ teamSlug: string;
5
+ path: string;
6
+ setValue: string | null;
7
+ addValue: string | null;
8
+ removeValue: string | null;
9
+ clear: boolean;
10
+ }
11
+ /**
12
+ * Runs one `orizu team egress-allowlist` command to its exit code. Ctrl-C or
13
+ * SIGTERM ends the wait with 130 or 143; once the PUT was sent the person is
14
+ * told the update may be saved.
15
+ */
16
+ export declare function runEgressAllowlist(run: EgressAllowlistRun, io: EgressAllowlistCommandIo): Promise<number>;
17
+ //# sourceMappingURL=egress-allowlist-workflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"egress-allowlist-workflow.d.ts","sourceRoot":"","sources":["../src/egress-allowlist-workflow.ts"],"names":[],"mappings":"AAaA,OAAO,EAML,KAAK,wBAAwB,EAE9B,MAAM,2BAA2B,CAAA;AAoFlC,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,KAAK,EAAE,OAAO,CAAA;CACf;AAkDD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,kBAAkB,EAAE,EAAE,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CA6B/G"}
@@ -0,0 +1,183 @@
1
+ // ORI-2764 (ORI-2735c): `orizu team egress-allowlist` on Effect. Loaded only
2
+ // through `await import(...)` from egress-allowlist-cli.ts, so other commands
3
+ // never load Effect.
4
+ //
5
+ // - Each request has the dataset-transfer deadline (120 s by default).
6
+ // - The PUT is the only write. Once it was sent, a lost or late reply may
7
+ // still be saved. Every change is safe to repeat (`--set`/`--clear` replace
8
+ // the list, `--add`/`--remove` read it again first), so the CLI says
9
+ // running the same command again is safe.
10
+ // - Ctrl-C and SIGTERM stop a wait with 130 and 143.
11
+ import { Cause, Effect, Exit } from 'effect';
12
+ import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js';
13
+ import { deriveCapped, emit, splitDomains, stringArray, } from './egress-allowlist-cli.js';
14
+ import { authedFetch } from './http.js';
15
+ import { mayAlreadyBeSavedLine } from './one-request-write.js';
16
+ import { unreachedCode } from './request-outcome.js';
17
+ const MAY_BE = mayAlreadyBeSavedLine('egress allowlist', 'updated', 'running the same command again is safe');
18
+ /** A failure the command prints as one message, plus the may-be line when the update may be saved. */
19
+ class EgressFailure {
20
+ message;
21
+ mayBeSaved;
22
+ constructor(message, mayBeSaved = false) {
23
+ this.message = message;
24
+ this.mayBeSaved = mayBeSaved;
25
+ }
26
+ }
27
+ /** The server's `{ error }`, else the status text, else the status, as before. */
28
+ function replyMessage(reply) {
29
+ try {
30
+ const data = JSON.parse(reply.text);
31
+ const error = typeof data.error === 'string' ? data.error : null;
32
+ return error || reply.statusText || String(reply.status);
33
+ }
34
+ catch {
35
+ return reply.statusText || String(reply.status);
36
+ }
37
+ }
38
+ /**
39
+ * One request. A deadline says so under `label`; any other failure keeps
40
+ * today's text. For the PUT, a failure after sending may be saved.
41
+ */
42
+ function request(fetcher, path, init, label, timeoutMs, progress) {
43
+ return Effect.suspend(() => {
44
+ let sent = false;
45
+ let timedOut = false;
46
+ return Effect.tryPromise({
47
+ try: async (signal) => {
48
+ const controller = new AbortController();
49
+ const abort = () => controller.abort();
50
+ const timer = setTimeout(() => { timedOut = true; controller.abort(); }, timeoutMs);
51
+ if (signal.aborted)
52
+ abort();
53
+ else
54
+ signal.addEventListener('abort', abort, { once: true });
55
+ const markSent = () => { sent = true; if (progress)
56
+ progress.putSent = true; };
57
+ try {
58
+ const requestInit = { ...init, signal: controller.signal };
59
+ const response = fetcher
60
+ ? (markSent(), await fetcher(path, requestInit))
61
+ : await authedFetch(path, requestInit, markSent);
62
+ return { status: response.status, ok: response.ok, statusText: response.statusText, text: await response.text() };
63
+ }
64
+ finally {
65
+ clearTimeout(timer);
66
+ signal.removeEventListener('abort', abort);
67
+ controller.abort();
68
+ }
69
+ },
70
+ catch: cause => {
71
+ const mayBeSaved = progress !== null && sent;
72
+ if (timedOut)
73
+ return new EgressFailure(`${label}: the request exceeded its deadline`, mayBeSaved);
74
+ // Refused before it reached the server, or a 401 whose sign-in could not be renewed: nothing saved.
75
+ const refused = unreachedCode(cause) !== null || cause?.requestRejected === true;
76
+ return new EgressFailure(cause instanceof Error ? cause.message : String(cause), mayBeSaved && !refused);
77
+ },
78
+ });
79
+ });
80
+ }
81
+ /** A 2xx reply's JSON object; a non-JSON reply fails with the parse error, as before. */
82
+ function parseReply(reply) {
83
+ return Effect.try({
84
+ try: () => JSON.parse(reply.text),
85
+ catch: cause => new EgressFailure(cause instanceof Error ? cause.message : String(cause)),
86
+ });
87
+ }
88
+ function readCurrent(fetcher, path, timeoutMs) {
89
+ return Effect.gen(function* () {
90
+ const reply = yield* request(fetcher, path, { method: 'GET' }, 'show failed', timeoutMs, null);
91
+ if (!reply.ok)
92
+ return yield* Effect.fail(new EgressFailure(`show failed (${reply.status}): ${replyMessage(reply)}`));
93
+ const data = yield* parseReply(reply);
94
+ return { domains: stringArray(data.extraDomains), invalid: stringArray(data.invalidDomains), capped: stringArray(data.cappedDomains) };
95
+ });
96
+ }
97
+ const isMutation = (run) => run.setValue !== null || run.addValue !== null || run.removeValue !== null || run.clear;
98
+ function egressAllowlist(run, io, timeoutMs, progress) {
99
+ return Effect.gen(function* () {
100
+ if (!isMutation(run)) {
101
+ emit(io, run.teamSlug, yield* readCurrent(io.fetcher, run.path, timeoutMs));
102
+ return 0;
103
+ }
104
+ // --set/--clear replace; --add/--remove change the list read first.
105
+ let next;
106
+ if (run.setValue !== null) {
107
+ next = splitDomains(run.setValue);
108
+ }
109
+ else if (run.clear) {
110
+ next = [];
111
+ }
112
+ else {
113
+ const current = yield* readCurrent(io.fetcher, run.path, timeoutMs);
114
+ const set = new Set(current.domains);
115
+ if (run.addValue)
116
+ for (const d of splitDomains(run.addValue))
117
+ set.add(d);
118
+ if (run.removeValue)
119
+ for (const d of splitDomains(run.removeValue))
120
+ set.delete(d);
121
+ next = [...set];
122
+ }
123
+ const label = 'egress-allowlist update failed';
124
+ const reply = yield* request(io.fetcher, run.path, {
125
+ method: 'PUT',
126
+ headers: { 'Content-Type': 'application/json' },
127
+ body: JSON.stringify({ domains: next }),
128
+ }, label, timeoutMs, progress);
129
+ if (!reply.ok) {
130
+ // A refusal (4xx) saved nothing; a 5xx after the PUT may have.
131
+ return yield* Effect.fail(new EgressFailure(`${label} (${reply.status}): ${replyMessage(reply)}`, reply.status >= 500));
132
+ }
133
+ const data = yield* parseReply(reply);
134
+ const stored = Array.isArray(data.extraDomains) ? stringArray(data.extraDomains) : next;
135
+ const invalid = stringArray(data.invalidDomains);
136
+ // Grandfathered retention: a successful PUT may still STORE legacy
137
+ // invalid entries the admin retained — the server flags them and we show
138
+ // it. Round 5 (P2): a >100-entry legacy row can only shrink one entry per
139
+ // PUT (the write RPC's GREATEST(100, stored-1) bound), so the returned
140
+ // set can still be OVER the cap. Re-derive the cap annotations from the
141
+ // returned set — same positional logic GET uses — instead of assuming
142
+ // the RPC left it at/under 100.
143
+ emit(io, run.teamSlug, { domains: stored, invalid, capped: deriveCapped(stored, invalid) });
144
+ return 0;
145
+ });
146
+ }
147
+ /**
148
+ * Runs one `orizu team egress-allowlist` command to its exit code. Ctrl-C or
149
+ * SIGTERM ends the wait with 130 or 143; once the PUT was sent the person is
150
+ * told the update may be saved.
151
+ */
152
+ export async function runEgressAllowlist(run, io) {
153
+ const printErr = io.printErr ?? io.print;
154
+ const controller = new AbortController();
155
+ const progress = { putSent: false };
156
+ let interrupted;
157
+ const interrupt = () => { interrupted ??= 130; controller.abort(); };
158
+ const terminate = () => { interrupted ??= 143; controller.abort(); };
159
+ process.on('SIGINT', interrupt);
160
+ process.on('SIGTERM', terminate);
161
+ try {
162
+ const exit = await Effect.runPromiseExit(egressAllowlist(run, io, datasetTransferTimeoutMs(), progress), { signal: controller.signal });
163
+ if (Exit.isSuccess(exit))
164
+ return exit.value;
165
+ if (interrupted !== undefined) {
166
+ const message = isMutation(run) ? 'Egress allowlist update interrupted.' : 'Egress allowlist read interrupted.';
167
+ printErr(progress.putSent ? `${message}\n${MAY_BE}` : message);
168
+ return interrupted;
169
+ }
170
+ // Anything else that went wrong prints its message with exit 1, as before.
171
+ const failure = Cause.squash(exit.cause);
172
+ if (!(failure instanceof EgressFailure)) {
173
+ printErr(failure instanceof Error ? failure.message : String(failure));
174
+ return 1;
175
+ }
176
+ printErr(failure.mayBeSaved ? `${failure.message}\n${MAY_BE}` : failure.message);
177
+ return 1;
178
+ }
179
+ finally {
180
+ process.removeListener('SIGINT', interrupt);
181
+ process.removeListener('SIGTERM', terminate);
182
+ }
183
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAsLA,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAEhE,eAAO,MAAM,wBAAwB,GAAI,iBAAiB,MAAM;;;EACJ,CAAA;AAE5D,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AA+sCtD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CA0B1G;AAED,eAAO,MAAM,iBAAiB,SAAU,CAAA;AACxC,eAAO,MAAM,uBAAuB,QAAkB,CAAA;AACtD,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,UAAU,CAexH;AAmMD,wBAAsB,IAAI,CAAC,OAAO,WAAwB,iBAiiBzD;AA8DD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAEhF,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAuLA,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAEhE,eAAO,MAAM,wBAAwB,GAAI,iBAAiB,MAAM;;;EACJ,CAAA;AAE5D,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAmqCtD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CA0B1G;AAED,eAAO,MAAM,iBAAiB,SAAU,CAAA;AACxC,eAAO,MAAM,uBAAuB,QAAkB,CAAA;AACtD,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,UAAU,CAexH;AAmMD,wBAAsB,IAAI,CAAC,OAAO,WAAwB,iBAiiBzD;AAuED,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAEhF,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA"}
package/dist/index.js CHANGED
@@ -62,7 +62,8 @@ import { killSwitchCommand } from './kill-switch-cli.js';
62
62
  import { egressAllowlistCommand } from './egress-allowlist-cli.js';
63
63
  import { teamConnectorsCommand } from './team-connectors-cli.js';
64
64
  import { inspectExistingWorkspaceTeam, getWorkspaceRoot } from './workspace.js';
65
- import { writeLastErrorRecord, extractLastErrorCode } from './last-error-record.js';
65
+ import { errorOutputSecrets, writeLastErrorRecord, extractLastErrorCode } from './last-error-record.js';
66
+ import { redactSecrets } from './secret-redaction.js';
66
67
  export { loadInstructionSet } from './instruction-set-loader.js';
67
68
  export const materializeRunnerVersion = (runnerVersionId) => materializeRegisteredRunnerVersion(runnerVersionId, false);
68
69
  export { parseJsonResponse, sanitizeTerminalText } from './json-response.js';
@@ -123,26 +124,21 @@ async function logOptimizationEvent() {
123
124
  if (!eventType || eventType.startsWith('--') || !runId || !Number.isInteger(sequence) || sequence <= 0) {
124
125
  throw new Error('Usage: orizu log <event_type> --run-id <id> --sequence <n> --payload @event.json');
125
126
  }
126
- const response = await authedFetch(`/api/cli/optimization-runs/${encodeURIComponent(runId)}/events`, {
127
- method: 'POST',
128
- headers: { 'Content-Type': 'application/json' },
129
- body: JSON.stringify({
130
- eventId: getArg('--event-id') || randomUUID(),
131
- sequence,
132
- eventType,
133
- eventLayer: getArg('--event-layer') || 'core',
134
- optimizerFamily: getArg('--optimizer-family'),
135
- iteration: getArg('--iteration') ? Number(getArg('--iteration')) : undefined,
136
- candidateId: getArg('--candidate-id'),
137
- parentCandidateId: getArg('--parent-candidate-id'),
138
- childCandidateId: getArg('--child-candidate-id'),
139
- payload,
140
- }),
141
- });
142
- if (!response.ok) {
143
- throw new Error(`Failed to log event: ${await response.text()}`);
144
- }
145
- const data = await parseJsonResponse(response, 'Optimization event log');
127
+ const writes = await import('./cli-write-commands.js');
128
+ const data = await writes.sendCommandWrite(writes.optimizationEvent(runId, {
129
+ eventId: getArg('--event-id') || randomUUID(),
130
+ sequence,
131
+ eventType,
132
+ eventLayer: getArg('--event-layer') || 'core',
133
+ optimizerFamily: getArg('--optimizer-family'),
134
+ iteration: getArg('--iteration') ? Number(getArg('--iteration')) : undefined,
135
+ candidateId: getArg('--candidate-id'),
136
+ parentCandidateId: getArg('--parent-candidate-id'),
137
+ childCandidateId: getArg('--child-candidate-id'),
138
+ payload,
139
+ }), { json: hasJsonFlag(), print: printLine });
140
+ if (!data)
141
+ return;
146
142
  if (hasJsonFlag()) {
147
143
  printJson({ eventId: data.eventId });
148
144
  return;
@@ -822,25 +818,20 @@ async function startOptimizationRun() {
822
818
  trackedScope: 'best_candidate',
823
819
  })),
824
820
  ];
825
- const response = await authedFetch(`/api/cli/optimization-runs?project=${encodeURIComponent(project)}`, {
826
- method: 'POST',
827
- headers: { 'Content-Type': 'application/json' },
828
- body: JSON.stringify({
829
- optimizerVersionId,
830
- promptVersionIds,
831
- judgeVersionIds,
832
- scorers,
833
- datasetVersionId,
834
- splitSetId,
835
- trainSplitName: getArg('--train-split') || undefined,
836
- validationSplitName: getArg('--validation-split') || undefined,
837
- metadata,
838
- }),
839
- });
840
- if (!response.ok) {
841
- throw new Error(`Failed to start optimization run: ${await response.text()}`);
842
- }
843
- const data = await parseJsonResponse(response, 'Optimization run start');
821
+ const writes = await import('./cli-write-commands.js');
822
+ const data = await writes.sendCommandWrite(writes.optimizationStart(project, {
823
+ optimizerVersionId,
824
+ promptVersionIds,
825
+ judgeVersionIds,
826
+ scorers,
827
+ datasetVersionId,
828
+ splitSetId,
829
+ trainSplitName: getArg('--train-split') || undefined,
830
+ validationSplitName: getArg('--validation-split') || undefined,
831
+ metadata,
832
+ }), { json: hasJsonFlag(), print: printLine });
833
+ if (!data)
834
+ return;
844
835
  if (hasJsonFlag()) {
845
836
  printJson(data);
846
837
  return;
@@ -897,15 +888,10 @@ async function updateOptimizationRunLifecycle(action) {
897
888
  body.resultPromptVersionId = resultPromptVersionId;
898
889
  }
899
890
  }
900
- const response = await authedFetch(`/api/cli/optimization-runs/${encodeURIComponent(runId)}`, {
901
- method: 'PATCH',
902
- headers: { 'Content-Type': 'application/json' },
903
- body: JSON.stringify(body),
904
- });
905
- if (!response.ok) {
906
- throw new Error(`Failed to update optimization run: ${await response.text()}`);
907
- }
908
- const data = await parseJsonResponse(response, 'Optimization run update');
891
+ const writes = await import('./cli-write-commands.js');
892
+ const data = await writes.sendCommandWrite(writes.optimizationLifecycle(runId, body), { json: hasJsonFlag(), print: printLine });
893
+ if (!data)
894
+ return;
909
895
  if (hasJsonFlag()) {
910
896
  printJson(data);
911
897
  return;
@@ -1805,9 +1791,17 @@ function isCliEntrypoint() {
1805
1791
  return false;
1806
1792
  }
1807
1793
  }
1794
+ // A secret this process holds (a login code, its verifier, a poll token, a
1795
+ // bearer, a forwarded runner key) is never printed, even when a server echoes
1796
+ // it back in an error. last-error.json hides the same values. Scrubbed again
1797
+ // after control characters are stripped, so one inside a secret can't hide it.
1798
+ function printErrorWithoutSecrets(message) {
1799
+ const secrets = errorOutputSecrets();
1800
+ printError(redactSecrets(sanitizeTerminalText(redactSecrets(message, { secrets })), { secrets }));
1801
+ }
1808
1802
  if (isCliEntrypoint()) {
1809
1803
  const rawArgs = process.argv.slice(2);
1810
- withPassiveUpdateNotice(rawArgs, getCliVersion, printError, args =>
1804
+ withPassiveUpdateNotice(rawArgs, getCliVersion, printErrorWithoutSecrets, args =>
1811
1805
  // ORI-2429: the process-boundary adapter stays a Promise (ADR-038 guide); the record write is Effect-owned and never replaces this error.
1812
1806
  main(args).catch(async (error) => {
1813
1807
  const message = error instanceof Error ? error.message : 'Unknown error';
@@ -1,8 +1,8 @@
1
- import { type SyncDiskOptions, type SyncDiskResult, type SyncPayload, type SyncRequestPlan } from './instruction-set-sync/index.js';
1
+ import { type Interrupted } from './cli-stops.js';
2
+ import { stageSyncPayload, type SyncDiskOptions, type SyncDiskResult, type SyncPayload, type SyncRequestPlan } from './instruction-set-sync/index.js';
2
3
  import { type UpdatePlan, type UpdateResult } from './instruction-set-update/index.js';
3
- export interface Interrupted {
4
- interrupted: 130 | 143;
5
- }
4
+ /** Stages and publishes one version under the folder lock (tests and the fixture script). */
5
+ export declare function syncPayloadToDisk(...args: Parameters<typeof stageSyncPayload>): Promise<SyncDiskResult>;
6
6
  export declare function syncInstructionSet(out: string, project: string, plan: SyncRequestPlan, options: SyncDiskOptions): Promise<Interrupted | {
7
7
  payload: SyncPayload;
8
8
  result: SyncDiskResult;
@@ -1 +1 @@
1
- {"version":3,"file":"instruction-delivery.d.ts","sourceRoot":"","sources":["../src/instruction-delivery.ts"],"names":[],"mappings":"AAIA,OAAO,EAA0D,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,iCAAiC,CAAA;AAC3L,OAAO,EAAsE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,mCAAmC,CAAA;AAK1J,MAAM,WAAW,WAAW;IAAG,WAAW,EAAE,GAAG,GAAG,GAAG,CAAA;CAAE;AAkEvD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe;;;GAM/G;AAED,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,OAAO,EACnB,MAAM,EAAE,OAAO,GACd,OAAO,CAAC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GAAG,WAAW,CAAC,CAiCnE"}
1
+ {"version":3,"file":"instruction-delivery.d.ts","sourceRoot":"","sources":["../src/instruction-delivery.ts"],"names":[],"mappings":"AAIA,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAG/D,OAAO,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,iCAAiC,CAAA;AACrJ,OAAO,EAA2F,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,mCAAmC,CAAA;AA4B/K,6FAA6F;AAC7F,wBAAgB,iBAAiB,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAEvG;AAeD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe;;;GAM/G;AAED,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,OAAO,EACnB,MAAM,EAAE,OAAO,GACd,OAAO,CAAC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GAAG,WAAW,CAAC,CAuCnE"}
@@ -1,11 +1,13 @@
1
+ import { join } from 'node:path';
1
2
  import { Cause, Effect, Exit } from 'effect';
3
+ import { runWithStops } from './cli-stops.js';
2
4
  import { authedFetch } from './http.js';
3
5
  import { responsePayload } from './json-response.js';
4
- import { stageSyncPayload, startSyncLockClaim, tryClaimSyncLock } from './instruction-set-sync/index.js';
5
- import { applyUpdate, lockedProfileIdentity, makeUpdatePlan, readUpdateLock } from './instruction-set-update/index.js';
6
- // Existing file steps throw plain errors; keep them as the failure value so the
7
- // CLI prints exactly the same message.
8
- const attempt = (operation) => Effect.try({ try: operation, catch: cause => cause });
6
+ import { stageSyncPayload } from './instruction-set-sync/index.js';
7
+ import { applyUpdate, lockedProfileIdentity, makeUpdatePlan, publishNoSyncUpdate, readUpdateLock } from './instruction-set-update/index.js';
8
+ import { attempt, holdSyncLock } from './instruction-sync-lock.js';
9
+ // Loaded only through `await import(...)` from instruction-sets-cli, so CLI
10
+ // startup and the published loader never load Effect.
9
11
  function readPayload(path, action) {
10
12
  return Effect.tryPromise({
11
13
  // The body stays unchecked here, as before: stageSyncPayload and makeUpdatePlan validate it.
@@ -14,51 +16,29 @@ function readPayload(path, action) {
14
16
  catch: cause => cause,
15
17
  });
16
18
  }
17
- // Waiting for another process's lock is the only interruptible part: nothing
18
- // is held yet. Publishing runs to completion once the lock is ours.
19
- function holdSyncLock(appRoot, use) {
20
- const acquire = Effect.gen(function* () {
21
- const claim = yield* attempt(() => startSyncLockClaim(appRoot));
22
- while (true) {
23
- const claimed = yield* attempt(() => tryClaimSyncLock(claim));
24
- if (typeof claimed === 'function')
25
- return claimed;
26
- yield* Effect.interruptible(Effect.sleep(claimed));
27
- }
28
- });
29
- return Effect.acquireUseRelease(acquire, () => use, release => Effect.sync(release));
30
- }
31
- // Stage the version, publish it under the lock, and always remove an
32
- // unpublished stage, including after Ctrl-C or SIGTERM.
19
+ // Stage the version, publish it under the lock (to completion once the lock
20
+ // is ours), and always remove an unpublished stage, including after Ctrl-C or
21
+ // SIGTERM.
33
22
  function deliverPayload(...args) {
34
23
  return Effect.acquireUseRelease(attempt(() => stageSyncPayload(...args)), staged => holdSyncLock(staged.appRoot, attempt(staged.publish)).pipe(Effect.map(warnings => ({ destination: staged.destination, wasPresent: staged.wasPresent, warnings }))), staged => Effect.sync(staged.discard));
35
24
  }
25
+ /** Stages and publishes one version under the folder lock (tests and the fixture script). */
26
+ export function syncPayloadToDisk(...args) {
27
+ return runOrThrow(deliverPayload(...args), new AbortController().signal);
28
+ }
36
29
  async function runOrThrow(program, signal) {
37
30
  const exit = await Effect.runPromiseExit(program, { signal });
38
31
  if (Exit.isSuccess(exit))
39
32
  return exit.value;
40
33
  throw Cause.squash(exit.cause);
41
34
  }
42
- // `once`: a second signal falls through to Node's default kill.
43
35
  async function runDelivery(program) {
44
- const controller = new AbortController();
45
- let interrupted;
46
- const handleInterrupt = () => { interrupted ??= 130; controller.abort(); };
47
- const handleTerminate = () => { interrupted ??= 143; controller.abort(); };
48
- process.once('SIGINT', handleInterrupt);
49
- process.once('SIGTERM', handleTerminate);
50
- try {
51
- return await runOrThrow(program(controller.signal), controller.signal);
52
- }
53
- catch (error) {
54
- if (interrupted !== undefined)
55
- return { interrupted };
56
- throw error;
57
- }
58
- finally {
59
- process.removeListener('SIGINT', handleInterrupt);
60
- process.removeListener('SIGTERM', handleTerminate);
61
- }
36
+ const { exit, interrupted } = await runWithStops(program);
37
+ if (Exit.isSuccess(exit))
38
+ return exit.value;
39
+ if (interrupted !== undefined)
40
+ return { interrupted };
41
+ throw Cause.squash(exit.cause);
62
42
  }
63
43
  export function syncInstructionSet(out, project, plan, options) {
64
44
  return runDelivery(() => Effect.gen(function* () {
@@ -84,12 +64,18 @@ export function updateInstructionSets(out, project, isApproved, noSync) {
84
64
  const plan = yield* attempt(() => makeUpdatePlan(project, lock, payloads));
85
65
  if (!isApproved)
86
66
  return { plan, result: { absent: [], warnings: [] } };
87
- // applyUpdate's own steps are not interruptible; each version it syncs is
88
- // a nested delivery that the shared signal interrupts and cleans up.
89
- const result = yield* Effect.uninterruptible(Effect.tryPromise({
90
- try: () => applyUpdate(out, project, plan, noSync, undefined, (...args) => runOrThrow(deliverPayload(...args), signal)),
91
- catch: cause => cause,
92
- }));
67
+ // --no-sync writes the Lock under the folder lock. Otherwise applyUpdate's
68
+ // own steps are not interruptible; each version it syncs is a nested
69
+ // delivery that the shared signal interrupts and cleans up.
70
+ const result = noSync
71
+ ? yield* holdSyncLock(join(out, 'orizu'), Effect.uninterruptible(Effect.tryPromise({
72
+ try: () => publishNoSyncUpdate(out, project, plan),
73
+ catch: cause => cause,
74
+ })))
75
+ : yield* Effect.uninterruptible(Effect.tryPromise({
76
+ try: () => applyUpdate(out, project, plan, () => new Date(), (...args) => runOrThrow(deliverPayload(...args), signal)),
77
+ catch: cause => cause,
78
+ }));
93
79
  return { plan, result };
94
80
  });
95
81
  // --no-sync stages nothing to clean up, so keep Node's default signal
@@ -0,0 +1,24 @@
1
+ import { type Interrupted } from './cli-stops.js';
2
+ import { type PrunePlan, type PruneResult } from './instruction-set-update/index.js';
3
+ /** What the removal step did before a stop was handled. */
4
+ export type PruneOutcome = {
5
+ kind: 'not started';
6
+ } | {
7
+ kind: 'removed';
8
+ count: number;
9
+ } | {
10
+ kind: 'rolled back';
11
+ error: unknown;
12
+ } | {
13
+ kind: 'rollback failed';
14
+ };
15
+ export type PruneStopped = Interrupted & {
16
+ outcome: PruneOutcome;
17
+ };
18
+ /**
19
+ * Removes the approved Versions under the folder lock. Waiting for the lock
20
+ * and planning again can be stopped and change nothing. The removal itself
21
+ * always settles once it starts: applied and verified, or fully rolled back.
22
+ */
23
+ export declare function pruneInstructionSets(out: string, plan: PrunePlan, keepSpecifiers: string[]): Promise<PruneResult | PruneStopped>;
24
+ //# sourceMappingURL=instruction-prune.d.ts.map