orizu 0.5.41 → 0.5.43

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 (118) hide show
  1. package/dist/account-directory.d.ts.map +1 -1
  2. package/dist/account-directory.js +6 -32
  3. package/dist/app-publish-workspace.js +1 -1
  4. package/dist/archive-artifact-workflow.d.ts +23 -0
  5. package/dist/archive-artifact-workflow.d.ts.map +1 -0
  6. package/dist/archive-artifact-workflow.js +100 -0
  7. package/dist/archive-artifacts-cli.d.ts.map +1 -1
  8. package/dist/archive-artifacts-cli.js +7 -11
  9. package/dist/artifact-archive.d.ts.map +1 -1
  10. package/dist/artifact-archive.js +2 -0
  11. package/dist/credentials.d.ts.map +1 -1
  12. package/dist/credentials.js +2 -0
  13. package/dist/dataset-commands.d.ts.map +1 -1
  14. package/dist/dataset-commands.js +19 -29
  15. package/dist/dataset-split-create-workflow.d.ts +16 -0
  16. package/dist/dataset-split-create-workflow.d.ts.map +1 -0
  17. package/dist/dataset-split-create-workflow.js +115 -0
  18. package/dist/git-credential.js +1 -1
  19. package/dist/help.d.ts.map +1 -1
  20. package/dist/help.js +10 -2
  21. package/dist/hosted-boot.d.ts +7 -0
  22. package/dist/hosted-boot.d.ts.map +1 -1
  23. package/dist/hosted-boot.js +290 -60
  24. package/dist/hosted-harness.d.ts +1 -0
  25. package/dist/hosted-harness.d.ts.map +1 -1
  26. package/dist/hosted-harness.js +2 -0
  27. package/dist/hosted-harvest.d.ts +2 -1
  28. package/dist/hosted-harvest.d.ts.map +1 -1
  29. package/dist/hosted-harvest.js +29 -17
  30. package/dist/hosted-live-channel.d.ts +31 -0
  31. package/dist/hosted-live-channel.d.ts.map +1 -0
  32. package/dist/hosted-live-channel.js +147 -0
  33. package/dist/hosted-loop.d.ts +8 -1
  34. package/dist/hosted-loop.d.ts.map +1 -1
  35. package/dist/hosted-loop.js +45 -0
  36. package/dist/hosted-run-event-sink.d.ts +19 -0
  37. package/dist/hosted-run-event-sink.d.ts.map +1 -1
  38. package/dist/hosted-run-event-sink.js +106 -3
  39. package/dist/hosted-runtime-assets.js +1 -1
  40. package/dist/hosted-session-cli.d.ts.map +1 -1
  41. package/dist/hosted-session-cli.js +4 -0
  42. package/dist/hosted-session-seed.d.ts +46 -6
  43. package/dist/hosted-session-seed.d.ts.map +1 -1
  44. package/dist/hosted-session-seed.js +95 -9
  45. package/dist/hosted-skill-staging.d.ts +36 -12
  46. package/dist/hosted-skill-staging.d.ts.map +1 -1
  47. package/dist/hosted-skill-staging.js +96 -34
  48. package/dist/http.d.ts +2 -1
  49. package/dist/http.d.ts.map +1 -1
  50. package/dist/http.js +10 -6
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +16 -16
  53. package/dist/orizu-skill-links.d.ts +48 -0
  54. package/dist/orizu-skill-links.d.ts.map +1 -0
  55. package/dist/orizu-skill-links.js +99 -0
  56. package/dist/preview-runtime.d.ts +1 -0
  57. package/dist/preview-runtime.d.ts.map +1 -1
  58. package/dist/preview-runtime.js +15 -0
  59. package/dist/repository-backup-core.d.ts.map +1 -1
  60. package/dist/repository-backup-core.js +2 -0
  61. package/dist/request-outcome.d.ts +5 -0
  62. package/dist/request-outcome.d.ts.map +1 -0
  63. package/dist/request-outcome.js +27 -0
  64. package/dist/score-save-workflow.d.ts +26 -0
  65. package/dist/score-save-workflow.d.ts.map +1 -0
  66. package/dist/score-save-workflow.js +120 -0
  67. package/dist/setup-and-skills-commands.d.ts.map +1 -1
  68. package/dist/setup-and-skills-commands.js +52 -7
  69. package/dist/setup-onboarding.d.ts.map +1 -1
  70. package/dist/setup-onboarding.js +6 -2
  71. package/dist/skill-installer.d.ts +16 -0
  72. package/dist/skill-installer.d.ts.map +1 -1
  73. package/dist/skill-installer.js +35 -5
  74. package/dist/team-create-workflow.d.ts +16 -0
  75. package/dist/team-create-workflow.d.ts.map +1 -0
  76. package/dist/team-create-workflow.js +107 -0
  77. package/dist/team-member-change-workflow.d.ts +25 -0
  78. package/dist/team-member-change-workflow.d.ts.map +1 -0
  79. package/dist/team-member-change-workflow.js +132 -0
  80. package/dist/workbench-cli.d.ts.map +1 -1
  81. package/dist/workbench-cli.js +24 -4
  82. package/dist/workspace.d.ts.map +1 -1
  83. package/dist/workspace.js +2 -0
  84. package/package.json +1 -1
  85. package/src/account-directory.ts +6 -41
  86. package/src/app-publish-workspace.ts +1 -1
  87. package/src/archive-artifact-workflow.ts +101 -0
  88. package/src/archive-artifacts-cli.ts +7 -28
  89. package/src/artifact-archive.ts +2 -0
  90. package/src/credentials.ts +2 -0
  91. package/src/dataset-commands.ts +19 -31
  92. package/src/dataset-split-create-workflow.ts +128 -0
  93. package/src/git-credential.ts +1 -1
  94. package/src/help.ts +10 -2
  95. package/src/hosted-boot.ts +292 -59
  96. package/src/hosted-harness.ts +2 -0
  97. package/src/hosted-harvest.ts +30 -17
  98. package/src/hosted-live-channel.ts +167 -0
  99. package/src/hosted-loop.ts +50 -0
  100. package/src/hosted-run-event-sink.ts +118 -3
  101. package/src/hosted-runtime-assets.ts +1 -1
  102. package/src/hosted-session-cli.ts +4 -0
  103. package/src/hosted-session-seed.ts +111 -10
  104. package/src/hosted-skill-staging.ts +110 -36
  105. package/src/http.ts +12 -7
  106. package/src/index.ts +17 -21
  107. package/src/orizu-skill-links.ts +114 -0
  108. package/src/preview-runtime.ts +16 -0
  109. package/src/repository-backup-core.ts +2 -0
  110. package/src/request-outcome.ts +28 -0
  111. package/src/score-save-workflow.ts +132 -0
  112. package/src/setup-and-skills-commands.ts +60 -6
  113. package/src/setup-onboarding.ts +6 -2
  114. package/src/skill-installer.ts +46 -5
  115. package/src/team-create-workflow.ts +109 -0
  116. package/src/team-member-change-workflow.ts +146 -0
  117. package/src/workbench-cli.ts +24 -4
  118. package/src/workspace.ts +2 -0
