@fitlab-ai/agent-infra 0.9.3 → 0.9.5

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 (203) hide show
  1. package/README.md +3 -1
  2. package/README.zh-CN.md +3 -1
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +144 -123
  6. package/dist/lib/agent-clients/adapter.js +94 -4
  7. package/dist/lib/{sandbox/credentials.js → agent-clients/adapters/claude-code-credentials.js} +4 -14
  8. package/dist/lib/agent-clients/adapters/claude-code-refresh.js +85 -0
  9. package/dist/lib/agent-clients/adapters/claude-code-sandbox.js +247 -0
  10. package/dist/lib/agent-clients/adapters/claude-code.js +17 -3
  11. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +382 -0
  12. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +163 -0
  13. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -0
  14. package/dist/lib/agent-clients/adapters/codex.js +2 -2
  15. package/dist/lib/agent-clients/adapters/opencode-sandbox.js +108 -0
  16. package/dist/lib/agent-clients/adapters/opencode.js +15 -3
  17. package/dist/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  18. package/dist/lib/agent-clients/registry.js +9 -1
  19. package/dist/lib/git/worktree-identity.js +64 -0
  20. package/dist/lib/internal/codex-lifecycle.js +202 -0
  21. package/dist/lib/internal/sandbox-control.js +24 -0
  22. package/dist/lib/internal/task-activity.js +45 -13
  23. package/dist/lib/internal/task-create.js +48 -0
  24. package/dist/lib/internal/task-ledger.js +12 -3
  25. package/dist/lib/internal/task-lifecycle.js +19 -1
  26. package/dist/lib/internal/task-orchestration.js +170 -27
  27. package/dist/lib/platform/github-client.js +19 -2
  28. package/dist/lib/platform/pr-checks.js +1 -1
  29. package/dist/lib/platform/pull-requests.js +47 -1
  30. package/dist/lib/platform/releases.js +13 -5
  31. package/dist/lib/process-data/index.js +239 -0
  32. package/dist/lib/process-data/normalize.js +198 -0
  33. package/dist/lib/process-data/privacy.js +33 -0
  34. package/dist/lib/process-data/reconcile.js +95 -0
  35. package/dist/lib/process-data/repair.js +58 -0
  36. package/dist/lib/process-data/sources.js +345 -0
  37. package/dist/lib/process-data/store.js +282 -0
  38. package/dist/lib/process-data/types.js +2 -0
  39. package/dist/lib/run/index.js +4 -3
  40. package/dist/lib/run/tui.js +7 -8
  41. package/dist/lib/sandbox/agent-client-reconciler.js +26 -2
  42. package/dist/lib/sandbox/capture.js +7 -2
  43. package/dist/lib/sandbox/commands/create.js +110 -228
  44. package/dist/lib/sandbox/commands/enter.js +15 -47
  45. package/dist/lib/sandbox/commands/list-running.js +16 -6
  46. package/dist/lib/sandbox/commands/ls.js +10 -6
  47. package/dist/lib/sandbox/commands/rebuild.js +4 -3
  48. package/dist/lib/sandbox/commands/refresh.js +4 -84
  49. package/dist/lib/sandbox/commands/rm.js +24 -3
  50. package/dist/lib/sandbox/commands/show.js +12 -3
  51. package/dist/lib/sandbox/commands/start.js +7 -4
  52. package/dist/lib/sandbox/config.js +2 -0
  53. package/dist/lib/sandbox/constants.js +6 -0
  54. package/dist/lib/sandbox/control/client.js +58 -0
  55. package/dist/lib/sandbox/control/protocol.js +54 -0
  56. package/dist/lib/sandbox/control/server.js +144 -0
  57. package/dist/lib/sandbox/dockerfile.js +36 -13
  58. package/dist/lib/sandbox/mounts.js +17 -4
  59. package/dist/lib/sandbox/recovery.js +128 -5
  60. package/dist/lib/sandbox/redaction.js +12 -0
  61. package/dist/lib/sandbox/runtimes/base.dockerfile +24 -74
  62. package/dist/lib/sandbox/workspace-identity.js +95 -0
  63. package/dist/lib/sandbox/workspace-view.js +109 -0
  64. package/dist/lib/task/activity-intent.js +257 -51
  65. package/dist/lib/task/activity-log.js +32 -5
  66. package/dist/lib/task/commit-finalization.js +278 -0
  67. package/dist/lib/task/commit-intent.js +165 -0
  68. package/dist/lib/task/create-service.js +137 -0
  69. package/dist/lib/task/create.js +333 -0
  70. package/dist/lib/task/decision-intents.js +31 -17
  71. package/dist/lib/task/delegation-receipts.js +2 -2
  72. package/dist/lib/task/implementation-inputs.js +54 -7
  73. package/dist/lib/task/ledger-intents.js +35 -1
  74. package/dist/lib/task/orchestration.js +505 -34
  75. package/dist/lib/task/resolve-ref.js +41 -1
  76. package/dist/lib/task/review-fingerprint.js +4 -0
  77. package/dist/lib/task/workspace-snapshot.js +78 -3
  78. package/lib/agent-clients/adapter.ts +130 -6
  79. package/lib/{sandbox/credentials.ts → agent-clients/adapters/claude-code-credentials.ts} +3 -18
  80. package/lib/agent-clients/adapters/claude-code-refresh.ts +128 -0
  81. package/lib/agent-clients/adapters/claude-code-sandbox.ts +295 -0
  82. package/lib/agent-clients/adapters/claude-code.ts +21 -3
  83. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +442 -0
  84. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +307 -0
  85. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +250 -0
  86. package/lib/agent-clients/adapters/codex.ts +2 -2
  87. package/lib/agent-clients/adapters/opencode-sandbox.ts +136 -0
  88. package/lib/agent-clients/adapters/opencode.ts +18 -3
  89. package/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  90. package/lib/agent-clients/registry.ts +9 -1
  91. package/lib/git/worktree-identity.ts +86 -0
  92. package/lib/internal/codex-lifecycle.ts +204 -0
  93. package/lib/internal/sandbox-control.ts +24 -0
  94. package/lib/internal/task-activity.ts +50 -14
  95. package/lib/internal/task-create.ts +49 -0
  96. package/lib/internal/task-ledger.ts +12 -3
  97. package/lib/internal/task-lifecycle.ts +22 -1
  98. package/lib/internal/task-orchestration.ts +117 -21
  99. package/lib/platform/github-client.ts +20 -2
  100. package/lib/platform/pr-checks.ts +1 -1
  101. package/lib/platform/pull-requests.ts +49 -1
  102. package/lib/platform/releases.ts +16 -4
  103. package/lib/process-data/index.ts +224 -0
  104. package/lib/process-data/normalize.ts +202 -0
  105. package/lib/process-data/privacy.ts +41 -0
  106. package/lib/process-data/reconcile.ts +110 -0
  107. package/lib/process-data/repair.ts +64 -0
  108. package/lib/process-data/sources.ts +354 -0
  109. package/lib/process-data/store.ts +315 -0
  110. package/lib/process-data/types.ts +135 -0
  111. package/lib/run/index.ts +5 -3
  112. package/lib/run/tui.ts +11 -8
  113. package/lib/sandbox/agent-client-reconciler.ts +44 -2
  114. package/lib/sandbox/capture.ts +9 -2
  115. package/lib/sandbox/commands/create.ts +127 -260
  116. package/lib/sandbox/commands/enter.ts +13 -59
  117. package/lib/sandbox/commands/list-running.ts +23 -6
  118. package/lib/sandbox/commands/ls.ts +22 -6
  119. package/lib/sandbox/commands/rebuild.ts +4 -3
  120. package/lib/sandbox/commands/refresh.ts +4 -126
  121. package/lib/sandbox/commands/rm.ts +24 -3
  122. package/lib/sandbox/commands/show.ts +29 -3
  123. package/lib/sandbox/commands/start.ts +6 -4
  124. package/lib/sandbox/config.ts +4 -0
  125. package/lib/sandbox/constants.ts +8 -0
  126. package/lib/sandbox/control/client.ts +79 -0
  127. package/lib/sandbox/control/protocol.ts +110 -0
  128. package/lib/sandbox/control/server.ts +155 -0
  129. package/lib/sandbox/dockerfile.ts +66 -13
  130. package/lib/sandbox/mounts.ts +24 -4
  131. package/lib/sandbox/recovery.ts +163 -6
  132. package/lib/sandbox/redaction.ts +14 -0
  133. package/lib/sandbox/runtimes/base.dockerfile +24 -74
  134. package/lib/sandbox/tool-types.ts +16 -0
  135. package/lib/sandbox/workspace-identity.ts +123 -0
  136. package/lib/sandbox/workspace-view.ts +150 -0
  137. package/lib/task/activity-intent.ts +350 -61
  138. package/lib/task/activity-log.ts +46 -7
  139. package/lib/task/commit-finalization.ts +376 -0
  140. package/lib/task/commit-intent.ts +230 -0
  141. package/lib/task/create-service.ts +171 -0
  142. package/lib/task/create.ts +372 -0
  143. package/lib/task/decision-intents.ts +32 -12
  144. package/lib/task/delegation-receipts.ts +2 -2
  145. package/lib/task/implementation-inputs.ts +62 -8
  146. package/lib/task/ledger-intents.ts +43 -3
  147. package/lib/task/orchestration.ts +673 -41
  148. package/lib/task/resolve-ref.ts +46 -1
  149. package/lib/task/review-fingerprint.ts +9 -0
  150. package/lib/task/workspace-snapshot.ts +98 -4
  151. package/package.json +7 -7
  152. package/templates/.agents/hooks/lifecycle-delegation.js +47 -3
  153. package/templates/.agents/rules/README.en.md +1 -1
  154. package/templates/.agents/rules/README.zh-CN.md +1 -1
  155. package/templates/.agents/rules/human-decision-context.en.md +11 -2
  156. package/templates/.agents/rules/human-decision-context.zh-CN.md +11 -2
  157. package/templates/.agents/rules/pr-checks-commands.en.md +1 -1
  158. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  159. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +2 -2
  160. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +1 -1
  161. package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
  162. package/templates/.agents/rules/task-management.en.md +3 -0
  163. package/templates/.agents/rules/task-management.zh-CN.md +3 -0
  164. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  165. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
  166. package/templates/.agents/skills/commit/SKILL.en.md +21 -17
  167. package/templates/.agents/skills/commit/SKILL.zh-CN.md +28 -16
  168. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +50 -0
  169. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +56 -0
  170. package/templates/.agents/skills/commit/reference/task-status-update.en.md +4 -4
  171. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +4 -4
  172. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  173. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  174. package/templates/.agents/skills/create-pr/SKILL.en.md +4 -4
  175. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +4 -4
  176. package/templates/.agents/skills/create-task/SKILL.en.md +26 -43
  177. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +26 -43
  178. package/templates/.agents/skills/create-task/reference/context-capture.en.md +34 -0
  179. package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +34 -0
  180. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  181. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  182. package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
  183. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
  184. package/templates/.agents/skills/review-pr/SKILL.en.md +36 -8
  185. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +36 -8
  186. package/templates/.agents/skills/review-pr/reference/report-template.en.md +1 -1
  187. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +1 -1
  188. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +67 -50
  189. package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
  190. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
  191. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +2 -2
  192. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +2 -2
  193. package/templates/.agents/templates/task.en.md +1 -1
  194. package/templates/.agents/templates/task.zh-CN.md +1 -1
  195. package/templates/.claude/commands/watch-pr.en.md +1 -1
  196. package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
  197. package/templates/.claude/rules/tool-preferences.en.md +20 -0
  198. package/templates/.claude/rules/tool-preferences.zh-CN.md +20 -0
  199. package/templates/.codex/hooks.json +50 -0
  200. package/templates/.opencode/commands/watch-pr.en.md +1 -1
  201. package/templates/.opencode/commands/watch-pr.zh-CN.md +1 -1
  202. package/templates/.gemini/commands/_project_/review-pr.en.toml +0 -10
  203. package/templates/.gemini/commands/_project_/review-pr.zh-CN.toml +0 -10
