@edspencer/paddock 0.61.0 → 0.61.1
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/README.md +15 -4
- package/package.json +1 -1
- package/packages/server/dist/agent-errors.d.ts +41 -0
- package/packages/server/dist/agent-errors.js +127 -0
- package/packages/server/dist/app.js +22 -1
- package/packages/server/dist/claude-home.d.ts +68 -1
- package/packages/server/dist/claude-home.js +118 -7
- package/packages/server/dist/cli/args.d.ts +9 -1
- package/packages/server/dist/cli/args.js +21 -5
- package/packages/server/dist/cli/claude-home-choice.d.ts +44 -0
- package/packages/server/dist/cli/claude-home-choice.js +108 -0
- package/packages/server/dist/cli/paddock.js +51 -8
- package/packages/server/dist/sweep.js +13 -1
- package/packages/server/dist/transcripts.d.ts +15 -8
- package/packages/server/dist/transcripts.js +18 -0
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-CzMqTdUU.js → InstanceConfigPage-BgNXqAox.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-YSEZHWDe.js → MarkdownRenderer-DfD_UtqU.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-DLWxZbqK.js → ProjectRedirect-1EXdHXmO.js} +1 -1
- package/packages/web/dist/assets/{ProjectView-BFX8ff2n.js → ProjectView-DHb5vNUp.js} +3 -3
- package/packages/web/dist/assets/{ProjectsGrid-DLOeY_ww.js → ProjectsGrid-Br-EhLZE.js} +1 -1
- package/packages/web/dist/assets/{arc-BwC1ik-b.js → arc-C2Jekujs.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-CsVvXQWw.js → architectureDiagram-3BPJPVTR-CflCA-em.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-DXQk2bIa.js → blockDiagram-GPEHLZMM-Ds7GLNnC.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-DXiA26It.js → c4Diagram-AAUBKEIU-D4WyGO6I.js} +1 -1
- package/packages/web/dist/assets/channel-B1K_gwAH.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-Dt5nC02A.js → chunk-2J33WTMH-tQunO_M5.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-DcjnWvl6.js → chunk-4BX2VUAB-DT5dOWd2.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-CRa3LkU4.js → chunk-55IACEB6-CfTUqUEF.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-H3V-11qu.js → chunk-727SXJPM-sK5bOAA0.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-DPCrENYV.js → chunk-AQP2D5EJ-DcMgVe-P.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-C4UO0ZTc.js → chunk-FMBD7UC4-In3h_-7v.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-BtI37CnI.js → chunk-ND2GUHAM-DuaIeRaC.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-C5bTaK5j.js → chunk-QZHKN3VN-DeT5mR5T.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DYQQOKrW.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DYQQOKrW.js +2 -0
- package/packages/web/dist/assets/{core-DB3cpm5N.js → core-DgOgLvnM.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-q-cjTZO4.js → cose-bilkent-S5V4N54A-CyO-rznJ.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-DYEV5HFo.js → dagre-BM42HDAG-B_9Ihzgn.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-D16XCw78.js → diagram-2AECGRRQ-z5mvZ5YT.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-CeztryYE.js → diagram-5GNKFQAL-Bsz0cgfn.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-D5SLIMDS.js → diagram-KO2AKTUF-B29k5f4q.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-Bw-sVPxM.js → diagram-LMA3HP47-CFrwsHoI.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-DsRMITgx.js → diagram-OG6HWLK6-D9Sjc2HR.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-T8Qqc_ku.js → erDiagram-TEJ5UH35-C3vbkD7M.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-Cyx47UWU.js → flowDiagram-I6XJVG4X-DkMIgntd.js} +1 -1
- package/packages/web/dist/assets/{format-D2I0duZa.js → format-BFDqyFuw.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-DxYJmG9o.js → ganttDiagram-6RSMTGT7-nvHanjpW.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-COK5YOTD.js → gitGraphDiagram-PVQCEYII-DbCNfOu7.js} +1 -1
- package/packages/web/dist/assets/{index-wBXQMkGI.js → index-DGCs1YIO.js} +2 -2
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-BamRN_Nn.js → infoDiagram-5YYISTIA-aJjH9x3D.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-DuOHdpEH.js → ishikawaDiagram-YF4QCWOH-DoifwNnZ.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-Bri7GKTW.js → journeyDiagram-JHISSGLW-Cz9kG9HN.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-BdkgUnzI.js → kanban-definition-UN3LZRKU-BoXnfr7A.js} +1 -1
- package/packages/web/dist/assets/{linear-DOcNPCpB.js → linear-BlTTO9yR.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-D9SbVBpN.js → mermaid.core-YAG8eSPQ.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-CVmLm7zn.js → mindmap-definition-RKZ34NQL-DtUBKfkr.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-D0N10eVU.js → pieDiagram-4H26LBE5-B3Rwpmy0.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-B5gnYdTa.js → quadrantDiagram-W4KKPZXB-B8eJtTyk.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-CwtxezNZ.js → requirementDiagram-4Y6WPE33-CpAluiY9.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-D79O3cux.js → sankeyDiagram-5OEKKPKP-Cl_Rd1tN.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-xhKcI6tI.js → sequenceDiagram-3UESZ5HK-CRpqKSGm.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-DBYMAjcX.js → stateDiagram-AJRCARHV-h3h8gXv_.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-BKRI9TnH.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-e-MYF4lm.js → timeline-definition-PNZ67QCA-PVr5V_pG.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-B8UsvRDM.js → vennDiagram-CIIHVFJN-De-4NPAX.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-1ucACjTl.js → wardley-L42UT6IY-Dtv10D2d.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-DJufLd_u.js → wardleyDiagram-YWT4CUSO-p2fJuQ6C.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-qqaZNlu9.js → xychartDiagram-2RQKCTM6-DyYF-k92.js} +1 -1
- package/packages/web/dist/index.html +1 -1
- package/packages/web/dist/sw.js +1 -1
- package/packages/web/dist/assets/channel-DDgTXrGP.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DU1kd_Vu.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DU1kd_Vu.js +0 -2
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CZtr9Jvq.js +0 -2
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which Claude home the INTERACTIVE CLI runs against (#683).
|
|
3
|
+
*
|
|
4
|
+
* ## The problem this exists for
|
|
5
|
+
*
|
|
6
|
+
* Since #620 paddock owns `<dataDir>/claude-home`, and herdctl therefore hands
|
|
7
|
+
* Claude Code a `CLAUDE_CONFIG_DIR`. Claude Code derives its **secure-storage
|
|
8
|
+
* service name** from whether that variable is SET AT ALL — unset gets the plain
|
|
9
|
+
* name, set gets a path-hash suffix — so a macOS login held in the Keychain under
|
|
10
|
+
* the plain name becomes invisible the moment paddock points at its own home.
|
|
11
|
+
*
|
|
12
|
+
* #620 anticipated this and bridges credentials by symlinking
|
|
13
|
+
* `~/.claude/.credentials.json` in. That is the FILE-based store: it covers Linux
|
|
14
|
+
* and the Docker image and is structurally incapable of covering macOS, because a
|
|
15
|
+
* Keychain entry cannot be symlinked. Net effect on a Mac: `npx paddock --here`
|
|
16
|
+
* boots, the UI loads, and every turn fails with `Not logged in`, two directories
|
|
17
|
+
* away from a perfectly good login.
|
|
18
|
+
*
|
|
19
|
+
* ## Why the fix is a home choice and not a credential copy
|
|
20
|
+
*
|
|
21
|
+
* There is exactly one lever. herdctl injects `CLAUDE_CONFIG_DIR` only when the
|
|
22
|
+
* configured home differs from `~/.claude` — it documents that same service-name
|
|
23
|
+
* branch as its reason for staying quiet otherwise. So the only way to get the
|
|
24
|
+
* plain service name, and with it the user's Keychain login, is to run against
|
|
25
|
+
* `~/.claude` itself.
|
|
26
|
+
*
|
|
27
|
+
* The alternative — reading the Keychain secret and writing it to disk as
|
|
28
|
+
* `.credentials.json` — is deliberately NOT done anywhere in this codebase. It
|
|
29
|
+
* downgrades OS-protected storage to a plaintext file, and it would diverge the
|
|
30
|
+
* moment Claude Code refreshed the token.
|
|
31
|
+
*
|
|
32
|
+
* ## Why the interactive CLI, and only the interactive CLI
|
|
33
|
+
*
|
|
34
|
+
* `--here` means *my* directory, *my* sessions, *my* login; isolation is a server
|
|
35
|
+
* concern. A daemon, the container image and `node dist/index.js` all keep the
|
|
36
|
+
* owned home — nothing here runs for them, and `resolveClaudeHome`'s precedence
|
|
37
|
+
* is untouched. This module only decides what the CLI puts in `CLAUDE_HOME`
|
|
38
|
+
* before it hands over, which is the same mechanism a user has always had.
|
|
39
|
+
*
|
|
40
|
+
* ## What continuity costs
|
|
41
|
+
*
|
|
42
|
+
* Running against the user's home means paddock plants no transcript symlink
|
|
43
|
+
* there (#682, and the reason this was blocked on it), so transcripts stay in
|
|
44
|
+
* `~/.claude/projects/<encoded-cwd>/` rather than being relocated into the
|
|
45
|
+
* workspace's `.chats/`. Chats still list, open, resume and rename — paddock and
|
|
46
|
+
* the engine read the one home — but the workspace directory is no longer
|
|
47
|
+
* self-contained. The CLI says so out loud when it makes this choice; see
|
|
48
|
+
* `paddock.ts`.
|
|
49
|
+
*
|
|
50
|
+
* What it does NOT change, contrary to the obvious guess: **import consent**.
|
|
51
|
+
* Reading the home a user's sessions live in does not make them paddock's chats.
|
|
52
|
+
* Session discovery is attribution-based, so a transcript paddock never ran is
|
|
53
|
+
* offered by `adoptable-chats` and absent from `chats` — exactly as under an
|
|
54
|
+
* owned home — and #663's confirmation still gates every import. Checked against
|
|
55
|
+
* a running instance rather than reasoned about, because the reasoning went the
|
|
56
|
+
* other way.
|
|
57
|
+
*/
|
|
58
|
+
import path from "node:path";
|
|
59
|
+
/** Env vars that authenticate Claude Code without any on-disk credential. */
|
|
60
|
+
const TOKEN_ENV_VARS = ["CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"];
|
|
61
|
+
/** Variables that are an explicit choice of home, and are never overridden. */
|
|
62
|
+
const HOME_ENV_VARS = ["CLAUDE_HOME", "CLAUDE_CONFIG_DIR"];
|
|
63
|
+
/**
|
|
64
|
+
* Decide whether the interactive CLI should run against the user's own Claude
|
|
65
|
+
* home for credential continuity.
|
|
66
|
+
*
|
|
67
|
+
* Pure, so the whole decision table is testable on a platform that has no
|
|
68
|
+
* Keychain. Continuity is chosen only when all four hold:
|
|
69
|
+
*
|
|
70
|
+
* 1. **No explicit home in the environment.** `CLAUDE_HOME` or
|
|
71
|
+
* `CLAUDE_CONFIG_DIR` is somebody deciding this already; deferring to them is
|
|
72
|
+
* the same rule herdctl#423 follows.
|
|
73
|
+
* 2. **No token in the environment.** A token authenticates regardless of home,
|
|
74
|
+
* so the isolated home works and is strictly better — this is the shape every
|
|
75
|
+
* CI, container and server run has.
|
|
76
|
+
* 3. **`--isolated-claude-home` not passed.** The explicit opt-out.
|
|
77
|
+
* 4. **`~/.claude` actually exists.** With no user home there is no login to be
|
|
78
|
+
* continuous with, and the owned home self-initializes cleanly.
|
|
79
|
+
*
|
|
80
|
+
* Note what is NOT a condition: the platform. The isolation/continuity trade is
|
|
81
|
+
* the same argument on Linux, where it additionally makes the `.credentials.json`
|
|
82
|
+
* bridge unnecessary rather than merely insufficient. Branching on `darwin` would
|
|
83
|
+
* mean two behaviours to reason about and one of them untested on most machines.
|
|
84
|
+
*/
|
|
85
|
+
export function chooseClaudeHome(input) {
|
|
86
|
+
const setHomeVar = HOME_ENV_VARS.find((v) => (input.env[v] ?? "").trim().length > 0);
|
|
87
|
+
if (setHomeVar !== undefined) {
|
|
88
|
+
return { reason: `${setHomeVar} is set — honouring it` };
|
|
89
|
+
}
|
|
90
|
+
if (input.forceIsolated) {
|
|
91
|
+
return { reason: "--isolated-claude-home was passed" };
|
|
92
|
+
}
|
|
93
|
+
const tokenVar = TOKEN_ENV_VARS.find((v) => (input.env[v] ?? "").trim().length > 0);
|
|
94
|
+
if (tokenVar !== undefined) {
|
|
95
|
+
return { reason: `${tokenVar} is set — the isolated home can authenticate on its own` };
|
|
96
|
+
}
|
|
97
|
+
if (!input.userHomeExists) {
|
|
98
|
+
return { reason: `no ${input.userClaudeHome} — nothing to inherit` };
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
claudeHome: input.userClaudeHome,
|
|
102
|
+
reason: `using your existing Claude Code login and history in ${input.userClaudeHome}`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** `~/.claude`, given a home directory. Kept here so callers do not re-spell it. */
|
|
106
|
+
export function userClaudeHomePath(homedir) {
|
|
107
|
+
return path.join(homedir, ".claude");
|
|
108
|
+
}
|
|
@@ -24,6 +24,7 @@ import path from "node:path";
|
|
|
24
24
|
import { spawn } from "node:child_process";
|
|
25
25
|
import { fileURLToPath } from "node:url";
|
|
26
26
|
import { HERE_MARKER, isHereWorkspace, countClaudeSessions, ensureGitignored } from "./here.js";
|
|
27
|
+
import { chooseClaudeHome, userClaudeHomePath } from "./claude-home-choice.js";
|
|
27
28
|
import { CliError, USAGE, parseArgs, nodeVersionProblem, explainListenError, } from "./args.js";
|
|
28
29
|
/** This module's own directory: `<…>/packages/server/dist/cli`. */
|
|
29
30
|
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -114,12 +115,26 @@ function noteFirstRun(dataDir) {
|
|
|
114
115
|
* effects are announced rather than asked about. Printed only on the first
|
|
115
116
|
* `--here` in a directory; resuming an already-opened one says nothing.
|
|
116
117
|
*/
|
|
117
|
-
function announceHereConsent(dir, dataDir) {
|
|
118
|
+
function announceHereConsent(dir, dataDir, inheritedHome) {
|
|
118
119
|
console.log([
|
|
119
120
|
"",
|
|
120
121
|
` Opening ${dir} as a Paddock workspace.`,
|
|
121
122
|
` · ${path.relative(dir, dataDir) || HERE_MARKER}/ and .chats/ created here, and added to .gitignore`,
|
|
122
|
-
|
|
123
|
+
// Import consent is UNCHANGED by continuity, which is worth stating
|
|
124
|
+
// because it is not what you would predict: reading the user's home does
|
|
125
|
+
// not turn their sessions into paddock's chats. Verified against a running
|
|
126
|
+
// instance — a pre-existing transcript for the directory comes back from
|
|
127
|
+
// `adoptable-chats` (offered) and NOT from `chats` (listed), because
|
|
128
|
+
// session discovery is attribution-based, not "whatever is in the folder".
|
|
129
|
+
// So #663's confirmation still gates every import.
|
|
130
|
+
//
|
|
131
|
+
// What continuity DOES change is where transcripts live: with no symlink
|
|
132
|
+
// planted in a home paddock does not own (#682), Claude Code keeps writing
|
|
133
|
+
// to `~/.claude/projects/<encoded-cwd>/` and `.chats/` stays empty.
|
|
134
|
+
inheritedHome === undefined
|
|
135
|
+
? " · ~/.claude sessions for this directory are offered for import (nothing is moved)"
|
|
136
|
+
: ` · running against your ${inheritedHome}: sessions there are offered for import ` +
|
|
137
|
+
`(nothing is moved), and new transcripts stay there rather than in .chats/`,
|
|
123
138
|
" Later runs here resume it — no flag needed.",
|
|
124
139
|
"",
|
|
125
140
|
].join("\n"));
|
|
@@ -204,6 +219,20 @@ async function main() {
|
|
|
204
219
|
// is `.git`. A flagless run anywhere else touches nothing.
|
|
205
220
|
const resuming = !opts.here && isHereWorkspace(cwd);
|
|
206
221
|
const hereMode = opts.here || resuming;
|
|
222
|
+
// Credential continuity (#683). Decided HERE, in the interactive CLI, and
|
|
223
|
+
// nowhere else: `resolveClaudeHome`'s precedence is untouched, so a server, a
|
|
224
|
+
// container and `node dist/index.js` all keep the owned home. All this does is
|
|
225
|
+
// set the same `CLAUDE_HOME` a user could set by hand — the difference is that
|
|
226
|
+
// on macOS they currently have no way to know they need to.
|
|
227
|
+
const userHome = userClaudeHomePath(os.homedir());
|
|
228
|
+
const homeChoice = chooseClaudeHome({
|
|
229
|
+
env: process.env,
|
|
230
|
+
userClaudeHome: userHome,
|
|
231
|
+
userHomeExists: fs.existsSync(userHome),
|
|
232
|
+
forceIsolated: opts.isolatedClaudeHome,
|
|
233
|
+
});
|
|
234
|
+
if (homeChoice.claudeHome !== undefined)
|
|
235
|
+
process.env.CLAUDE_HOME = homeChoice.claudeHome;
|
|
207
236
|
// Apply CLI defaults as env vars. This is the whole integration surface: the
|
|
208
237
|
// server resolves config inside `buildApp()`, so anything set before the
|
|
209
238
|
// dynamic import below is picked up with no special-casing in config.ts.
|
|
@@ -213,7 +242,7 @@ async function main() {
|
|
|
213
242
|
(hereMode ? path.join(cwd, HERE_MARKER) : path.join(os.homedir(), ".paddock")));
|
|
214
243
|
if (hereMode) {
|
|
215
244
|
if (!resuming)
|
|
216
|
-
announceHereConsent(cwd, dataDir);
|
|
245
|
+
announceHereConsent(cwd, dataDir, homeChoice.claudeHome);
|
|
217
246
|
// The root workspace IS `projectsRoot` (its key is ""), so pointing this at
|
|
218
247
|
// the user's directory makes that directory the workspace. No schema change,
|
|
219
248
|
// no new project type — see cli/here.ts.
|
|
@@ -239,19 +268,27 @@ async function main() {
|
|
|
239
268
|
// `[fleet-manager] …` lines via console.info — pino's
|
|
240
269
|
// level cannot reach those.
|
|
241
270
|
// An explicit value for either is left alone, and `--verbose` skips both.
|
|
271
|
+
// A LEVEL is not enough, which is what #684 was about. Background job failures
|
|
272
|
+
// are logged at `error` — level 50, above every threshold either variable can
|
|
273
|
+
// set — and one of them is a bare `console.error` inside the engine. So quiet
|
|
274
|
+
// mode is also stated as a fact the server can act on: `agent-errors.ts` reads
|
|
275
|
+
// this to collapse a recognised, non-fatal failure to its cause instead of
|
|
276
|
+
// printing a stack trace with 2 KB of system prompt in it. Internal to the
|
|
277
|
+
// CLI; `--verbose` leaves it unset and nothing is suppressed.
|
|
242
278
|
if (!opts.verbose) {
|
|
243
279
|
if (process.env.LOG_LEVEL === undefined)
|
|
244
280
|
process.env.LOG_LEVEL = "warn";
|
|
245
281
|
if (process.env.HERDCTL_LOG_LEVEL === undefined)
|
|
246
282
|
process.env.HERDCTL_LOG_LEVEL = "warn";
|
|
283
|
+
process.env.PADDOCK_QUIET = "1";
|
|
247
284
|
}
|
|
248
285
|
// No credential preflight here: `ensureClaudeHome` (claude-home.ts) already
|
|
249
286
|
// warns at boot, and it is the only check that can be right. It tests the home
|
|
250
|
-
// paddock will ACTUALLY use —
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
//
|
|
287
|
+
// paddock will ACTUALLY use — after the bridge has symlinked in any
|
|
288
|
+
// `~/.claude/.credentials.json`, and, on darwin, after probing the Keychain for
|
|
289
|
+
// the login `CLAUDE_CONFIG_DIR` scoping hides (#683). It logs at `warn`, which
|
|
290
|
+
// survives the quiet default set just above. Continuity (chosen above) usually
|
|
291
|
+
// means it has nothing to say, because there is nothing left to bridge.
|
|
255
292
|
const port = process.env.PORT ?? "4000";
|
|
256
293
|
const host = process.env.HOST ?? "127.0.0.1";
|
|
257
294
|
// Imported dynamically, AFTER the env above is set: a static import would
|
|
@@ -271,6 +308,12 @@ async function main() {
|
|
|
271
308
|
"",
|
|
272
309
|
` Paddock is running at ${url}`,
|
|
273
310
|
` Workspace: ${hereMode ? cwd : dataDir}${resuming ? " (resumed)" : ""}`,
|
|
311
|
+
// Same rule as the workspace line: running against somebody's own Claude
|
|
312
|
+
// home is a real difference in what this instance reads and writes, so it
|
|
313
|
+
// is stated every run rather than only on the one that decided it.
|
|
314
|
+
...(homeChoice.claudeHome !== undefined
|
|
315
|
+
? [` Claude home: ${homeChoice.claudeHome} (yours — sessions and login shared)`]
|
|
316
|
+
: []),
|
|
274
317
|
" Press Ctrl-C to stop.",
|
|
275
318
|
"",
|
|
276
319
|
].join("\n"));
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
import { promises as fs } from "node:fs";
|
|
39
39
|
import path from "node:path";
|
|
40
40
|
import { keeperAgentName } from "./herdctl.js";
|
|
41
|
+
import { classifyAgentError } from "./agent-errors.js";
|
|
41
42
|
import { DEFAULT_CURATION, resolveCurationConfig, } from "./curation-config.js";
|
|
42
43
|
import { curatorTriggerOf, triggerPromptFileAbsPath, } from "./trigger-config.js";
|
|
43
44
|
const STATE_FILE = "sweep-state.json";
|
|
@@ -204,7 +205,18 @@ export class SweepService {
|
|
|
204
205
|
lastSweptSessionMtime: wm.lastSweptSessionMtime,
|
|
205
206
|
lastSweptAt: Date.now(),
|
|
206
207
|
});
|
|
207
|
-
this
|
|
208
|
+
// #684: this is a best-effort background task whose failure is already
|
|
209
|
+
// labelled non-fatal, so a recognised cause gets ONE line at warn — no
|
|
210
|
+
// `err`, because pino serialising it is what put four copies of the
|
|
211
|
+
// curator system prompt on a first-time user's terminal. An unrecognised
|
|
212
|
+
// failure keeps the full object: that is the case somebody needs it.
|
|
213
|
+
const cause = classifyAgentError(err);
|
|
214
|
+
if (cause === undefined) {
|
|
215
|
+
this.log.error({ err, slug }, "sweep: sweeper run errored (non-fatal)");
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
this.log.warn({ slug }, `sweep: skipped, ${cause} (non-fatal — chat is unaffected)`);
|
|
219
|
+
}
|
|
208
220
|
}
|
|
209
221
|
finally {
|
|
210
222
|
this.running.delete(slug);
|
|
@@ -31,14 +31,21 @@ export declare function projectChatsDir(projectDir: string): string;
|
|
|
31
31
|
* Making the parameter required is what stops that class of bug at the type
|
|
32
32
|
* level.
|
|
33
33
|
*
|
|
34
|
-
* `home.owned` gates
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
34
|
+
* `home.owned` gates EVERY write into the home (#620, #682). Two of them:
|
|
35
|
+
*
|
|
36
|
+
* - Migrating an existing real transcript directory into `.chats/` copies the
|
|
37
|
+
* files and then REMOVES the originals. That is correct self-healing inside a
|
|
38
|
+
* home paddock owns, where such a directory can only be paddock's own doing.
|
|
39
|
+
* Inside the user's `~/.claude` it is somebody else's data — a `claude` CLI
|
|
40
|
+
* session run in a directory paddock happens to also manage — so we leave it
|
|
41
|
+
* strictly alone and let the SDK keep writing there.
|
|
42
|
+
* - Planting the redirect symlink at a path that does not exist yet. Not
|
|
43
|
+
* destructive on the day it happens, which is why it was missed, but it
|
|
44
|
+
* silently claims where the user's FUTURE sessions for that directory get
|
|
45
|
+
* written (#682). See the branch itself for the full argument.
|
|
46
|
+
*
|
|
47
|
+
* Either way those transcripts stay importable through adoption (#588), which is
|
|
48
|
+
* the deliberate, user-driven version of the same move.
|
|
42
49
|
*/
|
|
43
50
|
export interface ClaudeHomeTarget {
|
|
44
51
|
/** Absolute path to the Claude home to plant the redirect symlink in. */
|
|
@@ -76,6 +76,24 @@ export async function ensureProjectChats(workingDir, chatsHostDir, home) {
|
|
|
76
76
|
await fs.symlink(chatsDir, encoded);
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
|
+
// Nothing there yet. In a home paddock does NOT own, planting here is the
|
|
80
|
+
// data-loss branch (#682): the encoded path is where the USER's future
|
|
81
|
+
// `claude` sessions for this directory land, so a link redirects every one of
|
|
82
|
+
// them into paddock's `.chats/` — from that moment "their history" and "our
|
|
83
|
+
// store" are the same files, and an ordinary `rm -rf .chats` destroys
|
|
84
|
+
// transcripts paddock never owned. It also breaks unrelated tooling that
|
|
85
|
+
// expects `~/.claude/projects/<enc>` to be a directory. Creating a name the
|
|
86
|
+
// user has not used yet is not "overwriting", so it slipped past the letter
|
|
87
|
+
// of claude-home.ts's invariant while breaking its intent.
|
|
88
|
+
//
|
|
89
|
+
// The cost of bailing is that transcripts stay in the user's home instead of
|
|
90
|
+
// being self-contained in `.chats/`, which is exactly the pre-#620 trade for
|
|
91
|
+
// the `CLAUDE_HOME=~/.claude` escape hatch. Chat still works end to end:
|
|
92
|
+
// paddock and the engine both resolve THIS home, so discovery, resume and
|
|
93
|
+
// rename all read the real directory. Adoption (#588) remains the deliberate,
|
|
94
|
+
// user-driven way to pull those transcripts into a project.
|
|
95
|
+
if (!home.owned)
|
|
96
|
+
return;
|
|
79
97
|
// Nothing there yet — just create the symlink so future turns land in .chats.
|
|
80
98
|
await fs.symlink(chatsDir, encoded);
|
|
81
99
|
}
|
package/packages/web/dist/assets/{InstanceConfigPage-CzMqTdUU.js → InstanceConfigPage-BgNXqAox.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as o,b as f,j as e,A as b,k as E,l as A,m as O,M as F,n as I}from"./index-
|
|
1
|
+
import{r as o,b as f,j as e,A as b,k as E,l as A,m as O,M as F,n as I}from"./index-DGCs1YIO.js";function $(){const[t,s]=o.useState(null),[l,i]=o.useState(!0),[r,c]=o.useState(null),[d,m]=o.useState({}),[h,k]=o.useState(!1),[j,p]=o.useState(null),[N,u]=o.useState(!1);o.useEffect(()=>{let a=!0;return i(!0),f.getInstanceConfig().then(n=>{a&&(s(n),c(null))}).catch(n=>a&&c(n instanceof Error?n.message:String(n))).finally(()=>a&&i(!1)),()=>{a=!1}},[]);const v=(a,n)=>{u(!1),p(null),m(g=>({...g,[a]:n}))},S=a=>Object.prototype.hasOwnProperty.call(d,a.key)?d[a.key]:a.value,y=o.useMemo(()=>t?t.groups.flatMap(a=>a.fields):[],[t]),x=o.useMemo(()=>y.filter(a=>a.editable&&!a.envOverridden).filter(a=>Object.prototype.hasOwnProperty.call(d,a.key)).filter(a=>!B(d[a.key],a.value)).map(a=>a.key),[y,d]),w=async()=>{if(x.length===0)return;const a={};for(const n of x)a[n]=d[n];k(!0),p(null);try{await f.updateInstanceConfig(a);const n=await f.getInstanceConfig();s(n),m({}),u(!0)}catch(n){p(n instanceof A||n instanceof Error?n.message:String(n))}finally{k(!1)}},C=()=>{m({}),p(null),u(!1)};return e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"min-h-0 flex-1 overflow-y-auto px-4 py-5 sm:px-6",children:e.jsxs("div",{className:"mx-auto max-w-2xl",children:[e.jsx(L,{saved:N,configPath:t==null?void 0:t.configPath}),l&&e.jsx("p",{className:"text-sm text-paddock-500",children:"Loading…"}),r&&e.jsxs("p",{className:"text-sm text-red-600 dark:text-red-400",role:"alert",children:["Failed to load settings: ",r]}),t&&t.groups.map(a=>e.jsx(M,{title:a.label,description:a.description,children:e.jsx("div",{className:"grid grid-cols-1 gap-4",children:a.fields.map(n=>e.jsx(P,{field:n,value:S(n),onChange:g=>v(n.key,g)},n.key))})},a.id))]})}),t&&e.jsxs("footer",{className:"flex items-center gap-3 border-t border-paddock-200 px-4 py-3 dark:border-paddock-800 sm:px-6",children:[j&&e.jsxs("span",{className:"flex items-center gap-1.5 text-[13px] text-red-600 dark:text-red-400",role:"alert",children:[e.jsx(b,{width:14,height:14,className:"shrink-0"}),j]}),e.jsx("span",{className:"ml-auto text-[12px] text-paddock-400",children:x.length>0?`${x.length} unsaved change${x.length===1?"":"s"}`:"No changes"}),e.jsx("button",{type:"button",className:"btn-subtle",onClick:C,disabled:x.length===0||h,children:"Reset"}),e.jsx("button",{type:"button",className:"btn-primary",onClick:w,disabled:x.length===0||h,children:h?"Saving…":"Save changes"})]})]})}function L({saved:t,configPath:s}){return e.jsxs("div",{role:"status",className:`mb-6 flex items-start gap-2 rounded-lg border px-3 py-2.5 text-[13px] leading-snug ${t?"border-emerald-300 bg-emerald-50 text-emerald-800 dark:border-emerald-700/60 dark:bg-emerald-900/20 dark:text-emerald-300":"border-amber-300 bg-amber-50 text-amber-800 dark:border-amber-700/60 dark:bg-amber-900/20 dark:text-amber-300"}`,children:[t?e.jsx(E,{width:15,height:15,className:"mt-0.5 shrink-0"}):e.jsx(b,{width:15,height:15,className:"mt-0.5 shrink-0"}),e.jsx("span",{children:t?e.jsxs(e.Fragment,{children:[e.jsx("strong",{children:"Saved to disk."})," These changes take effect only after the server restarts."]}):e.jsxs(e.Fragment,{children:["Changes here are written to ",e.jsx("code",{className:"font-mono text-[12px]",children:T(s)})," ","and take effect only after the server restarts — the running instance keeps its current config until then."]})})]})}function M({title:t,description:s,children:l}){return e.jsxs("section",{className:"mb-6",children:[e.jsx("h2",{className:"text-sm font-semibold uppercase tracking-wide text-paddock-500",children:t}),s&&e.jsx("p",{className:"mb-3 mt-0.5 text-[13px] text-paddock-500",children:s}),e.jsx("div",{className:`card ${s?"":"mt-2"}`,children:l})]})}function P({field:t,value:s,onChange:l}){const i=!t.editable||t.envOverridden,r=`cfg-${t.key}`;return e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("label",{htmlFor:i?void 0:r,className:"text-sm font-medium",children:[t.label,t.sensitive&&e.jsx("span",{className:"ml-1.5 align-middle text-[10px] font-semibold uppercase tracking-wide text-amber-600 dark:text-amber-400",children:"sensitive"})]}),t.type==="boolean"&&!i&&e.jsx("input",{id:r,type:"checkbox",className:"h-4 w-4 accent-accent",checked:!!s,onChange:c=>l(c.target.checked)})]}),t.help&&e.jsx("p",{className:"mt-0.5 text-[12px] leading-snug text-paddock-500",children:t.help}),i?e.jsx(V,{field:t,value:s}):t.type!=="boolean"&&e.jsx(R,{field:t,value:s,onChange:l,inputId:r}),t.envOverridden&&e.jsxs("p",{className:"mt-1 flex items-start gap-1.5 text-[12px] leading-snug text-amber-600 dark:text-amber-400",children:[e.jsx(b,{width:13,height:13,className:"mt-0.5 shrink-0"}),e.jsxs("span",{children:["Overridden by environment variable ",e.jsx("code",{className:"font-mono",children:t.envVar})," — edit that env var (and restart) to change it."]})]})]})}function R({field:t,value:s,onChange:l,inputId:i}){if(t.type==="enum")return e.jsx("select",{id:i,className:"input mt-1.5",value:String(s??""),onChange:r=>l(r.target.value),children:(t.enumValues??[]).map(r=>e.jsx("option",{value:r,children:r},r))});if(t.type==="number")return e.jsx("input",{id:i,type:"number",className:"input mt-1.5",value:s==null?"":String(s),placeholder:t.default===null?"default":String(t.default),onChange:r=>l(r.target.value===""?null:Number(r.target.value))});if(t.type==="text"){const r=s==null||s===t.default,c=s==null?String(t.default??""):String(s),d=typeof t.default=="string"&&!r;return e.jsxs("div",{className:"mt-1.5",children:[e.jsx("textarea",{id:i,className:"input min-h-[9rem] resize-y font-mono text-[12px] leading-relaxed",rows:10,spellCheck:!1,value:c,onChange:m=>l(m.target.value)}),e.jsxs("div",{className:"mt-1 flex items-center justify-between gap-3 text-[12px] text-paddock-500",children:[e.jsx("span",{children:r?"Using the built-in default.":s===""?"Empty — nothing will be appended.":`${c.length.toLocaleString()} characters.`}),d&&e.jsx("button",{type:"button",className:"shrink-0 underline underline-offset-2 hover:text-paddock-700 dark:hover:text-paddock-300",onClick:()=>l(null),children:"Restore default"})]})]})}if(t.type==="string-list"){const r=Array.isArray(s)?s.join(", "):String(s??"");return e.jsx("input",{id:i,type:"text",className:"input mt-1.5",value:r,onChange:c=>l(c.target.value.split(",").map(d=>d.trim()).filter(Boolean))})}return e.jsx("input",{id:i,type:"text",className:"input mt-1.5",value:s==null?"":String(s),placeholder:t.default===null?"":String(t.default),onChange:r=>l(r.target.value)})}function V({field:t,value:s}){return e.jsx("div",{className:"mt-1.5 rounded-md border border-dashed border-paddock-300 bg-paddock-50 px-2.5 py-1.5 text-[13px] text-paddock-600 dark:border-paddock-700 dark:bg-paddock-900/40 dark:text-paddock-300",children:t.type==="boolean"?e.jsx("span",{className:"font-mono",children:s?"true":"false"}):s==null||s===""?e.jsx("span",{className:"italic text-paddock-400",children:t.type==="text"&&s===""?"(empty — nothing appended)":"(not set)"}):t.type==="text"?e.jsx("span",{className:"block max-h-64 overflow-y-auto whitespace-pre-wrap font-mono text-[12px] leading-relaxed",children:String(s)}):e.jsx("span",{className:"break-all font-mono",children:Array.isArray(s)?s.join(", "):String(s)})})}function B(t,s){return Array.isArray(t)&&Array.isArray(s)?t.length===s.length&&t.every((l,i)=>l===s[i]):t===s}function T(t){if(!t)return"paddock.config.yaml";const s=t.split("/");return s[s.length-1]||t}function z(){const t=O(),s=(t==null?void 0:t.openNav)??(()=>{});return e.jsxs("div",{className:"flex h-full min-h-0 flex-col",children:[e.jsxs("header",{className:"pt-safe flex items-center gap-2 border-b border-paddock-200 px-3 pb-2.5 dark:border-paddock-800 sm:px-6 lg:py-4",children:[e.jsx("button",{type:"button",onClick:s,"aria-label":"Open menu",className:"btn-subtle -ml-1 shrink-0 px-2 py-1.5 lg:hidden",children:e.jsx(F,{width:20,height:20})}),e.jsx(I,{width:18,height:18,className:"shrink-0 text-paddock-400"}),e.jsx("h1",{className:"text-[15px] font-semibold tracking-tight",children:"Config"})]}),e.jsx($,{})]})}export{z as InstanceConfigPage};
|
|
2
2
|
|