@openclaw-cloud/agent-controller 1.0.0 → 2.1.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/bin/agent-controller.js +11 -2
- package/dist/api.d.ts +4 -0
- package/dist/api.js +8 -0
- package/dist/api.js.map +1 -1
- package/dist/commands/channel-server.d.ts +17 -0
- package/dist/commands/channel-server.js +71 -0
- package/dist/commands/channel-server.js.map +1 -0
- package/dist/config-file.js +6 -0
- package/dist/config-file.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.js +20 -0
- package/dist/config.js.map +1 -1
- package/dist/connection.d.ts +2 -0
- package/dist/connection.js +2 -0
- package/dist/connection.js.map +1 -1
- package/dist/handlers/board-handler.js +56 -20
- package/dist/handlers/board-handler.js.map +1 -1
- package/dist/handlers/chat.d.ts +17 -0
- package/dist/handlers/chat.js +93 -0
- package/dist/handlers/chat.js.map +1 -1
- package/dist/handlers/memory.d.ts +7 -0
- package/dist/handlers/memory.js +41 -0
- package/dist/handlers/memory.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +73 -1
- package/dist/index.js.map +1 -1
- package/dist/memory-mcp-server.d.ts +8 -0
- package/dist/memory-mcp-server.js +291 -0
- package/dist/memory-mcp-server.js.map +1 -0
- package/dist/providers/claude-code/channel-server.d.ts +60 -0
- package/dist/providers/claude-code/channel-server.js +155 -0
- package/dist/providers/claude-code/channel-server.js.map +1 -0
- package/dist/providers/claude-code/index.d.ts +68 -0
- package/dist/providers/claude-code/index.js +276 -0
- package/dist/providers/claude-code/index.js.map +1 -0
- package/dist/providers/claude-code/login-flow.d.ts +26 -0
- package/dist/providers/claude-code/login-flow.js +129 -0
- package/dist/providers/claude-code/login-flow.js.map +1 -0
- package/dist/providers/claude-code/settings-writer.d.ts +29 -0
- package/dist/providers/claude-code/settings-writer.js +79 -0
- package/dist/providers/claude-code/settings-writer.js.map +1 -0
- package/dist/providers/claude-code/socket-bridge.d.ts +88 -0
- package/dist/providers/claude-code/socket-bridge.js +302 -0
- package/dist/providers/claude-code/socket-bridge.js.map +1 -0
- package/dist/providers/claude-code/spawn-claude.d.ts +44 -0
- package/dist/providers/claude-code/spawn-claude.js +108 -0
- package/dist/providers/claude-code/spawn-claude.js.map +1 -0
- package/dist/providers/index.d.ts +3 -1
- package/dist/providers/index.js +12 -0
- package/dist/providers/index.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utils/knowledge-graph.d.ts +66 -0
- package/dist/utils/knowledge-graph.js +163 -0
- package/dist/utils/knowledge-graph.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { logCollector } from '../../connection.js';
|
|
5
|
+
import { toErrorMessage } from '../../utils/response.js';
|
|
6
|
+
import { writeClaudeCodeSettings } from './settings-writer.js';
|
|
7
|
+
import { runSetupTokenFlow } from './login-flow.js';
|
|
8
|
+
import { ChannelBridgeServer, DEFAULT_SOCKET_PATH } from './socket-bridge.js';
|
|
9
|
+
import { ClaudeProcess } from './spawn-claude.js';
|
|
10
|
+
/**
|
|
11
|
+
* Claude Code provider — orchestrates a local Claude Code subprocess plus a
|
|
12
|
+
* channel-server worker that bridges Claude's stdio MCP channel into this
|
|
13
|
+
* controller process via a Unix domain socket.
|
|
14
|
+
*
|
|
15
|
+
* Architecture:
|
|
16
|
+
* [Claude Code] ── stdio MCP ──> [agent-controller channel-server worker]
|
|
17
|
+
* ── unix socket ──> [main agent-controller]
|
|
18
|
+
*
|
|
19
|
+
* See `socket-bridge.ts` for the wire protocol.
|
|
20
|
+
*/
|
|
21
|
+
export class ClaudeCodeProvider {
|
|
22
|
+
cfg;
|
|
23
|
+
name = 'claude-code';
|
|
24
|
+
_connected = false;
|
|
25
|
+
bridge = null;
|
|
26
|
+
claude = null;
|
|
27
|
+
lastHeartbeatAt = 0;
|
|
28
|
+
pendingLoginUrl = null;
|
|
29
|
+
pendingCodeResolvers = [];
|
|
30
|
+
replyPublisher = null;
|
|
31
|
+
constructor(cfg) {
|
|
32
|
+
this.cfg = cfg;
|
|
33
|
+
}
|
|
34
|
+
/** Register an outbound publisher; called by the chat handler at startup. */
|
|
35
|
+
setReplyPublisher(fn) {
|
|
36
|
+
this.replyPublisher = fn;
|
|
37
|
+
}
|
|
38
|
+
/** Used by the chat handler to feed user-submitted codes into the login flow. */
|
|
39
|
+
submitLoginCode(code) {
|
|
40
|
+
const resolver = this.pendingCodeResolvers.shift();
|
|
41
|
+
if (resolver) {
|
|
42
|
+
resolver(code);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
/** The chat handler checks this to short-circuit the first message with the login URL. */
|
|
48
|
+
getPendingLoginUrl() {
|
|
49
|
+
return this.pendingLoginUrl;
|
|
50
|
+
}
|
|
51
|
+
/** Push an inbound chat/board message to Claude via the channel worker. */
|
|
52
|
+
pushToChannel(content, meta) {
|
|
53
|
+
if (!this.bridge) {
|
|
54
|
+
logCollector?.push('claude_push_no_bridge', 'warn', 'pushToChannel called but bridge is not up yet');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.bridge.push(content, meta);
|
|
58
|
+
}
|
|
59
|
+
/** Internal — invoked by the bridge when Claude replies via the tool. */
|
|
60
|
+
handleChannelReply(content, meta, tool) {
|
|
61
|
+
this.lastHeartbeatAt = Date.now();
|
|
62
|
+
const reply = { text: content, reply_to: meta.message_id };
|
|
63
|
+
this.replyPublisher?.(reply, tool, meta);
|
|
64
|
+
}
|
|
65
|
+
// ─── Lifecycle ────────────────────────────────────────────────────────────
|
|
66
|
+
async connect() {
|
|
67
|
+
if (this._connected)
|
|
68
|
+
return;
|
|
69
|
+
try {
|
|
70
|
+
if (!this.hasCredentials()) {
|
|
71
|
+
logCollector?.push('claude_connect_login_needed', 'info', 'No Claude credentials found — waiting for user login via chat');
|
|
72
|
+
void this.startLoginFlow();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
await this.materializeWorkspace();
|
|
76
|
+
await this.startBridgeAndClaude();
|
|
77
|
+
this._connected = true;
|
|
78
|
+
logCollector?.push('claude_connect_ok', 'info', 'claude-code provider connected');
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
logCollector?.push('claude_connect_failed', 'error', `claude-code connect failed: ${toErrorMessage(err)}`);
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
disconnect() {
|
|
86
|
+
this._connected = false;
|
|
87
|
+
this.claude?.stop();
|
|
88
|
+
this.claude = null;
|
|
89
|
+
void this.bridge?.close();
|
|
90
|
+
this.bridge = null;
|
|
91
|
+
}
|
|
92
|
+
isConnected() {
|
|
93
|
+
return this._connected;
|
|
94
|
+
}
|
|
95
|
+
async healthCheck() {
|
|
96
|
+
const alive = this.claude?.isRunning() ?? false;
|
|
97
|
+
const fresh = this.lastHeartbeatAt === 0 || Date.now() - this.lastHeartbeatAt < 60_000;
|
|
98
|
+
return { ok: this._connected && alive && fresh, rpcOk: alive };
|
|
99
|
+
}
|
|
100
|
+
async restart() {
|
|
101
|
+
this.disconnect();
|
|
102
|
+
await this.connect();
|
|
103
|
+
}
|
|
104
|
+
async abort() {
|
|
105
|
+
this.claude?.interrupt();
|
|
106
|
+
}
|
|
107
|
+
// ─── Messaging ────────────────────────────────────────────────────────────
|
|
108
|
+
async sendMessage(params) {
|
|
109
|
+
try {
|
|
110
|
+
if (this.pendingLoginUrl) {
|
|
111
|
+
params.onDone?.(`To connect, log in at: ${this.pendingLoginUrl}\n\nPaste the verification code in this chat once ready.`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (!this._connected)
|
|
115
|
+
await this.connect();
|
|
116
|
+
if (!this.bridge) {
|
|
117
|
+
params.onError?.('claude-code channel bridge not running');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.bridge.push(params.text, {
|
|
121
|
+
chat_id: params.sessionKey,
|
|
122
|
+
source: 'web',
|
|
123
|
+
message_id: params.idempotencyKey,
|
|
124
|
+
});
|
|
125
|
+
// Reply flows back asynchronously via the reply publisher — no onDone here.
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
params.onError?.(toErrorMessage(err));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async listSessions() {
|
|
132
|
+
const dir = this.projectJsonlDir();
|
|
133
|
+
if (!dir)
|
|
134
|
+
return [];
|
|
135
|
+
try {
|
|
136
|
+
const entries = await fs.promises.readdir(dir);
|
|
137
|
+
const sessions = await Promise.all(entries
|
|
138
|
+
.filter((f) => f.endsWith('.jsonl'))
|
|
139
|
+
.map(async (file) => {
|
|
140
|
+
const full = path.join(dir, file);
|
|
141
|
+
const st = await fs.promises.stat(full);
|
|
142
|
+
const sessionId = file.replace(/\.jsonl$/, '');
|
|
143
|
+
return {
|
|
144
|
+
key: sessionId,
|
|
145
|
+
sessionId,
|
|
146
|
+
kind: 'claude-code',
|
|
147
|
+
label: sessionId,
|
|
148
|
+
updatedAt: st.mtimeMs,
|
|
149
|
+
};
|
|
150
|
+
}));
|
|
151
|
+
return sessions.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async getHistory(sessionKey, limit = 200) {
|
|
158
|
+
const dir = this.projectJsonlDir();
|
|
159
|
+
if (!dir)
|
|
160
|
+
return [];
|
|
161
|
+
const file = path.join(dir, `${sessionKey}.jsonl`);
|
|
162
|
+
try {
|
|
163
|
+
const content = await fs.promises.readFile(file, 'utf-8');
|
|
164
|
+
const lines = content.split(/\r?\n/).filter((l) => l.trim());
|
|
165
|
+
const msgs = [];
|
|
166
|
+
for (const line of lines) {
|
|
167
|
+
try {
|
|
168
|
+
const row = JSON.parse(line);
|
|
169
|
+
const role = row.role ??
|
|
170
|
+
row.message?.role ??
|
|
171
|
+
(row.type === 'user' ? 'user' : row.type === 'assistant' ? 'assistant' : undefined);
|
|
172
|
+
if (!role)
|
|
173
|
+
continue;
|
|
174
|
+
const rawContent = row.message?.content ?? '';
|
|
175
|
+
const content = typeof rawContent === 'string' ? rawContent : rawContent;
|
|
176
|
+
msgs.push({
|
|
177
|
+
role,
|
|
178
|
+
content,
|
|
179
|
+
timestamp: row.timestamp ?? new Date().toISOString(),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
/* skip malformed line */
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return msgs.slice(-limit);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// ─── Internals ────────────────────────────────────────────────────────────
|
|
193
|
+
hasCredentials() {
|
|
194
|
+
if (this.cfg.anthropicAuthToken || this.cfg.anthropicApiKey || this.cfg.claudeCodeOauthToken) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
return fs.statSync(path.join(os.homedir(), '.claude', '.credentials.json')).isFile();
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async materializeWorkspace() {
|
|
205
|
+
await writeClaudeCodeSettings({
|
|
206
|
+
workspaceDir: this.cfg.workspaceDir,
|
|
207
|
+
anthropicApiKey: this.cfg.anthropicApiKey,
|
|
208
|
+
anthropicAuthToken: this.cfg.anthropicAuthToken,
|
|
209
|
+
backendUrl: this.cfg.backendUrl,
|
|
210
|
+
agentToken: this.cfg.agentToken,
|
|
211
|
+
claudeMdContent: this.cfg.claudeMdContent,
|
|
212
|
+
agentControllerBin: this.cfg.agentControllerBin,
|
|
213
|
+
channelSocketPath: this.cfg.socketPath,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
async startBridgeAndClaude() {
|
|
217
|
+
// 1) Spin up the socket bridge FIRST so the worker can connect as soon as
|
|
218
|
+
// Claude spawns it.
|
|
219
|
+
this.bridge = new ChannelBridgeServer({
|
|
220
|
+
socketPath: this.cfg.socketPath ?? DEFAULT_SOCKET_PATH,
|
|
221
|
+
});
|
|
222
|
+
this.bridge.on('reply', (content, meta, tool) => {
|
|
223
|
+
this.handleChannelReply(content, meta, tool);
|
|
224
|
+
});
|
|
225
|
+
this.bridge.on('connect', () => {
|
|
226
|
+
this.lastHeartbeatAt = Date.now();
|
|
227
|
+
});
|
|
228
|
+
await this.bridge.listen();
|
|
229
|
+
// 2) Spawn Claude — it will exec `agent-controller channel-server` as a
|
|
230
|
+
// stdio MCP server per the workspace's `.mcp.json`.
|
|
231
|
+
this.claude = new ClaudeProcess({
|
|
232
|
+
workspaceDir: this.cfg.workspaceDir,
|
|
233
|
+
channelsFlag: 'mcp:agent-controller-channel',
|
|
234
|
+
});
|
|
235
|
+
this.claude.on('stderr', () => {
|
|
236
|
+
this.lastHeartbeatAt = Date.now();
|
|
237
|
+
});
|
|
238
|
+
this.claude.start();
|
|
239
|
+
}
|
|
240
|
+
async startLoginFlow() {
|
|
241
|
+
try {
|
|
242
|
+
await runSetupTokenFlow({
|
|
243
|
+
publishLoginUrl: (url) => {
|
|
244
|
+
this.pendingLoginUrl = url;
|
|
245
|
+
},
|
|
246
|
+
waitForCode: () => new Promise((resolve) => {
|
|
247
|
+
this.pendingCodeResolvers.push(resolve);
|
|
248
|
+
}),
|
|
249
|
+
});
|
|
250
|
+
this.pendingLoginUrl = null;
|
|
251
|
+
await this.materializeWorkspace();
|
|
252
|
+
await this.startBridgeAndClaude();
|
|
253
|
+
this._connected = true;
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
logCollector?.push('claude_login_flow_error', 'error', `login flow failed: ${toErrorMessage(err)}`);
|
|
257
|
+
this.pendingLoginUrl = null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
projectJsonlDir() {
|
|
261
|
+
const cwd = this.cfg.workspaceDir;
|
|
262
|
+
if (!cwd)
|
|
263
|
+
return null;
|
|
264
|
+
const encoded = cwd.replace(/\//g, '-');
|
|
265
|
+
const dir = path.join(os.homedir(), '.claude', 'projects', encoded);
|
|
266
|
+
try {
|
|
267
|
+
if (fs.statSync(dir).isDirectory())
|
|
268
|
+
return dir;
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
/* ignore */
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/providers/claude-code/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
export interface LoginFlowDeps {
|
|
3
|
+
/** Called with the login URL captured from `claude setup-token` stdout. */
|
|
4
|
+
publishLoginUrl(url: string): void | Promise<void>;
|
|
5
|
+
/** Resolves with the verification code that the user submitted in chat. */
|
|
6
|
+
waitForCode(): Promise<string>;
|
|
7
|
+
/** Override for tests — inject a fake child_process. */
|
|
8
|
+
spawnFn?: typeof spawn;
|
|
9
|
+
/** Override credentials path (tests). Defaults to `~/.claude/.credentials.json`. */
|
|
10
|
+
credentialsPath?: string;
|
|
11
|
+
/** Overall timeout in ms. Defaults to 10 minutes. */
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Drive the `claude setup-token` subscription login flow.
|
|
16
|
+
*
|
|
17
|
+
* Flow:
|
|
18
|
+
* 1. Spawn `claude setup-token` and watch stdout for the `claude.ai/oauth/authorize?...` URL.
|
|
19
|
+
* 2. Push the URL to chat via `deps.publishLoginUrl`.
|
|
20
|
+
* 3. Await `deps.waitForCode()` — resolves with the code the user submitted.
|
|
21
|
+
* 4. Write `code + "\n"` to subprocess stdin, wait for exit.
|
|
22
|
+
* 5. Verify `~/.claude/.credentials.json` was created/updated → success.
|
|
23
|
+
*
|
|
24
|
+
* Emits structured logCollector events at each milestone.
|
|
25
|
+
*/
|
|
26
|
+
export declare function runSetupTokenFlow(deps: LoginFlowDeps): Promise<void>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { logCollector } from '../../connection.js';
|
|
6
|
+
import { toErrorMessage } from '../../utils/response.js';
|
|
7
|
+
const AUTH_URL_RE = /https:\/\/claude\.ai\/oauth\/authorize\?\S+/;
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000; // 10 min
|
|
9
|
+
/**
|
|
10
|
+
* Drive the `claude setup-token` subscription login flow.
|
|
11
|
+
*
|
|
12
|
+
* Flow:
|
|
13
|
+
* 1. Spawn `claude setup-token` and watch stdout for the `claude.ai/oauth/authorize?...` URL.
|
|
14
|
+
* 2. Push the URL to chat via `deps.publishLoginUrl`.
|
|
15
|
+
* 3. Await `deps.waitForCode()` — resolves with the code the user submitted.
|
|
16
|
+
* 4. Write `code + "\n"` to subprocess stdin, wait for exit.
|
|
17
|
+
* 5. Verify `~/.claude/.credentials.json` was created/updated → success.
|
|
18
|
+
*
|
|
19
|
+
* Emits structured logCollector events at each milestone.
|
|
20
|
+
*/
|
|
21
|
+
export async function runSetupTokenFlow(deps) {
|
|
22
|
+
const spawnFn = deps.spawnFn ?? spawn;
|
|
23
|
+
const credPath = deps.credentialsPath ?? path.join(os.homedir(), '.claude', '.credentials.json');
|
|
24
|
+
const timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
25
|
+
const startedAt = Date.now();
|
|
26
|
+
logCollector?.push('claude_login_start', 'info', 'Starting claude setup-token flow');
|
|
27
|
+
const credMtimeBefore = safeMtime(credPath);
|
|
28
|
+
const child = spawnFn('claude', ['setup-token'], {
|
|
29
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
30
|
+
});
|
|
31
|
+
let stdoutBuf = '';
|
|
32
|
+
let stderrBuf = '';
|
|
33
|
+
let urlPublished = false;
|
|
34
|
+
let codeWritten = false;
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const timeoutHandle = setTimeout(() => {
|
|
37
|
+
cleanup();
|
|
38
|
+
logCollector?.push('claude_login_failed', 'error', 'Claude login timed out before completion', { durationMs: Date.now() - startedAt });
|
|
39
|
+
try {
|
|
40
|
+
child.kill('SIGKILL');
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
/* ignore */
|
|
44
|
+
}
|
|
45
|
+
reject(new Error('claude setup-token timed out'));
|
|
46
|
+
}, timeoutMs);
|
|
47
|
+
const onStdout = (chunk) => {
|
|
48
|
+
const text = chunk.toString();
|
|
49
|
+
stdoutBuf += text;
|
|
50
|
+
if (!urlPublished) {
|
|
51
|
+
const match = stdoutBuf.match(AUTH_URL_RE);
|
|
52
|
+
if (match) {
|
|
53
|
+
urlPublished = true;
|
|
54
|
+
const url = match[0];
|
|
55
|
+
logCollector?.push('claude_login_url_published', 'info', 'Captured OAuth URL', { url });
|
|
56
|
+
Promise.resolve(deps.publishLoginUrl(url))
|
|
57
|
+
.then(() => deps.waitForCode())
|
|
58
|
+
.then((code) => {
|
|
59
|
+
if (!code || typeof code !== 'string') {
|
|
60
|
+
throw new Error('waitForCode returned empty value');
|
|
61
|
+
}
|
|
62
|
+
codeWritten = true;
|
|
63
|
+
logCollector?.push('claude_login_code_received', 'info', 'User submitted code');
|
|
64
|
+
child.stdin.write(code.trim() + '\n');
|
|
65
|
+
child.stdin.end();
|
|
66
|
+
})
|
|
67
|
+
.catch((err) => {
|
|
68
|
+
cleanup();
|
|
69
|
+
logCollector?.push('claude_login_failed', 'error', `Login pre-exit step failed: ${toErrorMessage(err)}`);
|
|
70
|
+
try {
|
|
71
|
+
child.kill('SIGKILL');
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
/* ignore */
|
|
75
|
+
}
|
|
76
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const onStderr = (chunk) => {
|
|
82
|
+
stderrBuf += chunk.toString();
|
|
83
|
+
};
|
|
84
|
+
const onError = (err) => {
|
|
85
|
+
cleanup();
|
|
86
|
+
logCollector?.push('claude_login_failed', 'error', `spawn error: ${err.message}`);
|
|
87
|
+
reject(err);
|
|
88
|
+
};
|
|
89
|
+
const onExit = (code) => {
|
|
90
|
+
cleanup();
|
|
91
|
+
if (code === 0 && codeWritten) {
|
|
92
|
+
const credMtimeAfter = safeMtime(credPath);
|
|
93
|
+
const credChanged = credMtimeAfter !== null && credMtimeAfter !== credMtimeBefore;
|
|
94
|
+
if (credChanged) {
|
|
95
|
+
logCollector?.push('claude_login_completed', 'info', 'Claude login succeeded', {
|
|
96
|
+
durationMs: Date.now() - startedAt,
|
|
97
|
+
});
|
|
98
|
+
resolve();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
logCollector?.push('claude_login_failed', 'error', 'Login exited 0 but credentials file was not updated', { credPath });
|
|
102
|
+
reject(new Error('credentials file not updated after setup-token'));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
logCollector?.push('claude_login_failed', 'error', `claude setup-token exited with code ${code ?? 'null'}`, { stderrTail: stderrBuf.slice(-500) });
|
|
106
|
+
reject(new Error(`claude setup-token exited with code ${code ?? 'null'}`));
|
|
107
|
+
};
|
|
108
|
+
function cleanup() {
|
|
109
|
+
clearTimeout(timeoutHandle);
|
|
110
|
+
child.stdout.off('data', onStdout);
|
|
111
|
+
child.stderr.off('data', onStderr);
|
|
112
|
+
child.off('error', onError);
|
|
113
|
+
child.off('exit', onExit);
|
|
114
|
+
}
|
|
115
|
+
child.stdout.on('data', onStdout);
|
|
116
|
+
child.stderr.on('data', onStderr);
|
|
117
|
+
child.on('error', onError);
|
|
118
|
+
child.on('exit', onExit);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function safeMtime(p) {
|
|
122
|
+
try {
|
|
123
|
+
return fs.statSync(p).mtimeMs;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=login-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login-flow.js","sourceRoot":"","sources":["../../../src/providers/claude-code/login-flow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface ClaudeCodeSettingsContext {
|
|
2
|
+
/** Absolute path to the Claude workspace (cwd of `claude` process) */
|
|
3
|
+
workspaceDir: string;
|
|
4
|
+
/** Anthropic API key. If prefixed with `sk-ant-oat` → treated as OAuth token. */
|
|
5
|
+
anthropicApiKey?: string;
|
|
6
|
+
/** Alternative: explicit auth token (takes precedence over anthropicApiKey) */
|
|
7
|
+
anthropicAuthToken?: string;
|
|
8
|
+
/** Backend URL for the openclaw-backend MCP server entry */
|
|
9
|
+
backendUrl?: string;
|
|
10
|
+
/** Bearer token for the openclaw-backend MCP server */
|
|
11
|
+
agentToken?: string;
|
|
12
|
+
/** Content of CLAUDE.md to write into the workspace (persona / bootstrap) */
|
|
13
|
+
claudeMdContent?: string;
|
|
14
|
+
/** Port of the local agent-memory MCP server (defaults to 3457). */
|
|
15
|
+
memoryMcpPort?: number;
|
|
16
|
+
/** Absolute path to the `agent-controller` bin. Defaults to `agent-controller` on PATH. */
|
|
17
|
+
agentControllerBin?: string;
|
|
18
|
+
/** Override the socket path (tests). */
|
|
19
|
+
channelSocketPath?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Write `.claude/settings.json`, `.mcp.json`, and `CLAUDE.md` into the workspace.
|
|
23
|
+
*
|
|
24
|
+
* Idempotent — overwrites each call. Mirrors the logic in
|
|
25
|
+
* `src/utils/claude-env.ts` for OAuth vs API-key auth.
|
|
26
|
+
*/
|
|
27
|
+
export declare function writeClaudeCodeSettings(ctx: ClaudeCodeSettingsContext): Promise<void>;
|
|
28
|
+
export declare function buildSettings(ctx: ClaudeCodeSettingsContext): Record<string, unknown>;
|
|
29
|
+
export declare function buildMcpJson(ctx: ClaudeCodeSettingsContext): Record<string, unknown>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { config } from '../../config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Write `.claude/settings.json`, `.mcp.json`, and `CLAUDE.md` into the workspace.
|
|
6
|
+
*
|
|
7
|
+
* Idempotent — overwrites each call. Mirrors the logic in
|
|
8
|
+
* `src/utils/claude-env.ts` for OAuth vs API-key auth.
|
|
9
|
+
*/
|
|
10
|
+
export async function writeClaudeCodeSettings(ctx) {
|
|
11
|
+
const { workspaceDir } = ctx;
|
|
12
|
+
const dotClaude = path.join(workspaceDir, '.claude');
|
|
13
|
+
await fs.mkdir(dotClaude, { recursive: true });
|
|
14
|
+
await fs.writeFile(path.join(dotClaude, 'settings.json'), JSON.stringify(buildSettings(ctx), null, 2) + '\n');
|
|
15
|
+
await fs.writeFile(path.join(workspaceDir, '.mcp.json'), JSON.stringify(buildMcpJson(ctx), null, 2) + '\n');
|
|
16
|
+
if (ctx.claudeMdContent !== undefined) {
|
|
17
|
+
await fs.writeFile(path.join(workspaceDir, 'CLAUDE.md'), ctx.claudeMdContent);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Builders (exported for unit tests)
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
export function buildSettings(ctx) {
|
|
24
|
+
const permissions = { allow: ['Bash(*)', 'Read(*)', 'Write(*)', 'Edit(*)', 'mcp__*'] };
|
|
25
|
+
// Token precedence: explicit auth token > API key that looks like OAuth > API key
|
|
26
|
+
const authToken = ctx.anthropicAuthToken?.trim();
|
|
27
|
+
const apiKey = ctx.anthropicApiKey?.trim();
|
|
28
|
+
if (authToken) {
|
|
29
|
+
return { permissions, env: { ANTHROPIC_AUTH_TOKEN: authToken } };
|
|
30
|
+
}
|
|
31
|
+
if (apiKey?.startsWith('sk-ant-oat')) {
|
|
32
|
+
return { permissions, env: { ANTHROPIC_AUTH_TOKEN: apiKey } };
|
|
33
|
+
}
|
|
34
|
+
if (apiKey) {
|
|
35
|
+
return {
|
|
36
|
+
permissions,
|
|
37
|
+
env: { ANTHROPIC_API_KEY: apiKey },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// No static credentials: rely on ~/.claude/.credentials.json (subscription login)
|
|
41
|
+
return { permissions };
|
|
42
|
+
}
|
|
43
|
+
export function buildMcpJson(ctx) {
|
|
44
|
+
const memoryPort = ctx.memoryMcpPort ?? 3457;
|
|
45
|
+
const servers = {
|
|
46
|
+
'agent-memory': {
|
|
47
|
+
type: 'http',
|
|
48
|
+
url: `http://127.0.0.1:${memoryPort}/mcp`,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
const backendUrl = ctx.backendUrl ?? config.backendUrl;
|
|
52
|
+
const token = ctx.agentToken ?? config.agentToken;
|
|
53
|
+
if (backendUrl && token) {
|
|
54
|
+
servers['openclaw-backend'] = {
|
|
55
|
+
type: 'http',
|
|
56
|
+
url: `${backendUrl.replace(/\/+$/, '')}/mcp`,
|
|
57
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// Channel server — spawned by Claude Code as a stdio MCP plugin. Declares
|
|
61
|
+
// the `experimental.claude/channel` capability, so Claude routes inbound
|
|
62
|
+
// chat traffic through it (and invokes the `reply`/`edit_message` tools
|
|
63
|
+
// when it wants to reply to the user).
|
|
64
|
+
//
|
|
65
|
+
// We use a plain stdio MCP entry (Option B) rather than Claude's `--channels
|
|
66
|
+
// plugin:` install mechanism — it avoids any out-of-band plugin install
|
|
67
|
+
// step and works with stock Claude Code.
|
|
68
|
+
const binary = ctx.agentControllerBin?.trim() || 'agent-controller';
|
|
69
|
+
const channelArgs = ['channel-server'];
|
|
70
|
+
if (ctx.channelSocketPath) {
|
|
71
|
+
channelArgs.push('--socket-path', ctx.channelSocketPath);
|
|
72
|
+
}
|
|
73
|
+
servers['agent-controller-channel'] = {
|
|
74
|
+
command: binary,
|
|
75
|
+
args: channelArgs,
|
|
76
|
+
};
|
|
77
|
+
return { mcpServers: servers };
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=settings-writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-writer.js","sourceRoot":"","sources":["../../../src/providers/claude-code/settings-writer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { ChannelReply, ChannelMessageMeta, ChannelReplyMeta } from './channel-server.js';
|
|
3
|
+
/**
|
|
4
|
+
* Unix-domain-socket bridge between the main agent-controller process and the
|
|
5
|
+
* channel-server worker that holds Claude Code's stdio MCP channel.
|
|
6
|
+
*
|
|
7
|
+
* Protocol: newline-delimited JSON. Ops:
|
|
8
|
+
* { op: "hello", pid } — worker → main
|
|
9
|
+
* { op: "reply", content, meta } — worker → main (Claude reply tool)
|
|
10
|
+
* { op: "edit", content, meta } — worker → main (Claude edit_message tool)
|
|
11
|
+
* { op: "push", content, meta } — main → worker (inbound chat msg)
|
|
12
|
+
*
|
|
13
|
+
* Liberal parsing: malformed lines are logged + dropped (never crash the link).
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEFAULT_SOCKET_PATH: string;
|
|
16
|
+
export type SocketOp = {
|
|
17
|
+
op: 'hello';
|
|
18
|
+
pid: number;
|
|
19
|
+
} | {
|
|
20
|
+
op: 'reply';
|
|
21
|
+
content: string;
|
|
22
|
+
meta: ChannelReplyMeta;
|
|
23
|
+
} | {
|
|
24
|
+
op: 'edit';
|
|
25
|
+
content: string;
|
|
26
|
+
meta: ChannelReplyMeta;
|
|
27
|
+
} | {
|
|
28
|
+
op: 'push';
|
|
29
|
+
content: string;
|
|
30
|
+
meta: ChannelMessageMeta;
|
|
31
|
+
};
|
|
32
|
+
export interface ChannelBridgeServerEvents {
|
|
33
|
+
connect: [pid: number | null];
|
|
34
|
+
reply: [content: string, meta: ChannelReplyMeta, tool: 'reply' | 'edit_message'];
|
|
35
|
+
close: [];
|
|
36
|
+
error: [err: Error];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Server side — lives in the main agent-controller process.
|
|
40
|
+
*
|
|
41
|
+
* Accepts exactly one worker connection (extras are rejected + closed). Emits
|
|
42
|
+
* reply/edit ops; callers invoke `push(content, meta)` to emit an inbound
|
|
43
|
+
* chat message to the worker.
|
|
44
|
+
*/
|
|
45
|
+
export declare class ChannelBridgeServer extends EventEmitter {
|
|
46
|
+
private readonly opts;
|
|
47
|
+
private server;
|
|
48
|
+
private active;
|
|
49
|
+
private buf;
|
|
50
|
+
constructor(opts?: {
|
|
51
|
+
socketPath?: string;
|
|
52
|
+
});
|
|
53
|
+
get socketPath(): string;
|
|
54
|
+
listen(): Promise<void>;
|
|
55
|
+
private handleConnection;
|
|
56
|
+
private ingest;
|
|
57
|
+
private dispatch;
|
|
58
|
+
/** Push an inbound chat message to the worker (→ Claude MCP notification). */
|
|
59
|
+
push(content: string, meta: ChannelMessageMeta): void;
|
|
60
|
+
private write;
|
|
61
|
+
close(): Promise<void>;
|
|
62
|
+
isConnected(): boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Client side — lives in the channel-server worker subprocess.
|
|
66
|
+
*
|
|
67
|
+
* Connects to the bridge socket with retry/backoff (up to 30s). Parses
|
|
68
|
+
* incoming `push` ops and forwards outgoing `reply`/`edit` ops.
|
|
69
|
+
*/
|
|
70
|
+
export declare class ChannelBridgeClient extends EventEmitter {
|
|
71
|
+
private readonly opts;
|
|
72
|
+
private sock;
|
|
73
|
+
private buf;
|
|
74
|
+
private closed;
|
|
75
|
+
constructor(opts?: {
|
|
76
|
+
socketPath?: string;
|
|
77
|
+
maxWaitMs?: number;
|
|
78
|
+
});
|
|
79
|
+
get socketPath(): string;
|
|
80
|
+
connect(): Promise<void>;
|
|
81
|
+
private connectOnce;
|
|
82
|
+
sendHello(pid: number): void;
|
|
83
|
+
sendReply(content: string, meta: ChannelReplyMeta, tool: 'reply' | 'edit_message'): void;
|
|
84
|
+
private ingest;
|
|
85
|
+
private write;
|
|
86
|
+
close(): void;
|
|
87
|
+
}
|
|
88
|
+
export type { ChannelReply, ChannelMessageMeta, ChannelReplyMeta };
|