wtt-connect 0.2.26 → 0.2.28

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 CHANGED
@@ -25,7 +25,7 @@ Implemented production-oriented surfaces:
25
25
  - `none`
26
26
  - `macos-say` using the macOS `say` command, emitted as WAV when upload is enabled
27
27
  - HTTP task status update path
28
- - Permission broker for agent modes; `full-auto` is the default no-approval mode for Codex and Claude Code
28
+ - Permission broker for agent modes; Codex, Claude Code, and Gemini default to `yolo` no-approval/full-access mode when bound through `wtt-connect up`
29
29
  - Optional WTT media artifact upload path (`/media/sign` → direct upload → `/media/commit`)
30
30
  - 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
31
31
  - Attachment staging for WTT message media/files; image attachments are passed to adapters that support image inputs, such as Codex with `--image`; Claude Code stays on the Claude Code adapter and returns its own result if the selected model cannot reason over images
@@ -45,6 +45,7 @@ Install once, then bind each WTT agent identity with one command:
45
45
  npm install -g wtt-connect
46
46
  wtt-connect up codex agent-001 'wtt-tok-001'
47
47
  wtt-connect up claude-code agent-002 'wtt-tok-002'
48
+ wtt-connect up gemini agent-003 'wtt-tok-003'
48
49
  ```
49
50
 
50
51
  `up` writes a profile, installs a background service, enables it for the current user, starts it immediately, and prints status. On Linux it installs a `systemd --user` service; on macOS it installs a LaunchAgent. The npm install step installs package dependencies such as `node-pty`; users should not need to run `npm install` inside a cloned source tree.
@@ -89,7 +90,7 @@ Important settings:
89
90
  - `WTT_CONNECT_ADAPTERS=codex,claude-code,cursor,gemini,qoder,opencode,iflow,kimi,pi,acp,devin`
90
91
  - `WTT_CONNECT_WORKDIR`
91
92
  - `WTT_CONNECT_MODE=full-auto|auto-edit|yolo|suggest`
92
- - `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
+ - Codex, Claude Code, and Gemini default to `yolo` no-approval/full-access mode when no explicit mode is set. Codex runs with `--dangerously-bypass-approvals-and-sandbox`; Claude Code runs with `--dangerously-skip-permissions --permission-mode bypassPermissions`; Gemini runs with `-y`.
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
@@ -119,6 +120,7 @@ Session continuity:
119
120
  - `wtt-connect` uses the WTT topic/task id plus adapter and model as the local session key, for example `wtt:topic:<topic_id>:codex:gpt-5.5:high`.
120
121
  - Codex stores `codexThreadId` and resumes with `codex exec resume`.
121
122
  - Claude Code stores `claudeSessionId` and resumes the same topic with `claude --resume <session_id> -p ...`.
123
+ - Gemini stores `geminiSessionId` and resumes with `gemini --resume <session_id> --output-format stream-json`.
122
124
  - 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.
123
125
 
124
126
  A JSON config may also be supplied:
@@ -180,6 +182,22 @@ Examples:
180
182
  wtt-connect up codex agent-alice 'wtt-tok-alice'
181
183
  wtt-connect up codex agent-bob 'wtt-tok-bob'
182
184
  wtt-connect up claude-code agent-claude 'wtt-tok-claude'
185
+ wtt-connect up gemini agent-gemini 'wtt-tok-gemini'
186
+ ```
187
+
188
+ Gemini CLI uses Google OAuth by default. Install/authenticate Gemini on the agent host first, then bind it to WTT:
189
+
190
+ ```bash
191
+ npm install -g @google/gemini-cli
192
+ gemini
193
+ npm install -g wtt-connect
194
+ wtt-connect up gemini agent-gemini 'wtt-tok-gemini'
195
+ ```
196
+
197
+ If the `gemini` binary is not on `PATH`, set `WTT_GEMINI_BIN` in the profile or shell before starting:
198
+
199
+ ```bash
200
+ WTT_GEMINI_BIN="$HOME/.local/bin/gemini" wtt-connect up gemini agent-gemini 'wtt-tok-gemini'
183
201
  ```
184
202
 
185
203
  Each call creates an independent profile and service:
@@ -196,7 +214,7 @@ On macOS the same profile and state paths are used, while the service file is:
196
214
  ~/Library/LaunchAgents/com.wtt.connect.agent-alice-codex.plist
197
215
  ```
