pikiclaw 0.2.56 → 0.2.58
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/dist/agent-auto-update.js +194 -0
- package/dist/agent-npm.js +20 -0
- package/dist/bot-commands.js +6 -8
- package/dist/bot.js +2 -2
- package/dist/cli.js +6 -0
- package/dist/code-agent.js +3 -0
- package/dist/dashboard-ui.js +8 -8
- package/dist/dashboard.js +77 -5
- package/dist/driver-claude.js +10 -11
- package/dist/driver-gemini.js +253 -16
- package/dist/onboarding.js +3 -10
- package/dist/run.js +36 -4
- package/package.json +1 -1
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { getAgentLabel, getAgentPackage } from './agent-npm.js';
|
|
6
|
+
const AGENT_UPDATE_LOCK_STALE_MS = 60 * 60_000;
|
|
7
|
+
const AGENT_UPDATE_COMMAND_TIMEOUT_MS = 15 * 60_000;
|
|
8
|
+
function updaterLockPath() {
|
|
9
|
+
return path.join(os.homedir(), '.pikiclaw', 'agent-auto-update.lock');
|
|
10
|
+
}
|
|
11
|
+
function normalizeBooleanEnv(value) {
|
|
12
|
+
const text = String(value || '').trim();
|
|
13
|
+
if (!text)
|
|
14
|
+
return null;
|
|
15
|
+
if (/^(1|true|yes|on)$/i.test(text))
|
|
16
|
+
return true;
|
|
17
|
+
if (/^(0|false|no|off)$/i.test(text))
|
|
18
|
+
return false;
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
export function agentAutoUpdateEnabled(config) {
|
|
22
|
+
const env = normalizeBooleanEnv(process.env.PIKICLAW_AGENT_AUTO_UPDATE);
|
|
23
|
+
if (env != null)
|
|
24
|
+
return env;
|
|
25
|
+
if (typeof config.agentAutoUpdate === 'boolean')
|
|
26
|
+
return config.agentAutoUpdate;
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
export function extractAgentSemver(value) {
|
|
30
|
+
const text = String(value || '').trim();
|
|
31
|
+
if (!text)
|
|
32
|
+
return null;
|
|
33
|
+
const match = text.match(/\b\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?\b/);
|
|
34
|
+
return match?.[0] || null;
|
|
35
|
+
}
|
|
36
|
+
function isPathInside(parentDir, childPath) {
|
|
37
|
+
const parent = path.resolve(parentDir);
|
|
38
|
+
const child = path.resolve(childPath);
|
|
39
|
+
return child === parent || child.startsWith(`${parent}${path.sep}`);
|
|
40
|
+
}
|
|
41
|
+
export function resolveAgentUpdateStrategy(agent, npmPrefix) {
|
|
42
|
+
const id = String(agent.agent || '').trim();
|
|
43
|
+
const pkg = getAgentPackage(id);
|
|
44
|
+
if (!pkg)
|
|
45
|
+
return { kind: 'skip', reason: 'unsupported agent' };
|
|
46
|
+
const binPath = String(agent.path || '').trim();
|
|
47
|
+
const npmBinDir = npmPrefix ? path.join(path.resolve(npmPrefix), 'bin') : null;
|
|
48
|
+
const npmManaged = !!(binPath && npmBinDir && isPathInside(npmBinDir, binPath));
|
|
49
|
+
if (npmManaged)
|
|
50
|
+
return { kind: 'npm', pkg };
|
|
51
|
+
return { kind: 'skip', reason: 'non-npm install path' };
|
|
52
|
+
}
|
|
53
|
+
function labelForAgent(agent) {
|
|
54
|
+
return getAgentLabel(agent);
|
|
55
|
+
}
|
|
56
|
+
async function runCommand(cmd, args, opts = {}) {
|
|
57
|
+
return new Promise(resolve => {
|
|
58
|
+
let stdout = '';
|
|
59
|
+
let stderr = '';
|
|
60
|
+
let finished = false;
|
|
61
|
+
const child = spawn(cmd, args, {
|
|
62
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
63
|
+
env: { ...process.env, npm_config_yes: 'true' },
|
|
64
|
+
});
|
|
65
|
+
const timeoutMs = Math.max(500, opts.timeoutMs ?? AGENT_UPDATE_COMMAND_TIMEOUT_MS);
|
|
66
|
+
const timer = setTimeout(() => {
|
|
67
|
+
if (finished)
|
|
68
|
+
return;
|
|
69
|
+
finished = true;
|
|
70
|
+
child.kill('SIGTERM');
|
|
71
|
+
resolve({ ok: false, code: null, stdout, stderr, error: `Timed out after ${Math.round(timeoutMs / 1000)}s` });
|
|
72
|
+
}, timeoutMs);
|
|
73
|
+
child.stdout?.on('data', chunk => { stdout += String(chunk); });
|
|
74
|
+
child.stderr?.on('data', chunk => { stderr += String(chunk); });
|
|
75
|
+
child.on('error', err => {
|
|
76
|
+
if (finished)
|
|
77
|
+
return;
|
|
78
|
+
finished = true;
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
resolve({ ok: false, code: null, stdout, stderr, error: err.message });
|
|
81
|
+
});
|
|
82
|
+
child.on('close', code => {
|
|
83
|
+
if (finished)
|
|
84
|
+
return;
|
|
85
|
+
finished = true;
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
resolve({
|
|
88
|
+
ok: code === 0,
|
|
89
|
+
code,
|
|
90
|
+
stdout,
|
|
91
|
+
stderr,
|
|
92
|
+
error: code === 0 ? null : (stderr.trim() || stdout.trim() || `Exited with code ${code}`),
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async function getNpmGlobalPrefix() {
|
|
98
|
+
const result = await runCommand('npm', ['prefix', '-g'], { timeoutMs: 10_000 });
|
|
99
|
+
return result.ok ? result.stdout.trim().split('\n')[0] || null : null;
|
|
100
|
+
}
|
|
101
|
+
async function getLatestPackageVersion(pkg) {
|
|
102
|
+
const result = await runCommand('npm', ['view', pkg, 'version', '--json'], { timeoutMs: 20_000 });
|
|
103
|
+
if (!result.ok)
|
|
104
|
+
return null;
|
|
105
|
+
const raw = result.stdout.trim();
|
|
106
|
+
if (!raw)
|
|
107
|
+
return null;
|
|
108
|
+
try {
|
|
109
|
+
const parsed = JSON.parse(raw);
|
|
110
|
+
return typeof parsed === 'string' ? parsed.trim() || null : null;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return raw.replace(/^"+|"+$/g, '').trim() || null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function acquireUpdateLock(log) {
|
|
117
|
+
const filePath = updaterLockPath();
|
|
118
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
119
|
+
try {
|
|
120
|
+
const stat = fs.statSync(filePath);
|
|
121
|
+
if (Date.now() - stat.mtimeMs > AGENT_UPDATE_LOCK_STALE_MS)
|
|
122
|
+
fs.rmSync(filePath, { force: true });
|
|
123
|
+
}
|
|
124
|
+
catch { }
|
|
125
|
+
try {
|
|
126
|
+
fs.writeFileSync(filePath, `${process.pid}\n`, { flag: 'wx' });
|
|
127
|
+
let released = false;
|
|
128
|
+
return () => {
|
|
129
|
+
if (released)
|
|
130
|
+
return;
|
|
131
|
+
released = true;
|
|
132
|
+
try {
|
|
133
|
+
fs.rmSync(filePath, { force: true });
|
|
134
|
+
}
|
|
135
|
+
catch { }
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
log('agent auto-update already running in another process; skipping this startup check');
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function updateViaNpm(pkg) {
|
|
144
|
+
const result = await runCommand('npm', ['install', '-g', `${pkg}@latest`]);
|
|
145
|
+
return { ok: result.ok, detail: result.ok ? result.stdout.trim() || null : result.error };
|
|
146
|
+
}
|
|
147
|
+
export function startAgentAutoUpdate(opts) {
|
|
148
|
+
if (!agentAutoUpdateEnabled(opts.config))
|
|
149
|
+
return;
|
|
150
|
+
const installedAgents = opts.agents.filter(agent => agent.installed && agent.path);
|
|
151
|
+
if (!installedAgents.length)
|
|
152
|
+
return;
|
|
153
|
+
const releaseLock = acquireUpdateLock(opts.log);
|
|
154
|
+
if (!releaseLock)
|
|
155
|
+
return;
|
|
156
|
+
void (async () => {
|
|
157
|
+
try {
|
|
158
|
+
opts.log(`agent auto-update: checking ${installedAgents.length} installed agent${installedAgents.length === 1 ? '' : 's'} in background`);
|
|
159
|
+
const npmPrefix = await getNpmGlobalPrefix();
|
|
160
|
+
for (const agent of installedAgents) {
|
|
161
|
+
const id = String(agent.agent || '').trim();
|
|
162
|
+
const pkg = getAgentPackage(id);
|
|
163
|
+
if (!pkg)
|
|
164
|
+
continue;
|
|
165
|
+
const label = labelForAgent(id);
|
|
166
|
+
const currentVersion = extractAgentSemver(agent.version);
|
|
167
|
+
const latestVersion = await getLatestPackageVersion(pkg);
|
|
168
|
+
if (!latestVersion) {
|
|
169
|
+
opts.log(`agent auto-update: ${label} latest version lookup failed`);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (currentVersion === latestVersion) {
|
|
173
|
+
opts.log(`agent auto-update: ${label} is already up to date (${latestVersion})`);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const strategy = resolveAgentUpdateStrategy(agent, npmPrefix);
|
|
177
|
+
if (strategy.kind === 'skip') {
|
|
178
|
+
opts.log(`agent auto-update: ${label} is ${currentVersion || 'unknown'} and latest is ${latestVersion}, but update is skipped (${strategy.reason})`);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
opts.log(`agent auto-update: updating ${label} ${currentVersion || 'unknown'} -> ${latestVersion}`);
|
|
182
|
+
const result = await updateViaNpm(strategy.pkg);
|
|
183
|
+
if (result.ok)
|
|
184
|
+
opts.log(`agent auto-update: ${label} update completed`);
|
|
185
|
+
else
|
|
186
|
+
opts.log(`agent auto-update: ${label} update failed: ${result.detail || 'unknown error'}`);
|
|
187
|
+
}
|
|
188
|
+
opts.log('agent auto-update: finished');
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
releaseLock();
|
|
192
|
+
}
|
|
193
|
+
})();
|
|
194
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const AGENT_PACKAGES = {
|
|
2
|
+
claude: '@anthropic-ai/claude-code',
|
|
3
|
+
codex: '@openai/codex',
|
|
4
|
+
gemini: '@google/gemini-cli',
|
|
5
|
+
};
|
|
6
|
+
const AGENT_LABELS = {
|
|
7
|
+
claude: 'Claude Code',
|
|
8
|
+
codex: 'Codex',
|
|
9
|
+
gemini: 'Gemini CLI',
|
|
10
|
+
};
|
|
11
|
+
export function getAgentPackage(agent) {
|
|
12
|
+
return AGENT_PACKAGES[agent] || null;
|
|
13
|
+
}
|
|
14
|
+
export function getAgentLabel(agent) {
|
|
15
|
+
return AGENT_LABELS[agent] || agent;
|
|
16
|
+
}
|
|
17
|
+
export function getAgentInstallCommand(agent) {
|
|
18
|
+
const pkg = getAgentPackage(agent);
|
|
19
|
+
return pkg ? `npm install -g ${pkg}` : null;
|
|
20
|
+
}
|
package/dist/bot-commands.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { fmtTokens, fmtUptime, fmtBytes } from './bot.js';
|
|
13
|
-
import { getProjectSkillPaths } from './code-agent.js';
|
|
13
|
+
import { getProjectSkillPaths, normalizeClaudeModelId } from './code-agent.js';
|
|
14
14
|
import { getDriver } from './agent-driver.js';
|
|
15
15
|
import { buildWelcomeIntro, buildSkillCommandName, indexSkillsByCommand, SKILL_CMD_PREFIX } from './bot-menu.js';
|
|
16
16
|
import { buildBotMenuState } from './bot-orchestration.js';
|
|
@@ -156,15 +156,13 @@ export function getSkillsListData(bot, chatId) {
|
|
|
156
156
|
return { agent: cs.agent, workdir: bot.workdir, skills };
|
|
157
157
|
}
|
|
158
158
|
function claudeModelSelectionKey(modelId) {
|
|
159
|
-
const value =
|
|
159
|
+
const value = normalizeClaudeModelId(modelId).toLowerCase();
|
|
160
160
|
if (!value)
|
|
161
161
|
return null;
|
|
162
|
-
if (value === 'opus' || value
|
|
163
|
-
return
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return value === 'sonnet-1m' || value.endsWith('[1m]') ? 'sonnet-1m' : 'sonnet';
|
|
167
|
-
}
|
|
162
|
+
if (value === 'opus' || value.startsWith('claude-opus-'))
|
|
163
|
+
return 'opus';
|
|
164
|
+
if (value === 'sonnet' || value.startsWith('claude-sonnet-'))
|
|
165
|
+
return 'sonnet';
|
|
168
166
|
if (value === 'haiku' || value.startsWith('claude-haiku-'))
|
|
169
167
|
return 'haiku';
|
|
170
168
|
return null;
|
package/dist/bot.js
CHANGED
|
@@ -8,7 +8,7 @@ import fs from 'node:fs';
|
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { execSync, spawn } from 'node:child_process';
|
|
10
10
|
import { getActiveUserConfig, onUserConfigChange, resolveUserWorkdir, setUserWorkdir } from './user-config.js';
|
|
11
|
-
import { doStream, getSessions, getSessionTail, getUsage, initializeProjectSkills, listAgents, listModels, listSkills, stageSessionFiles, isPendingSessionId, } from './code-agent.js';
|
|
11
|
+
import { doStream, getSessions, getSessionTail, getUsage, initializeProjectSkills, listAgents, listModels, listSkills, stageSessionFiles, isPendingSessionId, normalizeClaudeModelId, } from './code-agent.js';
|
|
12
12
|
import { getDriver, hasDriver, allDriverIds } from './agent-driver.js';
|
|
13
13
|
import { terminateProcessTree } from './process-control.js';
|
|
14
14
|
import { VERSION } from './version.js';
|
|
@@ -221,7 +221,7 @@ function buildMcpDeliveryPrompt() {
|
|
|
221
221
|
}
|
|
222
222
|
function configModelValue(config, agent) {
|
|
223
223
|
switch (agent) {
|
|
224
|
-
case 'claude': return
|
|
224
|
+
case 'claude': return normalizeClaudeModelId(config.claudeModel || process.env.CLAUDE_MODEL || 'claude-opus-4-6');
|
|
225
225
|
case 'codex': return String(config.codexModel || process.env.CODEX_MODEL || 'gpt-5.4').trim();
|
|
226
226
|
case 'gemini': return String(config.geminiModel || process.env.GEMINI_MODEL || 'gemini-3.1-pro-preview').trim();
|
|
227
227
|
}
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
+
import { startAgentAutoUpdate } from './agent-auto-update.js';
|
|
7
8
|
import { envBool } from './bot.js';
|
|
8
9
|
import { TelegramBot } from './bot-telegram.js';
|
|
9
10
|
import { hasConfiguredChannelToken, resolveConfiguredChannels } from './cli-channels.js';
|
|
@@ -434,6 +435,11 @@ Docs: https://github.com/xiaotonng/pikiclaw
|
|
|
434
435
|
if (args.allowedIds && channel === 'telegram')
|
|
435
436
|
runtimeConfig.telegramAllowedChatIds = args.allowedIds;
|
|
436
437
|
applyUserConfig(runtimeConfig, undefined, { overwrite: true, clearMissing: true });
|
|
438
|
+
startAgentAutoUpdate({
|
|
439
|
+
config: runtimeConfig,
|
|
440
|
+
agents: listAgents({ includeVersion: true, refresh: true }).agents,
|
|
441
|
+
log: processLog,
|
|
442
|
+
});
|
|
437
443
|
if (args.model) {
|
|
438
444
|
const ag = args.agent || runtimeConfig.defaultAgent || 'codex';
|
|
439
445
|
if (ag === 'codex')
|
package/dist/code-agent.js
CHANGED
|
@@ -315,6 +315,9 @@ export function modelFamily(model) {
|
|
|
315
315
|
return 'sonnet';
|
|
316
316
|
return null;
|
|
317
317
|
}
|
|
318
|
+
export function normalizeClaudeModelId(model) {
|
|
319
|
+
return typeof model === 'string' ? model.trim() : '';
|
|
320
|
+
}
|
|
318
321
|
export function emptyUsage(agent, error) {
|
|
319
322
|
return { ok: false, agent, source: null, capturedAt: null, status: null, windows: [], error };
|
|
320
323
|
}
|