@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.
Files changed (75) hide show
  1. package/README.md +15 -4
  2. package/package.json +1 -1
  3. package/packages/server/dist/agent-errors.d.ts +41 -0
  4. package/packages/server/dist/agent-errors.js +127 -0
  5. package/packages/server/dist/app.js +22 -1
  6. package/packages/server/dist/claude-home.d.ts +68 -1
  7. package/packages/server/dist/claude-home.js +118 -7
  8. package/packages/server/dist/cli/args.d.ts +9 -1
  9. package/packages/server/dist/cli/args.js +21 -5
  10. package/packages/server/dist/cli/claude-home-choice.d.ts +44 -0
  11. package/packages/server/dist/cli/claude-home-choice.js +108 -0
  12. package/packages/server/dist/cli/paddock.js +51 -8
  13. package/packages/server/dist/sweep.js +13 -1
  14. package/packages/server/dist/transcripts.d.ts +15 -8
  15. package/packages/server/dist/transcripts.js +18 -0
  16. package/packages/server/package.json +1 -1
  17. package/packages/web/dist/assets/{InstanceConfigPage-CzMqTdUU.js → InstanceConfigPage-BgNXqAox.js} +1 -1
  18. package/packages/web/dist/assets/{MarkdownRenderer-YSEZHWDe.js → MarkdownRenderer-DfD_UtqU.js} +1 -1
  19. package/packages/web/dist/assets/{ProjectRedirect-DLWxZbqK.js → ProjectRedirect-1EXdHXmO.js} +1 -1
  20. package/packages/web/dist/assets/{ProjectView-BFX8ff2n.js → ProjectView-DHb5vNUp.js} +3 -3
  21. package/packages/web/dist/assets/{ProjectsGrid-DLOeY_ww.js → ProjectsGrid-Br-EhLZE.js} +1 -1
  22. package/packages/web/dist/assets/{arc-BwC1ik-b.js → arc-C2Jekujs.js} +1 -1
  23. package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-CsVvXQWw.js → architectureDiagram-3BPJPVTR-CflCA-em.js} +1 -1
  24. package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-DXQk2bIa.js → blockDiagram-GPEHLZMM-Ds7GLNnC.js} +1 -1
  25. package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-DXiA26It.js → c4Diagram-AAUBKEIU-D4WyGO6I.js} +1 -1
  26. package/packages/web/dist/assets/channel-B1K_gwAH.js +2 -0
  27. package/packages/web/dist/assets/{chunk-2J33WTMH-Dt5nC02A.js → chunk-2J33WTMH-tQunO_M5.js} +1 -1
  28. package/packages/web/dist/assets/{chunk-4BX2VUAB-DcjnWvl6.js → chunk-4BX2VUAB-DT5dOWd2.js} +1 -1
  29. package/packages/web/dist/assets/{chunk-55IACEB6-CRa3LkU4.js → chunk-55IACEB6-CfTUqUEF.js} +1 -1
  30. package/packages/web/dist/assets/{chunk-727SXJPM-H3V-11qu.js → chunk-727SXJPM-sK5bOAA0.js} +1 -1
  31. package/packages/web/dist/assets/{chunk-AQP2D5EJ-DPCrENYV.js → chunk-AQP2D5EJ-DcMgVe-P.js} +1 -1
  32. package/packages/web/dist/assets/{chunk-FMBD7UC4-C4UO0ZTc.js → chunk-FMBD7UC4-In3h_-7v.js} +1 -1
  33. package/packages/web/dist/assets/{chunk-ND2GUHAM-BtI37CnI.js → chunk-ND2GUHAM-DuaIeRaC.js} +1 -1
  34. package/packages/web/dist/assets/{chunk-QZHKN3VN-C5bTaK5j.js → chunk-QZHKN3VN-DeT5mR5T.js} +1 -1
  35. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DYQQOKrW.js +2 -0
  36. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DYQQOKrW.js +2 -0
  37. package/packages/web/dist/assets/{core-DB3cpm5N.js → core-DgOgLvnM.js} +1 -1
  38. package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-q-cjTZO4.js → cose-bilkent-S5V4N54A-CyO-rznJ.js} +1 -1
  39. package/packages/web/dist/assets/{dagre-BM42HDAG-DYEV5HFo.js → dagre-BM42HDAG-B_9Ihzgn.js} +1 -1
  40. package/packages/web/dist/assets/{diagram-2AECGRRQ-D16XCw78.js → diagram-2AECGRRQ-z5mvZ5YT.js} +1 -1
  41. package/packages/web/dist/assets/{diagram-5GNKFQAL-CeztryYE.js → diagram-5GNKFQAL-Bsz0cgfn.js} +1 -1
  42. package/packages/web/dist/assets/{diagram-KO2AKTUF-D5SLIMDS.js → diagram-KO2AKTUF-B29k5f4q.js} +1 -1
  43. package/packages/web/dist/assets/{diagram-LMA3HP47-Bw-sVPxM.js → diagram-LMA3HP47-CFrwsHoI.js} +1 -1
  44. package/packages/web/dist/assets/{diagram-OG6HWLK6-DsRMITgx.js → diagram-OG6HWLK6-D9Sjc2HR.js} +1 -1
  45. package/packages/web/dist/assets/{erDiagram-TEJ5UH35-T8Qqc_ku.js → erDiagram-TEJ5UH35-C3vbkD7M.js} +1 -1
  46. package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-Cyx47UWU.js → flowDiagram-I6XJVG4X-DkMIgntd.js} +1 -1
  47. package/packages/web/dist/assets/{format-D2I0duZa.js → format-BFDqyFuw.js} +1 -1
  48. package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-DxYJmG9o.js → ganttDiagram-6RSMTGT7-nvHanjpW.js} +1 -1
  49. package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-COK5YOTD.js → gitGraphDiagram-PVQCEYII-DbCNfOu7.js} +1 -1
  50. package/packages/web/dist/assets/{index-wBXQMkGI.js → index-DGCs1YIO.js} +2 -2
  51. package/packages/web/dist/assets/{infoDiagram-5YYISTIA-BamRN_Nn.js → infoDiagram-5YYISTIA-aJjH9x3D.js} +1 -1
  52. package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-DuOHdpEH.js → ishikawaDiagram-YF4QCWOH-DoifwNnZ.js} +1 -1
  53. package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-Bri7GKTW.js → journeyDiagram-JHISSGLW-Cz9kG9HN.js} +1 -1
  54. package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-BdkgUnzI.js → kanban-definition-UN3LZRKU-BoXnfr7A.js} +1 -1
  55. package/packages/web/dist/assets/{linear-DOcNPCpB.js → linear-BlTTO9yR.js} +1 -1
  56. package/packages/web/dist/assets/{mermaid.core-D9SbVBpN.js → mermaid.core-YAG8eSPQ.js} +4 -4
  57. package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-CVmLm7zn.js → mindmap-definition-RKZ34NQL-DtUBKfkr.js} +1 -1
  58. package/packages/web/dist/assets/{pieDiagram-4H26LBE5-D0N10eVU.js → pieDiagram-4H26LBE5-B3Rwpmy0.js} +1 -1
  59. package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-B5gnYdTa.js → quadrantDiagram-W4KKPZXB-B8eJtTyk.js} +1 -1
  60. package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-CwtxezNZ.js → requirementDiagram-4Y6WPE33-CpAluiY9.js} +1 -1
  61. package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-D79O3cux.js → sankeyDiagram-5OEKKPKP-Cl_Rd1tN.js} +1 -1
  62. package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-xhKcI6tI.js → sequenceDiagram-3UESZ5HK-CRpqKSGm.js} +1 -1
  63. package/packages/web/dist/assets/{stateDiagram-AJRCARHV-DBYMAjcX.js → stateDiagram-AJRCARHV-h3h8gXv_.js} +1 -1
  64. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-BKRI9TnH.js +2 -0
  65. package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-e-MYF4lm.js → timeline-definition-PNZ67QCA-PVr5V_pG.js} +1 -1
  66. package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-B8UsvRDM.js → vennDiagram-CIIHVFJN-De-4NPAX.js} +1 -1
  67. package/packages/web/dist/assets/{wardley-L42UT6IY-1ucACjTl.js → wardley-L42UT6IY-Dtv10D2d.js} +1 -1
  68. package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-DJufLd_u.js → wardleyDiagram-YWT4CUSO-p2fJuQ6C.js} +1 -1
  69. package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-qqaZNlu9.js → xychartDiagram-2RQKCTM6-DyYF-k92.js} +1 -1
  70. package/packages/web/dist/index.html +1 -1
  71. package/packages/web/dist/sw.js +1 -1
  72. package/packages/web/dist/assets/channel-DDgTXrGP.js +0 -2
  73. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DU1kd_Vu.js +0 -2
  74. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DU1kd_Vu.js +0 -2
  75. 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
- " · ~/.claude sessions for this directory are offered for import (nothing is moved)",
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 — `<dataDir>/claude-home` since #620 — after the
251
- // bridge has symlinked in any `~/.claude/.credentials.json`, and its remedy
252
- // names the `CLAUDE_CONFIG_DIR` keychain scoping that is the real reason a
253
- // logged-in user can still land with no credentials. It logs at `warn`, which
254
- // survives the quiet default set just above.
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.log.error({ err, slug }, "sweep: sweeper run errored (non-fatal)");
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 the one destructive branch (#620): migrating an existing
35
- * real transcript directory into `.chats/` copies the files and then REMOVES the
36
- * originals. That is correct self-healing inside a home paddock owns, where such
37
- * a directory can only be paddock's own doing. Inside the user's `~/.claude` it
38
- * is somebody else's data — a `claude` CLI session run in a directory paddock
39
- * happens to also manage — so we leave it strictly alone and let the SDK keep
40
- * writing there. Those transcripts stay importable through adoption (#588),
41
- * which is the deliberate, user-driven version of the same move.
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
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paddock/server",
3
- "version": "0.61.0",
3
+ "version": "0.61.1",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -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-wBXQMkGI.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};
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