wtt-connect 0.2.9 → 0.2.11
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 +12 -6
- package/package.json +1 -1
- package/src/adapters/claude-code.js +66 -8
- package/src/permissions.js +2 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ Implemented production-oriented surfaces:
|
|
|
26
26
|
- `none`
|
|
27
27
|
- `macos-say` using the macOS `say` command, emitted as WAV when upload is enabled
|
|
28
28
|
- HTTP task status update path
|
|
29
|
-
- Permission broker for
|
|
29
|
+
- Permission broker for agent modes; `full-auto` is the default no-approval mode for Codex and Claude Code
|
|
30
30
|
- Optional WTT media artifact upload path (`/media/sign` → direct upload → `/media/commit`)
|
|
31
31
|
- Agent-generated file artifacts for WTT feed chat (`.docx`, `.pptx`, `.xlsx`, `.pdf`, `.csv`, `.zip`) using explicit final-response markers that are converted into WTT file cards
|
|
32
32
|
- Attachment staging for WTT message media/files; image attachments are passed to Codex with `--image` (including Claude Code image fallback when the Claude provider cannot view images directly)
|
|
@@ -90,6 +90,7 @@ Important settings:
|
|
|
90
90
|
- `WTT_CONNECT_ADAPTERS=codex,claude-code,cursor,gemini,qoder,opencode,iflow,kimi,pi,acp,devin`
|
|
91
91
|
- `WTT_CONNECT_WORKDIR`
|
|
92
92
|
- `WTT_CONNECT_MODE=full-auto|auto-edit|yolo|suggest`
|
|
93
|
+
- `full-auto` is the default no-approval mode: Codex runs with `--dangerously-bypass-approvals-and-sandbox`; Claude Code runs with `--dangerously-skip-permissions --permission-mode bypassPermissions`
|
|
93
94
|
- `WTT_CONNECT_AGENT_ALIASES=alias1,alias2` for extra @mention names in discussion/collaborative topics; `WTT_AGENT_ID` always works
|
|
94
95
|
- `WTT_CONNECT_ALLOW_YOLO=1` only when intentionally using `yolo`
|
|
95
96
|
- `WTT_CONNECT_STATE_DIR` / `WTT_CONNECT_STORE_FILE` for durable sessions
|
|
@@ -101,6 +102,13 @@ Important settings:
|
|
|
101
102
|
- `WTT_CONNECT_SHELL_MODE=unsafe|readonly|off`; default `unsafe` runs one-shot shell commands without command filtering. The interactive Terminal always opens a real PTY shell on the agent host.
|
|
102
103
|
- `WTT_CONNECT_SHELL_TIMEOUT_SECONDS` and `WTT_CONNECT_SHELL_MAX_OUTPUT_CHARS` only bound legacy one-shot command execution and returned output
|
|
103
104
|
|
|
105
|
+
Session continuity:
|
|
106
|
+
|
|
107
|
+
- `wtt-connect` uses the WTT topic/task id as the local session key, for example `wtt:topic:<topic_id>`.
|
|
108
|
+
- Codex stores `codexThreadId` and resumes with `codex exec resume`.
|
|
109
|
+
- Claude Code stores `claudeSessionId` and resumes the same topic with `claude --resume <session_id> -p ...`.
|
|
110
|
+
- Keep `WTT_CONNECT_STATE_DIR`, `WTT_CONNECT_STORE_FILE`, and `WTT_CONNECT_WORKDIR` stable per claimed agent. If they are changed or deleted, the adapter cannot resume previous local sessions.
|
|
111
|
+
|
|
104
112
|
A JSON config may also be supplied:
|
|
105
113
|
|
|
106
114
|
```bash
|
|
@@ -260,7 +268,7 @@ wtt-connect up codex agent-codex '***'
|
|
|
260
268
|
wtt-connect up claude-code agent-claude '***'
|
|
261
269
|
```
|
|
262
270
|
|
|
263
|
-
Use named options when you need a custom service name, permission mode, or boot-before-login behavior:
|
|
271
|
+
Use named options when you need a custom service name, a stricter permission mode, or boot-before-login behavior:
|
|
264
272
|
|
|
265
273
|
```bash
|
|
266
274
|
./scripts/install-systemd-user.sh \
|
|
@@ -268,8 +276,7 @@ Use named options when you need a custom service name, permission mode, or boot-
|
|
|
268
276
|
--agent-id agent-codex \
|
|
269
277
|
--token '***' \
|
|
270
278
|
--adapter codex \
|
|
271
|
-
--mode
|
|
272
|
-
--allow-yolo \
|
|
279
|
+
--mode full-auto \
|
|
273
280
|
--publish-progress \
|
|
274
281
|
--enable-linger
|
|
275
282
|
|
|
@@ -278,8 +285,7 @@ Use named options when you need a custom service name, permission mode, or boot-
|
|
|
278
285
|
--agent-id agent-claude \
|
|
279
286
|
--token '***' \
|
|
280
287
|
--adapter claude-code \
|
|
281
|
-
--mode
|
|
282
|
-
--allow-yolo \
|
|
288
|
+
--mode full-auto \
|
|
283
289
|
--publish-progress \
|
|
284
290
|
--enable-linger
|
|
285
291
|
```
|
package/package.json
CHANGED
|
@@ -3,31 +3,72 @@ import readline from 'node:readline';
|
|
|
3
3
|
import { log } from '../logger.js';
|
|
4
4
|
|
|
5
5
|
export class ClaudeCodeAdapter {
|
|
6
|
-
constructor(config) {
|
|
6
|
+
constructor(config, deps = {}) {
|
|
7
7
|
this.config = config;
|
|
8
|
+
this.store = deps.store;
|
|
8
9
|
this.name = 'claude-code';
|
|
10
|
+
this.sessionByKey = new Map();
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
async run(prompt, context = {}) {
|
|
14
|
+
const sessionKey = context.sessionKey || 'default';
|
|
12
15
|
if (context.images?.length && this.config.codexBin) {
|
|
13
|
-
log('info', 'claude-code image fallback via codex', { sessionKey
|
|
16
|
+
log('info', 'claude-code image fallback via codex', { sessionKey, images: context.images.length });
|
|
14
17
|
return runCodexVision(this.config.codexBin, prompt, context.images, this.config.workDir, this.config.taskTimeoutSeconds * 1000, this.config, context.onProgress);
|
|
15
18
|
}
|
|
19
|
+
const stored = this.store?.getSession(sessionKey) || {};
|
|
20
|
+
const sessionId = this.sessionByKey.get(sessionKey) || stored.claudeSessionId;
|
|
21
|
+
const args = this.buildArgs(prompt, sessionId);
|
|
22
|
+
log('info', 'claude-code launch', { sessionKey, resume: Boolean(sessionId) });
|
|
23
|
+
let result;
|
|
24
|
+
try {
|
|
25
|
+
result = await runClaude(this.config.claudeBin, args, this.config.workDir, this.config.taskTimeoutSeconds * 1000, context.onProgress);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
if (!sessionId || !shouldRetryFreshSession(err)) throw err;
|
|
28
|
+
log('warn', 'claude-code resume failed; retrying with fresh session', { sessionKey, sessionId, error: err.message });
|
|
29
|
+
this.sessionByKey.delete(sessionKey);
|
|
30
|
+
this.store?.patchSession(sessionKey, { claudeSessionId: '', adapter: this.name });
|
|
31
|
+
result = await runClaude(this.config.claudeBin, this.buildArgs(prompt, ''), this.config.workDir, this.config.taskTimeoutSeconds * 1000, context.onProgress);
|
|
32
|
+
}
|
|
33
|
+
if (result.sessionId) {
|
|
34
|
+
this.sessionByKey.set(sessionKey, result.sessionId);
|
|
35
|
+
this.store?.patchSession(sessionKey, { claudeSessionId: result.sessionId, adapter: this.name });
|
|
36
|
+
}
|
|
37
|
+
return result.text.trim();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
buildArgs(prompt, sessionId = '') {
|
|
16
41
|
// Claude Code 2.x requires --verbose when stream-json is used with --print/-p.
|
|
17
|
-
const args = [
|
|
18
|
-
if (
|
|
42
|
+
const args = [];
|
|
43
|
+
if (sessionId) args.push('--resume', sessionId);
|
|
44
|
+
args.push('-p', prompt, '--output-format', 'stream-json', '--verbose');
|
|
45
|
+
if (['full-auto', 'yolo'].includes(this.config.mode)) args.push('--dangerously-skip-permissions', '--permission-mode', 'bypassPermissions');
|
|
19
46
|
if (this.config.model) args.push('--model', this.config.model);
|
|
20
|
-
|
|
21
|
-
return runClaude(this.config.claudeBin, args, this.config.workDir, this.config.taskTimeoutSeconds * 1000, context.onProgress);
|
|
47
|
+
return args;
|
|
22
48
|
}
|
|
23
49
|
}
|
|
24
50
|
|
|
51
|
+
function shouldRetryFreshSession(err) {
|
|
52
|
+
const message = String(err?.message || err || '').toLowerCase();
|
|
53
|
+
return [
|
|
54
|
+
'resume',
|
|
55
|
+
'session',
|
|
56
|
+
'conversation',
|
|
57
|
+
'not found',
|
|
58
|
+
'does not exist',
|
|
59
|
+
'invalid',
|
|
60
|
+
'timed out',
|
|
61
|
+
'timeout',
|
|
62
|
+
].some((needle) => message.includes(needle));
|
|
63
|
+
}
|
|
64
|
+
|
|
25
65
|
function runClaude(bin, args, cwd, timeoutMs, onEvent) {
|
|
26
66
|
return new Promise((resolve, reject) => {
|
|
27
67
|
const child = spawn(bin, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env } });
|
|
28
68
|
let stderr = '';
|
|
29
69
|
let finalResult = '';
|
|
30
70
|
let streamError = '';
|
|
71
|
+
let sessionId = '';
|
|
31
72
|
const rawLines = [];
|
|
32
73
|
const assistantTexts = [];
|
|
33
74
|
const timer = setTimeout(() => { child.kill('SIGTERM'); reject(new Error(`${bin} timed out`)); }, timeoutMs);
|
|
@@ -39,6 +80,8 @@ function runClaude(bin, args, cwd, timeoutMs, onEvent) {
|
|
|
39
80
|
rawLines.push(line);
|
|
40
81
|
if (rawLines.length > 20) rawLines.shift();
|
|
41
82
|
const ev = JSON.parse(line);
|
|
83
|
+
const eventSessionId = extractSessionId(ev);
|
|
84
|
+
if (eventSessionId) sessionId = eventSessionId;
|
|
42
85
|
if (onEvent) Promise.resolve(onEvent(ev)).catch(() => {});
|
|
43
86
|
const evError = extractEventError(ev);
|
|
44
87
|
if (evError) streamError = evError;
|
|
@@ -53,14 +96,29 @@ function runClaude(bin, args, cwd, timeoutMs, onEvent) {
|
|
|
53
96
|
child.on('close', (code) => {
|
|
54
97
|
clearTimeout(timer);
|
|
55
98
|
if (code !== 0) reject(new Error(`${bin} exited ${code}: ${errorDetail({ stderr, streamError, finalResult, rawLines })}`));
|
|
56
|
-
else resolve((finalResult || dedupeAdjacent(assistantTexts).join('\n')).trim());
|
|
99
|
+
else resolve({ text: (finalResult || dedupeAdjacent(assistantTexts).join('\n')).trim(), sessionId });
|
|
57
100
|
});
|
|
58
101
|
});
|
|
59
102
|
}
|
|
60
103
|
|
|
104
|
+
function extractSessionId(ev) {
|
|
105
|
+
if (!ev) return '';
|
|
106
|
+
return ev.session_id
|
|
107
|
+
|| ev.sessionId
|
|
108
|
+
|| ev.conversation_id
|
|
109
|
+
|| ev.conversationId
|
|
110
|
+
|| ev.thread_id
|
|
111
|
+
|| ev.threadId
|
|
112
|
+
|| ev.message?.session_id
|
|
113
|
+
|| ev.message?.sessionId
|
|
114
|
+
|| ev.result?.session_id
|
|
115
|
+
|| ev.result?.sessionId
|
|
116
|
+
|| '';
|
|
117
|
+
}
|
|
118
|
+
|
|
61
119
|
async function runCodexVision(bin, prompt, images, cwd, timeoutMs, config = {}, onEvent) {
|
|
62
120
|
const args = ['exec', '--skip-git-repo-check', '--json', '--cd', cwd];
|
|
63
|
-
if (
|
|
121
|
+
if (['full-auto', 'yolo'].includes(config.mode)) args.push('--dangerously-bypass-approvals-and-sandbox');
|
|
64
122
|
for (const img of images || []) args.push('--image', img);
|
|
65
123
|
args.push('-');
|
|
66
124
|
return new Promise((resolve, reject) => {
|
package/src/permissions.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const MODE_TO_CODEX = {
|
|
2
2
|
suggest: [],
|
|
3
3
|
'auto-edit': ['--full-auto'],
|
|
4
|
-
'full-auto': ['--
|
|
4
|
+
'full-auto': ['--dangerously-bypass-approvals-and-sandbox'],
|
|
5
5
|
yolo: ['--dangerously-bypass-approvals-and-sandbox'],
|
|
6
6
|
};
|
|
7
7
|
|
|
@@ -23,6 +23,7 @@ export class PermissionBroker {
|
|
|
23
23
|
|
|
24
24
|
describe() {
|
|
25
25
|
if (this.config.mode === 'yolo') return 'yolo/dangerously-bypass-approvals-and-sandbox';
|
|
26
|
+
if (this.config.mode === 'full-auto') return 'full-auto/dangerously-bypass-approvals-and-sandbox';
|
|
26
27
|
return this.config.mode;
|
|
27
28
|
}
|
|
28
29
|
}
|