198
216
 
199
- A profile contains exactly one WTT identity: `WTT_AGENT_ID`, `WTT_TOKEN`, adapter, workdir, permission mode, and state directory. Multiple profiles may point at the same local `codex` or `claude` CLI binary; they must not share `WTT_AGENT_ID`, `WTT_TOKEN`, or `WTT_CONNECT_STATE_DIR`.
217
+ A profile contains exactly one WTT identity: `WTT_AGENT_ID`, `WTT_TOKEN`, adapter, workdir, permission mode, and state directory. Multiple profiles may point at the same local `codex`, `claude`, or `gemini` CLI binary; they must not share `WTT_AGENT_ID`, `WTT_TOKEN`, or `WTT_CONNECT_STATE_DIR`.
200
218
 
201
219
  Workdir rules:
202
220
 
@@ -208,13 +226,13 @@ Useful flags:
208
226
 
209
227
  ```bash
210
228
  wtt-connect up codex agent-alice 'wtt-tok-alice' \
211
- --mode yolo \
212
- --allow-yolo \
213
229
  --workdir /data/workspaces/alice \
214
230
  --base-url https://www.waxbyte.com \
215
231
  --enable-linger
216
232
  ```
217
233
 
234
+ `codex`, `claude-code`, and `gemini` already default to `--mode yolo` with `WTT_CONNECT_ALLOW_YOLO=1`. Pass `--mode suggest` or `--mode auto-edit` only when you intentionally want stricter behavior.
235
+
218
236
  `--enable-linger` asks systemd to start the Linux user service at boot before an interactive login. Without it, the service starts when the user session starts. On macOS LaunchAgents start at user login; `--enable-linger` is ignored with a warning.
219
237
 
220
238
  Start an already-created profile in the foreground:
@@ -261,11 +279,22 @@ WTT_CONNECT_ADAPTERS=claude-code
261
279
  WTT_CONNECT_STATE_DIR=.wtt-connect-claude
262
280
  ```
263
281
 
282
+ ```dotenv
283
+ # .env.gemini
284
+ WTT_AGENT_ID=agent-gemini
285
+ WTT_TOKEN=***
286
+ WTT_CONNECT_ADAPTER=gemini
287
+ WTT_CONNECT_ADAPTERS=gemini
288
+ WTT_CONNECT_STATE_DIR=.wtt-connect-gemini
289
+ WTT_GEMINI_BIN=gemini
290
+ ```
291
+
264
292
  Start them separately:
265
293
 
266
294
  ```bash
267
295
  ./start.sh --env-file .env.codex
268
296
  ./start.sh --env-file .env.claude
297
+ ./start.sh --env-file .env.gemini
269
298
  ```
270
299
 
271
300
  Install them as separate macOS LaunchAgents:
@@ -273,6 +302,7 @@ Install them as separate macOS LaunchAgents:
273
302
  ```bash
274
303
  ./scripts/install-launchd.sh --name codex --env-file .env.codex
275
304
  ./scripts/install-launchd.sh --name claude --env-file .env.claude
305
+ ./scripts/install-launchd.sh --name gemini --env-file .env.gemini
276
306
  ```
277
307
 
278
308
  Install them as separate Linux `systemd --user` services from a source checkout if you are not using npm:
@@ -280,6 +310,7 @@ Install them as separate Linux `systemd --user` services from a source checkout
280
310
  ```bash
281
311
  ./scripts/install-agent.sh codex agent-codex '***'
282
312
  ./scripts/install-agent.sh claude-code agent-claude '***'
313
+ ./scripts/install-agent.sh gemini agent-gemini '***'
283
314
  ```
284
315
 
285
316
  The npm equivalent is preferred for end users:
@@ -287,6 +318,7 @@ The npm equivalent is preferred for end users:
287
318
  ```bash
288
319
  wtt-connect up codex agent-codex '***'
289
320
  wtt-connect up claude-code agent-claude '***'