@@ -8,11 +8,11 @@
8
8
  * a push of that `main` into the session copy. A Worker cannot run git, so
9
9
  * this is the first place in session start that can.
10
10
  *
11
- * It fills a copy only when the copy has no branches at all, which is what a
12
- * freshly created copy looks like. A retried or resumed boot finds `main` and
13
- * does nothing, so it never overwrites the agent's work, and a working
14
- * repository whose content lives on another branch is never touched. The
15
- * push is never forced; if another boot filled the copy first, that is fine.
11
+ * Team seeds leave populated copies alone. A selected session source is
12
+ * cloned even for a populated destination: its saved main must be an ancestor
13
+ * of destination main before boot may proceed. This preserves replacement
14
+ * commits while refusing a retry that contains only team files. Pushes are
15
+ * never forced. A lost push reply is accepted only after history readback.
16
16
  * Credentials come only through the `-c` settings the caller passes (the
17
17
  * boot's credential helper), never in a URL or argument.
18
18
  */
@@ -27,10 +27,12 @@ type SeedExec = (cmd: string, args: string[], opts?: {
27
27
  }) => SeedExecResult;
28
28
  export interface SeedSessionCopyInput {
29
29
  exec: SeedExec;
30
+ seedOrigin?: 'team' | 'session';
30
31
  sessionRemote: string;
31
32
  /** `git -c` settings that authenticate to the session copy. */
32
33
  sessionGitConfig: string[];
33
- /** Called only when the copy is empty: the team copy and its `-c` settings. */
34
+ /** Server-selected origin and credentials; selected session origins must
35
+ * also be resolved on populated retries. The legacy name serves team boots. */
34
36
  resolveTeam: () => Promise<{
35
37
  remote: string;
36
38
  gitConfig: string[];
@@ -41,5 +43,43 @@ export interface SeedSessionCopyInput {
41
43
  export declare function seedSessionCopy(input: SeedSessionCopyInput): Promise<{
42
44
  seeded: boolean;
43
45
  }>;
46
+ type SeedExecAsync = (cmd: string, args: string[], opts?: {
47
+ cwd?: string;
48
+ }) => Promise<SeedExecResult>;
49
+ export interface CloneTeamIntoWorkTreeInput {
50
+ exec: SeedExec;
51
+ /** Runs a command without blocking, so the check, the clone and the push
52
+ * can overlap with each other and with the rest of the boot. */
53
+ execAsync: SeedExecAsync;
54
+ sessionRemote: string;
55
+ sessionGitConfig: string[];
56
+ resolveTeam: () => Promise<{
57
+ remote: string;
58
+ gitConfig: string[];
59
+ }>;
60
+ /** A path owned only by this clone; cleared before and after use. */
61
+ scratchDir: string;
62
+ /** The boot's working folder: absent or empty. */
63
+ workTree: string;
64
+ }
65
+ export type CloneTeamIntoWorkTreeResult = {
66
+ cloned: false;
67
+ }
68
+ /** `filled` settles once the background push into the session copy ends:
69
+ * true when the copy now has main. It never rejects. */
70
+ | {
71
+ cloned: true;
72
+ filled: Promise<boolean>;
73
+ };
74
+ /**
75
+ * ORI-2512: a team boot of an empty session copy makes its working folder
76
+ * from ONE clone of the team copy, with the session copy as its push remote,
77
+ * and fills the session copy by pushing in the background. The session copy
78
+ * is checked at the same time as the clone; a copy that already has branches
79
+ * belongs to an earlier boot of this session, so the clone is dropped and the
80
+ * caller clones the session copy as usual. The clone keeps the whole history:
81
+ * an empty copy refuses a push from a shallow clone.
82
+ */
83
+ export declare function cloneTeamIntoWorkTree(input: CloneTeamIntoWorkTreeInput): Promise<CloneTeamIntoWorkTreeResult>;
44
84
  export {};
45
85
  //# sourceMappingURL=hosted-session-seed.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hosted-session-seed.d.ts","sourceRoot":"","sources":["../src/hosted-session-seed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,eAAO,MAAM,yBAAyB,wDAAwD,CAAA;AAE9F,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AACD,KAAK,QAAQ,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,cAAc,CAAA;AAExF,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,QAAQ,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,+DAA+D;IAC/D,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,+EAA+E;IAC/E,WAAW,EAAE,MAAM,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;IACnE,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAA;CACnB;AAoBD,wBAAsB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC,CAoB/F"}
1
+ {"version":3,"file":"hosted-session-seed.d.ts","sourceRoot":"","sources":["../src/hosted-session-seed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,eAAO,MAAM,yBAAyB,wDAAwD,CAAA;AAE9F,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AACD,KAAK,QAAQ,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,cAAc,CAAA;AAExF,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,QAAQ,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,aAAa,EAAE,MAAM,CAAA;IACrB,+DAA+D;IAC/D,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B;mFAC+E;IAC/E,WAAW,EAAE,MAAM,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;IACnE,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAA;CACnB;AA2BD,wBAAsB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC,CA6B/F;AAED,KAAK,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAA;AAEtG,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,QAAQ,CAAA;IACd;qEACiE;IACjE,SAAS,EAAE,aAAa,CAAA;IACxB,aAAa,EAAE,MAAM,CAAA;IACrB,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,WAAW,EAAE,MAAM,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;IACnE,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAA;IAClB,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,2BAA2B,GACnC;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE;AACnB;yDACyD;GACvD;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAA;AAE9C;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAkDnH"}
@@ -8,11 +8,11 @@
8
8
  * a push of that `main` into the session copy. A Worker cannot run git, so
9
9
  * this is the first place in session start that can.
10
10
  *
11
- * It fills a copy only when the copy has no branches at all, which is what a
12
- * freshly created copy looks like. A retried or resumed boot finds `main` and
13
- * does nothing, so it never overwrites the agent's work, and a working
14
- * repository whose content lives on another branch is never touched. The
15
- * push is never forced; if another boot filled the copy first, that is fine.
11
+ * Team seeds leave populated copies alone. A selected session source is
12
+ * cloned even for a populated destination: its saved main must be an ancestor
13
+ * of destination main before boot may proceed. This preserves replacement
14
+ * commits while refusing a retry that contains only team files. Pushes are
15
+ * never forced. A lost push reply is accepted only after history readback.
16
16
  * Credentials come only through the `-c` settings the caller passes (the
17
17
  * boot's credential helper), never in a URL or argument.
18
18
  */
@@ -33,20 +33,37 @@ function sessionHasBranches(input) {
33
33
  throw failure('reading the session copy', listed);
34
34
  return listed.stdout.trim().length > 0;
35
35
  }
36
+ function verifySourceSeedHistory(input) {
37
+ const fetched = input.exec('git', ['-C', input.scratchDir, ...withConfig(input.sessionGitConfig), 'fetch', '--quiet', input.sessionRemote, 'refs/heads/main:refs/seed/destination']);
38
+ if (fetched.status !== 0)
39
+ throw failure('reading destination saved history', fetched);
40
+ const ancestry = input.exec('git', ['-C', input.scratchDir, 'merge-base', '--is-ancestor', 'refs/heads/main', 'refs/seed/destination']);
41
+ if (ancestry.status !== 0)
42
+ throw new Error('destination does not contain the selected session saved history');
43
+ }
36
44
  export async function seedSessionCopy(input) {
37
- if (sessionHasBranches(input))
45
+ const hasBranches = sessionHasBranches(input);
46
+ if (hasBranches && input.seedOrigin !== 'session')
38
47
  return { seeded: false };
39
- const team = await input.resolveTeam();
48
+ const origin = await input.resolveTeam();
40
49
  try {
41
50
  // A sandbox shutdown can interrupt clone before finally runs. This path
42
51
  // belongs only to seeding; never clear the surrounding session directory.
43
52
  const cleared = input.exec('rm', ['-rf', '--', input.scratchDir]);
44
53
  if (cleared.status !== 0)
45
54
  throw failure('clearing the seed scratch', cleared);
46
- const fetched = input.exec('git', [...withConfig(team.gitConfig), 'clone', '--quiet', '--bare', '--single-branch', '--branch', 'main', team.remote, input.scratchDir]);
55
+ const fetched = input.exec('git', [...withConfig(origin.gitConfig), 'clone', '--quiet', '--bare', '--single-branch', '--branch', 'main', origin.remote, input.scratchDir]);
47
56
  if (fetched.status !== 0)
48
- throw failure('reading the team copy', fetched);
57
+ throw failure(input.seedOrigin === 'session' ? 'reading selected saved files' : 'reading the team copy', fetched);
58
+ if (hasBranches) {
59
+ verifySourceSeedHistory(input);
60
+ return { seeded: false };
61
+ }
49
62
  const pushed = input.exec('git', ['-C', input.scratchDir, ...withConfig(input.sessionGitConfig), 'push', '--quiet', input.sessionRemote, 'refs/heads/main:refs/heads/main']);
63
+ if (input.seedOrigin === 'session') {
64
+ verifySourceSeedHistory(input);
65
+ return { seeded: pushed.status === 0 };
66
+ }
50
67
  if (pushed.status !== 0) {
51
68
  // Another boot of the same session may have filled it first.
52
69
  if (sessionHasBranches(input))
@@ -59,3 +76,72 @@ export async function seedSessionCopy(input) {
59
76
  input.exec('rm', ['-rf', '--', input.scratchDir]);
60
77
  }
61
78
  }
79
+ /**
80
+ * ORI-2512: a team boot of an empty session copy makes its working folder
81
+ * from ONE clone of the team copy, with the session copy as its push remote,
82
+ * and fills the session copy by pushing in the background. The session copy
83
+ * is checked at the same time as the clone; a copy that already has branches
84
+ * belongs to an earlier boot of this session, so the clone is dropped and the
85
+ * caller clones the session copy as usual. The clone keeps the whole history:
86
+ * an empty copy refuses a push from a shallow clone.
87
+ */
88
+ export async function cloneTeamIntoWorkTree(input) {
89
+ const clear = () => input.exec('rm', ['-rf', '--', input.scratchDir]);
90
+ const listing = input.execAsync('git', [...withConfig(input.sessionGitConfig), 'ls-remote', '--heads', input.sessionRemote]);
91
+ const cloning = (async () => {
92
+ const origin = await input.resolveTeam();
93
+ const cleared = clear();
94
+ if (cleared.status !== 0)
95
+ throw failure('clearing the seed scratch', cleared);
96
+ return input.execAsync('git', [...withConfig(origin.gitConfig), 'clone', '--quiet', '--single-branch', '--branch', 'main', origin.remote, input.scratchDir]);
97
+ })();
98
+ const settled = cloning.then(() => undefined, () => undefined);
99
+ let listed;
100
+ try {
101
+ listed = await listing;
102
+ }
103
+ catch (error) {
104
+ await settled;
105
+ clear();
106
+ throw error;
107
+ }
108
+ // A copy with saved work: answer now. The unneeded clone ends on its own
109
+ // and its scratch is cleared then.
110
+ if (listed.status === 0 && listed.stdout.trim().length > 0) {
111
+ void settled.then(clear);
112
+ return { cloned: false };
113
+ }
114
+ try {
115
+ const fetched = await cloning;
116
+ if (listed.status !== 0)
117
+ throw failure('reading the session copy', listed);
118
+ if (fetched.status !== 0)
119
+ throw failure('reading the team copy', fetched);
120
+ const pointed = input.exec('git', ['-C', input.scratchDir, 'remote', 'set-url', 'origin', input.sessionRemote]);
121
+ if (pointed.status !== 0)
122
+ throw failure('pointing the working folder at the session copy', pointed);
123
+ // The runtime image may ship the folder empty; `mv` needs it gone.
124
+ input.exec('rmdir', ['--', input.workTree]);
125
+ // A folder that is still there (not empty, or a mount point) would make
126
+ // `mv` put the clone inside it.
127
+ const remaining = input.exec('test', ['-e', input.workTree]);
128
+ if (remaining.status === 0)
129
+ throw failure('making the working folder', { status: 1, stdout: '', stderr: `${input.workTree} is not empty` });
130
+ const moved = input.exec('mv', ['--', input.scratchDir, input.workTree]);
131
+ if (moved.status !== 0)
132
+ throw failure('making the working folder', moved);
133
+ }
134
+ finally {
135
+ clear();
136
+ }
137
+ const filled = input.execAsync('git', ['-C', input.workTree, ...withConfig(input.sessionGitConfig), 'push', '--quiet', 'origin', 'refs/heads/main:refs/heads/main'])
138
+ .then(async (pushed) => {
139
+ if (pushed.status === 0)
140
+ return true;
141
+ // Another boot, or the agent's own first save, may have filled it first.
142
+ const listed = await input.execAsync('git', [...withConfig(input.sessionGitConfig), 'ls-remote', '--heads', input.sessionRemote]);
143
+ return listed.status === 0 && listed.stdout.trim().length > 0;
144
+ })
145
+ .catch(() => false);
146
+ return { cloned: true, filled };
147
+ }
@@ -20,14 +20,18 @@
20
20
  * sandbox user's npm prefix never holds the sudo-installed package),
21
21
  * (4) `$(npm root -g)/orizu/vendor/skills/orizu` — for the non-prebaked
22
22
  * bootstrap, which installs orizu globally and has no /opt/orizu/cli.
23
- * Then SYMLINK it under `<workspaceDir>/.claude/skills/orizu`, falling back to
24
- * a copy if the symlink cannot be created. Resolved paths stay in shell vars
25
- * (never interpolated), so they cannot inject into the command.
23
+ * Then SYMLINK it into each folder in ORIZU_SKILL_LINK_FOLDERS (ORI-2521:
24
+ * `.claude/skills/orizu` for Claude Code and opencode, `.agents/skills/orizu`
25
+ * for opencode, pi and Codex), falling back to a copy if a symlink cannot be
26
+ * created. Resolved paths stay in shell vars (never interpolated), so they
27
+ * cannot inject into the command.
26
28
  *
27
- * HARVEST-SAFE (ALI-1051): the script appends `/.claude/skills/` to the repo-LOCAL
28
- * `.git/info/exclude` so auto-harvest's `git add -A` cannot sweep the staged
29
- * symlink into the session branch (hosted-harvest.ts also excludes it by pathspec,
30
- * belt-and-braces).
29
+ * SAVE-SAFE (ALI-1051, ORI-2442): a skills folder may itself be a link, so the
30
+ * script resolves each folder's real location first. It appends the exact
31
+ * repo-relative path of each staged link to the repo-LOCAL `.git/info/exclude`
32
+ * (so the agent's own `git add -A` skips it) and records it in
33
+ * `.git/orizu/staged-skill-links`, which auto-save and `session finish --push`
34
+ * read to unstage the links (orizu-skill-links.ts).
31
35
  */
32
36
  export interface SkillStageExecResult {
33
37
  exitCode: number;
@@ -45,25 +49,44 @@ export interface StageOrizuSkillOptions {
45
49
  /**
46
50
  * Directory the session branch is cloned into (ABSOLUTE on the DO path,
47
51
  * sandbox-root RELATIVE on the operator path). The skill is staged under
48
- * `<workspaceDir>/.claude/skills/orizu`.
52
+ * each of ORIZU_SKILL_LINK_FOLDERS.
49
53
  */
50
54
  workspaceDir: string;
51
55
  exec: SkillStageExec;
52
56
  }
57
+ type SkillLinkStageMethod = 'symlink' | 'copy' | 'preserved' | 'same_folder';
58
+ interface StagedSkillLink {
59
+ /** The link's real path, after resolving a linked skills folder. */
60
+ dest: string;
61
+ method: SkillLinkStageMethod | null;
62
+ }
53
63
  export interface StageOrizuSkillResult {
64
+ /** Every folder was staged, preserved, or is the same real folder as an earlier one. */
54
65
  ok: boolean;
55
- method: 'symlink' | 'copy' | 'preserved' | null;
66
+ /** The `.claude/skills` link's method; kept for the boot logs. */
67
+ method: SkillLinkStageMethod | null;
56
68
  /** `<workspaceDir>/.claude/skills/orizu`. */
57
69
  dest: string;
70
+ links: StagedSkillLink[];
58
71
  exitCode: number;
59
72
  stdout: string;
60
73
  stderr: string;
61
74
  }
62
75
  /** The exact staging script — exported for tests that assert its shape and run it
63
- * (`canonicalSkillDir` lets a test lay the canonical copy out under a temp root). */
76
+ * (`canonicalSkillDir` lets a test lay the canonical copy out under a temp root).
77
+ *
78
+ * For each folder in ORIZU_SKILL_LINK_FOLDERS it prints one line:
79
+ * SYMLINK <dest> <src> | COPY <dest> <src> — staged
80
+ * PRESERVED_TRACKED <dest> — the team tracks a valid skill there; left alone
81
+ * PRESERVED_INVALID <dest> — the team tracks something there without SKILL.md
82
+ * SAME_FOLDER <dest> — this folder is the same real folder as an earlier one
83
+ * UNRESOLVED <dest> — the folder could not be created
84
+ * OUTSIDE_REPO <dest> — the folder is a link out of the repo; skipped
85
+ * INSIDE_GIT_DIR <dest> — the folder is a link into the git directory; skipped
86
+ * or a single NO_SOURCE when no copy of the skill was found. */
64
87
  export declare function renderStageOrizuSkillScript(workspaceDir: string, canonicalSkillDir?: string): string;
65
88
  /**
66
- * Stage the orizu skill into `<workspaceDir>/.claude/skills/orizu`.
89
+ * Stage the orizu skill into each of ORIZU_SKILL_LINK_FOLDERS under `workspaceDir`.
67
90
  * Non-throwing (except via the injected exec): resolves to a structured result
68
91
  * both callers record their own way. A malformed `workspaceDir` returns a non-ok
69
92
  * result rather than throwing, so staging stays non-fatal to the boot.
@@ -72,7 +95,8 @@ export declare function renderStageOrizuSkillScript(workspaceDir: string, canoni
72
95
  * non-traversal directory. The shell-safety guard blocks injection metacharacters
73
96
  * but deliberately does not reject `..` or a leading `-` — do not pass untrusted
74
97
  * input here. Idempotent: safe to re-invoke (e.g. ALI-1060 resume/retry) — the
75
- * `.git/info/exclude` line is appended only when absent.
98
+ * `.git/info/exclude` and staged-links lines are appended only when absent.
76
99
  */
77
100
  export declare function stageOrizuSkill(opts: StageOrizuSkillOptions): Promise<StageOrizuSkillResult>;
101
+ export {};
78
102
  //# sourceMappingURL=hosted-skill-staging.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hosted-skill-staging.d.ts","sourceRoot":"","sources":["../src/hosted-skill-staging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAA;AAE/E,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,cAAc,CAAA;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,IAAI,CAAA;IAC/C,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAYD;sFACsF;AACtF,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,iBAAiB,SAAsB,GAAG,MAAM,CAqBjH;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAuBlG"}
1
+ {"version":3,"file":"hosted-skill-staging.d.ts","sourceRoot":"","sources":["../src/hosted-skill-staging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAA;AAE/E,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,cAAc,CAAA;CACrB;AAED,KAAK,oBAAoB,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,aAAa,CAAA;AAE5E,UAAU,eAAe;IACvB,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,qBAAqB;IACpC,wFAAwF;IACxF,EAAE,EAAE,OAAO,CAAA;IACX,kEAAkE;IAClE,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAAA;IACnC,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,eAAe,EAAE,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAmBD;;;;;;;;;;;iEAWiE;AACjE,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,iBAAiB,SAAsB,GAAG,MAAM,CAkDjH;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAkClG"}
@@ -20,15 +20,20 @@
20
20
  * sandbox user's npm prefix never holds the sudo-installed package),
21
21
  * (4) `$(npm root -g)/orizu/vendor/skills/orizu` — for the non-prebaked
22
22
  * bootstrap, which installs orizu globally and has no /opt/orizu/cli.
23
- * Then SYMLINK it under `<workspaceDir>/.claude/skills/orizu`, falling back to
24
- * a copy if the symlink cannot be created. Resolved paths stay in shell vars
25
- * (never interpolated), so they cannot inject into the command.
23
+ * Then SYMLINK it into each folder in ORIZU_SKILL_LINK_FOLDERS (ORI-2521:
24
+ * `.claude/skills/orizu` for Claude Code and opencode, `.agents/skills/orizu`
25
+ * for opencode, pi and Codex), falling back to a copy if a symlink cannot be
26
+ * created. Resolved paths stay in shell vars (never interpolated), so they
27
+ * cannot inject into the command.
26
28
  *
27
- * HARVEST-SAFE (ALI-1051): the script appends `/.claude/skills/` to the repo-LOCAL
28
- * `.git/info/exclude` so auto-harvest's `git add -A` cannot sweep the staged
29
- * symlink into the session branch (hosted-harvest.ts also excludes it by pathspec,
30
- * belt-and-braces).
29
+ * SAVE-SAFE (ALI-1051, ORI-2442): a skills folder may itself be a link, so the
30
+ * script resolves each folder's real location first. It appends the exact
31
+ * repo-relative path of each staged link to the repo-LOCAL `.git/info/exclude`
32
+ * (so the agent's own `git add -A` skips it) and records it in
33
+ * `.git/orizu/staged-skill-links`, which auto-save and `session finish --push`
34
+ * read to unstage the links (orizu-skill-links.ts).
31
35
  */
36
+ import { ORIZU_SKILL_LINK_FOLDERS, STAGED_SKILL_LINKS_FILE } from './orizu-skill-links.js';
32
37
  // `workspaceDir` is interpolated into a shell command, so refuse anything a shell
33
38
  // could treat specially — the same allow-list hosted-bootstrap.ts asserts up front.
34
39
  // NOTE: this admits `..` traversal and a leading `-`; it is a shell-injection guard,
@@ -37,32 +42,77 @@
37
42
  // operator path) — never an untrusted or user-supplied value.
38
43
  const SAFE_WORKSPACE_DIR = /^[A-Za-z0-9._/:@-]+$/;
39
44
  const CANONICAL_SKILL_DIR = '/opt/orizu/cli/vendor/skills/orizu';
45
+ const STATUS_METHODS = {
46
+ SYMLINK: 'symlink',
47
+ COPY: 'copy',
48
+ PRESERVED_TRACKED: 'preserved',
49
+ SAME_FOLDER: 'same_folder',
50
+ };
40
51
  /** The exact staging script — exported for tests that assert its shape and run it
41
- * (`canonicalSkillDir` lets a test lay the canonical copy out under a temp root). */
52
+ * (`canonicalSkillDir` lets a test lay the canonical copy out under a temp root).
53
+ *
54
+ * For each folder in ORIZU_SKILL_LINK_FOLDERS it prints one line:
55
+ * SYMLINK <dest> <src> | COPY <dest> <src> — staged
56
+ * PRESERVED_TRACKED <dest> — the team tracks a valid skill there; left alone
57
+ * PRESERVED_INVALID <dest> — the team tracks something there without SKILL.md
58
+ * SAME_FOLDER <dest> — this folder is the same real folder as an earlier one
59
+ * UNRESOLVED <dest> — the folder could not be created
60
+ * OUTSIDE_REPO <dest> — the folder is a link out of the repo; skipped
61
+ * INSIDE_GIT_DIR <dest> — the folder is a link into the git directory; skipped
62
+ * or a single NO_SOURCE when no copy of the skill was found. */
42
63
  export function renderStageOrizuSkillScript(workspaceDir, canonicalSkillDir = CANONICAL_SKILL_DIR) {
43
- const skillsDir = `${workspaceDir}/.claude/skills`;
44
64
  return [
45
- `mkdir -p ${skillsDir}`,
46
- // The staged skill is bootstrap-injected RUNTIME scaffolding (a symlink to the
47
- // sandbox-local CLI vendor dir), NOT the agent's work. Exclude it repo-LOCALLY
48
- // (.git/info/exclude — invisible to the diff, never committed) so auto-harvest's
49
- // `git add -A` can't sweep it into the session branch and (ALI-1051) auto-apply
50
- // a broken symlink to the customer's main. IDEMPOTENT (ALI-1060 resume/retry):
51
- // only append when the line is absent, so a re-invocation never duplicates it.
52
- `if [ -d ${workspaceDir}/.git ] && ! grep -qxF '/.claude/skills/' ${workspaceDir}/.git/info/exclude 2>/dev/null; then printf '%s\\n' '/.claude/skills/' >> ${workspaceDir}/.git/info/exclude; fi`,
53
- `dest="${skillsDir}/orizu"`,
54
- `if [ -d ${workspaceDir}/.git ] && git -C ${workspaceDir} ls-files -- '.claude/skills/orizu' '.claude/skills/orizu/**' | grep -q .; then if [ -f "$dest/SKILL.md" ]; then echo "PRESERVED_TRACKED $dest"; else echo "PRESERVED_INVALID $dest"; fi; exit 0; fi`,
65
+ `ws='${workspaceDir}'`,
66
+ `mkdir -p "$ws"`,
67
+ `root="$(cd "$ws" && pwd -P)" || exit 1`,
68
+ // Paths live in the git dir, never in the work tree. Both files are
69
+ // repo-local: `.git/info/exclude` hides each link from the agent's own
70
+ // `git add -A`, and the staged-links list tells the save steps where each
71
+ // link really landed (ORI-2442).
72
+ `gitdir="$(git -C "$ws" rev-parse --absolute-git-dir 2>/dev/null || true)"`,
73
+ `gitdir_p=''`,
74
+ `if [ -n "$gitdir" ]; then mkdir -p "$gitdir/info" "$(dirname "$gitdir/${STAGED_SKILL_LINKS_FILE}")"; gitdir_p="$(cd "$gitdir" && pwd -P)"; fi`,
75
+ // Sessions staged by an older CLI ignore the whole folder, which hides a
76
+ // team's new skills from saves; this script ignores exact link paths instead.
77
+ `if [ -n "$gitdir" ] && grep -qxF '/.claude/skills/' "$gitdir/info/exclude" 2>/dev/null; then grep -vxF '/.claude/skills/' "$gitdir/info/exclude" > "$gitdir/info/exclude.orizu-tmp"; mv "$gitdir/info/exclude.orizu-tmp" "$gitdir/info/exclude"; fi`,
55
78
  `src="${'${ORIZU_SKILL_SOURCE_DIR:-}'}"`,
56
79
  `if [ -z "$src" ] || [ ! -d "$src" ]; then src="$(orizu skills path 2>/dev/null || true)"; fi`,
57
80
  `if [ -z "$src" ] || [ ! -d "$src" ]; then src='${canonicalSkillDir}'; fi`,
58
81
  `if [ -z "$src" ] || [ ! -d "$src" ]; then r="$(npm root -g 2>/dev/null || true)"; if [ -n "$r" ] && [ -d "$r/orizu/vendor/skills/orizu" ]; then src="$r/orizu/vendor/skills/orizu"; fi; fi`,
59
- `if [ -z "$src" ] || [ ! -d "$src" ]; then echo "NO_SOURCE"; exit 0; fi`,
60
- `rm -rf "$dest"`,
61
- `if ln -s "$src" "$dest" 2>/dev/null; then echo "SYMLINK $src"; else cp -R "$src" "$dest" && echo "COPY $src"; fi`,
82
+ `if [ -z "$src" ] || [ ! -d "$src" ]; then src=''; fi`,
83
+ `seen=' '`,
84
+ `nosource=''`,
85
+ `stage_one() {`,
86
+ ` mkdir -p "$ws/$1" 2>/dev/null`,
87
+ // Resolve the folder's REAL location: when it is itself a link, the skill
88
+ // link lands at the link's target (ORI-2442).
89
+ ` phys="$(cd "$ws/$1" 2>/dev/null && pwd -P)" || { echo "UNRESOLVED $ws/$1/orizu"; return; }`,
90
+ ` dest="$phys/orizu"`,
91
+ ` case "$seen" in *" $phys "*) echo "SAME_FOLDER $dest"; return;; esac`,
92
+ ` seen="$seen$phys "`,
93
+ // Never write outside the repo: a folder linked elsewhere is skipped, so
94
+ // the `rm -rf` below cannot reach another directory.
95
+ ` case "$phys" in "$root"/*) rel="\${phys#"$root"/}/orizu";; *) echo "OUTSIDE_REPO $dest"; return;; esac`,
96
+ ` if [ -n "$gitdir_p" ]; then case "$phys" in "$gitdir_p"|"$gitdir_p"/*) echo "INSIDE_GIT_DIR $dest"; return;; esac; fi`,
97
+ ` if [ -n "$gitdir" ] && git -C "$ws" ls-files -- "$rel" "$rel/**" | grep -q .; then if [ -f "$dest/SKILL.md" ]; then echo "PRESERVED_TRACKED $dest"; else echo "PRESERVED_INVALID $dest"; fi; return; fi`,
98
+ // IDEMPOTENT (ALI-1060 resume/retry): each line is appended only when absent.
99
+ ` if [ -n "$gitdir" ]; then`,
100
+ // Escape ignore-pattern characters so the line matches this exact path.
101
+ ` pat="/$(printf '%s' "$rel" | sed 's/[][*?\\\\]/\\\\&/g')"`,
102
+ ` grep -qxF "$pat" "$gitdir/info/exclude" 2>/dev/null || printf '%s\\n' "$pat" >> "$gitdir/info/exclude"`,
103
+ ` grep -qxF "$rel" "$gitdir/${STAGED_SKILL_LINKS_FILE}" 2>/dev/null || printf '%s\\n' "$rel" >> "$gitdir/${STAGED_SKILL_LINKS_FILE}"`,
104
+ ` fi`,
105
+ ` if [ -z "$src" ]; then nosource=1; return; fi`,
106
+ ` rm -rf "$dest"`,
107
+ ` if ln -s "$src" "$dest" 2>/dev/null; then echo "SYMLINK $dest $src"; else cp -R "$src" "$dest" && echo "COPY $dest $src"; fi`,
108
+ `}`,
109
+ ...ORIZU_SKILL_LINK_FOLDERS.map(folder => `stage_one '${folder}'`),
110
+ `if [ -n "$nosource" ]; then echo "NO_SOURCE"; fi`,
111
+ `exit 0`,
62
112
  ].join('\n');
63
113
  }
64
114
  /**
65
- * Stage the orizu skill into `<workspaceDir>/.claude/skills/orizu`.
115
+ * Stage the orizu skill into each of ORIZU_SKILL_LINK_FOLDERS under `workspaceDir`.
66
116
  * Non-throwing (except via the injected exec): resolves to a structured result
67
117
  * both callers record their own way. A malformed `workspaceDir` returns a non-ok
68
118
  * result rather than throwing, so staging stays non-fatal to the boot.
@@ -71,7 +121,7 @@ export function renderStageOrizuSkillScript(workspaceDir, canonicalSkillDir = CA
71
121
  * non-traversal directory. The shell-safety guard blocks injection metacharacters
72
122
  * but deliberately does not reject `..` or a leading `-` — do not pass untrusted
73
123
  * input here. Idempotent: safe to re-invoke (e.g. ALI-1060 resume/retry) — the
74
- * `.git/info/exclude` line is appended only when absent.
124
+ * `.git/info/exclude` and staged-links lines are appended only when absent.
75
125
  */
76
126
  export async function stageOrizuSkill(opts) {
77
127
  const dest = `${opts.workspaceDir}/.claude/skills/orizu`;
@@ -80,20 +130,32 @@ export async function stageOrizuSkill(opts) {
80
130
  ok: false,
81
131
  method: null,
82
132
  dest,
133
+ links: [],
83
134
  exitCode: 1,
84
135
  stdout: '',
85
136
  stderr: `unsafe workspaceDir — refusing to interpolate into a shell command: ${opts.workspaceDir}`,
86
137
  };
87
138
  }
88
139
  const result = await opts.exec(renderStageOrizuSkillScript(opts.workspaceDir));
89
- const out = result.stdout.trim();
90
- const method = out.startsWith('SYMLINK')
91
- ? 'symlink'
92
- : out.startsWith('COPY')
93
- ? 'copy'
94
- : out.startsWith('PRESERVED_TRACKED')
95
- ? 'preserved'
96
- : null;
97
- const ok = result.exitCode === 0 && method !== null;
98
- return { ok, method, dest, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr };
140
+ const lines = result.stdout.split('\n').map(line => line.trim()).filter(Boolean);
141
+ const links = lines.flatMap((line) => {
142
+ const [status, linkDest] = line.split(' ');
143
+ if (!linkDest)
144
+ return [];
145
+ const method = STATUS_METHODS[status] ?? null;
146
+ return [{ dest: linkDest, method }];
147
+ });
148
+ const ok = result.exitCode === 0
149
+ && !lines.includes('NO_SOURCE')
150
+ && links.length === ORIZU_SKILL_LINK_FOLDERS.length
151
+ && links.every(link => link.method !== null);
152
+ return {
153
+ ok,
154
+ method: links[0]?.method ?? null,
155
+ dest,
156
+ links,
157
+ exitCode: result.exitCode,
158
+ stdout: result.stdout,
159
+ stderr: result.stderr,
160
+ };
99
161
  }
package/dist/http.d.ts CHANGED
@@ -20,7 +20,8 @@ export type AuthenticatedRequestSource = 'ORIZU_TOKEN' | 'ORIZU_TOKEN_FILE' | 's
20
20
  export interface AuthenticatedRequestContext {
21
21
  readonly baseUrl: string;
22
22
  readonly source: AuthenticatedRequestSource;
23
- fetch(path: string, init?: RequestInit): Promise<Response>;
23
+ /** `onRequestStart` runs just before each request goes out, as with `authedFetch`. */
24
+ fetch(path: string, init?: RequestInit, onRequestStart?: () => void): Promise<Response>;
24
25
  logout(signal?: AbortSignal | null): Promise<LogoutResult>;
25
26
  }
26
27
  export interface LogoutResult {
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAWA,OAAO,EAAsC,WAAW,EAAoB,MAAM,mBAAmB,CAAA;AAerG,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,IAAI,CAEtD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,QAGhD;AAED,wBAAgB,cAAc,CAAC,KAAK,GAAE,WAA0B,GAAG,MAAM,CAiBxE;AAED,UAAU,YAAY;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAID,wBAAgB,mBAAmB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,CAMnE;AAED,UAAU,WAAW;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,OAAO,CAAA;CACnB;AAED,wBAAgB,kBAAkB,CAAC,KAAK,GAAE,WAA0B,GAAG,WAAW,CAajF;AAED,wBAAgB,mBAAmB,CAAC,KAAK,GAAE,WAA0B,GAAG,MAAM,CAE7E;AAED,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAWD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,QAmBzD;AA+FD,wBAAgB,+BAA+B,IAAI,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAE/E;AAmGD,MAAM,MAAM,0BAA0B,GAAG,aAAa,GAAG,kBAAkB,GAAG,QAAQ,CAAA;AAEtF,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAA;IAC3C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC1D,MAAM,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;CAC3D;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,sBAAsB,EAAE,OAAO,CAAA;CAChC;AAED;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAChD,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAO,GACpE,2BAA2B,CAgI7B;AAID,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,EAAE,cAAc,CAAC,EAAE,MAAM,IAAI,qBAqElG"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAWA,OAAO,EAAsC,WAAW,EAAoB,MAAM,mBAAmB,CAAA;AAerG,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,IAAI,CAEtD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,QAGhD;AAED,wBAAgB,cAAc,CAAC,KAAK,GAAE,WAA0B,GAAG,MAAM,CAiBxE;AAED,UAAU,YAAY;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAID,wBAAgB,mBAAmB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,CAMnE;AAED,UAAU,WAAW;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,OAAO,CAAA;CACnB;AAED,wBAAgB,kBAAkB,CAAC,KAAK,GAAE,WAA0B,GAAG,WAAW,CAajF;AAED,wBAAgB,mBAAmB,CAAC,KAAK,GAAE,WAA0B,GAAG,MAAM,CAE7E;AAED,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAWD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,QAmBzD;AA+FD,wBAAgB,+BAA+B,IAAI,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAE/E;AAmGD,MAAM,MAAM,0BAA0B,GAAG,aAAa,GAAG,kBAAkB,GAAG,QAAQ,CAAA;AAEtF,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAA;IAC3C,sFAAsF;IACtF,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,EAAE,cAAc,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IACvF,MAAM,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;CAC3D;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,sBAAsB,EAAE,OAAO,CAAA;CAChC;AAED;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAChD,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAO,GACpE,2BAA2B,CAoI7B;AAID,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,EAAE,cAAc,CAAC,EAAE,MAAM,IAAI,qBAqElG"}
package/dist/http.js CHANGED
@@ -248,13 +248,15 @@ export function captureAuthenticatedRequestContext(options = {}) {
248
248
  return {
249
249
  baseUrl,
250
250
  source,
251
- async fetch(path, init = {}) {
251
+ async fetch(path, init = {}, onRequestStart) {
252
+ const headers = {
253
+ ...(init.headers || {}),
254
+ Authorization: `Bearer ${resolveRequestBearer()}`,
255
+ };
256
+ onRequestStart?.();
252
257
  return await fetch(`${baseUrl}${path}`, {
253
258
  ...init,
254
- headers: {
255
- ...(init.headers || {}),
256
- Authorization: `Bearer ${resolveRequestBearer()}`,
257
- },
259
+ headers,
258
260
  ...credentialRequestRedirectPolicy(),
259
261
  });
260
262
  },
@@ -279,7 +281,7 @@ export function captureAuthenticatedRequestContext(options = {}) {
279
281
  return {
280
282
  baseUrl,
281
283
  source: 'stored',
282
- async fetch(path, init = {}) {
284
+ async fetch(path, init = {}, onRequestStart) {
283
285
  let didRefresh = false;
284
286
  if (allowRefresh
285
287
  && isSessionCredentials(activeCredentials)
@@ -287,6 +289,7 @@ export function captureAuthenticatedRequestContext(options = {}) {
287
289
  await refreshCapturedCredentials(init.signal);
288
290
  didRefresh = true;
289
291
  }
292
+ onRequestStart?.();
290
293
  let response = await fetch(`${baseUrl}${path}`, {
291
294
  ...init,
292
295
  headers: {
@@ -300,6 +303,7 @@ export function captureAuthenticatedRequestContext(options = {}) {
300
303
  && isSessionCredentials(activeCredentials)
301
304
  && !didRefresh) {
302
305
  await refreshCapturedCredentials(init.signal);
306
+ onRequestStart?.();
303
307
  response = await fetch(`${baseUrl}${path}`, {
304
308
  ...init,
305
309
  headers: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAwLA,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;AA4tCtD,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;AA6DD,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":";AAwLA,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;AAwtCtD,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;AA6DD,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
@@ -578,9 +578,13 @@ async function submitScoreResults() {
578
578
  console.warn(scoreInput.aggregateWarning);
579
579
  }
580
580
  const aggregatePayload = scoreInput.aggregate || {};
581
- const response = await authedFetch(`/api/cli/scores/submit?project=${encodeURIComponent(project)}`, {
582
- method: 'POST',
583
- headers: { 'Content-Type': 'application/json' },
581
+ const { sendScoreSave } = await import('./score-save-workflow.js');
582
+ const data = await sendScoreSave({
583
+ command: 'submit',
584
+ path: `/api/cli/scores/submit?project=${encodeURIComponent(project)}`,
585
+ project,
586
+ scorerVersionId,
587
+ saves: true,
584
588
  body: JSON.stringify({
585
589
  ...aggregatePayload,
586
590
  ...instructionSetProvenanceFields(project),
@@ -593,11 +597,7 @@ async function submitScoreResults() {
593
597
  candidateId: candidateId || undefined,
594
598
  resultsJsonl: scoreInput.resultsJsonl,
595
599
  }),
596
- });
597
- if (!response.ok) {
598
- throw new Error(`Failed to submit score: ${await response.text()}`);
599
- }
600
- const data = await parseJsonResponse(response, 'Score submit');
600
+ }, authedFetch);
601
601
  if (hasJsonFlag()) {
602
602
  printJson(data);
603
603
  return;
@@ -706,9 +706,13 @@ async function execScorer() {
706
706
  }
707
707
  const instructionSetProvenance = instructionSetProvenanceFields(project);
708
708
  const isNoSubmit = hasArg('--no-submit');
709
- const response = await authedFetch(`/api/cli/scorers/exec?project=${encodeURIComponent(project)}`, {
710
- method: 'POST',
711
- headers: { 'Content-Type': 'application/json' },
709
+ const { sendScoreSave } = await import('./score-save-workflow.js');
710
+ const data = await sendScoreSave({
711
+ command: 'exec',
712
+ path: `/api/cli/scorers/exec?project=${encodeURIComponent(project)}`,
713
+ project,
714
+ scorerVersionId,
715
+ saves: !isNoSubmit,
712
716
  body: JSON.stringify({
713
717
  scorerVersionId,
714
718
  subjectPromptVersionId: subjectPromptVersionId || undefined,
@@ -724,11 +728,7 @@ async function execScorer() {
724
728
  dependencyResultsJsonl: readDependencyResultAssignments(getArg('--dependency-results')),
725
729
  ...instructionSetProvenance,
726
730
  }),
727
- });
728
- if (!response.ok) {
729
- throw new Error(`Failed to execute scorer: ${await response.text()}`);
730
- }
731
- const data = await parseJsonResponse(response, 'Scorer exec');
731
+ }, authedFetch);
732
732
  const noSubmitOutput = isNoSubmit
733
733
  ? noSubmitScorerOutput(data, instructionSetProvenance)
734
734
  : null;