pikiloop 0.4.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/LICENSE +21 -0
- package/README.md +353 -0
- package/README.v2.md +287 -0
- package/README.zh-CN.md +352 -0
- package/dashboard/dist/assets/AgentTab-UZPIhlkr.js +1 -0
- package/dashboard/dist/assets/DirBrowser-Ckcmi-Pi.js +1 -0
- package/dashboard/dist/assets/ExtensionsTab-KZhEDrdu.js +1 -0
- package/dashboard/dist/assets/IMAccessTab-Bd_IY1GQ.js +1 -0
- package/dashboard/dist/assets/Modal-CTeL0y7P.js +1 -0
- package/dashboard/dist/assets/Modals-axftHasy.js +1 -0
- package/dashboard/dist/assets/Select-C8tOdPhe.js +1 -0
- package/dashboard/dist/assets/SessionPanel-C1geSRxw.js +1 -0
- package/dashboard/dist/assets/SystemTab-DBDkaPiO.js +1 -0
- package/dashboard/dist/assets/anthropic-BAdojD7P.ico +0 -0
- package/dashboard/dist/assets/codex-DYadqqp0.png +0 -0
- package/dashboard/dist/assets/deepseek-BeYNZEk0.ico +0 -0
- package/dashboard/dist/assets/doubao-DloFDuFR.png +0 -0
- package/dashboard/dist/assets/feishu-C4OMrjCW.ico +0 -0
- package/dashboard/dist/assets/gemini-BYkEpiWr.svg +1 -0
- package/dashboard/dist/assets/hermes-BAarh-tH.png +0 -0
- package/dashboard/dist/assets/index-CpM4CqZJ.js +23 -0
- package/dashboard/dist/assets/index-DXSohzrE.js +3 -0
- package/dashboard/dist/assets/index-reSbuley.css +1 -0
- package/dashboard/dist/assets/markdown-DxQYQFeH.js +29 -0
- package/dashboard/dist/assets/minimax-PuEGTfrF.ico +0 -0
- package/dashboard/dist/assets/mlx-DhWwjtMw.png +0 -0
- package/dashboard/dist/assets/ollama-Bt9O-2K_.png +0 -0
- package/dashboard/dist/assets/openrouter-CsJ_bD5Q.ico +0 -0
- package/dashboard/dist/assets/playwright-BldPFZgC.ico +0 -0
- package/dashboard/dist/assets/qwen-xykkX0_y.png +0 -0
- package/dashboard/dist/assets/react-vendor-C7Sl8SE7.js +9 -0
- package/dashboard/dist/assets/router-DHISdpPk.js +3 -0
- package/dashboard/dist/assets/shared-BIP_4k4I.js +1 -0
- package/dashboard/dist/favicon.svg +28 -0
- package/dashboard/dist/index.html +17 -0
- package/dist/agent/acp-client.js +261 -0
- package/dist/agent/auto-update.js +432 -0
- package/dist/agent/await-resume.js +50 -0
- package/dist/agent/cli/auth.js +325 -0
- package/dist/agent/cli/catalog.js +40 -0
- package/dist/agent/cli/detector.js +136 -0
- package/dist/agent/cli/index.js +7 -0
- package/dist/agent/cli/registry.js +33 -0
- package/dist/agent/driver.js +39 -0
- package/dist/agent/drivers/claude-tui.js +2297 -0
- package/dist/agent/drivers/claude.js +2689 -0
- package/dist/agent/drivers/codex.js +2210 -0
- package/dist/agent/drivers/gemini.js +1059 -0
- package/dist/agent/drivers/hermes.js +795 -0
- package/dist/agent/goal.js +274 -0
- package/dist/agent/handover.js +130 -0
- package/dist/agent/images.js +355 -0
- package/dist/agent/index.js +50 -0
- package/dist/agent/mcp/bridge.js +791 -0
- package/dist/agent/mcp/extensions.js +637 -0
- package/dist/agent/mcp/oauth.js +353 -0
- package/dist/agent/mcp/registry.js +119 -0
- package/dist/agent/mcp/session-server.js +229 -0
- package/dist/agent/mcp/tools/ask-user.js +113 -0
- package/dist/agent/mcp/tools/await-resume.js +77 -0
- package/dist/agent/mcp/tools/goal.js +144 -0
- package/dist/agent/mcp/tools/types.js +12 -0
- package/dist/agent/mcp/tools/workspace.js +212 -0
- package/dist/agent/npm.js +31 -0
- package/dist/agent/session.js +1206 -0
- package/dist/agent/skill-installer.js +160 -0
- package/dist/agent/skills.js +257 -0
- package/dist/agent/stream.js +743 -0
- package/dist/agent/types.js +13 -0
- package/dist/agent/utils.js +687 -0
- package/dist/bot/bot.js +2499 -0
- package/dist/bot/command-ui.js +633 -0
- package/dist/bot/commands.js +513 -0
- package/dist/bot/headless-bot.js +36 -0
- package/dist/bot/host.js +192 -0
- package/dist/bot/human-loop.js +168 -0
- package/dist/bot/menu.js +48 -0
- package/dist/bot/orchestration.js +79 -0
- package/dist/bot/render-shared.js +309 -0
- package/dist/bot/session-hub.js +361 -0
- package/dist/bot/session-status.js +55 -0
- package/dist/bot/streaming.js +309 -0
- package/dist/browser-profile.js +579 -0
- package/dist/browser-supervisor.js +249 -0
- package/dist/catalog/cli-tools.js +421 -0
- package/dist/catalog/index.js +21 -0
- package/dist/catalog/local-models.js +94 -0
- package/dist/catalog/mcp-servers.js +315 -0
- package/dist/catalog/skill-repos.js +173 -0
- package/dist/channels/base.js +55 -0
- package/dist/channels/dingtalk/bot.js +549 -0
- package/dist/channels/dingtalk/channel.js +268 -0
- package/dist/channels/discord/bot.js +552 -0
- package/dist/channels/discord/channel.js +245 -0
- package/dist/channels/feishu/bot.js +1275 -0
- package/dist/channels/feishu/channel.js +911 -0
- package/dist/channels/feishu/markdown.js +91 -0
- package/dist/channels/feishu/render.js +619 -0
- package/dist/channels/health.js +109 -0
- package/dist/channels/slack/bot.js +554 -0
- package/dist/channels/slack/channel.js +283 -0
- package/dist/channels/states.js +6 -0
- package/dist/channels/telegram/bot.js +1310 -0
- package/dist/channels/telegram/channel.js +820 -0
- package/dist/channels/telegram/directory.js +111 -0
- package/dist/channels/telegram/live-preview.js +220 -0
- package/dist/channels/telegram/render.js +384 -0
- package/dist/channels/wecom/bot.js +558 -0
- package/dist/channels/wecom/channel.js +479 -0
- package/dist/channels/weixin/api.js +520 -0
- package/dist/channels/weixin/bot.js +1000 -0
- package/dist/channels/weixin/channel.js +222 -0
- package/dist/cli/autostart.js +262 -0
- package/dist/cli/channel-supervisor.js +313 -0
- package/dist/cli/channels.js +54 -0
- package/dist/cli/main.js +726 -0
- package/dist/cli/onboarding.js +227 -0
- package/dist/cli/run.js +308 -0
- package/dist/cli/setup-wizard.js +235 -0
- package/dist/core/config/runtime-config.js +201 -0
- package/dist/core/config/user-config.js +510 -0
- package/dist/core/config/validation.js +521 -0
- package/dist/core/constants.js +400 -0
- package/dist/core/git.js +145 -0
- package/dist/core/legacy-compat.js +60 -0
- package/dist/core/logging.js +101 -0
- package/dist/core/platform.js +59 -0
- package/dist/core/process-control.js +315 -0
- package/dist/core/secrets/index.js +42 -0
- package/dist/core/secrets/inline-seal.js +60 -0
- package/dist/core/secrets/ref.js +33 -0
- package/dist/core/secrets/resolver.js +65 -0
- package/dist/core/secrets/store.js +63 -0
- package/dist/core/utils.js +233 -0
- package/dist/core/version.js +15 -0
- package/dist/dashboard/platform.js +219 -0
- package/dist/dashboard/routes/agents.js +450 -0
- package/dist/dashboard/routes/cli.js +174 -0
- package/dist/dashboard/routes/config.js +523 -0
- package/dist/dashboard/routes/extensions.js +745 -0
- package/dist/dashboard/routes/local-models.js +290 -0
- package/dist/dashboard/routes/models.js +324 -0
- package/dist/dashboard/routes/sessions.js +838 -0
- package/dist/dashboard/runtime.js +410 -0
- package/dist/dashboard/server.js +237 -0
- package/dist/dashboard/session-control.js +347 -0
- package/dist/model/catalog.js +104 -0
- package/dist/model/index.js +20 -0
- package/dist/model/injector.js +272 -0
- package/dist/model/provider-models.js +112 -0
- package/dist/model/store.js +212 -0
- package/dist/model/types.js +13 -0
- package/dist/model/validation.js +203 -0
- package/package.json +82 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive terminal setup wizard.
|
|
3
|
+
*/
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { createInterface } from 'node:readline/promises';
|
|
6
|
+
import { buildSetupGuide, collectSetupState } from './onboarding.js';
|
|
7
|
+
import { getUserConfigPath, saveUserConfig } from '../core/config/user-config.js';
|
|
8
|
+
import { VALIDATION_TIMEOUTS } from '../core/constants.js';
|
|
9
|
+
function createTerminalIO() {
|
|
10
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
11
|
+
return {
|
|
12
|
+
ask(prompt) {
|
|
13
|
+
return rl.question(prompt);
|
|
14
|
+
},
|
|
15
|
+
write(text) {
|
|
16
|
+
process.stdout.write(text);
|
|
17
|
+
},
|
|
18
|
+
runCommand(command, args = []) {
|
|
19
|
+
return new Promise(resolve => {
|
|
20
|
+
const child = spawn(command, args, {
|
|
21
|
+
stdio: 'inherit',
|
|
22
|
+
env: process.env,
|
|
23
|
+
});
|
|
24
|
+
child.on('exit', code => resolve(code ?? 1));
|
|
25
|
+
child.on('error', () => resolve(1));
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
close() {
|
|
29
|
+
void rl.close();
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function parseChoice(raw) {
|
|
34
|
+
return String(raw || '').trim().toLowerCase();
|
|
35
|
+
}
|
|
36
|
+
async function askYesNo(io, prompt, def) {
|
|
37
|
+
const suffix = def ? ' [Y/n] ' : ' [y/N] ';
|
|
38
|
+
for (;;) {
|
|
39
|
+
const answer = parseChoice(await io.ask(`${prompt}${suffix}`));
|
|
40
|
+
if (!answer)
|
|
41
|
+
return def;
|
|
42
|
+
if (['y', 'yes'].includes(answer))
|
|
43
|
+
return true;
|
|
44
|
+
if (['n', 'no'].includes(answer))
|
|
45
|
+
return false;
|
|
46
|
+
io.write('Please answer y or n.\n');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function askAgentChoice(io, installed) {
|
|
50
|
+
io.write('Choose your local coding agent:\n');
|
|
51
|
+
io.write(` 1. Codex${installed.some(a => a.agent === 'codex' && a.installed) ? ' (installed)' : ''}\n`);
|
|
52
|
+
io.write(` 2. Claude Code${installed.some(a => a.agent === 'claude' && a.installed) ? ' (installed)' : ''}\n`);
|
|
53
|
+
io.write(' q. Quit setup\n');
|
|
54
|
+
for (;;) {
|
|
55
|
+
const answer = parseChoice(await io.ask('Selection [1/2/q]: '));
|
|
56
|
+
if (answer === '1' || answer === 'codex')
|
|
57
|
+
return 'codex';
|
|
58
|
+
if (answer === '2' || answer === 'claude')
|
|
59
|
+
return 'claude';
|
|
60
|
+
if (answer === 'q' || answer === 'quit')
|
|
61
|
+
return null;
|
|
62
|
+
io.write('Please choose 1, 2, or q.\n');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function preferredInstalledAgent(agents) {
|
|
66
|
+
if (agents.some(agent => agent.agent === 'codex' && agent.installed))
|
|
67
|
+
return 'codex';
|
|
68
|
+
if (agents.some(agent => agent.agent === 'claude' && agent.installed))
|
|
69
|
+
return 'claude';
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
function parseAgent(value) {
|
|
73
|
+
return value === 'claude' || value === 'codex' ? value : null;
|
|
74
|
+
}
|
|
75
|
+
function title(label) {
|
|
76
|
+
return `\n${label}\n${'-'.repeat(label.length)}\n`;
|
|
77
|
+
}
|
|
78
|
+
export async function validateTelegramToken(token) {
|
|
79
|
+
const value = String(token || '').trim();
|
|
80
|
+
if (!value)
|
|
81
|
+
return { ok: false, bot: null, error: 'The token was empty.' };
|
|
82
|
+
try {
|
|
83
|
+
const resp = await fetch(`https://api.telegram.org/bot${value}/getMe`, {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
signal: AbortSignal.timeout(VALIDATION_TIMEOUTS.telegramToken),
|
|
86
|
+
});
|
|
87
|
+
const raw = await resp.text();
|
|
88
|
+
let parsed = null;
|
|
89
|
+
try {
|
|
90
|
+
parsed = raw ? JSON.parse(raw) : null;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return { ok: false, bot: null, error: `Telegram returned invalid JSON (${resp.status}).` };
|
|
94
|
+
}
|
|
95
|
+
if (!resp.ok || parsed?.ok !== true || !parsed?.result) {
|
|
96
|
+
const detail = typeof parsed?.description === 'string' ? parsed.description : `HTTP ${resp.status}`;
|
|
97
|
+
return { ok: false, bot: null, error: `Telegram rejected this token: ${detail}` };
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
ok: true,
|
|
101
|
+
bot: {
|
|
102
|
+
id: parsed.result.id,
|
|
103
|
+
username: parsed.result.username || null,
|
|
104
|
+
displayName: parsed.result.first_name || null,
|
|
105
|
+
},
|
|
106
|
+
error: null,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
const msg = err instanceof Error ? err.message : String(err ?? 'unknown error');
|
|
111
|
+
return { ok: false, bot: null, error: `Failed to reach Telegram: ${msg}` };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export async function runSetupWizard(options) {
|
|
115
|
+
const io = options.io || createTerminalIO();
|
|
116
|
+
const validate = options.validateTelegramToken || validateTelegramToken;
|
|
117
|
+
const persistConfig = options.persistConfig || saveUserConfig;
|
|
118
|
+
let state = options.initialState;
|
|
119
|
+
let selectedAgent = parseAgent(options.argsAgent);
|
|
120
|
+
let token = String(options.currentToken || '').trim() || null;
|
|
121
|
+
let tokenCheck = null;
|
|
122
|
+
let configPath = null;
|
|
123
|
+
const refreshState = () => {
|
|
124
|
+
state = collectSetupState({
|
|
125
|
+
agents: options.listAgents(),
|
|
126
|
+
channel: options.channel,
|
|
127
|
+
tokenProvided: !!token,
|
|
128
|
+
});
|
|
129
|
+
return state;
|
|
130
|
+
};
|
|
131
|
+
try {
|
|
132
|
+
io.write(title(`pikiloop v${options.version} setup`));
|
|
133
|
+
if (options.channel !== 'telegram') {
|
|
134
|
+
io.write(buildSetupGuide(state, options.version));
|
|
135
|
+
io.write('\nInteractive setup is currently only available for Telegram.\n');
|
|
136
|
+
return { completed: false, token, agent: null, configPath: null, tokenCheck: null };
|
|
137
|
+
}
|
|
138
|
+
io.write('This wizard will help you install or verify a local agent, validate your Telegram bot token, and optionally save the setup for next time.\n');
|
|
139
|
+
refreshState();
|
|
140
|
+
io.write(title('Step 1: Local agent'));
|
|
141
|
+
if (!selectedAgent) {
|
|
142
|
+
const installedAgent = preferredInstalledAgent(state.agents);
|
|
143
|
+
if (state.agents.filter(agent => agent.installed).length > 1) {
|
|
144
|
+
selectedAgent = await askAgentChoice(io, state.agents);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
selectedAgent = installedAgent || await askAgentChoice(io, state.agents);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (!selectedAgent) {
|
|
151
|
+
io.write('Setup cancelled.\n');
|
|
152
|
+
return { completed: false, token, agent: null, configPath: null, tokenCheck: null };
|
|
153
|
+
}
|
|
154
|
+
let selectedState = refreshState().agents.find(agent => agent.agent === selectedAgent) || null;
|
|
155
|
+
if (!selectedState?.installed) {
|
|
156
|
+
io.write(`${selectedState?.label || selectedAgent} is not installed.\n`);
|
|
157
|
+
const installNow = await askYesNo(io, `Install ${selectedAgent === 'claude' ? 'Claude Code' : 'Codex'} now?`, true);
|
|
158
|
+
if (!installNow) {
|
|
159
|
+
io.write('Setup cancelled. Install the agent first, then run `npx pikiloop@latest` again.\n');
|
|
160
|
+
return { completed: false, token, agent: selectedAgent, configPath: null, tokenCheck: null };
|
|
161
|
+
}
|
|
162
|
+
io.write(`Running: ${selectedState?.installCommand || `npm install -g ${selectedAgent}`}\n`);
|
|
163
|
+
const exitCode = await io.runCommand('npm', ['install', '-g', selectedAgent === 'claude' ? '@anthropic-ai/claude-code' : '@openai/codex']);
|
|
164
|
+
selectedState = refreshState().agents.find(agent => agent.agent === selectedAgent) || null;
|
|
165
|
+
if (exitCode !== 0 || !selectedState?.installed) {
|
|
166
|
+
io.write(`Install did not complete successfully. You can try manually: ${selectedState?.installCommand || ''}\n`);
|
|
167
|
+
return { completed: false, token, agent: selectedAgent, configPath: null, tokenCheck: null };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
io.write(`Using ${selectedState.label}.\n`);
|
|
171
|
+
io.write(title('Step 2: Telegram bot token'));
|
|
172
|
+
if (token) {
|
|
173
|
+
tokenCheck = await validate(token);
|
|
174
|
+
if (tokenCheck.ok) {
|
|
175
|
+
io.write(`Existing token looks valid for @${tokenCheck.bot?.username || 'unknown_bot'}.\n`);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
io.write(`The existing token could not be verified: ${tokenCheck.error}\n`);
|
|
179
|
+
token = null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (!token) {
|
|
183
|
+
io.write('Get a bot token from Telegram first:\n');
|
|
184
|
+
io.write(' 1. Open Telegram and search for @BotFather\n');
|
|
185
|
+
io.write(' 2. Send /newbot\n');
|
|
186
|
+
io.write(' 3. Choose a display name and a username\n');
|
|
187
|
+
io.write(' 4. Paste the token here\n');
|
|
188
|
+
}
|
|
189
|
+
while (!token) {
|
|
190
|
+
const answer = String(await io.ask('Paste your Telegram bot token (or type q to quit): ')).trim();
|
|
191
|
+
if (!answer)
|
|
192
|
+
continue;
|
|
193
|
+
if (parseChoice(answer) === 'q') {
|
|
194
|
+
io.write('Setup cancelled.\n');
|
|
195
|
+
return { completed: false, token: null, agent: selectedAgent, configPath: null, tokenCheck: null };
|
|
196
|
+
}
|
|
197
|
+
const check = await validate(answer);
|
|
198
|
+
if (!check.ok) {
|
|
199
|
+
io.write(`${check.error}\n`);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
token = answer;
|
|
203
|
+
tokenCheck = check;
|
|
204
|
+
io.write(`Telegram bot verified: @${check.bot?.username || 'unknown_bot'}${check.bot?.displayName ? ` (${check.bot.displayName})` : ''}\n`);
|
|
205
|
+
}
|
|
206
|
+
io.write(title('Step 3: Save setup'));
|
|
207
|
+
const configFile = getUserConfigPath();
|
|
208
|
+
const saveIt = await askYesNo(io, `Save this setup to ${configFile}?`, true);
|
|
209
|
+
if (saveIt) {
|
|
210
|
+
configPath = persistConfig({
|
|
211
|
+
channel: 'telegram',
|
|
212
|
+
defaultAgent: selectedAgent,
|
|
213
|
+
telegramBotToken: token,
|
|
214
|
+
});
|
|
215
|
+
io.write(`Saved config to ${configPath}\n`);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
io.write('Skipping config save. This run will still start now, but you may need to provide the token again next time.\n');
|
|
219
|
+
}
|
|
220
|
+
io.write(title('Ready'));
|
|
221
|
+
io.write(`Agent: ${selectedAgent}\n`);
|
|
222
|
+
io.write(`Telegram bot: @${tokenCheck?.bot?.username || 'unknown_bot'}\n`);
|
|
223
|
+
io.write('Starting pikiloop now...\n');
|
|
224
|
+
return {
|
|
225
|
+
completed: true,
|
|
226
|
+
token,
|
|
227
|
+
agent: selectedAgent,
|
|
228
|
+
configPath,
|
|
229
|
+
tokenCheck,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
io.close();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime resolution of agent model and effort preferences.
|
|
3
|
+
*/
|
|
4
|
+
import { normalizeClaudeModelId } from '../../agent/index.js';
|
|
5
|
+
export const DEFAULT_AGENT_MODELS = {
|
|
6
|
+
claude: 'claude-opus-4-8',
|
|
7
|
+
codex: 'gpt-5.5',
|
|
8
|
+
gemini: 'gemini-3.1-pro-preview',
|
|
9
|
+
hermes: 'anthropic/claude-sonnet-4',
|
|
10
|
+
};
|
|
11
|
+
export const DEFAULT_AGENT_EFFORTS = {
|
|
12
|
+
claude: 'high',
|
|
13
|
+
codex: 'xhigh',
|
|
14
|
+
gemini: 'high',
|
|
15
|
+
hermes: 'medium',
|
|
16
|
+
};
|
|
17
|
+
function trimmed(value) {
|
|
18
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
19
|
+
}
|
|
20
|
+
/** Parse a boolean-ish string (env var / loose config). Empty → null (unset). */
|
|
21
|
+
function parseBoolish(value) {
|
|
22
|
+
const v = value.trim().toLowerCase();
|
|
23
|
+
if (!v)
|
|
24
|
+
return null;
|
|
25
|
+
if (v === '1' || v === 'true' || v === 'on' || v === 'yes' || v === 'enabled')
|
|
26
|
+
return true;
|
|
27
|
+
if (v === '0' || v === 'false' || v === 'off' || v === 'no' || v === 'disabled')
|
|
28
|
+
return false;
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
export function agentModelEnv(agent, env = process.env) {
|
|
32
|
+
switch (agent) {
|
|
33
|
+
case 'claude': return trimmed(env.CLAUDE_MODEL);
|
|
34
|
+
case 'codex': return trimmed(env.CODEX_MODEL);
|
|
35
|
+
case 'gemini': return trimmed(env.GEMINI_MODEL);
|
|
36
|
+
case 'hermes': return trimmed(env.HERMES_MODEL);
|
|
37
|
+
}
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
export function agentEffortEnv(agent, env = process.env) {
|
|
41
|
+
switch (agent) {
|
|
42
|
+
case 'claude': return trimmed(env.CLAUDE_REASONING_EFFORT).toLowerCase();
|
|
43
|
+
case 'codex': return trimmed(env.CODEX_REASONING_EFFORT).toLowerCase();
|
|
44
|
+
case 'gemini': return trimmed(env.GEMINI_REASONING_EFFORT).toLowerCase();
|
|
45
|
+
case 'hermes': return trimmed(env.HERMES_REASONING_EFFORT).toLowerCase();
|
|
46
|
+
}
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
export function resolveAgentModel(config, agent) {
|
|
50
|
+
let value = '';
|
|
51
|
+
switch (agent) {
|
|
52
|
+
case 'claude':
|
|
53
|
+
value = trimmed(config.claudeModel || agentModelEnv('claude') || DEFAULT_AGENT_MODELS.claude);
|
|
54
|
+
return normalizeClaudeModelId(value);
|
|
55
|
+
case 'codex':
|
|
56
|
+
value = trimmed(config.codexModel || agentModelEnv('codex') || DEFAULT_AGENT_MODELS.codex);
|
|
57
|
+
return value || DEFAULT_AGENT_MODELS.codex;
|
|
58
|
+
case 'gemini':
|
|
59
|
+
value = trimmed(config.geminiModel || agentModelEnv('gemini') || DEFAULT_AGENT_MODELS.gemini);
|
|
60
|
+
return value || DEFAULT_AGENT_MODELS.gemini;
|
|
61
|
+
case 'hermes':
|
|
62
|
+
value = trimmed(config.hermesModel || agentModelEnv('hermes') || DEFAULT_AGENT_MODELS.hermes);
|
|
63
|
+
return value || DEFAULT_AGENT_MODELS.hermes;
|
|
64
|
+
}
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
export function resolveAgentEffort(config, agent) {
|
|
68
|
+
switch (agent) {
|
|
69
|
+
case 'claude': {
|
|
70
|
+
const value = trimmed(config.claudeReasoningEffort || agentEffortEnv('claude') || DEFAULT_AGENT_EFFORTS.claude).toLowerCase();
|
|
71
|
+
return value || DEFAULT_AGENT_EFFORTS.claude || null;
|
|
72
|
+
}
|
|
73
|
+
case 'codex': {
|
|
74
|
+
const value = trimmed(config.codexReasoningEffort || agentEffortEnv('codex') || DEFAULT_AGENT_EFFORTS.codex).toLowerCase();
|
|
75
|
+
return value || DEFAULT_AGENT_EFFORTS.codex || null;
|
|
76
|
+
}
|
|
77
|
+
case 'gemini': {
|
|
78
|
+
const value = trimmed(config.geminiReasoningEffort || agentEffortEnv('gemini') || DEFAULT_AGENT_EFFORTS.gemini).toLowerCase();
|
|
79
|
+
return value || DEFAULT_AGENT_EFFORTS.gemini || null;
|
|
80
|
+
}
|
|
81
|
+
case 'hermes': {
|
|
82
|
+
const value = trimmed(config.hermesReasoningEffort || agentEffortEnv('hermes') || DEFAULT_AGENT_EFFORTS.hermes).toLowerCase();
|
|
83
|
+
return value || DEFAULT_AGENT_EFFORTS.hermes || null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Workflow (multi-agent orchestration) toggle
|
|
90
|
+
//
|
|
91
|
+
// Orthogonal to effort: effort tunes how deeply a *single* agent reasons;
|
|
92
|
+
// workflow grants the agent permission to author + run multi-agent Workflow
|
|
93
|
+
// orchestrations (fan-out / pipeline / verify). Only agents whose driver
|
|
94
|
+
// advertises `capabilities.workflow` honor it (claude today). Default OFF —
|
|
95
|
+
// the claude driver hard-disables the Workflow tool unless this is true, so a
|
|
96
|
+
// bare "workflow" keyword can never auto-spawn a fleet of sub-agents under the
|
|
97
|
+
// bypassPermissions mode pikiloop runs by default.
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
export function agentWorkflowEnv(agent, env = process.env) {
|
|
100
|
+
switch (agent) {
|
|
101
|
+
case 'claude': return trimmed(env.CLAUDE_WORKFLOW);
|
|
102
|
+
}
|
|
103
|
+
return '';
|
|
104
|
+
}
|
|
105
|
+
export function resolveAgentWorkflowEnabled(config, agent) {
|
|
106
|
+
switch (agent) {
|
|
107
|
+
case 'claude': {
|
|
108
|
+
const raw = config.claudeWorkflowEnabled;
|
|
109
|
+
if (typeof raw === 'boolean')
|
|
110
|
+
return raw;
|
|
111
|
+
const fromEnv = parseBoolish(agentWorkflowEnv('claude'));
|
|
112
|
+
return fromEnv ?? false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
export function setAgentWorkflowEnv(agent, value, env = process.env) {
|
|
118
|
+
switch (agent) {
|
|
119
|
+
case 'claude':
|
|
120
|
+
env.CLAUDE_WORKFLOW = value ? '1' : '0';
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export const DEFAULT_CLAUDE_ACCESS_MODE = 'subscription';
|
|
125
|
+
/**
|
|
126
|
+
* Access mode implied by env, or null when neither var has an opinion.
|
|
127
|
+
* PIKILOOP_CLAUDE_PRINT takes precedence over the legacy PIKILOOP_CLAUDE_TUI so
|
|
128
|
+
* a freshly-written print var resolves a stale tui var (setClaudeAccessModeEnv
|
|
129
|
+
* relies on this). Mirrors isClaudePrintModeForced() in the claude driver.
|
|
130
|
+
*/
|
|
131
|
+
export function claudeAccessModeEnv(env = process.env) {
|
|
132
|
+
const print = parseBoolish(trimmed(env.PIKILOOP_CLAUDE_PRINT));
|
|
133
|
+
if (print != null)
|
|
134
|
+
return print ? 'api' : 'subscription';
|
|
135
|
+
const tui = parseBoolish(trimmed(env.PIKILOOP_CLAUDE_TUI));
|
|
136
|
+
if (tui != null)
|
|
137
|
+
return tui ? 'subscription' : 'api';
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
export function resolveClaudeAccessMode(config) {
|
|
141
|
+
const raw = config.claudeAccessMode;
|
|
142
|
+
if (raw === 'subscription' || raw === 'api')
|
|
143
|
+
return raw;
|
|
144
|
+
return claudeAccessModeEnv() ?? DEFAULT_CLAUDE_ACCESS_MODE;
|
|
145
|
+
}
|
|
146
|
+
/** Mirror the choice into PIKILOOP_CLAUDE_PRINT so any env-only fallback path
|
|
147
|
+
* (and tooling that inspects the env) agrees with the persisted config. */
|
|
148
|
+
export function setClaudeAccessModeEnv(value, env = process.env) {
|
|
149
|
+
env.PIKILOOP_CLAUDE_PRINT = value === 'api' ? '1' : '0';
|
|
150
|
+
}
|
|
151
|
+
export function setAgentModelEnv(agent, value, env = process.env) {
|
|
152
|
+
switch (agent) {
|
|
153
|
+
case 'claude':
|
|
154
|
+
env.CLAUDE_MODEL = value;
|
|
155
|
+
break;
|
|
156
|
+
case 'codex':
|
|
157
|
+
env.CODEX_MODEL = value;
|
|
158
|
+
break;
|
|
159
|
+
case 'gemini':
|
|
160
|
+
env.GEMINI_MODEL = value;
|
|
161
|
+
break;
|
|
162
|
+
case 'hermes':
|
|
163
|
+
env.HERMES_MODEL = value;
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export function setAgentEffortEnv(agent, value, env = process.env) {
|
|
168
|
+
switch (agent) {
|
|
169
|
+
case 'claude':
|
|
170
|
+
env.CLAUDE_REASONING_EFFORT = value;
|
|
171
|
+
break;
|
|
172
|
+
case 'codex':
|
|
173
|
+
env.CODEX_REASONING_EFFORT = value;
|
|
174
|
+
break;
|
|
175
|
+
case 'gemini':
|
|
176
|
+
env.GEMINI_REASONING_EFFORT = value;
|
|
177
|
+
break;
|
|
178
|
+
case 'hermes':
|
|
179
|
+
env.HERMES_REASONING_EFFORT = value;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// "Ultra" effort rung — the single user-facing knob that folds workflow in
|
|
185
|
+
//
|
|
186
|
+
// Surfaced as the top rung of every effort picker (IM /models + dashboard),
|
|
187
|
+
// "ultra" means "max reasoning depth + permit multi-agent Workflow
|
|
188
|
+
// orchestration" — the same bundle as Claude's native `ultracode` mode. It is
|
|
189
|
+
// NOT a real --effort value (the CLI hard-rejects anything outside
|
|
190
|
+
// low|medium|high|xhigh|max), so every effort-write path decomposes it into a
|
|
191
|
+
// concrete effort plus the orthogonal workflow flag via this single helper.
|
|
192
|
+
// Because the rungs are mutually exclusive, picking any concrete level clears
|
|
193
|
+
// the orchestration opt-in. See Bot.switchEffortForChat for the IM mirror.
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
export const ULTRA_EFFORT = 'ultra';
|
|
196
|
+
export function decomposeEffortSelection(raw) {
|
|
197
|
+
const value = trimmed(raw).toLowerCase();
|
|
198
|
+
if (value === ULTRA_EFFORT)
|
|
199
|
+
return { effort: 'max', workflow: true };
|
|
200
|
+
return { effort: value, workflow: false };
|
|
201
|
+
}
|