@parall/claude-agent 1.15.1 → 1.17.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.
@@ -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;AAgBF,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;IA2FjG,WAAW,CAAC,EAAE,UAAU,EAAE,EAAE,QAAQ;IAIpC,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,eAAe;IAIrC,OAAO,CAAC,SAAS;CAkClB"}
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;AAEF,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;IAwFjG,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
@@ -1,21 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { spawn } from "node:child_process";
3
3
  import { parseClaudeStreamJson } from "./output-parser.js";
4
- function extractBashCommand(input) {
5
- if (!input || typeof input !== "object")
6
- return undefined;
7
- const command = input.command;
8
- return typeof command === "string" && command.trim() ? command.trim() : undefined;
9
- }
10
- function isParallSendCommand(command) {
11
- if (!command)
12
- return false;
13
- const normalized = command.replace(/\s+/g, " ").trim();
14
- const invokesParallCli = /(?:^| )(?:parall|npx @parall\/cli(?:@latest)?|pnpm (?:exec|dlx) parall)(?: |$)/.test(normalized);
15
- if (!invokesParallCli)
16
- return false;
17
- return /\b(messages send|dm)\b/.test(normalized);
18
- }
19
4
  export class ClaudeCodeAdapter {
20
5
  opts;
21
6
  constructor(opts) {
@@ -54,7 +39,6 @@ export class ClaudeCodeAdapter {
54
39
  });
55
40
  const groupKey = randomUUID();
56
41
  let sawError = false;
57
- let suppressProjectedText = false;
58
42
  let completed = false;
59
43
  try {
60
44
  for await (const event of parseClaudeStreamJson(proc.stdout)) {
@@ -62,20 +46,19 @@ export class ClaudeCodeAdapter {
62
46
  this.opts.sessionManager.recordSessionId(sessionKey, event.sessionId);
63
47
  continue;
64
48
  }
65
- if (event.type === "tool_call" && isParallSendCommand(extractBashCommand(event.input))) {
66
- suppressProjectedText = true;
67
- }
68
49
  if (event.type === "error") {
69
50
  sawError = true;
70
51
  yield event;
71
52
  continue;
72
53
  }
73
54
  if (event.type === "text") {
74
- const project = suppressProjectedText ? false : event.project;
75
- suppressProjectedText = false;
55
+ // Runtime output contract (symmetric with OpenClaw channel): Claude's
56
+ // plain text is never projected as a chat message. To reply, the agent
57
+ // must explicitly run `parall messages send` / `dm` via Bash. Text
58
+ // events are still recorded as suppressed session steps for audit.
76
59
  yield {
77
60
  ...event,
78
- project,
61
+ project: false,
79
62
  groupKey,
80
63
  };
81
64
  continue;
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,
@@ -1,2 +1,4 @@
1
- export declare function ensureClaudeWorkspace(workspaceDir: string): void;
1
+ export declare function ensureClaudeWorkspace(workspaceDir: string, log?: {
2
+ warn: (msg: string) => void;
3
+ }): void;
2
4
  //# sourceMappingURL=workspace.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AA8BA,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,QAQzD"}
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAkCA,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
@@ -2,35 +2,55 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  const CLAUDE_MD = `# CLAUDE.md
4
4
 
5
- You are an agent in Parall IM. You participate in chats, handle tasks, and can use Parall CLI commands for advanced operations.
5
+ You are an agent in Parall IM. You participate in chats, handle tasks, and interact exclusively through the Parall CLI.
6
6
 
7
7
  ## Message Model
8
8
 
9
9
  Incoming events are rendered as structured \`[Event: ...]\` blocks.
10
- Reply with plain text when you want Parall to project your response back into the triggering chat.
10
+ Each event includes \`[Chat: ... (prll://cht_xxx)]\` use that chat ID (or full URI) when replying.
11
+
12
+ **Your plain-text output is not delivered to anyone** — it is recorded as suppressed thinking in your session steps and discarded from the chat.
13
+ To say something in a chat, you **must** invoke the Parall CLI via the Bash tool. To stay silent, simply do not invoke it.
11
14
 
12
15
  ## Parall CLI
13
16
 
14
- Use CLI only when you need an explicit side effect instead of plain-text projection:
17
+ All outbound interactions go through \`@parall/cli\`. Credentials are pre-injected as environment variables no setup needed.
15
18
 
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\`
19
+ - \`npx --yes @parall/cli@latest messages send prll://cht_xxx --text "..."\` — reply into the triggering chat
20
+ - \`npx --yes @parall/cli@latest dm prll://usr_xxx --text "..." [--no-reply]\` — direct message another user
21
+ - \`npx --yes @parall/cli@latest tasks update prll://tsk_xxx --status in_progress\` — task state
22
+ - \`npx --yes @parall/cli@latest no-reply [--reason "..."]\` — explicitly declare this turn silent (audit signal; not required for silence, just clarifies intent)
19
23
 
20
- Credentials are already injected through environment variables:
21
- \`PRLL_API_URL\`, \`PRLL_API_KEY\`, \`PRLL_ORG_ID\`, \`PRLL_SESSION_ID\`, \`PRLL_CHAT_ID\`, \`PRLL_TRIGGER_MESSAGE_ID\`, \`PRLL_STEP_ID_FILE\`.
24
+ Available env: \`PRLL_API_URL\`, \`PRLL_API_KEY\`, \`PRLL_ORG_ID\`, \`PRLL_SESSION_ID\`, \`PRLL_CHAT_ID\`, \`PRLL_TRIGGER_MESSAGE_ID\`, \`PRLL_STEP_ID_FILE\`.
22
25
 
23
26
  ## Guardrails
24
27
 
25
- - If an event includes \`[Hint: no_reply]\`, do not send a reply.
26
- - Prefer plain text over CLI for normal chat replies.
27
- - Keep replies concise and task-focused.
28
+ - A dispatch may coalesce multiple events. Decide per event whether to reply via \`messages send\` / \`dm\` — events you do not act on simply receive no reply.
29
+ - If an event carries \`[Hint: no_reply]\`, do not send anything for that event. \`no-reply\` is optional and only useful as an explicit intent marker.
30
+ - Never try to "speak" by typing sentences like "No response needed" / "Noted" / "OK" — they are discarded, so they accomplish nothing except polluting your session log.
31
+ - Keep CLI replies concise and task-focused.
28
32
  `;
29
- export function ensureClaudeWorkspace(workspaceDir) {
33
+ export function ensureClaudeWorkspace(workspaceDir, log) {
30
34
  fs.mkdirSync(workspaceDir, { recursive: true });
31
35
  fs.mkdirSync(path.join(workspaceDir, ".claude"), { recursive: true });
36
+ // CLAUDE.md is bridge-managed: always overwrite so a hosted PVC keeps the
37
+ // current guardrails (e.g. the `no-reply` instruction). Operators must not
38
+ // hand-edit this file — workspace customizations should go into AGENTS.md
39
+ // / SOUL.md / TOOLS.md, which are env-driven and untouched here. Surface a
40
+ // warning when we replace a file whose content diverges, so an operator
41
+ // who did edit it locally gets a signal instead of silently losing changes.
32
42
  const claudeMdPath = path.join(workspaceDir, "CLAUDE.md");
33
- if (!fs.existsSync(claudeMdPath)) {
34
- fs.writeFileSync(claudeMdPath, CLAUDE_MD, "utf8");
43
+ if (log) {
44
+ try {
45
+ const existing = fs.readFileSync(claudeMdPath, "utf8");
46
+ if (existing !== CLAUDE_MD) {
47
+ log.warn(`claude-agent: overwriting divergent ${claudeMdPath} with bridge-managed template ` +
48
+ `(local edits to CLAUDE.md are not preserved — customize AGENTS.md / SOUL.md / TOOLS.md instead)`);
49
+ }
50
+ }
51
+ catch {
52
+ // file missing or unreadable — first-boot case, no warning needed
53
+ }
35
54
  }
55
+ fs.writeFileSync(claudeMdPath, CLAUDE_MD, "utf8");
36
56
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/claude-agent",
3
- "version": "1.15.1",
3
+ "version": "1.17.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.15.1",
29
- "@parall/cli": "1.15.1",
30
- "@parall/sdk": "1.15.1"
28
+ "@parall/sdk": "1.17.0",
29
+ "@parall/agent-core": "1.17.0",
30
+ "@parall/cli": "1.17.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^22.0.0",
package/src/dispatch.ts CHANGED
@@ -26,20 +26,6 @@ type ClaudeCodeAdapterOptions = Pick<
26
26
  sessionManager: ClaudeSessionManager;
27
27
  };
28
28
 
29
- function extractBashCommand(input: unknown): string | undefined {
30
- if (!input || typeof input !== "object") return undefined;
31
- const command = (input as { command?: unknown }).command;
32
- return typeof command === "string" && command.trim() ? command.trim() : undefined;
33
- }
34
-
35
- function isParallSendCommand(command: string | undefined): boolean {
36
- if (!command) return false;
37
- const normalized = command.replace(/\s+/g, " ").trim();
38
- const invokesParallCli = /(?:^| )(?:parall|npx @parall\/cli(?:@latest)?|pnpm (?:exec|dlx) parall)(?: |$)/.test(normalized);
39
- if (!invokesParallCli) return false;
40
- return /\b(messages send|dm)\b/.test(normalized);
41
- }
42
-
43
29
  export class ClaudeCodeAdapter implements DispatchAdapter {
44
30
  constructor(private readonly opts: ClaudeCodeAdapterOptions) {}
45
31
 
@@ -82,7 +68,6 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
82
68
 
83
69
  const groupKey = randomUUID();
84
70
  let sawError = false;
85
- let suppressProjectedText = false;
86
71
  let completed = false;
87
72
 
88
73
  try {
@@ -92,10 +77,6 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
92
77
  continue;
93
78
  }
94
79
 
95
- if (event.type === "tool_call" && isParallSendCommand(extractBashCommand(event.input))) {
96
- suppressProjectedText = true;
97
- }
98
-
99
80
  if (event.type === "error") {
100
81
  sawError = true;
101
82
  yield event;
@@ -103,11 +84,13 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
103
84
  }
104
85
 
105
86
  if (event.type === "text") {
106
- const project = suppressProjectedText ? false : event.project;
107
- suppressProjectedText = false;
87
+ // Runtime output contract (symmetric with OpenClaw channel): Claude's
88
+ // plain text is never projected as a chat message. To reply, the agent
89
+ // must explicitly run `parall messages send` / `dm` via Bash. Text
90
+ // events are still recorded as suppressed session steps for audit.
108
91
  yield {
109
92
  ...event,
110
- project,
93
+ project: false,
111
94
  groupKey,
112
95
  };
113
96
  continue;
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
@@ -3,37 +3,61 @@ import * as path from "node:path";
3
3
 
4
4
  const CLAUDE_MD = `# CLAUDE.md
5
5
 
6
- You are an agent in Parall IM. You participate in chats, handle tasks, and can use Parall CLI commands for advanced operations.
6
+ You are an agent in Parall IM. You participate in chats, handle tasks, and interact exclusively through the Parall CLI.
7
7
 
8
8
  ## Message Model
9
9
 
10
10
  Incoming events are rendered as structured \`[Event: ...]\` blocks.
11
- Reply with plain text when you want Parall to project your response back into the triggering chat.
11
+ Each event includes \`[Chat: ... (prll://cht_xxx)]\` use that chat ID (or full URI) when replying.
12
+
13
+ **Your plain-text output is not delivered to anyone** — it is recorded as suppressed thinking in your session steps and discarded from the chat.
14
+ To say something in a chat, you **must** invoke the Parall CLI via the Bash tool. To stay silent, simply do not invoke it.
12
15
 
13
16
  ## Parall CLI
14
17
 
15
- Use CLI only when you need an explicit side effect instead of plain-text projection:
18
+ All outbound interactions go through \`@parall/cli\`. Credentials are pre-injected as environment variables no setup needed.
16
19
 
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\`
20
+ - \`npx --yes @parall/cli@latest messages send prll://cht_xxx --text "..."\` — reply into the triggering chat
21
+ - \`npx --yes @parall/cli@latest dm prll://usr_xxx --text "..." [--no-reply]\` — direct message another user
22
+ - \`npx --yes @parall/cli@latest tasks update prll://tsk_xxx --status in_progress\` — task state
23
+ - \`npx --yes @parall/cli@latest no-reply [--reason "..."]\` — explicitly declare this turn silent (audit signal; not required for silence, just clarifies intent)
20
24
 
21
- Credentials are already injected through environment variables:
22
- \`PRLL_API_URL\`, \`PRLL_API_KEY\`, \`PRLL_ORG_ID\`, \`PRLL_SESSION_ID\`, \`PRLL_CHAT_ID\`, \`PRLL_TRIGGER_MESSAGE_ID\`, \`PRLL_STEP_ID_FILE\`.
25
+ Available env: \`PRLL_API_URL\`, \`PRLL_API_KEY\`, \`PRLL_ORG_ID\`, \`PRLL_SESSION_ID\`, \`PRLL_CHAT_ID\`, \`PRLL_TRIGGER_MESSAGE_ID\`, \`PRLL_STEP_ID_FILE\`.
23
26
 
24
27
  ## Guardrails
25
28
 
26
- - If an event includes \`[Hint: no_reply]\`, do not send a reply.
27
- - Prefer plain text over CLI for normal chat replies.
28
- - Keep replies concise and task-focused.
29
+ - A dispatch may coalesce multiple events. Decide per event whether to reply via \`messages send\` / \`dm\` — events you do not act on simply receive no reply.
30
+ - If an event carries \`[Hint: no_reply]\`, do not send anything for that event. \`no-reply\` is optional and only useful as an explicit intent marker.
31
+ - Never try to "speak" by typing sentences like "No response needed" / "Noted" / "OK" — they are discarded, so they accomplish nothing except polluting your session log.
32
+ - Keep CLI replies concise and task-focused.
29
33
  `;
30
34
 
31
- export function ensureClaudeWorkspace(workspaceDir: string) {
35
+ export function ensureClaudeWorkspace(
36
+ workspaceDir: string,
37
+ log?: { warn: (msg: string) => void },
38
+ ) {
32
39
  fs.mkdirSync(workspaceDir, { recursive: true });
33
40
  fs.mkdirSync(path.join(workspaceDir, ".claude"), { recursive: true });
34
41
 
42
+ // CLAUDE.md is bridge-managed: always overwrite so a hosted PVC keeps the
43
+ // current guardrails (e.g. the `no-reply` instruction). Operators must not
44
+ // hand-edit this file — workspace customizations should go into AGENTS.md
45
+ // / SOUL.md / TOOLS.md, which are env-driven and untouched here. Surface a
46
+ // warning when we replace a file whose content diverges, so an operator
47
+ // who did edit it locally gets a signal instead of silently losing changes.
35
48
  const claudeMdPath = path.join(workspaceDir, "CLAUDE.md");
36
- if (!fs.existsSync(claudeMdPath)) {
37
- fs.writeFileSync(claudeMdPath, CLAUDE_MD, "utf8");
49
+ if (log) {
50
+ try {
51
+ const existing = fs.readFileSync(claudeMdPath, "utf8");
52
+ if (existing !== CLAUDE_MD) {
53
+ log.warn(
54
+ `claude-agent: overwriting divergent ${claudeMdPath} with bridge-managed template ` +
55
+ `(local edits to CLAUDE.md are not preserved — customize AGENTS.md / SOUL.md / TOOLS.md instead)`,
56
+ );
57
+ }
58
+ } catch {
59
+ // file missing or unreadable — first-boot case, no warning needed
60
+ }
38
61
  }
62
+ fs.writeFileSync(claudeMdPath, CLAUDE_MD, "utf8");
39
63
  }