@@ -0,0 +1,295 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import type {
4
+ AgentClientSandboxHook,
5
+ SandboxHookCreateContext,
6
+ SandboxHookEnterContext
7
+ } from '../../sandbox/tool-types.ts';
8
+ import {
9
+ formatCredentialWarnings,
10
+ formatRemaining,
11
+ hasClaudeProviderAuth,
12
+ inspectClaudeKeychainStatus,
13
+ prepareClaudeCredentials,
14
+ reconcileClaudeCredentials,
15
+ redactCommandError,
16
+ validateClaudeCredentialsEnvOverride
17
+ } from './claude-code-credentials.ts';
18
+
19
+ type JsonObject = Record<string, unknown>;
20
+
21
+ function readHostJsonSafe(filePath: string): JsonObject | null {
22
+ if (!filePath || !fs.existsSync(filePath)) return null;
23
+ try {
24
+ const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8')) as unknown;
25
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
26
+ ? parsed as JsonObject
27
+ : null;
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ const CLAUDE_SETTINGS_INHERIT_TOP_LEVEL_KEYS = [
34
+ 'model',
35
+ 'fallbackModel',
36
+ 'availableModels',
37
+ 'modelOverrides',
38
+ 'enforceAvailableModels',
39
+ 'advisorModel',
40
+ 'apiKeyHelper',
41
+ 'effortLevel'
42
+ ];
43
+
44
+ function isJsonObjectRecord(value: unknown): value is JsonObject {
45
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
46
+ }
47
+
48
+ function mergeMissingStringEnvFields(target: JsonObject, source: JsonObject): boolean {
49
+ if (!isJsonObjectRecord(source.env)) return false;
50
+ if (Object.hasOwn(target, 'env') && !isJsonObjectRecord(target.env)) return false;
51
+
52
+ let targetEnv = target.env as JsonObject | undefined;
53
+ let changed = false;
54
+ for (const [key, value] of Object.entries(source.env)) {
55
+ if (typeof value !== 'string' || value === '') continue;
56
+ if (!targetEnv) {
57
+ targetEnv = {};
58
+ target.env = targetEnv;
59
+ }
60
+ if (!Object.hasOwn(targetEnv, key)) {
61
+ targetEnv[key] = value;
62
+ changed = true;
63
+ }
64
+ }
65
+ return changed;
66
+ }
67
+
68
+ function mergeMissingTopLevelSettings(target: JsonObject, source: JsonObject): boolean {
69
+ let changed = false;
70
+ for (const key of CLAUDE_SETTINGS_INHERIT_TOP_LEVEL_KEYS) {
71
+ if (!Object.hasOwn(source, key) || Object.hasOwn(target, key)) continue;
72
+ const value = source[key];
73
+ if (value === null || value === undefined || value === '') continue;
74
+ target[key] = value;
75
+ changed = true;
76
+ }
77
+ return changed;
78
+ }
79
+
80
+ function requireCreateContext(
81
+ context: SandboxHookCreateContext | undefined,
82
+ hookId: string
83
+ ): SandboxHookCreateContext {
84
+ if (!context) throw new Error(`${hookId} requires create context`);
85
+ return context;
86
+ }
87
+
88
+ function requireEnterContext(
89
+ context: SandboxHookEnterContext | undefined,
90
+ hookId: string
91
+ ): SandboxHookEnterContext {
92
+ if (!context) throw new Error(`${hookId} requires enter context`);
93
+ return context;
94
+ }
95
+
96
+ export function ensureClaudeOnboarding(toolDir: string, hostHomeDir?: string): void {
97
+ const claudeJsonPath = path.join(toolDir, '.claude.json');
98
+ let data: JsonObject & {
99
+ hasCompletedOnboarding?: boolean;
100
+ projects?: Record<string, { hasTrustDialogAccepted?: boolean }>;
101
+ model?: string;
102
+ } = {};
103
+ if (fs.existsSync(claudeJsonPath)) {
104
+ try {
105
+ data = JSON.parse(fs.readFileSync(claudeJsonPath, 'utf8')) as typeof data;
106
+ } catch {
107
+ // Malformed JSON is replaced with a valid managed sandbox baseline.
108
+ }
109
+ }
110
+ let changed = false;
111
+ if (!data.hasCompletedOnboarding) {
112
+ data.hasCompletedOnboarding = true;
113
+ changed = true;
114
+ }
115
+ if (!data.projects) {
116
+ data.projects = {};
117
+ changed = true;
118
+ }
119
+ if (!data.projects['/workspace']) {
120
+ data.projects['/workspace'] = {};
121
+ changed = true;
122
+ }
123
+ if (!data.projects['/workspace'].hasTrustDialogAccepted) {
124
+ data.projects['/workspace'].hasTrustDialogAccepted = true;
125
+ changed = true;
126
+ }
127
+ if (hostHomeDir) {
128
+ const hostClaudeJson = readHostJsonSafe(path.join(hostHomeDir, '.claude.json'));
129
+ if (
130
+ hostClaudeJson
131
+ && typeof hostClaudeJson.model === 'string'
132
+ && hostClaudeJson.model !== ''
133
+ && !Object.hasOwn(data, 'model')
134
+ ) {
135
+ data.model = hostClaudeJson.model;
136
+ changed = true;
137
+ }
138
+ if (hostClaudeJson) {
139
+ for (const key of Object.keys(hostClaudeJson)) {
140
+ if (
141
+ /^unpin.*LaunchEffort$/.test(key)
142
+ && hostClaudeJson[key] === true
143
+ && !Object.hasOwn(data, key)
144
+ ) {
145
+ data[key] = true;
146
+ changed = true;
147
+ }
148
+ }
149
+ }
150
+ }
151
+ if (changed) fs.writeFileSync(claudeJsonPath, JSON.stringify(data, null, 4), 'utf8');
152
+ }
153
+
154
+ export function ensureClaudeSettings(toolDir: string, hostHomeDir?: string): void {
155
+ const settingsPath = path.join(toolDir, 'settings.json');
156
+ let data: JsonObject & { skipDangerousModePermissionPrompt?: boolean } = {};
157
+ if (fs.existsSync(settingsPath)) {
158
+ try {
159
+ data = JSON.parse(fs.readFileSync(settingsPath, 'utf8')) as typeof data;
160
+ } catch {
161
+ // Malformed JSON is replaced with a valid managed sandbox baseline.
162
+ }
163
+ }
164
+ let changed = false;
165
+ if (data.skipDangerousModePermissionPrompt !== true) {
166
+ data.skipDangerousModePermissionPrompt = true;
167
+ changed = true;
168
+ }
169
+ if (hostHomeDir) {
170
+ const hostSettings = readHostJsonSafe(path.join(hostHomeDir, '.claude', 'settings.json'));
171
+ if (hostSettings) {
172
+ changed = mergeMissingStringEnvFields(data, hostSettings) || changed;
173
+ changed = mergeMissingTopLevelSettings(data, hostSettings) || changed;
174
+ }
175
+ }
176
+ if (changed) fs.writeFileSync(settingsPath, JSON.stringify(data, null, 4), 'utf8');
177
+ }
178
+
179
+ export function formatCredentialSyncStatus(
180
+ result: ReturnType<typeof reconcileClaudeCredentials>,
181
+ isTTY = process.stderr.isTTY,
182
+ providerAuthAvailable = false
183
+ ): string | null {
184
+ if (providerAuthAvailable && (result.status === 'STALE_ACCESS' || result.status === 'MISSING')) return null;
185
+ if (result.status === 'STALE_ACCESS') {
186
+ return 'Warning: Claude Code credentials on host appear stale. Run "ai sandbox refresh" or "claude /login" to renew.\n';
187
+ }
188
+ if (result.status === 'MISSING') {
189
+ return 'Warning: Claude Code credentials missing on host. Run "claude /login" to authenticate.\n';
190
+ }
191
+ if (result.status === 'KEYCHAIN_WRITE_FAILED') {
192
+ return `Warning: A sandbox refresh produced newer credentials but host Keychain write failed (${formatCredentialWarnings(result.warnings)}). Run "ai sandbox refresh" again or "claude /status" on the host to retry.\n`;
193
+ }
194
+ if (result.status === 'KEYCHAIN_LOCKED' || result.status === 'KEYCHAIN_ERROR') {
195
+ return 'Warning: Host keychain is unavailable; Claude credential sync skipped. Run "ai sandbox refresh" for details.\n';
196
+ }
197
+ if (result.status === 'OK' && result.authoritative !== 'host') {
198
+ const message = `Synced Claude Code credentials from sandbox refresh back to host (expires in ${formatRemaining(result.expiresAt)})`;
199
+ return isTTY ? `\x1b[2m${message}\x1b[0m\n` : `${message}\n`;
200
+ }
201
+ if (result.status === 'OK' && result.filesWritten.length > 0) {
202
+ const message = `Synced Claude Code credentials from host Keychain (expires in ${formatRemaining(result.expiresAt)})`;
203
+ return isTTY ? `\x1b[2m${message}\x1b[0m\n` : `${message}\n`;
204
+ }
205
+ return null;
206
+ }
207
+
208
+ const claudeCodeCredentialPreflightHook: AgentClientSandboxHook = {
209
+ id: 'claude-code-credential-preflight',
210
+ phase: 'prepare',
211
+ run: async ({ create }) => {
212
+ const { hostHome, hostEnv, project, resolvedTools } = requireCreateContext(
213
+ create,
214
+ 'claude-code-credential-preflight'
215
+ );
216
+ try {
217
+ validateClaudeCredentialsEnvOverride(hostEnv);
218
+ const outcome = prepareClaudeCredentials(
219
+ hostHome,
220
+ project,
221
+ [...resolvedTools],
222
+ undefined,
223
+ undefined,
224
+ (home) => inspectClaudeKeychainStatus(home, undefined, { envFn: () => ({ ...hostEnv }) })
225
+ );
226
+ if (outcome.status === 'SKIPPED') {
227
+ return {
228
+ status: 'warning',
229
+ message: 'Claude Code credentials not found on host - creating this sandbox WITHOUT Claude Code credentials.\n'
230
+ + ' Claude Code is still installed in the image but will not be authenticated.\n'
231
+ + ' To enable it: run "claude" once on the host to complete login, then re-run "ai sandbox create".'
232
+ };
233
+ }
234
+ return { status: 'ready' };
235
+ } catch (error) {
236
+ return {
237
+ status: 'fatal',
238
+ message: redactCommandError(error instanceof Error ? error.message : 'unknown credential error')
239
+ };
240
+ }
241
+ }
242
+ };
243
+
244
+ const claudeCodeBeforeContainerCreateHook: AgentClientSandboxHook = {
245
+ id: 'claude-code-before-container-create',
246
+ phase: 'before-container-create',
247
+ run: async ({ create }) => {
248
+ const { hostHome, resolvedTools } = requireCreateContext(
249
+ create,
250
+ 'claude-code-before-container-create'
251
+ );
252
+ const claudeCodeEntry = resolvedTools.find(({ tool }) => tool.id === 'claude-code');
253
+ if (claudeCodeEntry) {
254
+ ensureClaudeOnboarding(claudeCodeEntry.dir, hostHome);
255
+ ensureClaudeSettings(claudeCodeEntry.dir, hostHome);
256
+ }
257
+ return { status: 'ready' };
258
+ }
259
+ };
260
+
261
+ const claudeCodeBeforeEnterHook: AgentClientSandboxHook = {
262
+ id: 'claude-code-before-enter',
263
+ phase: 'before-enter',
264
+ run: async ({ enter }) => {
265
+ const { hostHome, hostEnv } = requireEnterContext(enter, 'claude-code-before-enter');
266
+ try {
267
+ validateClaudeCredentialsEnvOverride(hostEnv);
268
+ } catch (error) {
269
+ return {
270
+ status: 'fatal',
271
+ message: redactCommandError(error instanceof Error ? error.message : 'invalid credentials override')
272
+ };
273
+ }
274
+ try {
275
+ const providerAuthAvailable = hasClaudeProviderAuth(hostHome);
276
+ const result = reconcileClaudeCredentials(hostHome, { envFn: () => ({ ...hostEnv }) });
277
+ const message = formatCredentialSyncStatus(result, process.stderr.isTTY, providerAuthAvailable);
278
+ return {
279
+ status: 'ready',
280
+ ...(message === null ? {} : { message })
281
+ };
282
+ } catch (error) {
283
+ return {
284
+ status: 'warning',
285
+ message: `Failed to sync Claude Code credentials: ${redactCommandError(error instanceof Error ? error.message : 'unknown error')}`
286
+ };
287
+ }
288
+ }
289
+ };
290
+
291
+ export {
292
+ claudeCodeBeforeContainerCreateHook,
293
+ claudeCodeBeforeEnterHook,
294
+ claudeCodeCredentialPreflightHook
295
+ };
@@ -1,5 +1,10 @@
1
1
  import { defineAgentClientAdapter } from '../adapter.ts';
2
2
  import { hostJoin } from '../../sandbox/engines/wsl2-paths.ts';
3
+ import {
4
+ claudeCodeBeforeContainerCreateHook,
5
+ claudeCodeBeforeEnterHook,
6
+ claudeCodeCredentialPreflightHook
7
+ } from './claude-code-sandbox.ts';
3
8
 
4
9
  const claudeCodeAdapter = defineAgentClientAdapter({
5
10
  id: 'claude-code',
@@ -26,7 +31,7 @@ const claudeCodeAdapter = defineAgentClientAdapter({
26
31
  },
27
32
  project: {
28
33
  ownedPathPrefixes: ['.claude/'],
29
- managed: ['.claude/commands/', '.claude/agents/'],
34
+ managed: ['.claude/commands/', '.claude/agents/', '.claude/rules/'],
30
35
  merged: ['.claude/settings.json'],
31
36
  ejected: [],
32
37
  seedCommands: [{
@@ -35,9 +40,18 @@ const claudeCodeAdapter = defineAgentClientAdapter({
35
40
  'zh-CN': '.claude/commands/update-agent-infra.zh-CN.md'
36
41
  },
37
42
  target: '.claude/commands/update-agent-infra.md'
38
- }]
43
+ }],
44
+ customCommand: {
45
+ target: '.claude/commands/${skillName}.md',
46
+ frontmatter: {},
47
+ includeUsage: true,
48
+ inheritDisableModelInvocation: true
49
+ }
39
50
  },
40
51
  sandbox: {
52
+ image: {
53
+ dockerfileFragment: 'claude-code'
54
+ },
41
55
  createTool: ({ home, project }) => ({
42
56
  id: 'claude-code',
43
57
  name: 'Claude Code',
@@ -72,7 +86,11 @@ const claudeCodeAdapter = defineAgentClientAdapter({
72
86
  { name: 'claude-yolo', command: 'claude --dangerously-skip-permissions; tput ed' },
73
87
  { name: 'cy', command: 'claude --dangerously-skip-permissions; tput ed' }
74
88
  ],
75
- hooks: []
89
+ hooks: [
90
+ claudeCodeCredentialPreflightHook,
91
+ claudeCodeBeforeContainerCreateHook,
92
+ claudeCodeBeforeEnterHook
93
+ ]
76
94
  }
77
95
  });
78
96