@parall/claude-agent 1.15.1 → 1.16.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/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +60 -7
- package/dist/index.js +1 -1
- package/dist/workspace.d.ts +3 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +26 -7
- package/package.json +4 -4
- package/src/dispatch.ts +53 -7
- package/src/index.ts +1 -1
- package/src/workspace.ts +30 -7
package/dist/dispatch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,YAAY,EACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D,KAAK,wBAAwB,GAAG,IAAI,CAClC,iBAAiB,EACf,gBAAgB,GAChB,cAAc,GACd,oBAAoB,GACpB,WAAW,GACX,YAAY,GACZ,iBAAiB,GACjB,OAAO,GACP,gBAAgB,GAChB,cAAc,CACjB,GAAG;IACF,cAAc,EAAE,oBAAoB,CAAC;CACtC,CAAC;
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,YAAY,EACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D,KAAK,wBAAwB,GAAG,IAAI,CAClC,iBAAiB,EACf,gBAAgB,GAChB,cAAc,GACd,oBAAoB,GACpB,WAAW,GACX,YAAY,GACZ,iBAAiB,GACjB,OAAO,GACP,gBAAgB,GAChB,cAAc,CACjB,GAAG;IACF,cAAc,EAAE,oBAAoB,CAAC;CACtC,CAAC;AAiDF,qBAAa,iBAAkB,YAAW,eAAe;IAC3C,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,wBAAwB;IAEpD,QAAQ,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC;IAwGjG,WAAW,CAAC,EAAE,UAAU,EAAE,EAAE,QAAQ;IAIpC,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,eAAe;IAIrC,OAAO,CAAC,SAAS;CAkClB"}
|
package/dist/dispatch.js
CHANGED
|
@@ -7,14 +7,53 @@ function extractBashCommand(input) {
|
|
|
7
7
|
const command = input.command;
|
|
8
8
|
return typeof command === "string" && command.trim() ? command.trim() : undefined;
|
|
9
9
|
}
|
|
10
|
+
// Returns the non-flag tokens that follow a Parall CLI invocation in `command`,
|
|
11
|
+
// or null if the command isn't a Parall CLI call. Handles all the documented
|
|
12
|
+
// forms — bare `parall`, `npx [--yes|-y] @parall/cli[@version]`,
|
|
13
|
+
// `pnpm (exec|dlx) parall` — and skips `npx` flags so that `npx --yes`
|
|
14
|
+
// matches as well as `npx -y`. Tokenization avoids the regex false-match
|
|
15
|
+
// where `messages send --no-reply` looked like the receiver-side `no-reply`.
|
|
16
|
+
function parseParallCliInvocation(command) {
|
|
17
|
+
const tokens = command.replace(/\s+/g, " ").trim().split(" ");
|
|
18
|
+
let i = 0;
|
|
19
|
+
if (tokens[i] === "parall") {
|
|
20
|
+
i++;
|
|
21
|
+
}
|
|
22
|
+
else if (tokens[i] === "npx") {
|
|
23
|
+
i++;
|
|
24
|
+
while (i < tokens.length && tokens[i].startsWith("-"))
|
|
25
|
+
i++;
|
|
26
|
+
if (i >= tokens.length || !/^@parall\/cli(?:@.+)?$/.test(tokens[i]))
|
|
27
|
+
return null;
|
|
28
|
+
i++;
|
|
29
|
+
}
|
|
30
|
+
else if (tokens[i] === "pnpm") {
|
|
31
|
+
i++;
|
|
32
|
+
if (i < tokens.length && (tokens[i] === "exec" || tokens[i] === "dlx"))
|
|
33
|
+
i++;
|
|
34
|
+
if (i >= tokens.length || tokens[i] !== "parall")
|
|
35
|
+
return null;
|
|
36
|
+
i++;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return tokens.slice(i).filter((t) => !t.startsWith("-"));
|
|
42
|
+
}
|
|
10
43
|
function isParallSendCommand(command) {
|
|
11
44
|
if (!command)
|
|
12
45
|
return false;
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
46
|
+
const sub = parseParallCliInvocation(command);
|
|
47
|
+
if (!sub || sub.length === 0)
|
|
48
|
+
return false;
|
|
49
|
+
// `dm <user>` or `messages send <chat>`
|
|
50
|
+
return sub[0] === "dm" || (sub[0] === "messages" && sub[1] === "send");
|
|
51
|
+
}
|
|
52
|
+
function isParallNoReplyCommand(command) {
|
|
53
|
+
if (!command)
|
|
16
54
|
return false;
|
|
17
|
-
|
|
55
|
+
const sub = parseParallCliInvocation(command);
|
|
56
|
+
return sub?.[0] === "no-reply";
|
|
18
57
|
}
|
|
19
58
|
export class ClaudeCodeAdapter {
|
|
20
59
|
opts;
|
|
@@ -55,6 +94,10 @@ export class ClaudeCodeAdapter {
|
|
|
55
94
|
const groupKey = randomUUID();
|
|
56
95
|
let sawError = false;
|
|
57
96
|
let suppressProjectedText = false;
|
|
97
|
+
// Sticky for the whole dispatch: set when the agent explicitly calls
|
|
98
|
+
// `parall no-reply` to silence the turn. All subsequent text events are
|
|
99
|
+
// emitted with project=false so the bridge never leaks a chat message.
|
|
100
|
+
let noReplyCalled = false;
|
|
58
101
|
let completed = false;
|
|
59
102
|
try {
|
|
60
103
|
for await (const event of parseClaudeStreamJson(proc.stdout)) {
|
|
@@ -62,8 +105,14 @@ export class ClaudeCodeAdapter {
|
|
|
62
105
|
this.opts.sessionManager.recordSessionId(sessionKey, event.sessionId);
|
|
63
106
|
continue;
|
|
64
107
|
}
|
|
65
|
-
if (event.type === "tool_call"
|
|
66
|
-
|
|
108
|
+
if (event.type === "tool_call") {
|
|
109
|
+
const cmd = extractBashCommand(event.input);
|
|
110
|
+
if (isParallNoReplyCommand(cmd)) {
|
|
111
|
+
noReplyCalled = true;
|
|
112
|
+
}
|
|
113
|
+
else if (isParallSendCommand(cmd)) {
|
|
114
|
+
suppressProjectedText = true;
|
|
115
|
+
}
|
|
67
116
|
}
|
|
68
117
|
if (event.type === "error") {
|
|
69
118
|
sawError = true;
|
|
@@ -71,7 +120,11 @@ export class ClaudeCodeAdapter {
|
|
|
71
120
|
continue;
|
|
72
121
|
}
|
|
73
122
|
if (event.type === "text") {
|
|
74
|
-
const project =
|
|
123
|
+
const project = noReplyCalled
|
|
124
|
+
? false
|
|
125
|
+
: suppressProjectedText
|
|
126
|
+
? false
|
|
127
|
+
: event.project;
|
|
75
128
|
suppressProjectedText = false;
|
|
76
129
|
yield {
|
|
77
130
|
...event,
|
package/dist/index.js
CHANGED
|
@@ -15,8 +15,8 @@ function createLogger(prefix) {
|
|
|
15
15
|
}
|
|
16
16
|
async function main() {
|
|
17
17
|
const config = resolveClaudeAgentConfig(process.env);
|
|
18
|
-
ensureClaudeWorkspace(config.workspaceDir);
|
|
19
18
|
const log = createLogger("claude-agent");
|
|
19
|
+
ensureClaudeWorkspace(config.workspaceDir, log);
|
|
20
20
|
const client = new ParallClient({
|
|
21
21
|
baseUrl: config.apiUrl,
|
|
22
22
|
token: config.apiKey,
|
package/dist/workspace.d.ts
CHANGED
package/dist/workspace.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAiCA,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,MAAM,EACpB,GAAG,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,QA0BtC"}
|
package/dist/workspace.js
CHANGED
|
@@ -13,24 +13,43 @@ Reply with plain text when you want Parall to project your response back into th
|
|
|
13
13
|
|
|
14
14
|
Use CLI only when you need an explicit side effect instead of plain-text projection:
|
|
15
15
|
|
|
16
|
-
- \`npx @parall/cli@latest messages send CHT_ID --text "..." \`
|
|
17
|
-
- \`npx @parall/cli@latest tasks update TSK_ID --status in_progress\`
|
|
18
|
-
- \`npx @parall/cli@latest dm USER_ID --text "..." --no-reply\`
|
|
16
|
+
- \`npx --yes @parall/cli@latest messages send CHT_ID --text "..." \`
|
|
17
|
+
- \`npx --yes @parall/cli@latest tasks update TSK_ID --status in_progress\`
|
|
18
|
+
- \`npx --yes @parall/cli@latest dm USER_ID --text "..." --no-reply\`
|
|
19
|
+
- \`npx --yes @parall/cli@latest no-reply [--reason "..."]\` — end this turn without any visible reply
|
|
19
20
|
|
|
20
21
|
Credentials are already injected through environment variables:
|
|
21
22
|
\`PRLL_API_URL\`, \`PRLL_API_KEY\`, \`PRLL_ORG_ID\`, \`PRLL_SESSION_ID\`, \`PRLL_CHAT_ID\`, \`PRLL_TRIGGER_MESSAGE_ID\`, \`PRLL_STEP_ID_FILE\`.
|
|
22
23
|
|
|
23
24
|
## Guardrails
|
|
24
25
|
|
|
25
|
-
- If an event includes \`[Hint: no_reply]\`, do not
|
|
26
|
+
- If an event includes \`[Hint: no_reply]\`, **run \`npx --yes @parall/cli@latest no-reply\` as your first action** and do not type anything — not even "No response needed" / "Noted" / "OK". Plain text becomes a projected chat message; only the \`no-reply\` command silences the turn.
|
|
27
|
+
- Text emitted BEFORE \`no-reply\` in the same turn is already projected as a message and cannot be retracted; text AFTER is dropped by the bridge.
|
|
28
|
+
- A single dispatch may carry several events coalesced together. Once you call \`no-reply\` in a dispatch, **plain text suppression is sticky for the whole turn** — if some events still need a reply, send them explicitly via \`messages send\` / \`dm\`. Plain-text replies after \`no-reply\` are dropped silently.
|
|
26
29
|
- Prefer plain text over CLI for normal chat replies.
|
|
27
30
|
- Keep replies concise and task-focused.
|
|
28
31
|
`;
|
|
29
|
-
export function ensureClaudeWorkspace(workspaceDir) {
|
|
32
|
+
export function ensureClaudeWorkspace(workspaceDir, log) {
|
|
30
33
|
fs.mkdirSync(workspaceDir, { recursive: true });
|
|
31
34
|
fs.mkdirSync(path.join(workspaceDir, ".claude"), { recursive: true });
|
|
35
|
+
// CLAUDE.md is bridge-managed: always overwrite so a hosted PVC keeps the
|
|
36
|
+
// current guardrails (e.g. the `no-reply` instruction). Operators must not
|
|
37
|
+
// hand-edit this file — workspace customizations should go into AGENTS.md
|
|
38
|
+
// / SOUL.md / TOOLS.md, which are env-driven and untouched here. Surface a
|
|
39
|
+
// warning when we replace a file whose content diverges, so an operator
|
|
40
|
+
// who did edit it locally gets a signal instead of silently losing changes.
|
|
32
41
|
const claudeMdPath = path.join(workspaceDir, "CLAUDE.md");
|
|
33
|
-
if (
|
|
34
|
-
|
|
42
|
+
if (log) {
|
|
43
|
+
try {
|
|
44
|
+
const existing = fs.readFileSync(claudeMdPath, "utf8");
|
|
45
|
+
if (existing !== CLAUDE_MD) {
|
|
46
|
+
log.warn(`claude-agent: overwriting divergent ${claudeMdPath} with bridge-managed template ` +
|
|
47
|
+
`(local edits to CLAUDE.md are not preserved — customize AGENTS.md / SOUL.md / TOOLS.md instead)`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// file missing or unreadable — first-boot case, no warning needed
|
|
52
|
+
}
|
|
35
53
|
}
|
|
54
|
+
fs.writeFileSync(claudeMdPath, CLAUDE_MD, "utf8");
|
|
36
55
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/claude-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "Claude Code bridge runtime for self-hosted Parall agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"src"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@parall/agent-core": "1.
|
|
29
|
-
"@parall/cli": "1.
|
|
30
|
-
"@parall/sdk": "1.
|
|
28
|
+
"@parall/agent-core": "1.16.0",
|
|
29
|
+
"@parall/cli": "1.16.0",
|
|
30
|
+
"@parall/sdk": "1.16.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^22.0.0",
|
package/src/dispatch.ts
CHANGED
|
@@ -32,12 +32,45 @@ function extractBashCommand(input: unknown): string | undefined {
|
|
|
32
32
|
return typeof command === "string" && command.trim() ? command.trim() : undefined;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
// Returns the non-flag tokens that follow a Parall CLI invocation in `command`,
|
|
36
|
+
// or null if the command isn't a Parall CLI call. Handles all the documented
|
|
37
|
+
// forms — bare `parall`, `npx [--yes|-y] @parall/cli[@version]`,
|
|
38
|
+
// `pnpm (exec|dlx) parall` — and skips `npx` flags so that `npx --yes`
|
|
39
|
+
// matches as well as `npx -y`. Tokenization avoids the regex false-match
|
|
40
|
+
// where `messages send --no-reply` looked like the receiver-side `no-reply`.
|
|
41
|
+
function parseParallCliInvocation(command: string): string[] | null {
|
|
42
|
+
const tokens = command.replace(/\s+/g, " ").trim().split(" ");
|
|
43
|
+
let i = 0;
|
|
44
|
+
if (tokens[i] === "parall") {
|
|
45
|
+
i++;
|
|
46
|
+
} else if (tokens[i] === "npx") {
|
|
47
|
+
i++;
|
|
48
|
+
while (i < tokens.length && tokens[i].startsWith("-")) i++;
|
|
49
|
+
if (i >= tokens.length || !/^@parall\/cli(?:@.+)?$/.test(tokens[i])) return null;
|
|
50
|
+
i++;
|
|
51
|
+
} else if (tokens[i] === "pnpm") {
|
|
52
|
+
i++;
|
|
53
|
+
if (i < tokens.length && (tokens[i] === "exec" || tokens[i] === "dlx")) i++;
|
|
54
|
+
if (i >= tokens.length || tokens[i] !== "parall") return null;
|
|
55
|
+
i++;
|
|
56
|
+
} else {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return tokens.slice(i).filter((t) => !t.startsWith("-"));
|
|
60
|
+
}
|
|
61
|
+
|
|
35
62
|
function isParallSendCommand(command: string | undefined): boolean {
|
|
36
63
|
if (!command) return false;
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return
|
|
64
|
+
const sub = parseParallCliInvocation(command);
|
|
65
|
+
if (!sub || sub.length === 0) return false;
|
|
66
|
+
// `dm <user>` or `messages send <chat>`
|
|
67
|
+
return sub[0] === "dm" || (sub[0] === "messages" && sub[1] === "send");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isParallNoReplyCommand(command: string | undefined): boolean {
|
|
71
|
+
if (!command) return false;
|
|
72
|
+
const sub = parseParallCliInvocation(command);
|
|
73
|
+
return sub?.[0] === "no-reply";
|
|
41
74
|
}
|
|
42
75
|
|
|
43
76
|
export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
@@ -83,6 +116,10 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
83
116
|
const groupKey = randomUUID();
|
|
84
117
|
let sawError = false;
|
|
85
118
|
let suppressProjectedText = false;
|
|
119
|
+
// Sticky for the whole dispatch: set when the agent explicitly calls
|
|
120
|
+
// `parall no-reply` to silence the turn. All subsequent text events are
|
|
121
|
+
// emitted with project=false so the bridge never leaks a chat message.
|
|
122
|
+
let noReplyCalled = false;
|
|
86
123
|
let completed = false;
|
|
87
124
|
|
|
88
125
|
try {
|
|
@@ -92,8 +129,13 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
92
129
|
continue;
|
|
93
130
|
}
|
|
94
131
|
|
|
95
|
-
if (event.type === "tool_call"
|
|
96
|
-
|
|
132
|
+
if (event.type === "tool_call") {
|
|
133
|
+
const cmd = extractBashCommand(event.input);
|
|
134
|
+
if (isParallNoReplyCommand(cmd)) {
|
|
135
|
+
noReplyCalled = true;
|
|
136
|
+
} else if (isParallSendCommand(cmd)) {
|
|
137
|
+
suppressProjectedText = true;
|
|
138
|
+
}
|
|
97
139
|
}
|
|
98
140
|
|
|
99
141
|
if (event.type === "error") {
|
|
@@ -103,7 +145,11 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
103
145
|
}
|
|
104
146
|
|
|
105
147
|
if (event.type === "text") {
|
|
106
|
-
const project =
|
|
148
|
+
const project = noReplyCalled
|
|
149
|
+
? false
|
|
150
|
+
: suppressProjectedText
|
|
151
|
+
? false
|
|
152
|
+
: event.project;
|
|
107
153
|
suppressProjectedText = false;
|
|
108
154
|
yield {
|
|
109
155
|
...event,
|
package/src/index.ts
CHANGED
|
@@ -24,8 +24,8 @@ function createLogger(prefix: string) {
|
|
|
24
24
|
|
|
25
25
|
async function main() {
|
|
26
26
|
const config = resolveClaudeAgentConfig(process.env);
|
|
27
|
-
ensureClaudeWorkspace(config.workspaceDir);
|
|
28
27
|
const log = createLogger("claude-agent");
|
|
28
|
+
ensureClaudeWorkspace(config.workspaceDir, log);
|
|
29
29
|
|
|
30
30
|
const client = new ParallClient({
|
|
31
31
|
baseUrl: config.apiUrl,
|
package/src/workspace.ts
CHANGED
|
@@ -14,26 +14,49 @@ Reply with plain text when you want Parall to project your response back into th
|
|
|
14
14
|
|
|
15
15
|
Use CLI only when you need an explicit side effect instead of plain-text projection:
|
|
16
16
|
|
|
17
|
-
- \`npx @parall/cli@latest messages send CHT_ID --text "..." \`
|
|
18
|
-
- \`npx @parall/cli@latest tasks update TSK_ID --status in_progress\`
|
|
19
|
-
- \`npx @parall/cli@latest dm USER_ID --text "..." --no-reply\`
|
|
17
|
+
- \`npx --yes @parall/cli@latest messages send CHT_ID --text "..." \`
|
|
18
|
+
- \`npx --yes @parall/cli@latest tasks update TSK_ID --status in_progress\`
|
|
19
|
+
- \`npx --yes @parall/cli@latest dm USER_ID --text "..." --no-reply\`
|
|
20
|
+
- \`npx --yes @parall/cli@latest no-reply [--reason "..."]\` — end this turn without any visible reply
|
|
20
21
|
|
|
21
22
|
Credentials are already injected through environment variables:
|
|
22
23
|
\`PRLL_API_URL\`, \`PRLL_API_KEY\`, \`PRLL_ORG_ID\`, \`PRLL_SESSION_ID\`, \`PRLL_CHAT_ID\`, \`PRLL_TRIGGER_MESSAGE_ID\`, \`PRLL_STEP_ID_FILE\`.
|
|
23
24
|
|
|
24
25
|
## Guardrails
|
|
25
26
|
|
|
26
|
-
- If an event includes \`[Hint: no_reply]\`, do not
|
|
27
|
+
- If an event includes \`[Hint: no_reply]\`, **run \`npx --yes @parall/cli@latest no-reply\` as your first action** and do not type anything — not even "No response needed" / "Noted" / "OK". Plain text becomes a projected chat message; only the \`no-reply\` command silences the turn.
|
|
28
|
+
- Text emitted BEFORE \`no-reply\` in the same turn is already projected as a message and cannot be retracted; text AFTER is dropped by the bridge.
|
|
29
|
+
- A single dispatch may carry several events coalesced together. Once you call \`no-reply\` in a dispatch, **plain text suppression is sticky for the whole turn** — if some events still need a reply, send them explicitly via \`messages send\` / \`dm\`. Plain-text replies after \`no-reply\` are dropped silently.
|
|
27
30
|
- Prefer plain text over CLI for normal chat replies.
|
|
28
31
|
- Keep replies concise and task-focused.
|
|
29
32
|
`;
|
|
30
33
|
|
|
31
|
-
export function ensureClaudeWorkspace(
|
|
34
|
+
export function ensureClaudeWorkspace(
|
|
35
|
+
workspaceDir: string,
|
|
36
|
+
log?: { warn: (msg: string) => void },
|
|
37
|
+
) {
|
|
32
38
|
fs.mkdirSync(workspaceDir, { recursive: true });
|
|
33
39
|
fs.mkdirSync(path.join(workspaceDir, ".claude"), { recursive: true });
|
|
34
40
|
|
|
41
|
+
// CLAUDE.md is bridge-managed: always overwrite so a hosted PVC keeps the
|
|
42
|
+
// current guardrails (e.g. the `no-reply` instruction). Operators must not
|
|
43
|
+
// hand-edit this file — workspace customizations should go into AGENTS.md
|
|
44
|
+
// / SOUL.md / TOOLS.md, which are env-driven and untouched here. Surface a
|
|
45
|
+
// warning when we replace a file whose content diverges, so an operator
|
|
46
|
+
// who did edit it locally gets a signal instead of silently losing changes.
|
|
35
47
|
const claudeMdPath = path.join(workspaceDir, "CLAUDE.md");
|
|
36
|
-
if (
|
|
37
|
-
|
|
48
|
+
if (log) {
|
|
49
|
+
try {
|
|
50
|
+
const existing = fs.readFileSync(claudeMdPath, "utf8");
|
|
51
|
+
if (existing !== CLAUDE_MD) {
|
|
52
|
+
log.warn(
|
|
53
|
+
`claude-agent: overwriting divergent ${claudeMdPath} with bridge-managed template ` +
|
|
54
|
+
`(local edits to CLAUDE.md are not preserved — customize AGENTS.md / SOUL.md / TOOLS.md instead)`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// file missing or unreadable — first-boot case, no warning needed
|
|
59
|
+
}
|
|
38
60
|
}
|
|
61
|
+
fs.writeFileSync(claudeMdPath, CLAUDE_MD, "utf8");
|
|
39
62
|
}
|