321
+ wtt-connect up gemini agent-gemini '***'
290
322
  ```
291
323
 
292
324
  Use named options when you need a custom service name, a stricter permission mode, or boot-before-login behavior:
@@ -306,12 +338,19 @@ Use named options when you need a custom service name, a stricter permission mod
306
338
  --agent-id agent-claude \
307
339
  --token '***' \
308
340
  --adapter claude-code \
309
- --mode full-auto \
341
+ --publish-progress \
342
+ --enable-linger
343
+
344
+ ./scripts/install-systemd-user.sh \
345
+ --name alice-gemini \
346
+ --agent-id agent-gemini \
347
+ --token '***' \
348
+ --adapter gemini \
310
349
  --publish-progress \
311
350
  --enable-linger
312
351
  ```
313
352
 
314
- Each invocation writes a separate `.env.<name>`, `.wtt-connect-<name>` state directory, and `wtt-connect-<name>.service`. Multiple WTT agent identities may share the same local `codex` or `claude` CLI binary; only the WTT identity and state directory need to be unique.
353
+ Each invocation writes a separate `.env.<name>`, `.wtt-connect-<name>` state directory, and `wtt-connect-<name>.service`. Multiple WTT agent identities may share the same local `codex`, `claude`, or `gemini` CLI binary; only the WTT identity and state directory need to be unique.
315
354
 
316
355
  Uninstall one identity with:
317
356
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wtt-connect",
3
- "version": "0.2.26",
3
+ "version": "0.2.28",
4
4
  "private": false,
5
5
  "description": "WTT-native connector daemon for Codex, Claude Code, Cursor, Gemini, ACP, and other coding agent surfaces.",
6
6
  "type": "module",
@@ -28,8 +28,8 @@ Options:
28
28
  --workdir <dir> Agent working directory (default: wtt-connect root)
29
29
  --env-file <file> Env file to write (default: .env.<name>)
30
30
  --state-dir <dir> State directory (default: .wtt-connect-<name>)
31
- --mode <mode> full-auto, auto-edit, suggest, yolo (default: full-auto)
32
- --allow-yolo Required when --mode yolo is used
31
+ --mode <mode> full-auto, auto-edit, suggest, yolo (default: yolo for codex/claude-code/gemini)
32
+ --allow-yolo Allow yolo mode for adapters that do not default to full access
33
33
  --publish-progress Publish agent progress/status messages
34
34
  --timeout <seconds> Per-run timeout (default: 3600)
35
35
  --node-bin <path> node binary (default: command -v node)
@@ -49,7 +49,7 @@ BASE_URL="https://www.waxbyte.com"
49
49
  WORKDIR="$ROOT"
50
50
  ENV_FILE=""
51
51
  STATE_DIR=""
52
- MODE="${WTT_CONNECT_MODE:-full-auto}"
52
+ MODE="${WTT_CONNECT_MODE:-}"
53
53
  ALLOW_YOLO=0
54
54
  PUBLISH_PROGRESS=0
55
55
  TIMEOUT_SECONDS=3600
@@ -98,6 +98,21 @@ if [[ "${#POSITIONAL[@]}" -gt 0 ]]; then
98
98
  fi
99
99
 
100
100
  ADAPTER="${ADAPTER:-codex}"
101
+ case "$ADAPTER" in
102
+ claude) ADAPTER="claude-code" ;;
103
+ cc-connect) ADAPTER="codex" ;;
104
+ esac
105
+ if [[ -z "$MODE" ]]; then
106
+ case "$ADAPTER" in
107
+ codex|claude-code|gemini)
108
+ MODE="yolo"
109
+ ALLOW_YOLO=1
110
+ ;;
111
+ *)
112
+ MODE="full-auto"
113
+ ;;
114
+ esac
115
+ fi
101
116
  if [[ -z "$NAME" && -n "$AGENT_ID" ]]; then
102
117
  NAME="$AGENT_ID-$ADAPTER"
103
118
  fi
@@ -144,6 +144,12 @@ export class GenericCliAdapter {
144
144
  if (context.onProgress) context.onProgress(event).catch(() => {});
145
145
  }, this.config.taskTimeoutSeconds * 1000);
146
146
  if (result.sessionId) this.store?.patchSession(sessionKey, { [storedKey]: result.sessionId, adapter: this.name });
