@alecsibilia/luca 14.0.0-alpha.24 → 14.0.0-alpha.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/board/package.json +1 -1
- package/board/server/luca-version.ts +1 -1
- package/engine/agents/claude-options.ts +5 -1
- package/engine/agents/launch-paths.ts +41 -1
- package/engine/agents/launch-session.ts +10 -1
- package/engine/guards/sandbox-settings.ts +22 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/board/package.json
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The Luca version of this board, stamped when the package was packed. */
|
|
2
|
-
export const LUCA_VERSION: string = "14.0.0-alpha.
|
|
2
|
+
export const LUCA_VERSION: string = "14.0.0-alpha.25"
|
|
@@ -173,7 +173,7 @@ export const resultJsonSchema = ({
|
|
|
173
173
|
* hooks.
|
|
174
174
|
*
|
|
175
175
|
* @example
|
|
176
|
-
* const options = agentOptions({ role, may_edit_tests, cwd, model: CLAUDE_MODEL, effort: 'xhigh', claude_path, config, common_git_dir, home, env, guard_hook, luca_server: null, delivery_hook: null })
|
|
176
|
+
* const options = agentOptions({ role, may_edit_tests, cwd, model: CLAUDE_MODEL, effort: 'xhigh', claude_path, config, common_git_dir, home, user_temp_dir, env, guard_hook, luca_server: null, delivery_hook: null })
|
|
177
177
|
*/
|
|
178
178
|
export const agentOptions = ({
|
|
179
179
|
role,
|
|
@@ -185,6 +185,7 @@ export const agentOptions = ({
|
|
|
185
185
|
config,
|
|
186
186
|
common_git_dir,
|
|
187
187
|
home,
|
|
188
|
+
user_temp_dir,
|
|
188
189
|
env,
|
|
189
190
|
guard_hook,
|
|
190
191
|
luca_server,
|
|
@@ -203,6 +204,8 @@ export const agentOptions = ({
|
|
|
203
204
|
config: EngineConfig
|
|
204
205
|
common_git_dir: string
|
|
205
206
|
home: string
|
|
207
|
+
/** macOS's per-user temp folder (`darwinUserTempDir`), or `null`. */
|
|
208
|
+
user_temp_dir: string | null
|
|
206
209
|
env: Record<string, string>
|
|
207
210
|
guard_hook: HookCallback
|
|
208
211
|
/** The engine's `luca` server (`createLucaServer`), or `null` for none. */
|
|
@@ -253,6 +256,7 @@ export const agentOptions = ({
|
|
|
253
256
|
worktree: cwd,
|
|
254
257
|
common_git_dir,
|
|
255
258
|
home,
|
|
259
|
+
user_temp_dir,
|
|
256
260
|
config,
|
|
257
261
|
}),
|
|
258
262
|
env,
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Where the Claude launcher finds things on disk: the real path of the
|
|
3
|
-
* `claude` binary,
|
|
3
|
+
* `claude` binary, the repo's common git dir (shared by its worktrees), and
|
|
4
|
+
* macOS's per-user temp folder.
|
|
4
5
|
*/
|
|
5
6
|
import { realpathSync } from 'node:fs'
|
|
7
|
+
import { isAbsolute } from 'node:path'
|
|
6
8
|
|
|
7
9
|
import { runCommand } from '../shell/run-command'
|
|
8
10
|
|
|
@@ -27,3 +29,41 @@ export const commonGitDir = async ({
|
|
|
27
29
|
}
|
|
28
30
|
return realpathSync(result.stdout.trim())
|
|
29
31
|
}
|
|
32
|
+
|
|
33
|
+
/** The command that prints macOS's per-user temp folder. */
|
|
34
|
+
export const GETCONF_USER_TEMP_DIR = ['getconf', 'DARWIN_USER_TEMP_DIR']
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The real path of macOS's per-user temp folder (`getconf
|
|
38
|
+
* DARWIN_USER_TEMP_DIR`, such as `/private/var/folders/ab/cd1234/T`), or
|
|
39
|
+
* `null` off macOS, or when `getconf` fails or names no folder there.
|
|
40
|
+
*
|
|
41
|
+
* A sandboxed writer needs it: Foundation stages every atomic write there,
|
|
42
|
+
* not in `$TMPDIR`, so `swift test` and `xcodebuild` can't rebuild without
|
|
43
|
+
* it ("unable to write manifest ... Operation not permitted").
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* const user_temp_dir = await darwinUserTempDir({ platform: process.platform, getconf: GETCONF_USER_TEMP_DIR })
|
|
47
|
+
*/
|
|
48
|
+
export const darwinUserTempDir = async ({
|
|
49
|
+
platform,
|
|
50
|
+
getconf,
|
|
51
|
+
}: {
|
|
52
|
+
platform: NodeJS.Platform
|
|
53
|
+
/** The command to ask; the launcher passes `GETCONF_USER_TEMP_DIR`. */
|
|
54
|
+
getconf: string[]
|
|
55
|
+
}): Promise<string | null> => {
|
|
56
|
+
if (platform !== 'darwin') return null
|
|
57
|
+
try {
|
|
58
|
+
const result = await runCommand({
|
|
59
|
+
cmd: getconf,
|
|
60
|
+
cwd: '/',
|
|
61
|
+
timeout_ms: 10_000,
|
|
62
|
+
})
|
|
63
|
+
const path = result.stdout.trim()
|
|
64
|
+
if (result.exit_code !== 0 || !isAbsolute(path)) return null
|
|
65
|
+
return realpathSync(path)
|
|
66
|
+
} catch {
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -25,7 +25,12 @@ import {
|
|
|
25
25
|
} from './claude-messages'
|
|
26
26
|
import { agentEnv, agentOptions, checkLaunchModel } from './claude-options'
|
|
27
27
|
import { createInputChannel } from './input-channel'
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
commonGitDir,
|
|
30
|
+
darwinUserTempDir,
|
|
31
|
+
GETCONF_USER_TEMP_DIR,
|
|
32
|
+
realClaudePath,
|
|
33
|
+
} from './launch-paths'
|
|
29
34
|
import { createDeliveryHook, createLucaServer } from './message-tool'
|
|
30
35
|
import {
|
|
31
36
|
nextOutcome,
|
|
@@ -116,6 +121,10 @@ export const createLaunch = ({
|
|
|
116
121
|
config,
|
|
117
122
|
common_git_dir: gitDir,
|
|
118
123
|
home: realpathSync(homedir()),
|
|
124
|
+
user_temp_dir: await darwinUserTempDir({
|
|
125
|
+
platform: process.platform,
|
|
126
|
+
getconf: GETCONF_USER_TEMP_DIR,
|
|
127
|
+
}),
|
|
119
128
|
env: agentEnv({ source: process.env }),
|
|
120
129
|
guard_hook: createGuardHook({
|
|
121
130
|
role: guardRoleOf({ role }),
|
|
@@ -2,6 +2,7 @@ import { isAbsolute, join } from 'node:path'
|
|
|
2
2
|
|
|
3
3
|
import type { SandboxSettings } from '@anthropic-ai/claude-agent-sdk'
|
|
4
4
|
|
|
5
|
+
import { isWriter } from './guard-roles'
|
|
5
6
|
import { LOCKFILES, type GuardRole } from './role-rules'
|
|
6
7
|
|
|
7
8
|
import type { EngineConfig } from '../config/engine-config'
|
|
@@ -37,8 +38,13 @@ const mustBeAbsolute = (paths: Record<string, string>): void => {
|
|
|
37
38
|
* learner, and a test-writer that may not edit tests may not write the
|
|
38
39
|
* worktree at all.
|
|
39
40
|
*
|
|
41
|
+
* Test-writers and implementers, who run the test commands, may also write
|
|
42
|
+
* macOS's per-user temp folder (`user_temp_dir`), where Foundation stages
|
|
43
|
+
* atomic writes: without it, `swift test` and `xcodebuild` can't rebuild.
|
|
44
|
+
* Nothing else is opened, and nothing at all without that folder.
|
|
45
|
+
*
|
|
40
46
|
* @example
|
|
41
|
-
* const sandbox = sandboxSettings({ role: 'implementer', may_edit_tests: false, worktree, common_git_dir, home, config })
|
|
47
|
+
* const sandbox = sandboxSettings({ role: 'implementer', may_edit_tests: false, worktree, common_git_dir, home, user_temp_dir, config })
|
|
42
48
|
*/
|
|
43
49
|
export const sandboxSettings = ({
|
|
44
50
|
role,
|
|
@@ -46,6 +52,7 @@ export const sandboxSettings = ({
|
|
|
46
52
|
worktree,
|
|
47
53
|
common_git_dir,
|
|
48
54
|
home,
|
|
55
|
+
user_temp_dir,
|
|
49
56
|
config,
|
|
50
57
|
}: {
|
|
51
58
|
role: GuardRole
|
|
@@ -56,9 +63,19 @@ export const sandboxSettings = ({
|
|
|
56
63
|
/** The repo's shared `.git` folder (`git rev-parse --git-common-dir`). */
|
|
57
64
|
common_git_dir: string
|
|
58
65
|
home: string
|
|
66
|
+
/**
|
|
67
|
+
* macOS's per-user temp folder (`darwinUserTempDir`), real and
|
|
68
|
+
* absolute, or `null` off macOS or when it can't be found.
|
|
69
|
+
*/
|
|
70
|
+
user_temp_dir: string | null
|
|
59
71
|
config: EngineConfig
|
|
60
72
|
}): SandboxSettings => {
|
|
61
|
-
mustBeAbsolute({
|
|
73
|
+
mustBeAbsolute({
|
|
74
|
+
worktree,
|
|
75
|
+
common_git_dir,
|
|
76
|
+
home,
|
|
77
|
+
...(user_temp_dir === null ? {} : { user_temp_dir }),
|
|
78
|
+
})
|
|
62
79
|
const git = [common_git_dir, join(worktree, '.git')]
|
|
63
80
|
const inWorktree = (patterns: string[]) =>
|
|
64
81
|
patterns.map((pattern) => join(worktree, pattern))
|
|
@@ -87,6 +104,9 @@ export const sandboxSettings = ({
|
|
|
87
104
|
allowLocalBinding: false,
|
|
88
105
|
},
|
|
89
106
|
filesystem: {
|
|
107
|
+
...(user_temp_dir !== null && isWriter(role)
|
|
108
|
+
? { allowWrite: [user_temp_dir] }
|
|
109
|
+
: {}),
|
|
90
110
|
denyWrite: [...git, ...byRole[role]],
|
|
91
111
|
denyRead: SECRET_PATHS.map((path) => join(home, path)),
|
|
92
112
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alecsibilia/luca",
|
|
3
3
|
"description": "Luca: turns a spec on GitHub Issues into a reviewed pull request, run from Paseo with Claude Code",
|
|
4
|
-
"version": "14.0.0-alpha.
|
|
4
|
+
"version": "14.0.0-alpha.25",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Alec Sibilia <sibilia.alec@gmail.com>",
|
|
7
7
|
"homepage": "https://github.com/asibilia/luca-framework#readme",
|