@edspencer/paddock 0.61.0 → 0.62.0
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 +36 -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 +57 -9
- package/packages/server/dist/claude-credentials.d.ts +116 -0
- package/packages/server/dist/claude-credentials.js +103 -0
- package/packages/server/dist/claude-home.d.ts +164 -25
- package/packages/server/dist/claude-home.js +528 -77
- package/packages/server/dist/claude-instructions.d.ts +121 -0
- package/packages/server/dist/claude-instructions.js +114 -0
- package/packages/server/dist/claude-mcp.d.ts +146 -0
- package/packages/server/dist/claude-mcp.js +367 -0
- package/packages/server/dist/claude-settings.d.ts +159 -0
- package/packages/server/dist/claude-settings.js +159 -0
- package/packages/server/dist/cli/args.d.ts +1 -1
- package/packages/server/dist/cli/args.js +39 -5
- package/packages/server/dist/cli/paddock.js +30 -6
- package/packages/server/dist/config.d.ts +166 -30
- package/packages/server/dist/config.js +106 -26
- package/packages/server/dist/herdctl-agent-config.d.ts +11 -1
- package/packages/server/dist/herdctl-agent-config.js +53 -7
- package/packages/server/dist/herdctl-agent-names.d.ts +11 -0
- package/packages/server/dist/herdctl-agent-names.js +31 -0
- package/packages/server/dist/herdctl.d.ts +69 -13
- package/packages/server/dist/herdctl.js +70 -18
- package/packages/server/dist/instance-config.js +36 -0
- package/packages/server/dist/mcp-servers.d.ts +168 -0
- package/packages/server/dist/mcp-servers.js +305 -0
- package/packages/server/dist/routes/chats.js +10 -4
- package/packages/server/dist/sweep.js +13 -1
- package/packages/server/dist/transcripts.d.ts +62 -24
- package/packages/server/dist/transcripts.js +141 -28
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-CzMqTdUU.js → InstanceConfigPage-Ca905oey.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-YSEZHWDe.js → MarkdownRenderer-ChlC1ezH.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-DLWxZbqK.js → ProjectRedirect-CslqbhUb.js} +1 -1
- package/packages/web/dist/assets/{ProjectView-BFX8ff2n.js → ProjectView-DkNdilGo.js} +3 -3
- package/packages/web/dist/assets/{ProjectsGrid-DLOeY_ww.js → ProjectsGrid-BrlvLc-Y.js} +1 -1
- package/packages/web/dist/assets/{arc-BwC1ik-b.js → arc-CGqCI5nQ.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-CsVvXQWw.js → architectureDiagram-3BPJPVTR-Cp4KTxE2.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-DXQk2bIa.js → blockDiagram-GPEHLZMM-yFxaTv04.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-DXiA26It.js → c4Diagram-AAUBKEIU-59lhWjRt.js} +1 -1
- package/packages/web/dist/assets/channel-CZs6Z1i5.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-Dt5nC02A.js → chunk-2J33WTMH-BrpXGnJg.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-DcjnWvl6.js → chunk-4BX2VUAB-UxBDb5pi.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-CRa3LkU4.js → chunk-55IACEB6-D4ZJyUHW.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-H3V-11qu.js → chunk-727SXJPM-DWdPj_8E.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-DPCrENYV.js → chunk-AQP2D5EJ-BIpNbflY.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-C4UO0ZTc.js → chunk-FMBD7UC4-f6iJDfdI.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-BtI37CnI.js → chunk-ND2GUHAM-Bz3TKIJk.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-C5bTaK5j.js → chunk-QZHKN3VN-buQmkafK.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-CrbEUUCd.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-CrbEUUCd.js +2 -0
- package/packages/web/dist/assets/{core-DB3cpm5N.js → core-TyFI6p0n.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-q-cjTZO4.js → cose-bilkent-S5V4N54A-CIkdi0XF.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-DYEV5HFo.js → dagre-BM42HDAG-BnLJCfNs.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-D16XCw78.js → diagram-2AECGRRQ-D0OqfDB9.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-CeztryYE.js → diagram-5GNKFQAL-DQ2spGrK.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-D5SLIMDS.js → diagram-KO2AKTUF-RDIVQ_S3.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-Bw-sVPxM.js → diagram-LMA3HP47-DKoTfh2C.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-DsRMITgx.js → diagram-OG6HWLK6-6k0OuSAL.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-T8Qqc_ku.js → erDiagram-TEJ5UH35-B-oQaYAC.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-Cyx47UWU.js → flowDiagram-I6XJVG4X-B7qpYe9C.js} +1 -1
- package/packages/web/dist/assets/{format-D2I0duZa.js → format-PHdEBNOi.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-DxYJmG9o.js → ganttDiagram-6RSMTGT7-SfBsqMmx.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-COK5YOTD.js → gitGraphDiagram-PVQCEYII-CuM4Rl_i.js} +1 -1
- package/packages/web/dist/assets/{index-wBXQMkGI.js → index-D_1ogxg1.js} +2 -2
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-BamRN_Nn.js → infoDiagram-5YYISTIA-Dq-32Oj4.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-DuOHdpEH.js → ishikawaDiagram-YF4QCWOH-BLFHCj_o.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-Bri7GKTW.js → journeyDiagram-JHISSGLW-CHP-Yu63.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-BdkgUnzI.js → kanban-definition-UN3LZRKU-DRuehSqn.js} +1 -1
- package/packages/web/dist/assets/{linear-DOcNPCpB.js → linear-Bwz2vnH7.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-D9SbVBpN.js → mermaid.core-CeMMnCip.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-CVmLm7zn.js → mindmap-definition-RKZ34NQL-CEdqeoRW.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-D0N10eVU.js → pieDiagram-4H26LBE5-BdDXBx87.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-B5gnYdTa.js → quadrantDiagram-W4KKPZXB-Bovd1Zhx.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-CwtxezNZ.js → requirementDiagram-4Y6WPE33-DiyrKnJ2.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-D79O3cux.js → sankeyDiagram-5OEKKPKP-BuX7TRVw.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-xhKcI6tI.js → sequenceDiagram-3UESZ5HK-BjetZWJo.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-DBYMAjcX.js → stateDiagram-AJRCARHV-4AUBV6dF.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-Cq84MOtb.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-e-MYF4lm.js → timeline-definition-PNZ67QCA-BpQ8g8Bm.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-B8UsvRDM.js → vennDiagram-CIIHVFJN-Cj99BWER.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-1ucACjTl.js → wardley-L42UT6IY-S7AGTOWw.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-DJufLd_u.js → wardleyDiagram-YWT4CUSO-LNXkUA7S.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-qqaZNlu9.js → xychartDiagram-2RQKCTM6-DGdTh4Xk.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
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whose transcripts this instance uses — the `claude.transcripts` config key.
|
|
3
|
+
*
|
|
4
|
+
* `own` = paddock's, inside the data dir / project. `host` = this machine's
|
|
5
|
+
* Claude Code, shared live. See the module doc for the mechanism.
|
|
6
|
+
*/
|
|
7
|
+
export type TranscriptsMode = "own" | "host";
|
|
8
|
+
/** Isolation is the default: nothing outside the data dir is written (#691). */
|
|
9
|
+
export declare const DEFAULT_TRANSCRIPTS_MODE: TranscriptsMode;
|
|
10
|
+
/** Type guard, so an unknown config value falls back instead of failing a boot. */
|
|
11
|
+
export declare function isKnownTranscriptsMode(value: string): value is TranscriptsMode;
|
|
1
12
|
/**
|
|
2
13
|
* Encode a working directory the way Claude Code names its transcript dir.
|
|
3
14
|
* For paths under 200 chars this is just the non-alphanumeric→'-' replacement
|
|
@@ -8,21 +19,21 @@ export declare function encodeProjectDir(dir: string): string;
|
|
|
8
19
|
/** Absolute path to the project's local transcript directory. */
|
|
9
20
|
export declare function projectChatsDir(projectDir: string): string;
|
|
10
21
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* existing real transcript dir in the process). Safe to call on every agent
|
|
14
|
-
* registration. Never throws — a failure here must not block agent registration
|
|
15
|
-
* or chat.
|
|
22
|
+
* The transcript folder a Claude home holds for `workingDir` —
|
|
23
|
+
* `<home>/projects/<encoded-cwd>`.
|
|
16
24
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
25
|
+
* The LITERAL path, before any symlink is followed, which is what makes it worth
|
|
26
|
+
* naming: it is the string Claude Code builds its session file AND its agent
|
|
27
|
+
* memory dir (`<here>/memory`) out of, and the agent harness refuses to write to
|
|
28
|
+
* any path with a `.claude` component in it (#690). Under paddock's own home it
|
|
29
|
+
* never has one, in either {@link TranscriptsMode}.
|
|
30
|
+
*/
|
|
31
|
+
export declare function encodedTranscriptDir(claudeHome: string, workingDir: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Where a project's transcripts are planted, and where they point.
|
|
23
34
|
*
|
|
24
|
-
* `
|
|
25
|
-
* no default (#620). Callers must pass `cfg.claudeHome`: the engine is
|
|
35
|
+
* `path` is the Claude home to plant the redirect symlink in, and it is REQUIRED
|
|
36
|
+
* — no default (#620). Callers must pass `cfg.claudeHome`: the engine is
|
|
26
37
|
* constructed with that same resolved value, and a symlink planted in a
|
|
27
38
|
* different home than the one the engine reads is exactly the "chats list but
|
|
28
39
|
* open empty" failure (#588). It used to default to the env-derived home for
|
|
@@ -31,21 +42,48 @@ export declare function projectChatsDir(projectDir: string): string;
|
|
|
31
42
|
* Making the parameter required is what stops that class of bug at the type
|
|
32
43
|
* level.
|
|
33
44
|
*
|
|
34
|
-
* `
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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.
|
|
45
|
+
* Since #691 paddock ALWAYS owns `path`, so the old `owned` flag is gone: the
|
|
46
|
+
* question it used to answer ("may we write here?") now has one answer, and the
|
|
47
|
+
* question that actually varies — whose transcripts these are — is
|
|
48
|
+
* {@link transcripts}.
|
|
42
49
|
*/
|
|
43
50
|
export interface ClaudeHomeTarget {
|
|
44
|
-
/** Absolute path to
|
|
51
|
+
/** Absolute path to paddock's own Claude home (`cfg.claudeHome`). */
|
|
45
52
|
path: string;
|
|
46
|
-
/**
|
|
47
|
-
|
|
53
|
+
/** Whose transcripts this instance uses (`cfg.claude.transcripts`). */
|
|
54
|
+
transcripts: TranscriptsMode;
|
|
55
|
+
/** The user's own `~/.claude` — the store under `transcripts: "host"`. */
|
|
56
|
+
userHome: string;
|
|
48
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Ensure the project's transcript store exists and that Claude Code's encoded
|
|
60
|
+
* transcript path for `workingDir` is a symlink pointing at it (migrating an
|
|
61
|
+
* existing real transcript dir in the process). Safe to call on every agent
|
|
62
|
+
* registration. Never throws — a failure here must not block agent registration
|
|
63
|
+
* or chat.
|
|
64
|
+
*
|
|
65
|
+
* `workingDir` is the agent's cwd (which Claude encodes into the transcript dir
|
|
66
|
+
* name); `chatsHostDir` is where the `.chats/` store actually lives. For a
|
|
67
|
+
* notebook project these are the same dir (the default). For a REPO-BACKED
|
|
68
|
+
* project (issue #187) the keeper's cwd is the nested checkout but the `.chats/`
|
|
69
|
+
* store stays in the project's metadata dir (the data repo) — so transcripts
|
|
70
|
+
* never pollute the external repo's working tree — hence the two are split.
|
|
71
|
+
*
|
|
72
|
+
* The store is `<chatsHostDir>/.chats/` under `transcripts: "own"` and the user's
|
|
73
|
+
* own `~/.claude/projects/<encoded-cwd>/` under `transcripts: "host"`; everything
|
|
74
|
+
* below is the same code either way.
|
|
75
|
+
*
|
|
76
|
+
* ## The one write into `~/.claude`, and why it is not the #682 mistake
|
|
77
|
+
*
|
|
78
|
+
* Under `host` this creates `~/.claude/projects/<encoded-cwd>/` if it is missing,
|
|
79
|
+
* because a symlink to a non-existent directory is not something Claude Code can
|
|
80
|
+
* `mkdir -p` through. That is a directory Claude Code itself would create the
|
|
81
|
+
* first time the user ran `claude` there, made under a config key whose entire
|
|
82
|
+
* meaning is "share the host's transcripts". #682 was the opposite: planting a
|
|
83
|
+
* link that silently REDIRECTED the user's future sessions into paddock's store
|
|
84
|
+
* on an instance that had asked for no such thing. Under `own` — the default —
|
|
85
|
+
* nothing under `~/.claude` is created, read or written at all.
|
|
86
|
+
*/
|
|
49
87
|
export declare function ensureProjectChats(workingDir: string, chatsHostDir: string, home: ClaudeHomeTarget): Promise<void>;
|
|
50
88
|
/**
|
|
51
89
|
* Read a session's FIRST user message text, untruncated, straight from its
|
|
@@ -1,23 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Transcript relocation (backing-store Phase 3).
|
|
2
|
+
* Transcript relocation (backing-store Phase 3), and the `transcripts` lever (#691).
|
|
3
3
|
*
|
|
4
4
|
* Claude Code writes a session transcript to `<claudeHome>/projects/<encoded-cwd>/`,
|
|
5
|
-
* where `<claudeHome>` is `CLAUDE_CONFIG_DIR`
|
|
6
|
-
* since #
|
|
7
|
-
* non-[A-Za-z0-9] char replaced by '-'. That makes
|
|
8
|
-
*
|
|
9
|
-
* **symlink
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
5
|
+
* where `<claudeHome>` is `CLAUDE_CONFIG_DIR` — always paddock's own
|
|
6
|
+
* `<dataDir>/claude-home` since #691 — and `<encoded-cwd>` is the agent's absolute
|
|
7
|
+
* working directory with every non-[A-Za-z0-9] char replaced by '-'. That makes
|
|
8
|
+
* transcripts path-coupled and not portable, so paddock makes that encoded path a
|
|
9
|
+
* **symlink** and Claude Code writes through it (validated empirically); herdctl's
|
|
10
|
+
* discovery, resume, delete and rename all resolve through it transparently.
|
|
11
|
+
*
|
|
12
|
+
* What the symlink POINTS AT is the one thing {@link TranscriptsMode} chooses:
|
|
13
|
+
*
|
|
14
|
+
* - **`own`** (default) — `<projectDir>/.chats/`. Transcripts are paddock's own
|
|
15
|
+
* copy, the project dir is self-contained and travels with the repo / NAS.
|
|
16
|
+
* - **`host`** — `~/.claude/projects/<encoded-cwd>/`, the user's REAL Claude Code
|
|
17
|
+
* folder for that same directory. One set of files, both directions: an append
|
|
18
|
+
* made by `claude --resume` in a terminal shows up in paddock with no restart
|
|
19
|
+
* and no re-import, and vice versa.
|
|
20
|
+
*
|
|
21
|
+
* ## Why `host` is an outward symlink and not a repointed config dir
|
|
22
|
+
*
|
|
23
|
+
* The obvious implementation of "share the user's transcripts" is to point
|
|
24
|
+
* `CLAUDE_CONFIG_DIR` at `~/.claude`, which is what 0.61.1 did. It drags memory,
|
|
25
|
+
* `.claude.json` and the credential-store service name along as collateral — and
|
|
26
|
+
* it breaks agent memory (#690), because the agent harness refuses to write to any
|
|
27
|
+
* path containing a `.claude` component and the memory dir is
|
|
28
|
+
* `<claudeHome>/projects/<enc>/memory`. Keeping paddock's own home and pointing one
|
|
29
|
+
* symlink outward fixes that by construction: the LITERAL path handed to the agent
|
|
30
|
+
* is always under `<dataDir>/claude-home/…`, which has no `.claude` component,
|
|
31
|
+
* while the files it resolves to are the user's. Pinned by
|
|
32
|
+
* `test/unit/transcripts.test.ts` in BOTH modes — do not "simplify" this back.
|
|
13
33
|
*
|
|
14
34
|
* `ensureProjectChats` is idempotent and self-healing: on first run for a project
|
|
15
35
|
* whose encoded path is still a real directory (existing transcripts), it migrates
|
|
16
|
-
* those files into
|
|
36
|
+
* those files into the store and replaces the directory with the symlink.
|
|
17
37
|
*/
|
|
18
38
|
import { promises as fs, createReadStream } from "node:fs";
|
|
19
39
|
import { createInterface } from "node:readline";
|
|
20
40
|
import path from "node:path";
|
|
41
|
+
/** Isolation is the default: nothing outside the data dir is written (#691). */
|
|
42
|
+
export const DEFAULT_TRANSCRIPTS_MODE = "own";
|
|
43
|
+
/** Type guard, so an unknown config value falls back instead of failing a boot. */
|
|
44
|
+
export function isKnownTranscriptsMode(value) {
|
|
45
|
+
return value === "own" || value === "host";
|
|
46
|
+
}
|
|
21
47
|
/**
|
|
22
48
|
* Encode a working directory the way Claude Code names its transcript dir.
|
|
23
49
|
* For paths under 200 chars this is just the non-alphanumeric→'-' replacement
|
|
@@ -31,36 +57,123 @@ export function encodeProjectDir(dir) {
|
|
|
31
57
|
export function projectChatsDir(projectDir) {
|
|
32
58
|
return path.join(projectDir, ".chats");
|
|
33
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* The transcript folder a Claude home holds for `workingDir` —
|
|
62
|
+
* `<home>/projects/<encoded-cwd>`.
|
|
63
|
+
*
|
|
64
|
+
* The LITERAL path, before any symlink is followed, which is what makes it worth
|
|
65
|
+
* naming: it is the string Claude Code builds its session file AND its agent
|
|
66
|
+
* memory dir (`<here>/memory`) out of, and the agent harness refuses to write to
|
|
67
|
+
* any path with a `.claude` component in it (#690). Under paddock's own home it
|
|
68
|
+
* never has one, in either {@link TranscriptsMode}.
|
|
69
|
+
*/
|
|
70
|
+
export function encodedTranscriptDir(claudeHome, workingDir) {
|
|
71
|
+
return path.join(claudeHome, "projects", encodeProjectDir(workingDir));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Point `<chatsHostDir>/.chats/` at the user's own transcript folder, so
|
|
75
|
+
* paddock's own by-path readers keep working under `transcripts: "host"`.
|
|
76
|
+
*
|
|
77
|
+
* NOT redundant with the redirect symlink, and not in #691's sketch — it is
|
|
78
|
+
* needed because roughly a dozen server modules (`subagents.ts`, `usage.ts`,
|
|
79
|
+
* `tooldetails.ts`, `localcommand.ts`, `recovery.ts`, `readFirstUserText`,
|
|
80
|
+
* the usage-limit notice scan) resolve a transcript as
|
|
81
|
+
* `<projectDir>/.chats/<sessionId>.jsonl` rather than through the Claude home.
|
|
82
|
+
* Without this the chat itself renders (herdctl reads through the home) while
|
|
83
|
+
* sub-agent panels, token usage, tool details, slash-command names and full
|
|
84
|
+
* previews all silently come back empty — which is what 0.61.1's `--here` does
|
|
85
|
+
* today. Both links point at the SAME real folder; nothing is chained.
|
|
86
|
+
*
|
|
87
|
+
* Non-destructive: a `.chats/` that is a real directory with anything in it is
|
|
88
|
+
* left exactly as it is (a mode switch does not get to delete a store), at the
|
|
89
|
+
* cost of those readers seeing the old copy until the operator moves it.
|
|
90
|
+
*/
|
|
91
|
+
async function pointChatsDirAt(chatsDir, store) {
|
|
92
|
+
// The parent may not exist yet — `ensureSweeperHome` calls in with a
|
|
93
|
+
// `<dataDir>/sweepers/<slug>` that nothing has created. Under `own` the
|
|
94
|
+
// recursive mkdir of `.chats/` itself covered that; here the leaf is a symlink,
|
|
95
|
+
// and `fs.symlink` into a missing parent is an ENOENT that the caller's
|
|
96
|
+
// catch-all would swallow — silently leaving the agent with no redirect at all.
|
|
97
|
+
await fs.mkdir(path.dirname(chatsDir), { recursive: true });
|
|
98
|
+
const st = await fs.lstat(chatsDir).catch(() => null);
|
|
99
|
+
if (st?.isSymbolicLink()) {
|
|
100
|
+
const target = await fs.readlink(chatsDir).catch(() => "");
|
|
101
|
+
if (path.resolve(path.dirname(chatsDir), target) === path.resolve(store))
|
|
102
|
+
return;
|
|
103
|
+
await fs.rm(chatsDir, { force: true });
|
|
104
|
+
}
|
|
105
|
+
else if (st?.isDirectory()) {
|
|
106
|
+
if ((await fs.readdir(chatsDir).catch(() => ["?"])).length > 0)
|
|
107
|
+
return;
|
|
108
|
+
await fs.rmdir(chatsDir);
|
|
109
|
+
}
|
|
110
|
+
else if (st) {
|
|
111
|
+
return; // a regular file sitting on the name: not ours to remove
|
|
112
|
+
}
|
|
113
|
+
await fs.symlink(store, chatsDir);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Ensure the project's transcript store exists and that Claude Code's encoded
|
|
117
|
+
* transcript path for `workingDir` is a symlink pointing at it (migrating an
|
|
118
|
+
* existing real transcript dir in the process). Safe to call on every agent
|
|
119
|
+
* registration. Never throws — a failure here must not block agent registration
|
|
120
|
+
* or chat.
|
|
121
|
+
*
|
|
122
|
+
* `workingDir` is the agent's cwd (which Claude encodes into the transcript dir
|
|
123
|
+
* name); `chatsHostDir` is where the `.chats/` store actually lives. For a
|
|
124
|
+
* notebook project these are the same dir (the default). For a REPO-BACKED
|
|
125
|
+
* project (issue #187) the keeper's cwd is the nested checkout but the `.chats/`
|
|
126
|
+
* store stays in the project's metadata dir (the data repo) — so transcripts
|
|
127
|
+
* never pollute the external repo's working tree — hence the two are split.
|
|
128
|
+
*
|
|
129
|
+
* The store is `<chatsHostDir>/.chats/` under `transcripts: "own"` and the user's
|
|
130
|
+
* own `~/.claude/projects/<encoded-cwd>/` under `transcripts: "host"`; everything
|
|
131
|
+
* below is the same code either way.
|
|
132
|
+
*
|
|
133
|
+
* ## The one write into `~/.claude`, and why it is not the #682 mistake
|
|
134
|
+
*
|
|
135
|
+
* Under `host` this creates `~/.claude/projects/<encoded-cwd>/` if it is missing,
|
|
136
|
+
* because a symlink to a non-existent directory is not something Claude Code can
|
|
137
|
+
* `mkdir -p` through. That is a directory Claude Code itself would create the
|
|
138
|
+
* first time the user ran `claude` there, made under a config key whose entire
|
|
139
|
+
* meaning is "share the host's transcripts". #682 was the opposite: planting a
|
|
140
|
+
* link that silently REDIRECTED the user's future sessions into paddock's store
|
|
141
|
+
* on an instance that had asked for no such thing. Under `own` — the default —
|
|
142
|
+
* nothing under `~/.claude` is created, read or written at all.
|
|
143
|
+
*/
|
|
34
144
|
export async function ensureProjectChats(workingDir, chatsHostDir, home) {
|
|
35
|
-
const claudeHomePath = home.path;
|
|
36
145
|
try {
|
|
37
146
|
const chatsDir = projectChatsDir(chatsHostDir);
|
|
38
|
-
|
|
39
|
-
|
|
147
|
+
const store = home.transcripts === "host"
|
|
148
|
+
? encodedTranscriptDir(home.userHome, workingDir)
|
|
149
|
+
: chatsDir;
|
|
150
|
+
await fs.mkdir(store, { recursive: true });
|
|
151
|
+
if (home.transcripts === "host")
|
|
152
|
+
await pointChatsDirAt(chatsDir, store);
|
|
153
|
+
const encoded = encodedTranscriptDir(home.path, workingDir);
|
|
40
154
|
await fs.mkdir(path.dirname(encoded), { recursive: true });
|
|
41
155
|
const st = await fs.lstat(encoded).catch(() => null);
|
|
42
|
-
// Already a symlink — point it at
|
|
156
|
+
// Already a symlink — point it at the store if it drifted, else done. The
|
|
157
|
+
// drift case is also how a `transcripts` flip takes effect on the next boot.
|
|
43
158
|
if (st?.isSymbolicLink()) {
|
|
44
159
|
const target = await fs.readlink(encoded).catch(() => "");
|
|
45
160
|
const resolved = path.resolve(path.dirname(encoded), target);
|
|
46
|
-
if (resolved !== path.resolve(
|
|
161
|
+
if (resolved !== path.resolve(store)) {
|
|
47
162
|
await fs.rm(encoded, { force: true });
|
|
48
|
-
await fs.symlink(
|
|
163
|
+
await fs.symlink(store, encoded);
|
|
49
164
|
}
|
|
50
165
|
return;
|
|
51
166
|
}
|
|
52
|
-
// A real directory of existing transcripts
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
return;
|
|
59
|
-
// A real directory of existing transcripts — migrate into .chats, then link.
|
|
167
|
+
// A real directory of existing transcripts — migrate into the store, then
|
|
168
|
+
// link. This is inside a home paddock owns, so such a directory can only be
|
|
169
|
+
// paddock's own doing: a boot where the symlink could not be planted, or an
|
|
170
|
+
// agent that ran before registration. (Pre-#691 this branch had to be gated
|
|
171
|
+
// on ownership, because the home could BE `~/.claude` and the files somebody
|
|
172
|
+
// else's. It cannot any more — see `resolveClaudeHome`.)
|
|
60
173
|
if (st?.isDirectory()) {
|
|
61
174
|
for (const entry of await fs.readdir(encoded)) {
|
|
62
175
|
const from = path.join(encoded, entry);
|
|
63
|
-
const to = path.join(
|
|
176
|
+
const to = path.join(store, entry);
|
|
64
177
|
if (await fs.lstat(to).then(() => true).catch(() => false))
|
|
65
178
|
continue; // don't clobber
|
|
66
179
|
// cp+rm is robust across filesystems (rename would EXDEV across mounts).
|
|
@@ -73,11 +186,11 @@ export async function ensureProjectChats(workingDir, chatsHostDir, home) {
|
|
|
73
186
|
await fs.rm(from, { recursive: true, force: true });
|
|
74
187
|
}
|
|
75
188
|
await fs.rmdir(encoded).catch(() => undefined);
|
|
76
|
-
await fs.symlink(
|
|
189
|
+
await fs.symlink(store, encoded);
|
|
77
190
|
return;
|
|
78
191
|
}
|
|
79
|
-
// Nothing there yet — just create the symlink so future turns land in .
|
|
80
|
-
await fs.symlink(
|
|
192
|
+
// Nothing there yet — just create the symlink so future turns land in the store.
|
|
193
|
+
await fs.symlink(store, encoded);
|
|
81
194
|
}
|
|
82
195
|
catch {
|
|
83
196
|
/* non-fatal: fall back to Claude Code's default location for this project */
|
package/packages/web/dist/assets/{InstanceConfigPage-CzMqTdUU.js → InstanceConfigPage-Ca905oey.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-D_1ogxg1.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
|
|