pikiloom 0.4.25 → 0.4.27
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/dashboard/dist/assets/{AgentTab-B8Ai-_zs.js → AgentTab-DIP-y4dl.js} +1 -1
- package/dashboard/dist/assets/ConnectionModal-BCfvhkiI.js +1 -0
- package/dashboard/dist/assets/{DirBrowser-CklI2Lh4.js → DirBrowser-Dot7ua9-.js} +1 -1
- package/dashboard/dist/assets/{ExtensionsTab-BYLt52eV.js → ExtensionsTab-GioMx4S6.js} +1 -1
- package/dashboard/dist/assets/{IMAccessTab-BlwB4Hje.js → IMAccessTab-C9_r8jJP.js} +1 -1
- package/dashboard/dist/assets/{Modal-DoamIusy.js → Modal-M1AP1sgR.js} +1 -1
- package/dashboard/dist/assets/{Modals-BGRFYHLW.js → Modals-CCm3UUfJ.js} +1 -1
- package/dashboard/dist/assets/{Select-DCGLDJPj.js → Select-YubDM16P.js} +1 -1
- package/dashboard/dist/assets/SessionPanel-Cnv9o96n.js +1 -0
- package/dashboard/dist/assets/SystemTab-VgNivIe3.js +1 -0
- package/dashboard/dist/assets/index-BY7l20bi.js +23 -0
- package/dashboard/dist/assets/index-BlqvzDUL.js +3 -0
- package/dashboard/dist/assets/index-CXIN3nTr.css +1 -0
- package/dashboard/dist/assets/{shared-DsGKwuTP.js → shared-C0SAInnT.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/agent/acp-client.js +0 -59
- package/dist/agent/artifacts.js +1 -38
- package/dist/agent/auto-update.js +2 -78
- package/dist/agent/await-resume.js +0 -20
- package/dist/agent/cli/auth.js +3 -51
- package/dist/agent/cli/catalog.js +0 -3
- package/dist/agent/cli/detector.js +1 -19
- package/dist/agent/cli/index.js +0 -3
- package/dist/agent/cli/registry.js +0 -25
- package/dist/agent/driver.js +0 -14
- package/dist/agent/drivers/claude-tui.js +2 -769
- package/dist/agent/drivers/claude.js +12 -631
- package/dist/agent/drivers/codex.js +11 -162
- package/dist/agent/drivers/gemini.js +6 -114
- package/dist/agent/drivers/hermes.js +0 -128
- package/dist/agent/goal.js +0 -45
- package/dist/agent/handover.js +1 -46
- package/dist/agent/images.js +7 -133
- package/dist/agent/index.js +1 -29
- package/dist/agent/mcp/bridge.js +1 -197
- package/dist/agent/mcp/extensions.js +5 -84
- package/dist/agent/mcp/oauth.js +2 -69
- package/dist/agent/mcp/registry.js +2 -33
- package/dist/agent/mcp/session-server.js +2 -45
- package/dist/agent/mcp/tools/ask-user.js +0 -6
- package/dist/agent/mcp/tools/await-resume.js +0 -15
- package/dist/agent/mcp/tools/goal.js +0 -25
- package/dist/agent/mcp/tools/types.js +0 -5
- package/dist/agent/mcp/tools/workspace.js +0 -23
- package/dist/agent/npm.js +0 -14
- package/dist/agent/session.js +90 -192
- package/dist/agent/skill-installer.js +3 -70
- package/dist/agent/skills.js +0 -43
- package/dist/agent/stream.js +5 -148
- package/dist/agent/types.js +0 -12
- package/dist/agent/utils.js +2 -120
- package/dist/bot/bot.js +3 -468
- package/dist/bot/command-ui.js +0 -45
- package/dist/bot/commands.js +0 -51
- package/dist/bot/headless-bot.js +0 -21
- package/dist/bot/host.js +1 -5
- package/dist/bot/human-loop.js +0 -8
- package/dist/bot/menu.js +0 -3
- package/dist/bot/orchestration.js +0 -3
- package/dist/bot/render-shared.js +0 -63
- package/dist/bot/session-hub.js +14 -92
- package/dist/bot/session-status.js +1 -5
- package/dist/bot/streaming.js +0 -53
- package/dist/browser-profile.js +0 -51
- package/dist/browser-supervisor.js +0 -75
- package/dist/catalog/cli-tools.js +0 -53
- package/dist/catalog/index.js +0 -18
- package/dist/catalog/local-models.js +0 -18
- package/dist/catalog/mcp-servers.js +0 -35
- package/dist/catalog/skill-repos.js +0 -32
- package/dist/channels/base.js +0 -19
- package/dist/channels/dingtalk/bot.js +0 -3
- package/dist/channels/dingtalk/channel.js +0 -33
- package/dist/channels/discord/bot.js +0 -7
- package/dist/channels/discord/channel.js +0 -22
- package/dist/channels/feishu/bot.js +0 -71
- package/dist/channels/feishu/channel.js +0 -78
- package/dist/channels/feishu/markdown.js +0 -5
- package/dist/channels/feishu/render.js +0 -45
- package/dist/channels/health.js +0 -46
- package/dist/channels/slack/bot.js +0 -7
- package/dist/channels/slack/channel.js +1 -27
- package/dist/channels/states.js +0 -3
- package/dist/channels/telegram/bot.js +0 -60
- package/dist/channels/telegram/channel.js +2 -99
- package/dist/channels/telegram/directory.js +0 -3
- package/dist/channels/telegram/live-preview.js +0 -8
- package/dist/channels/telegram/render.js +0 -30
- package/dist/channels/wecom/bot.js +0 -3
- package/dist/channels/wecom/channel.js +0 -58
- package/dist/channels/weixin/api.js +0 -9
- package/dist/channels/weixin/bot.js +0 -88
- package/dist/channels/weixin/channel.js +0 -6
- package/dist/cli/autostart.js +1 -44
- package/dist/cli/channel-supervisor.js +1 -59
- package/dist/cli/channels.js +0 -6
- package/dist/cli/main.js +1 -145
- package/dist/cli/onboarding.js +0 -6
- package/dist/cli/run.js +0 -11
- package/dist/cli/setup-wizard.js +0 -3
- package/dist/core/config/runtime-config.js +0 -38
- package/dist/core/config/user-config.js +0 -63
- package/dist/core/config/validation.js +0 -9
- package/dist/core/constants.js +0 -268
- package/dist/core/git.js +0 -31
- package/dist/core/legacy-compat.js +1 -28
- package/dist/core/logging.js +0 -3
- package/dist/core/platform.js +0 -28
- package/dist/core/process-control.js +0 -8
- package/dist/core/secrets/index.js +0 -17
- package/dist/core/secrets/inline-seal.js +0 -7
- package/dist/core/secrets/ref.js +0 -19
- package/dist/core/secrets/resolver.js +0 -8
- package/dist/core/secrets/store.js +1 -12
- package/dist/core/utils.js +1 -37
- package/dist/core/version.js +0 -3
- package/dist/dashboard/platform.js +1 -35
- package/dist/dashboard/routes/agents.js +2 -71
- package/dist/dashboard/routes/cli.js +0 -27
- package/dist/dashboard/routes/config.js +0 -54
- package/dist/dashboard/routes/extensions.js +0 -132
- package/dist/dashboard/routes/local-models.js +0 -68
- package/dist/dashboard/routes/models.js +0 -46
- package/dist/dashboard/routes/sessions.js +0 -100
- package/dist/dashboard/runtime.js +1 -58
- package/dist/dashboard/server.js +0 -49
- package/dist/dashboard/session-control.js +2 -69
- package/dist/model/catalog.js +1 -17
- package/dist/model/index.js +0 -15
- package/dist/model/injector.js +1 -181
- package/dist/model/provider-models.js +1 -48
- package/dist/model/responses-bridge.js +3 -45
- package/dist/model/store.js +0 -17
- package/dist/model/types.js +0 -12
- package/dist/model/validation.js +1 -18
- package/dist/pikichannel/adapter-pikiloom.js +0 -31
- package/dist/pikichannel/code.js +0 -12
- package/dist/pikichannel/codec.js +0 -14
- package/dist/pikichannel/host.js +4 -33
- package/dist/pikichannel/protocol.js +0 -52
- package/dist/pikichannel/rendezvous-broker.js +5 -26
- package/dist/pikichannel/rendezvous-host.js +3 -16
- package/dist/pikichannel/server.js +6 -55
- package/dist/pikichannel/transport.js +0 -25
- package/dist/pikichannel/transports/webrtc-host.js +0 -18
- package/dist/pikichannel/transports/webrtc-shared.js +4 -34
- package/dist/pikichannel/transports/websocket-host.js +1 -14
- package/dist/pikichannel/turn.js +2 -69
- package/dist/pikichannel/web/sdk.js +14 -71
- package/package.json +1 -1
- package/dashboard/dist/assets/ConnectionModal-YR4dc2lx.js +0 -1
- package/dashboard/dist/assets/SessionPanel-DZziN6ft.js +0 -1
- package/dashboard/dist/assets/SystemTab-DaJk9s8R.js +0 -1
- package/dashboard/dist/assets/index-ClrAJ5vv.js +0 -23
- package/dashboard/dist/assets/index-CoBaePpK.js +0 -3
- package/dashboard/dist/assets/index-CyCspgtc.css +0 -1
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Background agent CLI version checking and update prompts.
|
|
3
|
-
*/
|
|
4
1
|
import fs from 'node:fs';
|
|
5
2
|
import os from 'node:os';
|
|
6
3
|
import path from 'node:path';
|
|
@@ -17,39 +14,18 @@ function setUpdateState(agent, patch) {
|
|
|
17
14
|
const current = updateStates.get(agent) || emptyState();
|
|
18
15
|
updateStates.set(agent, { ...current, ...patch });
|
|
19
16
|
}
|
|
20
|
-
/** Returns the cached update state for a specific agent (or null). */
|
|
21
17
|
export function getAgentUpdateState(agent) {
|
|
22
18
|
return updateStates.get(agent) || null;
|
|
23
19
|
}
|
|
24
|
-
/** Returns all cached update states. */
|
|
25
20
|
export function getAllAgentUpdateStates() {
|
|
26
21
|
const result = {};
|
|
27
22
|
for (const [key, value] of updateStates)
|
|
28
23
|
result[key] = value;
|
|
29
24
|
return result;
|
|
30
25
|
}
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// Spawn coordination
|
|
33
|
-
//
|
|
34
|
-
// A CLI reinstall (`npm install -g` / `brew upgrade`) briefly tears down and
|
|
35
|
-
// rewrites the bin symlink, so an agent spawn that races it execs into
|
|
36
|
-
// "/bin/sh: <cli>: command not found" (exit 127). The updating process drops a
|
|
37
|
-
// per-agent marker file for the duration of the destructive step; the spawn
|
|
38
|
-
// path waits while it's held. The marker is cross-process (this worker AND the
|
|
39
|
-
// `npx pikiloom@latest` self-bootstrap both update + spawn), and carries the
|
|
40
|
-
// updater's pid so a marker orphaned by a crashed updater is detected via
|
|
41
|
-
// liveness and cleaned up — a spawn never hangs on a stale marker, and a
|
|
42
|
-
// genuinely-missing CLI (no marker) fails fast instead of waiting.
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
/** Path of the in-flight-reinstall marker for `agent` (one file per agent). */
|
|
45
26
|
export function agentUpdateMarkerPath(agent) {
|
|
46
27
|
return path.join(os.homedir(), STATE_DIR_NAME, `agent-update-${agent}.installing`);
|
|
47
28
|
}
|
|
48
|
-
/**
|
|
49
|
-
* True when a live updater is mid-reinstall of `agent`. Reads the marker's pid
|
|
50
|
-
* and probes liveness with `kill(pid, 0)`; an orphaned marker (writer gone) is
|
|
51
|
-
* removed and reported idle so spawns don't hang on a crashed updater.
|
|
52
|
-
*/
|
|
53
29
|
function agentReinstallInFlight(agent) {
|
|
54
30
|
const marker = agentUpdateMarkerPath(agent);
|
|
55
31
|
let raw;
|
|
@@ -57,12 +33,12 @@ function agentReinstallInFlight(agent) {
|
|
|
57
33
|
raw = fs.readFileSync(marker, 'utf8');
|
|
58
34
|
}
|
|
59
35
|
catch {
|
|
60
|
-
return false;
|
|
36
|
+
return false;
|
|
61
37
|
}
|
|
62
38
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
63
39
|
if (Number.isInteger(pid) && pid > 0) {
|
|
64
40
|
try {
|
|
65
|
-
process.kill(pid, 0);
|
|
41
|
+
process.kill(pid, 0);
|
|
66
42
|
return true;
|
|
67
43
|
}
|
|
68
44
|
catch {
|
|
@@ -73,7 +49,6 @@ function agentReinstallInFlight(agent) {
|
|
|
73
49
|
return false;
|
|
74
50
|
}
|
|
75
51
|
}
|
|
76
|
-
// Unreadable pid — fall back to mtime so a corrupt marker still ages out.
|
|
77
52
|
try {
|
|
78
53
|
return Date.now() - fs.statSync(marker).mtimeMs <= AGENT_UPDATE_COMMAND_TIMEOUT_MS;
|
|
79
54
|
}
|
|
@@ -81,11 +56,6 @@ function agentReinstallInFlight(agent) {
|
|
|
81
56
|
return false;
|
|
82
57
|
}
|
|
83
58
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Marks `agent` as mid-reinstall (cross-process) for the duration of
|
|
86
|
-
* `install()` so concurrent spawns can wait it out. Always clears the marker,
|
|
87
|
-
* even when the install throws.
|
|
88
|
-
*/
|
|
89
59
|
export async function withAgentUpdateGate(agent, install) {
|
|
90
60
|
const id = String(agent || '').trim();
|
|
91
61
|
if (!id)
|
|
@@ -106,11 +76,6 @@ export async function withAgentUpdateGate(agent, install) {
|
|
|
106
76
|
catch { }
|
|
107
77
|
}
|
|
108
78
|
}
|
|
109
|
-
/**
|
|
110
|
-
* Resolves once no live reinstall of `agent` is in flight, or after
|
|
111
|
-
* `timeoutMs`. No-op (returns immediately) when nothing is updating. Called
|
|
112
|
-
* from the spawn path so we never exec a half-swapped CLI binary.
|
|
113
|
-
*/
|
|
114
79
|
export async function awaitAgentUpdateIdle(agent, timeoutMs) {
|
|
115
80
|
const id = String(agent || '').trim();
|
|
116
81
|
if (!id)
|
|
@@ -179,10 +144,6 @@ function isNpmPackageOwnedBinary(binPath, pkg, npmRoot) {
|
|
|
179
144
|
return true;
|
|
180
145
|
return isPathInside(realPackageDir, binPath);
|
|
181
146
|
}
|
|
182
|
-
/**
|
|
183
|
-
* Check if a binary was installed via Homebrew by resolving its real path.
|
|
184
|
-
* Homebrew cask binaries typically symlink through Caskroom or Cellar.
|
|
185
|
-
*/
|
|
186
147
|
function isBrewInstalledBinary(binPath) {
|
|
187
148
|
const realPath = realPathOrNull(binPath);
|
|
188
149
|
const target = realPath || binPath;
|
|
@@ -196,14 +157,12 @@ export function resolveAgentUpdateStrategy(agent, npmPrefix, npmRoot = null) {
|
|
|
196
157
|
const binPath = String(agent.path || '').trim();
|
|
197
158
|
if (!binPath)
|
|
198
159
|
return { kind: 'skip', reason: 'no binary path' };
|
|
199
|
-
// Check for Homebrew install first (binary resolves to Caskroom/Cellar).
|
|
200
160
|
if (isBrewInstalledBinary(binPath)) {
|
|
201
161
|
const cask = getAgentBrewCask(id);
|
|
202
162
|
if (cask)
|
|
203
163
|
return { kind: 'brew', cask };
|
|
204
164
|
return { kind: 'skip', reason: 'brew-installed but no known cask' };
|
|
205
165
|
}
|
|
206
|
-
// Check for npm global install.
|
|
207
166
|
const npmBinDir = npmPrefix ? path.join(path.resolve(npmPrefix), 'bin') : null;
|
|
208
167
|
const npmManaged = !!(npmBinDir && isPathInside(npmBinDir, binPath));
|
|
209
168
|
if (!npmManaged)
|
|
@@ -223,14 +182,6 @@ async function runCommand(cmd, args, opts = {}) {
|
|
|
223
182
|
let finished = false;
|
|
224
183
|
const child = spawn(cmd, args, {
|
|
225
184
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
226
|
-
// HOMEBREW_NO_AUTO_UPDATE=1 skips brew's implicit `brew update` before
|
|
227
|
-
// each command — we already resolve the latest version via the
|
|
228
|
-
// formulae.brew.sh API, so the refresh is redundant. NOTE: this does NOT
|
|
229
|
-
// prevent brew's vendor-install-ruby step, which contends with concurrent
|
|
230
|
-
// brew processes (Homebrew's launchd autoupdate, a manual brew run, etc.)
|
|
231
|
-
// on the `vendor-install-ruby` lockf and surfaces as "Failed to upgrade
|
|
232
|
-
// Homebrew Portable Ruby". Those transient collisions are handled by
|
|
233
|
-
// `isBrewBusyError` below, which downgrades the failure to a soft skip.
|
|
234
185
|
env: { ...process.env, npm_config_yes: 'true', HOMEBREW_NO_AUTO_UPDATE: '1' },
|
|
235
186
|
});
|
|
236
187
|
const timeoutMs = Math.max(500, opts.timeoutMs ?? AGENT_UPDATE_COMMAND_TIMEOUT_MS);
|
|
@@ -274,9 +225,6 @@ async function getNpmGlobalRoot() {
|
|
|
274
225
|
return result.ok ? result.stdout.trim().split('\n')[0] || null : null;
|
|
275
226
|
}
|
|
276
227
|
async function getLatestPackageVersion(pkg) {
|
|
277
|
-
// `--prefer-online` bypasses the local npm metadata cache so we always see
|
|
278
|
-
// the registry's current `latest` tag. Without it, `npm view` can serve a
|
|
279
|
-
// stale version for several minutes after a release.
|
|
280
228
|
const result = await runCommand('npm', ['view', pkg, 'version', '--json', '--prefer-online'], { timeoutMs: AGENT_UPDATE_TIMEOUTS.npmView });
|
|
281
229
|
if (!result.ok)
|
|
282
230
|
return null;
|
|
@@ -322,28 +270,11 @@ async function updateViaNpm(pkg) {
|
|
|
322
270
|
const result = await runCommand('npm', ['install', '-g', `${pkg}@latest`]);
|
|
323
271
|
return { ok: result.ok, detail: result.ok ? result.stdout.trim() || null : result.error };
|
|
324
272
|
}
|
|
325
|
-
/**
|
|
326
|
-
* Detects the transient brew contention surfaced when another brew process is
|
|
327
|
-
* already running its `vendor-install ruby` step (Homebrew's launchd
|
|
328
|
-
* autoupdate, a manual `brew upgrade`, etc.). We treat these as soft skips
|
|
329
|
-
* rather than failures so the dashboard doesn't shout an error at the user for
|
|
330
|
-
* what is really "try again in a minute".
|
|
331
|
-
*/
|
|
332
273
|
function isBrewBusyError(text) {
|
|
333
274
|
if (!text)
|
|
334
275
|
return false;
|
|
335
276
|
return /vendor-install ruby|already locked|Failed to upgrade Homebrew Portable Ruby|is already running/i.test(text);
|
|
336
277
|
}
|
|
337
|
-
// ---------------------------------------------------------------------------
|
|
338
|
-
// Homebrew helpers
|
|
339
|
-
// ---------------------------------------------------------------------------
|
|
340
|
-
/** Get latest available version for a Homebrew cask.
|
|
341
|
-
*
|
|
342
|
-
* Queries Homebrew's public formulae API rather than `brew info`, because the
|
|
343
|
-
* local cask metadata is only refreshed by `brew update` — without it, a
|
|
344
|
-
* just-published cask version can stay invisible for hours/days. The HTTPS API
|
|
345
|
-
* always returns the current cask manifest. Falls back to local `brew info`
|
|
346
|
-
* if the network call fails. */
|
|
347
278
|
async function getLatestBrewCaskVersion(cask) {
|
|
348
279
|
const apiVersion = await fetchBrewCaskVersionFromApi(cask);
|
|
349
280
|
if (apiVersion)
|
|
@@ -413,7 +344,6 @@ export function startAgentAutoUpdate(opts) {
|
|
|
413
344
|
setUpdateState(id, { status: 'skipped', detail: strategy.reason, checkedAt: Date.now() });
|
|
414
345
|
continue;
|
|
415
346
|
}
|
|
416
|
-
// Use brew version check for brew installs, npm for npm installs.
|
|
417
347
|
const latestVersion = strategy.kind === 'brew'
|
|
418
348
|
? await getLatestBrewCaskVersion(strategy.cask)
|
|
419
349
|
: await getLatestPackageVersion(pkg);
|
|
@@ -456,10 +386,6 @@ export function startAgentAutoUpdate(opts) {
|
|
|
456
386
|
}
|
|
457
387
|
})();
|
|
458
388
|
}
|
|
459
|
-
// ---------------------------------------------------------------------------
|
|
460
|
-
// On-demand version check (called from dashboard)
|
|
461
|
-
// ---------------------------------------------------------------------------
|
|
462
|
-
/** Check latest version for a single agent. Uses brew or npm depending on install method. */
|
|
463
389
|
export async function checkAgentLatestVersion(agent) {
|
|
464
390
|
const id = String(agent.agent || '').trim();
|
|
465
391
|
const pkg = getAgentPackage(id);
|
|
@@ -467,7 +393,6 @@ export async function checkAgentLatestVersion(agent) {
|
|
|
467
393
|
return { ...emptyState(), status: 'skipped', detail: 'unsupported agent', checkedAt: Date.now() };
|
|
468
394
|
const currentVersion = extractAgentSemver(agent.version);
|
|
469
395
|
setUpdateState(id, { currentVersion, status: 'checking' });
|
|
470
|
-
// Detect brew install and use brew version check.
|
|
471
396
|
const binPath = String(agent.path || '').trim();
|
|
472
397
|
const brewCask = binPath && isBrewInstalledBinary(binPath) ? getAgentBrewCask(id) : null;
|
|
473
398
|
const latestVersion = brewCask
|
|
@@ -490,7 +415,6 @@ export async function checkAgentLatestVersion(agent) {
|
|
|
490
415
|
setUpdateState(id, state);
|
|
491
416
|
return state;
|
|
492
417
|
}
|
|
493
|
-
/** Manually trigger an update for a specific agent (auto-detects brew vs npm). */
|
|
494
418
|
export async function manualAgentUpdate(agent, log) {
|
|
495
419
|
const id = String(agent.agent || '').trim();
|
|
496
420
|
const pkg = getAgentPackage(id);
|
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* await-resume.ts — "waiting for background work" session marker.
|
|
3
|
-
*
|
|
4
|
-
* <sessionRoot>/awaiting.json — persisted marker
|
|
5
|
-
*
|
|
6
|
-
* A turn runs as a one-shot `claude -p` process that exits at its `result`
|
|
7
|
-
* event. When the agent launches truly detached work (a daemon outliving the
|
|
8
|
-
* harness, e.g. an install that must survive a restart) and ends the turn
|
|
9
|
-
* intending to report back, the process exits and the session reads as plainly
|
|
10
|
-
* "completed" — there is no live process for the background task to wake. This
|
|
11
|
-
* marker lets the agent declare that intent (via the `await_background` MCP
|
|
12
|
-
* tool, which writes the file directly) so the dashboard can show a distinct
|
|
13
|
-
* "waiting" state instead of "completed".
|
|
14
|
-
*
|
|
15
|
-
* The MCP tool (child process) writes the file; the parent only reads and
|
|
16
|
-
* clears it — the same split used by goal.ts / tools/goal.ts. The marker is
|
|
17
|
-
* cleared the next time the session runs (clearAwaitResume in stream.ts), so a
|
|
18
|
-
* resumed turn naturally drops back out of the "waiting" state.
|
|
19
|
-
*/
|
|
20
1
|
import fs from 'node:fs';
|
|
21
2
|
import path from 'node:path';
|
|
22
3
|
const AWAIT_FILE = 'awaiting.json';
|
|
@@ -45,6 +26,5 @@ export function clearAwaitResume(workdir, agent, sessionId) {
|
|
|
45
26
|
fs.rmSync(sessionAwaitPath(workdir, agent, sessionId), { force: true });
|
|
46
27
|
}
|
|
47
28
|
catch {
|
|
48
|
-
/* best-effort */
|
|
49
29
|
}
|
|
50
30
|
}
|
package/dist/agent/cli/auth.js
CHANGED
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI auth + install runner.
|
|
3
|
-
*
|
|
4
|
-
* Two flows share a single streaming-child-session core:
|
|
5
|
-
*
|
|
6
|
-
* - oauth-web: spawn `loginArgv`, stream stdout/stderr to the UI, poll
|
|
7
|
-
* `statusArgv` in the background, and settle when the CLI reports ready.
|
|
8
|
-
* Used by CLIs that print a device code / one-time-code non-interactively
|
|
9
|
-
* (gh, wrangler, supabase, …). CLIs whose sign-in needs a real TTY use
|
|
10
|
-
* `manualLoginCommands` instead and never reach this runner.
|
|
11
|
-
* - install: spawn the `npm install -g <pkg>` argv, stream output, re-detect
|
|
12
|
-
* once the child exits.
|
|
13
|
-
*
|
|
14
|
-
* Token CLIs (aws, mocli) skip this entirely — credentials come in via a plain
|
|
15
|
-
* POST and are applied synchronously by `applyCliToken`.
|
|
16
|
-
*/
|
|
17
1
|
import { spawn } from 'node:child_process';
|
|
18
2
|
import { randomUUID } from 'node:crypto';
|
|
19
3
|
import { EventEmitter } from 'node:events';
|
|
@@ -24,9 +8,6 @@ const MAX_SESSION_AGE_MS = 15 * 60 * 1000;
|
|
|
24
8
|
const POLL_INTERVAL_MS = 2_000;
|
|
25
9
|
const POLL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
26
10
|
const BACKLOG_LINES = 200;
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
// Session lifecycle
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
11
|
function reapExpiredSessions() {
|
|
31
12
|
const now = Date.now();
|
|
32
13
|
for (const [id, s] of SESSIONS) {
|
|
@@ -49,7 +30,6 @@ function startStreamingSession(opts) {
|
|
|
49
30
|
const { cli, argv, env, shell, pollUntilReady, computeOk } = opts;
|
|
50
31
|
const sessionId = randomUUID();
|
|
51
32
|
const events = new EventEmitter();
|
|
52
|
-
// Unlimited listeners — SSE clients may resubscribe multiple times.
|
|
53
33
|
events.setMaxListeners(0);
|
|
54
34
|
const session = {
|
|
55
35
|
sessionId,
|
|
@@ -95,7 +75,7 @@ function startStreamingSession(opts) {
|
|
|
95
75
|
try {
|
|
96
76
|
finalStatus = await detectCli(cli);
|
|
97
77
|
}
|
|
98
|
-
catch {
|
|
78
|
+
catch { }
|
|
99
79
|
if (finalStatus) {
|
|
100
80
|
events.emit('event', { type: 'status', status: finalStatus });
|
|
101
81
|
}
|
|
@@ -124,15 +104,12 @@ function startStreamingSession(opts) {
|
|
|
124
104
|
void settle(child.exitCode);
|
|
125
105
|
}
|
|
126
106
|
}
|
|
127
|
-
catch {
|
|
107
|
+
catch { }
|
|
128
108
|
}, POLL_INTERVAL_MS);
|
|
129
109
|
}
|
|
130
110
|
child.on('close', (code) => { void settle(code); });
|
|
131
111
|
return { ok: true, sessionId };
|
|
132
112
|
}
|
|
133
|
-
// ---------------------------------------------------------------------------
|
|
134
|
-
// oauth-web login
|
|
135
|
-
// ---------------------------------------------------------------------------
|
|
136
113
|
export async function startCliAuthSession(cliId) {
|
|
137
114
|
const cli = getRecommendedCli(cliId);
|
|
138
115
|
if (!cli)
|
|
@@ -140,8 +117,6 @@ export async function startCliAuthSession(cliId) {
|
|
|
140
117
|
if (cli.auth.type !== 'oauth-web' || !cli.auth.loginArgv) {
|
|
141
118
|
return { ok: false, error: `cli ${cliId} does not support oauth-web sign-in` };
|
|
142
119
|
}
|
|
143
|
-
// CLIs configured for manual login should never spawn here — guard so a
|
|
144
|
-
// misbehaving client can't end-run the documented terminal flow.
|
|
145
120
|
if (cli.auth.manualLoginCommands && cli.auth.manualLoginCommands.length > 0) {
|
|
146
121
|
return { ok: false, error: `cli ${cliId} uses manual sign-in (run the commands in your own terminal)` };
|
|
147
122
|
}
|
|
@@ -155,12 +130,6 @@ export async function startCliAuthSession(cliId) {
|
|
|
155
130
|
}
|
|
156
131
|
const NPM_GLOBAL_INSTALL_RE = /^npm\s+(?:install|i)\s+(?:-g|--global)\s+(\S.*)$/;
|
|
157
132
|
const SHELL_METACHAR_RE = /[|;&`$()<>]/;
|
|
158
|
-
/**
|
|
159
|
-
* Inspect a CLI's install spec for the current platform and return a single
|
|
160
|
-
* argv that's safe to spawn without user-side approvals. Returns null when no
|
|
161
|
-
* such command exists (brew/apt/dnf/winget/scoop/curl-pipe-sh entries are all
|
|
162
|
-
* rejected on purpose — those require sudo or interactive confirmation).
|
|
163
|
-
*/
|
|
164
133
|
export function resolveAutoInstallSpec(cli, platform) {
|
|
165
134
|
const commands = cli.install[platform] || [];
|
|
166
135
|
for (const c of commands) {
|
|
@@ -190,24 +159,14 @@ export async function startCliInstallSession(cliId) {
|
|
|
190
159
|
cli,
|
|
191
160
|
argv: spec.argv,
|
|
192
161
|
env: { ...process.env, NO_COLOR: '1', TERM: 'dumb', CI: '1' },
|
|
193
|
-
// npm on Windows is npm.cmd — needs shell resolution. spawn() args are
|
|
194
|
-
// still passed argv-style; we don't concat into a shell string.
|
|
195
162
|
shell: process.platform === 'win32',
|
|
196
163
|
pollUntilReady: false,
|
|
197
164
|
computeOk: (exit, status) => exit === 0 && (status ? status.state !== 'not_installed' : true),
|
|
198
165
|
});
|
|
199
166
|
}
|
|
200
|
-
// ---------------------------------------------------------------------------
|
|
201
|
-
// Token auth — apply-credentials flow
|
|
202
|
-
// ---------------------------------------------------------------------------
|
|
203
167
|
import fs from 'node:fs';
|
|
204
168
|
import path from 'node:path';
|
|
205
169
|
import os from 'node:os';
|
|
206
|
-
/**
|
|
207
|
-
* Apply a set of credentials for a `token`-auth CLI and verify. Each CLI gets a
|
|
208
|
-
* tailored write path because there's no universal convention — we only do this
|
|
209
|
-
* for CLIs we explicitly support.
|
|
210
|
-
*/
|
|
211
170
|
export async function applyCliToken(cliId, values) {
|
|
212
171
|
const cli = getRecommendedCli(cliId);
|
|
213
172
|
if (!cli)
|
|
@@ -232,8 +191,6 @@ export async function applyCliToken(cliId, values) {
|
|
|
232
191
|
}
|
|
233
192
|
}
|
|
234
193
|
else if (cli.id === 'mocli') {
|
|
235
|
-
// mocli stores its key via `mocli auth init --apik <KEY>` — let the CLI
|
|
236
|
-
// own its storage path so future schema changes upstream don't break us.
|
|
237
194
|
const key = (values.MOWEN_API_KEY || '').trim();
|
|
238
195
|
if (!key)
|
|
239
196
|
return { ok: false, error: 'Mowen API Key is required' };
|
|
@@ -262,16 +219,12 @@ export async function applyCliToken(cliId, values) {
|
|
|
262
219
|
return { ok: false, error: e?.message || 'failed to apply token' };
|
|
263
220
|
}
|
|
264
221
|
}
|
|
265
|
-
/**
|
|
266
|
-
* Merge a `[section]` block into an INI-style file, replacing any existing
|
|
267
|
-
* block with the same name. Idempotent; preserves other sections and comments.
|
|
268
|
-
*/
|
|
269
222
|
function mergeIniSection(filePath, section, block) {
|
|
270
223
|
let current = '';
|
|
271
224
|
try {
|
|
272
225
|
current = fs.readFileSync(filePath, 'utf-8');
|
|
273
226
|
}
|
|
274
|
-
catch {
|
|
227
|
+
catch { }
|
|
275
228
|
const header = `[${section}]`;
|
|
276
229
|
const lines = current.split(/\r?\n/);
|
|
277
230
|
const out = [];
|
|
@@ -287,7 +240,6 @@ function mergeIniSection(filePath, section, block) {
|
|
|
287
240
|
inTarget = false;
|
|
288
241
|
out.push(line);
|
|
289
242
|
}
|
|
290
|
-
// else: drop old line
|
|
291
243
|
}
|
|
292
244
|
else {
|
|
293
245
|
out.push(line);
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI catalog — merges the recommended registry with live detection status.
|
|
3
|
-
*/
|
|
4
1
|
import { getRecommendedClis } from './registry.js';
|
|
5
2
|
import { detectCli, getCachedCliStatus, currentPlatform } from './detector.js';
|
|
6
3
|
import { resolveAutoInstallSpec } from './auth.js';
|
|
@@ -1,18 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI detector — checks whether a CLI binary is on PATH, what version it is,
|
|
3
|
-
* and whether the user is already signed in.
|
|
4
|
-
*
|
|
5
|
-
* Results are cached with a short TTL so catalog rendering stays snappy.
|
|
6
|
-
*/
|
|
7
1
|
import { execFile } from 'node:child_process';
|
|
8
2
|
import path from 'node:path';
|
|
9
3
|
import os from 'node:os';
|
|
10
4
|
import fs from 'node:fs';
|
|
11
5
|
const DETECT_TTL_MS = 30_000;
|
|
12
6
|
const cache = new Map();
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Helpers
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
7
|
function runArgv(argv, timeoutMs) {
|
|
17
8
|
return new Promise((resolve) => {
|
|
18
9
|
const [cmd, ...rest] = argv;
|
|
@@ -48,7 +39,7 @@ function which(binary) {
|
|
|
48
39
|
if (stat.isFile())
|
|
49
40
|
return candidate;
|
|
50
41
|
}
|
|
51
|
-
catch {
|
|
42
|
+
catch { }
|
|
52
43
|
}
|
|
53
44
|
}
|
|
54
45
|
return null;
|
|
@@ -58,7 +49,6 @@ function extractVersion(stdout, stderr) {
|
|
|
58
49
|
if (!text)
|
|
59
50
|
return undefined;
|
|
60
51
|
const firstLine = text.split(/\r?\n/, 1)[0].trim();
|
|
61
|
-
// Common patterns: "gh version 2.56.0 (...)" / "1.2.3" / "aws-cli/2.x ..."
|
|
62
52
|
const m = firstLine.match(/\b(\d+\.\d+(?:\.\d+)?(?:[-.+][0-9A-Za-z.]+)?)\b/);
|
|
63
53
|
return m ? m[1] : firstLine.slice(0, 80);
|
|
64
54
|
}
|
|
@@ -69,9 +59,6 @@ function trimDetail(s) {
|
|
|
69
59
|
const first = text.split(/\r?\n/, 1)[0].trim();
|
|
70
60
|
return first.slice(0, 200);
|
|
71
61
|
}
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
// Public
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
62
|
export function getCachedCliStatus(id) {
|
|
76
63
|
const cached = cache.get(id);
|
|
77
64
|
if (!cached)
|
|
@@ -95,7 +82,6 @@ export async function detectCli(cli) {
|
|
|
95
82
|
cache.set(cli.id, status);
|
|
96
83
|
return status;
|
|
97
84
|
}
|
|
98
|
-
// Installed — read version, best-effort.
|
|
99
85
|
let version;
|
|
100
86
|
if (cli.versionArgv && cli.versionArgv.length) {
|
|
101
87
|
const v = await runArgv(cli.versionArgv, 5_000);
|
|
@@ -109,8 +95,6 @@ export async function detectCli(cli) {
|
|
|
109
95
|
return status;
|
|
110
96
|
}
|
|
111
97
|
const result = await runArgv(cli.auth.statusArgv, 6_000);
|
|
112
|
-
// Some CLIs (gcloud) report success via exit 0 but emit empty output when no
|
|
113
|
-
// account is configured — fall back to a content check when declared.
|
|
114
98
|
const patternOk = !cli.auth.statusReadyPattern
|
|
115
99
|
|| new RegExp(cli.auth.statusReadyPattern).test(result.stdout);
|
|
116
100
|
const state = (result.ok && patternOk) ? 'ready' : 'installed_not_auth';
|
|
@@ -122,7 +106,6 @@ export async function detectCli(cli) {
|
|
|
122
106
|
cache.set(cli.id, status);
|
|
123
107
|
return status;
|
|
124
108
|
}
|
|
125
|
-
/** Best-effort platform key for picking install commands. */
|
|
126
109
|
export function currentPlatform() {
|
|
127
110
|
if (process.platform === 'darwin')
|
|
128
111
|
return 'darwin';
|
|
@@ -130,7 +113,6 @@ export function currentPlatform() {
|
|
|
130
113
|
return 'win';
|
|
131
114
|
return 'linux';
|
|
132
115
|
}
|
|
133
|
-
/** Where AWS credentials go for the `token` auth flow. */
|
|
134
116
|
export function awsCredentialsPath() {
|
|
135
117
|
return path.join(os.homedir(), '.aws', 'credentials');
|
|
136
118
|
}
|
package/dist/agent/cli/index.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Barrel entry for the CLI extension module.
|
|
3
|
-
*/
|
|
4
1
|
export { getRecommendedClis, getRecommendedCli, } from './registry.js';
|
|
5
2
|
export { detectCli, getCachedCliStatus, invalidateCliStatus, currentPlatform, } from './detector.js';
|
|
6
3
|
export { getCliCatalog, refreshCliStatus, } from './catalog.js';
|
|
@@ -1,30 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI tool registry — curated command-line tools agents commonly need.
|
|
3
|
-
*
|
|
4
|
-
* Each entry declares how to install the binary on each OS, how to detect the
|
|
5
|
-
* install / auth state, and (optionally) how to drive the sign-in flow.
|
|
6
|
-
*
|
|
7
|
-
* Two auth types are supported today:
|
|
8
|
-
* - oauth-web: the CLI has a first-party `<cli> auth login --web` flavor that
|
|
9
|
-
* prints a device code and opens the browser. We spawn it, stream output,
|
|
10
|
-
* and poll the status command to know when the user finished in the browser.
|
|
11
|
-
* - token: the user pastes an API key; we set it via the CLI's config command
|
|
12
|
-
* (or via env var for CLIs that only read from env).
|
|
13
|
-
*
|
|
14
|
-
* Keep this list opinionated and small — better to nail the common case than to
|
|
15
|
-
* ship a wall of half-working entries.
|
|
16
|
-
*/
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// Recommended CLI tools — data lives in src/catalog/
|
|
19
|
-
//
|
|
20
|
-
// This module owns the *types* and helper functions. Edit
|
|
21
|
-
// `src/catalog/cli-tools.ts` to add or hide a CLI tool entry.
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
1
|
import { CLI_TOOLS } from '../../catalog/index.js';
|
|
24
2
|
const RECOMMENDED_CLIS = CLI_TOOLS;
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// Public API
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
3
|
export function getRecommendedClis() {
|
|
29
4
|
return RECOMMENDED_CLIS;
|
|
30
5
|
}
|
package/dist/agent/driver.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* agent-driver.ts — Agent driver interface and registry.
|
|
3
|
-
*
|
|
4
|
-
* Each CLI agent (claude, codex, gemini, ...) implements AgentDriver.
|
|
5
|
-
* Register with `registerDriver()`, look up with `getDriver()`.
|
|
6
|
-
*/
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
// Registry
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
1
|
const drivers = new Map();
|
|
11
2
|
export function registerDriver(d) { drivers.set(d.id, d); }
|
|
12
3
|
export function getDriver(id) {
|
|
@@ -29,11 +20,6 @@ export function getDriverCapabilities(id) {
|
|
|
29
20
|
return DEFAULT_CAPABILITIES;
|
|
30
21
|
return { ...DEFAULT_CAPABILITIES, ...d.capabilities };
|
|
31
22
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Provider kinds this driver can route through. Empty array means the driver
|
|
34
|
-
* declared no compatibility (so no Profiles will be listed for it). Callers
|
|
35
|
-
* should treat this as the filter for cross-provider model offerings.
|
|
36
|
-
*/
|
|
37
23
|
export function getAcceptedProviderKinds(id) {
|
|
38
24
|
return drivers.get(id)?.acceptedProviderKinds ?? [];
|
|
39
25
|
}
|