147
+ const runtimePatch = {
148
+ ...(result.model ? { current_model: result.model, model: result.model, model_source: `${this.name}_runtime` } : {}),
149
+ ...(result.reasoningEffort ? { reasoning_effort: result.reasoningEffort } : {}),
150
+ ...(result.thinkingMode ? { thinking_mode: result.thinkingMode } : {}),
151
+ };
152
+ if (Object.keys(runtimePatch).length) this.store?.patchRuntime(runtimePatch);
147
153
  return result.text.trim();
148
154
  }
149
155
  }
@@ -166,6 +172,9 @@ function runCli(bin, args, stdinText, cwd, onEvent, timeoutMs) {
166
172
  let stderr = '';
167
173
  let plainStdout = '';
168
174
  let sessionId = '';
175
+ let model = '';
176
+ let reasoningEffort = '';
177
+ let thinkingMode = '';
169
178
  const texts = [];
170
179
  const timer = setTimeout(() => {
171
180
  child.kill('SIGTERM');
@@ -183,6 +192,9 @@ function runCli(bin, args, stdinText, cwd, onEvent, timeoutMs) {
183
192
  }
184
193
  const eventSessionId = extractSessionId(event);
185
194
  if (eventSessionId) sessionId = eventSessionId;
195
+ model = extractModel(event) || model;
196
+ reasoningEffort = extractReasoningEffort(event) || reasoningEffort;
197
+ thinkingMode = extractThinkingMode(event) || thinkingMode;
186
198
  const text = extractText(event);
187
199
  if (text) texts.push({ text, delta: event.delta === true });
188
200
  onEvent?.(event);
@@ -190,12 +202,55 @@ function runCli(bin, args, stdinText, cwd, onEvent, timeoutMs) {
190
202
  child.on('close', (code) => {
191
203
  clearTimeout(timer);
192
204
  if (code !== 0) reject(new Error(`${bin} exited ${code}: ${stderr.trim()}`));
193
- else resolve({ text: collapseText(texts, plainStdout), sessionId });
205
+ else resolve({ text: collapseText(texts, plainStdout), sessionId, model, reasoningEffort, thinkingMode });
194
206
  });
195
207
  if (stdinText != null) child.stdin.end(stdinText);
196
208
  });
197
209
  }
198
210
 
211
+ function extractModel(event) {
212
+ if (!event || typeof event !== 'object') return '';
213
+ for (const key of ['model', 'model_id', 'modelId', 'current_model']) {
214
+ const value = event[key];
215
+ if (typeof value === 'string' && value.trim()) return value.trim();
216
+ }
217
+ const statsModels = event.stats?.models;
218
+ if (statsModels && typeof statsModels === 'object' && !Array.isArray(statsModels)) {
219
+ const entries = Object.entries(statsModels).filter(([name]) => String(name || '').trim());
220
+ if (entries.length) {
221
+ entries.sort((a, b) => modelTokenTotal(b[1]) - modelTokenTotal(a[1]));
222
+ return String(entries[0][0]).trim();
223
+ }
224
+ }
225
+ return '';
226
+ }
227
+
228
+ function modelTokenTotal(value) {
229
+ if (!value || typeof value !== 'object') return 0;
230
+ return Number(value.total_tokens || value.totalTokens || value.output_tokens || value.outputTokens || 0) || 0;
231
+ }
232
+
233
+ function extractReasoningEffort(event) {
234
+ const raw = event?.reasoning_effort || event?.reasoningEffort || event?.thinking_effort || event?.thinkingEffort;
235
+ return normalizeReasoning(raw);
236
+ }
237
+
238
+ function extractThinkingMode(event) {
239
+ const raw = event?.thinking_mode || event?.thinkingMode || event?.thinking || event?.displayThinking;
240
+ if (typeof raw === 'boolean') return raw ? 'high' : 'off';
241
+ return typeof raw === 'string' ? raw.trim() : '';
242
+ }
243
+
244
+ function normalizeReasoning(value) {
245
+ const raw = String(value || '').trim().toLowerCase();
246
+ if (!raw) return '';
247
+ if (['off', 'none', 'disabled', 'false', '0'].includes(raw)) return 'off';
248
+ if (['low', 'minimal'].includes(raw)) return 'low';
249
+ if (['medium', 'normal', 'auto'].includes(raw)) return 'medium';
250
+ if (['high', 'full', 'max', 'maximum', 'xhigh'].includes(raw)) return 'high';
251
+ return raw;
252
+ }
253
+
199
254
  function collapseText(texts, plainStdout) {
200
255
  const filtered = texts
201
256
  .map((entry) => ({ text: String(entry?.text || ''), delta: entry?.delta === true }))
package/src/config.js CHANGED
@@ -3,6 +3,8 @@ import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { parseBool, parseIntEnv } from './env.js';
5
5
 
6
+ const FULL_ACCESS_DEFAULT_ADAPTERS = new Set(['codex', 'claude-code', 'gemini']);
7
+
6
8
  function wsUrl(base, agentId) {
7
9
  let b = String(base || '').replace(/\/$/, '');
8
10
  if (b.endsWith('/api/v1')) b = b.slice(0, -7);
@@ -27,6 +29,8 @@ export function loadConfig(argv = {}) {
27
29
  const token = process.env.WTT_TOKEN || process.env.WTT_AGENT_TOKEN || fileConfig.token || '';
28
30
  const adapter = process.env.WTT_CONNECT_ADAPTER || fileConfig.adapter || 'codex';
29
31
  const adapters = String(process.env.WTT_CONNECT_ADAPTERS || fileConfig.adapters || adapter).split(',').map((x) => x.trim()).filter(Boolean);
32
+ const defaultMode = defaultModeForAdapters(adapter, adapters);
33
+ const mode = process.env.WTT_CONNECT_MODE || fileConfig.mode || defaultMode;
30
34
  const explicitWorkDir = process.env.WTT_CONNECT_WORKDIR || fileConfig.workDir || '';
31
35
  const workDir = resolveAgentWorkDir(explicitWorkDir, agentId);
32
36
  const stateDir = process.env.WTT_CONNECT_STATE_DIR || fileConfig.stateDir || path.join(workDir, '.wtt-connect');
@@ -52,8 +56,8 @@ export function loadConfig(argv = {}) {
52
56
  },
53
57
  workDir,
54
58
  model: process.env.WTT_CONNECT_MODEL || fileConfig.model || '',
55
- mode: process.env.WTT_CONNECT_MODE || fileConfig.mode || 'full-auto',
56
- allowYolo: parseBool(process.env.WTT_CONNECT_ALLOW_YOLO, fileConfig.allowYolo ?? false),
59
+ mode,
60
+ allowYolo: parseBool(process.env.WTT_CONNECT_ALLOW_YOLO, fileConfig.allowYolo ?? mode === 'yolo'),
57
61
  reasoningEffort: process.env.WTT_CONNECT_REASONING_EFFORT || fileConfig.reasoningEffort || 'low',
58
62
  codexBin: process.env.WTT_CODEX_BIN || fileConfig.codexBin || 'codex',
59
63
  claudeBin: process.env.WTT_CLAUDE_BIN || fileConfig.claudeBin || 'claude',
@@ -112,6 +116,20 @@ export function loadConfig(argv = {}) {
112
116
  };
113
117
  }
114
118
 
119
+ function defaultModeForAdapters(adapter, adapters) {
120
+ const names = [adapter, ...(Array.isArray(adapters) ? adapters : [])]
121
+ .map(normalizeAdapter)
122
+ .filter(Boolean);
123
+ return names.some((name) => FULL_ACCESS_DEFAULT_ADAPTERS.has(name)) ? 'yolo' : 'full-auto';
124
+ }
125
+
126
+ function normalizeAdapter(name) {
127
+ const normalized = String(name || '').trim().toLowerCase();
128
+ if (normalized === 'claude') return 'claude-code';
129
+ if (normalized === 'cc-connect') return 'codex';
130
+ return normalized;
131
+ }
132
+
115
133
  export function resolveAgentWorkDir(explicitWorkDir, agentId) {
116
134
  if (explicitWorkDir) return path.resolve(String(explicitWorkDir));
117
135
  return path.join(os.homedir(), '.wtt-connect', safeAgentPathSegment(agentId));
package/src/runner.js CHANGED
@@ -388,6 +388,7 @@ export class Runner {
388
388
  ...(reasoning ? { reasoning_effort: reasoning } : {}),
389
389
  status,
390
390
  });
391
+ this.wtt.sendHeartbeat().catch(() => {});
391
392
  }
392
393
 
393
394
  async transcribeAttachments(files) {
@@ -8,6 +8,8 @@ export function buildRuntimeInfo(config, runtimeState = {}) {
8
8
  const git = gitInfo(workdir);
9
9
  const adapter = String(runtimeState.adapter || config.adapter || '').trim();
10
10
  const model = runtimeModel(config, adapter, runtimeState);
11
+ const reasoning = runtimeReasoningEffort(config, adapter, runtimeState);
12
+ const thinkingMode = runtimeThinkingMode(config, adapter, runtimeState);
11
13
  return {
12
14
  kind: 'wtt-connect',
13
15
  agent_id: config.agentId,
@@ -16,7 +18,8 @@ export function buildRuntimeInfo(config, runtimeState = {}) {
16
18
  ...(model ? { model, current_model: model } : {}),
17
19
  ...(runtimeState.model_source ? { model_source: String(runtimeState.model_source) } : {}),
18
20
  ...(runtimeState.status ? { runtime_status: String(runtimeState.status) } : {}),
19
- ...(runtimeState.reasoning_effort || config.reasoningEffort ? { reasoning_effort: String(runtimeState.reasoning_effort || config.reasoningEffort) } : {}),
21
+ ...(reasoning ? { reasoning_effort: reasoning } : {}),
22
+ ...(thinkingMode ? { thinking_mode: thinkingMode } : {}),
20
23
  workdir,
21
24
  workdir_name: path.basename(workdir || ''),
22
25
  cwd: safeRealpath(process.cwd()),
@@ -45,9 +48,37 @@ function runtimeModel(config, adapter, runtimeState = {}) {
45
48
  if (normalizedAdapter === 'claude-code' || normalizedAdapter === 'claude' || normalizedAdapter === 'claude_code') {
46
49
  return String(process.env.ANTHROPIC_MODEL || readClaudeConfigModel() || '').trim();
47
50
  }
51
+ if (normalizedAdapter === 'gemini') {
52
+ return String(process.env.GEMINI_MODEL || process.env.GEMINI_CLI_MODEL || process.env.GOOGLE_GEMINI_MODEL || readGeminiConfigModel() || '').trim();
53
+ }
48
54
  return String(process.env.WTT_CONNECT_MODEL || '').trim();
49
55
  }
50
56
 
57
+ function runtimeReasoningEffort(config, adapter, runtimeState = {}) {
58
+ const fromRun = normalizeReasoning(runtimeState.reasoning_effort || runtimeState.reasoningEffort);
59
+ if (fromRun) return fromRun;
60
+
61
+ const explicit = normalizeReasoning(config.reasoningEffort || config.reasoning_effort);
62
+ if (explicit) return explicit;
63
+
64
+ const normalizedAdapter = String(adapter || config.adapter || '').trim().toLowerCase();
65
+ if (normalizedAdapter === 'codex') return normalizeReasoning(process.env.OPENAI_REASONING_EFFORT || process.env.CODEX_REASONING_EFFORT || readCodexReasoningEffort());
66
+ if (normalizedAdapter === 'gemini') return normalizeReasoning(process.env.GEMINI_THINKING || process.env.GEMINI_THINKING_MODE || readGeminiThinkingMode());
67
+ if (normalizedAdapter === 'claude-code' || normalizedAdapter === 'claude' || normalizedAdapter === 'claude_code') {
68
+ return normalizeReasoning(process.env.CLAUDE_THINKING || process.env.ANTHROPIC_THINKING || readClaudeThinkingMode());
69
+ }
70
+ return normalizeReasoning(process.env.WTT_CONNECT_REASONING_EFFORT);
71
+ }
72
+
73
+ function runtimeThinkingMode(config, adapter, runtimeState = {}) {
74
+ const value = String(runtimeState.thinking_mode || runtimeState.thinkingMode || '').trim();
75
+ if (value) return value;
76
+ const normalizedAdapter = String(adapter || config.adapter || '').trim().toLowerCase();
77
+ if (normalizedAdapter === 'gemini') return readGeminiThinkingMode();
78
+ if (normalizedAdapter === 'claude-code' || normalizedAdapter === 'claude' || normalizedAdapter === 'claude_code') return readClaudeThinkingMode();
79
+ return '';
80
+ }
81
+
51
82
  function readCodexConfigModel() {
52
83
  const home = os.homedir();
53
84
  const candidates = [
@@ -61,6 +92,19 @@ function readCodexConfigModel() {
61
92
  return '';
62
93
  }
63
94
 
95
+ function readCodexReasoningEffort() {
96
+ const home = os.homedir();
97
+ const candidates = [
98
+ path.join(home, '.codex', 'config.toml'),
99
+ path.join(home, '.config', 'codex', 'config.toml'),
100
+ ];
101
+ for (const file of candidates) {
102
+ const effort = readTomlStringKey(file, 'model_reasoning_effort') || readTomlStringKey(file, 'reasoning_effort');
103
+ if (effort) return effort;
104
+ }
105
+ return '';
106
+ }
107
+
64
108
  function readClaudeConfigModel() {
65
109
  const home = os.homedir();
66
110
  const candidates = [
@@ -75,6 +119,50 @@ function readClaudeConfigModel() {
75
119
  return '';
76
120
  }
77
121
 
122
+ function readClaudeThinkingMode() {
123
+ const home = os.homedir();
124
+ const candidates = [
125
+ path.join(home, '.claude.json'),
126
+ path.join(home, '.claude', 'settings.json'),
127
+ path.join(home, '.config', 'claude', 'settings.json'),
128
+ ];
129
+ for (const file of candidates) {
130
+ const mode = readJsonThinkingKey(file);
131
+ if (mode) return mode;
132
+ }
133
+ return '';
134
+ }
135
+
136
+ function readGeminiConfigModel() {
137
+ const home = os.homedir();
138
+ const candidates = [
139
+ path.join(home, '.gemini', 'settings.json'),
140
+ path.join(home, '.gemini', 'config.json'),
141
+ path.join(home, '.config', 'gemini', 'settings.json'),
142
+ path.join(home, '.config', 'gemini', 'config.json'),
143
+ ];
144
+ for (const file of candidates) {
145
+ const model = readJsonModelKey(file);
146
+ if (model) return model;
147
+ }
148
+ return '';
149
+ }
150
+
151
+ function readGeminiThinkingMode() {
152
+ const home = os.homedir();
153
+ const candidates = [
154
+ path.join(home, '.gemini', 'settings.json'),
155
+ path.join(home, '.gemini', 'config.json'),
156
+ path.join(home, '.config', 'gemini', 'settings.json'),
157
+ path.join(home, '.config', 'gemini', 'config.json'),
158
+ ];
159
+ for (const file of candidates) {
160
+ const mode = readJsonThinkingKey(file);
161
+ if (mode) return mode;
162
+ }
163
+ return '';
164
+ }
165
+
78
166
  function readJsonModelKey(file) {
79
167
  try {
80
168
  if (!fs.existsSync(file)) return '';
@@ -85,8 +173,23 @@ function readJsonModelKey(file) {
85
173
  }
86
174
  }
87
175
 
176
+ function readJsonThinkingKey(file) {
177
+ try {
178
+ if (!fs.existsSync(file)) return '';
179
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
180
+ return findThinkingValue(parsed);
181
+ } catch {
182
+ return '';
183
+ }
184
+ }
185
+
88
186
  function findModelValue(value, depth = 0) {
89
187
  if (!value || typeof value !== 'object' || depth > 4) return '';
188
+ const modelObj = value.model;
189
+ if (modelObj && typeof modelObj === 'object') {
190
+ const name = modelObj.name;
191
+ if (typeof name === 'string' && name.trim()) return name.trim();
192
+ }
90
193
  for (const key of ['model', 'model_id', 'modelId', 'defaultModel', 'default_model']) {
91
194
  const raw = value[key];
92
195
  if (typeof raw === 'string' && raw.trim()) return raw.trim();
@@ -98,6 +201,37 @@ function findModelValue(value, depth = 0) {
98
201
  return '';
99
202
  }
100
203
 
204
+ function findThinkingValue(value, depth = 0) {
205
+ if (!value || typeof value !== 'object' || depth > 6) return '';
206
+ for (const key of ['thinking_mode', 'thinkingMode', 'thinking', 'displayThinking', 'reasoning_effort', 'reasoningEffort', 'thinkingLevel']) {
207
+ const raw = value[key];
208
+ if (typeof raw === 'string' && raw.trim()) return raw.trim();
209
+ if (typeof raw === 'boolean') return raw ? 'high' : 'off';
210
+ }
211
+ const budget = value.thinkingBudget ?? value.thinking_budget;
212
+ if (typeof budget === 'number') {
213
+ if (budget <= 0) return 'off';
214
+ if (budget >= 8192) return 'high';
215
+ if (budget >= 2048) return 'medium';
216
+ return 'low';
217
+ }
218
+ for (const child of Object.values(value)) {
219
+ const found = findThinkingValue(child, depth + 1);
220
+ if (found) return found;
221
+ }
222
+ return '';
223
+ }
224
+
225
+ function normalizeReasoning(value) {
226
+ const raw = String(value || '').trim().toLowerCase();
227
+ if (!raw) return '';
228
+ if (['off', 'none', 'disabled', 'false', '0'].includes(raw)) return 'off';
229
+ if (['low', 'minimal'].includes(raw)) return 'low';
230
+ if (['medium', 'normal', 'auto'].includes(raw)) return 'medium';
231
+ if (['high', 'full', 'max', 'maximum', 'xhigh'].includes(raw)) return 'high';
232
+ return raw;
233
+ }
234
+
101
235
  function readTomlStringKey(file, key) {
102
236
  try {
103
237
  if (!fs.existsSync(file)) return '';
@@ -8,6 +8,7 @@ import { resolveAgentWorkDir } from './config.js';
8
8
 
9
9
  const DEFAULT_BASE_URL = 'https://www.waxbyte.com';
10
10
  const DEFAULT_MODE = 'full-auto';
11
+ const FULL_ACCESS_DEFAULT_ADAPTERS = new Set(['codex', 'claude-code', 'gemini']);
11
12
  const VALID_MODES = new Set(['suggest', 'auto-edit', 'full-auto', 'yolo']);
12
13
 
13
14
  export function resolveProfileEnvFile(profile) {
@@ -23,9 +24,11 @@ export async function up(argv) {
23
24
  }
24
25
 
25
26
  const profile = sanitizeProfile(argv.profile || argv.name || `${agentId}-${adapter}`);
26
- const mode = argv.mode || process.env.WTT_CONNECT_MODE || DEFAULT_MODE;
27
+ const defaultMode = FULL_ACCESS_DEFAULT_ADAPTERS.has(adapter) ? 'yolo' : DEFAULT_MODE;
28
+ const mode = argv.mode || process.env.WTT_CONNECT_MODE || defaultMode;
27
29
  if (!VALID_MODES.has(mode)) throw new Error(`invalid --mode: ${mode}`);
28
- if (mode === 'yolo' && !argv.allowYolo && !argv.yes) {
30
+ const allowYolo = mode === 'yolo' && FULL_ACCESS_DEFAULT_ADAPTERS.has(adapter) ? true : Boolean(argv.allowYolo || argv.yes);
31
+ if (mode === 'yolo' && !allowYolo) {
29
32
  throw new Error('--mode yolo requires --allow-yolo or --yes');
30
33
  }
31
34
 
@@ -53,7 +56,7 @@ export async function up(argv) {
53
56
  WTT_CONNECT_ADAPTERS: adapter,
54
57
  WTT_CONNECT_WORKDIR: workDir,
55
58
  WTT_CONNECT_MODE: mode,
56
- WTT_CONNECT_ALLOW_YOLO: mode === 'yolo' || argv.allowYolo || argv.yes ? '1' : '',
59
+ WTT_CONNECT_ALLOW_YOLO: mode === 'yolo' || allowYolo ? '1' : '',
57
60
  WTT_CONNECT_ENABLE_CHAT: '1',
58
61
  WTT_CONNECT_PUBLISH_PROGRESS: argv.publishProgress ? '1' : '0',
59
62
  WTT_CONNECT_TASK_TIMEOUT_SECONDS: String(argv.timeoutSeconds || (argv.timeout ? Math.round(argv.timeout / 1000) : 3600)),