@pingagent/sdk 0.1.11 → 0.1.13
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/bin/pingagent.js +179 -32
- package/dist/chunk-N2GCIMAW.js +3800 -0
- package/dist/index.d.ts +53 -1
- package/dist/index.js +3 -1
- package/dist/web-server.js +324 -45
- package/package.json +2 -2
package/bin/pingagent.js
CHANGED
|
@@ -5,6 +5,8 @@ import * as path from 'node:path';
|
|
|
5
5
|
import * as os from 'node:os';
|
|
6
6
|
import * as readline from 'node:readline';
|
|
7
7
|
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
8
10
|
import {
|
|
9
11
|
PingAgentClient,
|
|
10
12
|
generateIdentity,
|
|
@@ -40,6 +42,8 @@ import {
|
|
|
40
42
|
} from '../dist/index.js';
|
|
41
43
|
import { ERROR_HINTS, SCHEMA_TEXT } from '@pingagent/schemas';
|
|
42
44
|
|
|
45
|
+
const require = createRequire(import.meta.url);
|
|
46
|
+
const THIS_FILE = fileURLToPath(import.meta.url);
|
|
43
47
|
const DEFAULT_SERVER = 'https://pingagent.chat';
|
|
44
48
|
const UPGRADE_URL = 'https://pingagent.chat';
|
|
45
49
|
const DEFAULT_IDENTITY_PATH = path.join(os.homedir(), '.pingagent', 'identity.json');
|
|
@@ -140,29 +144,69 @@ function writeTrustPolicyDoc(identityPath, doc) {
|
|
|
140
144
|
|
|
141
145
|
function findOpenClawInstallScript() {
|
|
142
146
|
const explicit = process.env.PINGAGENT_OPENCLAW_INSTALL_BIN;
|
|
143
|
-
if (explicit) return { cmd: process.execPath, args: [resolvePath(explicit)] };
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
+
if (explicit) return { cmd: process.execPath, args: [resolvePath(explicit)], source: 'env' };
|
|
148
|
+
|
|
149
|
+
const localCandidates = [
|
|
150
|
+
path.resolve(process.cwd(), 'packages', 'openclaw-install', 'install.mjs'),
|
|
151
|
+
path.resolve(path.dirname(THIS_FILE), '..', '..', 'openclaw-install', 'install.mjs'),
|
|
152
|
+
];
|
|
153
|
+
for (const candidate of localCandidates) {
|
|
154
|
+
if (fs.existsSync(candidate)) return { cmd: process.execPath, args: [candidate], source: 'repo' };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const pkgJsonPath = require.resolve('@pingagent/openclaw-install/package.json');
|
|
159
|
+
const installedScript = path.join(path.dirname(pkgJsonPath), 'install.mjs');
|
|
160
|
+
if (fs.existsSync(installedScript)) {
|
|
161
|
+
return { cmd: process.execPath, args: [installedScript], source: 'installed-package' };
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
164
|
+
// fall through to npx fallback
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return { cmd: 'npx', args: ['-y', '@pingagent/openclaw-install'], source: 'npx' };
|
|
147
168
|
}
|
|
148
169
|
|
|
149
170
|
function runOpenClawInstall(args) {
|
|
150
171
|
const resolved = findOpenClawInstallScript();
|
|
151
|
-
if (!resolved) {
|
|
152
|
-
return { ok: false, stdout: '', stderr: 'OpenClaw installer script not found locally. Set PINGAGENT_OPENCLAW_INSTALL_BIN.' };
|
|
153
|
-
}
|
|
154
172
|
const result = spawnSync(resolved.cmd, [...resolved.args, ...args], {
|
|
155
173
|
encoding: 'utf-8',
|
|
156
174
|
env: process.env,
|
|
157
175
|
});
|
|
176
|
+
const errorMessage = result.error
|
|
177
|
+
? (result.error.code === 'ENOENT'
|
|
178
|
+
? `Failed to launch ${resolved.cmd}. Install npx/Node tooling or set PINGAGENT_OPENCLAW_INSTALL_BIN.`
|
|
179
|
+
: String(result.error.message || result.error))
|
|
180
|
+
: '';
|
|
158
181
|
return {
|
|
159
|
-
ok: result.status === 0,
|
|
182
|
+
ok: result.status === 0 && !result.error,
|
|
160
183
|
stdout: String(result.stdout ?? ''),
|
|
161
|
-
stderr: String(result.stderr ?? ''),
|
|
184
|
+
stderr: errorMessage || String(result.stderr ?? ''),
|
|
162
185
|
status: result.status ?? 1,
|
|
186
|
+
source: resolved.source,
|
|
163
187
|
};
|
|
164
188
|
}
|
|
165
189
|
|
|
190
|
+
function getHostPanelSurfaceUrl() {
|
|
191
|
+
const portRaw = String(process.env.PINGAGENT_WEB_PORT || '3846').trim();
|
|
192
|
+
const port = Number.parseInt(portRaw, 10);
|
|
193
|
+
return `http://127.0.0.1:${Number.isFinite(port) ? port : 3846}/host-panel`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function getSurfaceRecommendationLines(primaryCommandPrefix = 'npx @pingagent/sdk', secondaryCommandPrefix = 'pingagent') {
|
|
197
|
+
return [
|
|
198
|
+
'With GUI: Host Panel',
|
|
199
|
+
` Start locally: ${primaryCommandPrefix} web`,
|
|
200
|
+
...(secondaryCommandPrefix && secondaryCommandPrefix !== primaryCommandPrefix ? [` Or via local bin: ${secondaryCommandPrefix} web`] : []),
|
|
201
|
+
` URL when running: ${getHostPanelSurfaceUrl()}`,
|
|
202
|
+
'Headless / low-token: TUI',
|
|
203
|
+
` ${primaryCommandPrefix} host tui`,
|
|
204
|
+
...(secondaryCommandPrefix && secondaryCommandPrefix !== primaryCommandPrefix ? [` ${secondaryCommandPrefix} host tui`] : []),
|
|
205
|
+
` ${primaryCommandPrefix} host tui --once`,
|
|
206
|
+
'MCP: agent/runtime control surface, not the default human operator UI',
|
|
207
|
+
];
|
|
208
|
+
}
|
|
209
|
+
|
|
166
210
|
function clearScreen() {
|
|
167
211
|
process.stdout.write('\x1Bc');
|
|
168
212
|
}
|
|
@@ -194,11 +238,11 @@ function truncateLine(value, max = 100) {
|
|
|
194
238
|
|
|
195
239
|
function formatSessionRow(session, selected) {
|
|
196
240
|
const marker = selected ? '>' : ' ';
|
|
197
|
-
const
|
|
241
|
+
const reconnect = session.binding_alert ? ' !reconnect' : '';
|
|
198
242
|
const trust = session.trust_state || 'unknown';
|
|
199
243
|
const unread = session.unread_count ?? 0;
|
|
200
244
|
const who = truncateLine(session.remote_did || session.conversation_id || 'unknown', 40);
|
|
201
|
-
return `${marker} ${who} [${trust}] unread=${unread}${
|
|
245
|
+
return `${marker} ${who} [${trust}] unread=${unread}${reconnect}`;
|
|
202
246
|
}
|
|
203
247
|
|
|
204
248
|
function formatMessageRow(message) {
|
|
@@ -410,13 +454,13 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
410
454
|
'PingAgent Host TUI',
|
|
411
455
|
`DID: ${hostState.identity.did}`,
|
|
412
456
|
`status=${formatStatusLine(uiState?.statusLevel || 'info', uiState?.statusMessage || '(ready)')}${statusTs}${statusCountdown}`,
|
|
413
|
-
`runtime_mode=${hostState.runtimeMode} receive_mode=${hostState.ingressRuntime?.receive_mode || 'webhook'}
|
|
457
|
+
`runtime_mode=${hostState.runtimeMode} receive_mode=${hostState.ingressRuntime?.receive_mode || 'webhook'} current_openclaw_chat=${hostState.activeChatSession || '(none)'}`,
|
|
414
458
|
`ingress=${ingressLabel}${degraded ? ' action=[f] fix-now' : ''}`,
|
|
415
459
|
uiState?.publicLinkUrl ? `public_link=${uiState.publicLinkUrl}` : null,
|
|
416
460
|
`sessions=${sessions.length} unread_total=${hostState.unreadTotal ?? 0} alert_sessions=${hostState.alertSessions ?? 0} view=${view}`,
|
|
417
461
|
`policy=${hostState.policyPath}`,
|
|
418
|
-
`
|
|
419
|
-
`
|
|
462
|
+
`chat_link_map=${hostState.sessionMapPath}`,
|
|
463
|
+
`chat_link_alerts=${hostState.sessionBindingAlertsPath}`,
|
|
420
464
|
hostState.ingressRuntime?.hooks_last_error ? `hooks_error=${truncateLine(hostState.ingressRuntime.hooks_last_error, 120)}` : null,
|
|
421
465
|
'',
|
|
422
466
|
].filter(Boolean);
|
|
@@ -443,8 +487,8 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
443
487
|
lines.push('- A: apply first open trust recommendation for selected session');
|
|
444
488
|
lines.push('- D: dismiss current open recommendation');
|
|
445
489
|
lines.push('- R: reopen dismissed/superseded recommendation');
|
|
446
|
-
lines.push('- b:
|
|
447
|
-
lines.push('- c:
|
|
490
|
+
lines.push('- b: attach selected session to the current OpenClaw chat');
|
|
491
|
+
lines.push('- c: detach the selected chat link');
|
|
448
492
|
lines.push('- q: quit');
|
|
449
493
|
} else if (view === 'history') {
|
|
450
494
|
lines.push('Conversation History');
|
|
@@ -543,13 +587,13 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
543
587
|
lines.push(`remote=${selected.remote_did || '(unknown)'}`);
|
|
544
588
|
lines.push(`trust=${selected.trust_state} unread=${selected.unread_count}`);
|
|
545
589
|
lines.push(`last_preview=${selected.last_message_preview || '(none)'}`);
|
|
546
|
-
lines.push(`
|
|
547
|
-
lines.push(`
|
|
590
|
+
lines.push(`chat_link=${selected.binding?.session_key || '(none)'}`);
|
|
591
|
+
lines.push(`current_openclaw_chat=${hostState.activeChatSession || '(none)'}`);
|
|
548
592
|
if (selected.binding_alert) {
|
|
549
|
-
lines.push(
|
|
593
|
+
lines.push('needs_reconnect=true');
|
|
550
594
|
lines.push(`warning=${selected.binding_alert.message}`);
|
|
551
595
|
} else {
|
|
552
|
-
lines.push('
|
|
596
|
+
lines.push('needs_reconnect=false');
|
|
553
597
|
}
|
|
554
598
|
if (openRecommendation) {
|
|
555
599
|
lines.push(`trust_action=${getTrustRecommendationActionLabel(openRecommendation)}`);
|
|
@@ -573,8 +617,8 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
573
617
|
'[S] summary',
|
|
574
618
|
'[o] history',
|
|
575
619
|
'[t] tasks',
|
|
576
|
-
'[b]
|
|
577
|
-
'[c]
|
|
620
|
+
'[b] attach-chat',
|
|
621
|
+
'[c] detach-chat',
|
|
578
622
|
].filter(Boolean).join(' ');
|
|
579
623
|
lines.push(`actions=${actionBar}`);
|
|
580
624
|
lines.push('');
|
|
@@ -611,7 +655,7 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
611
655
|
}
|
|
612
656
|
|
|
613
657
|
lines.push('');
|
|
614
|
-
lines.push('Keys: ↑/↓ or j/k select Enter/l open Esc/h back g/G jump r refresh a approve A apply-rec D dismiss-rec R reopen-rec d demo m read p reply o history s search t tasks x cancel-task y dump f fix-hooks b
|
|
658
|
+
lines.push('Keys: ↑/↓ or j/k select Enter/l open Esc/h back g/G jump r refresh a approve A apply-rec D dismiss-rec R reopen-rec d demo m read p reply o history s search t tasks x cancel-task y dump f fix-hooks b attach-chat c detach-chat ? help q quit');
|
|
615
659
|
return lines.join('\n');
|
|
616
660
|
}
|
|
617
661
|
|
|
@@ -1156,18 +1200,18 @@ async function runHostTui(identityPath, opts) {
|
|
|
1156
1200
|
const selected = (latestState.sessions || []).find((session) => session.session_key === uiState.selectedSessionKey);
|
|
1157
1201
|
if (!selected?.conversation_id) return;
|
|
1158
1202
|
const current = latestState.activeChatSession || '(none)';
|
|
1159
|
-
const previous = selected.binding?.session_key || '(
|
|
1160
|
-
const confirmed = await confirmAction(`
|
|
1203
|
+
const previous = selected.binding?.session_key || '(none)';
|
|
1204
|
+
const confirmed = await confirmAction(`Attach chat link for conversation ${selected.conversation_id}\nRemote DID: ${selected.remote_did || '(unknown)'}\nCurrent OpenClaw chat: ${current}\nPrevious chat link: ${previous}\nProceed?`);
|
|
1161
1205
|
if (confirmed) {
|
|
1162
1206
|
if (!latestState.activeChatSession) {
|
|
1163
|
-
setStatus('
|
|
1207
|
+
setStatus('Attach failed: no active OpenClaw chat.', 'err');
|
|
1164
1208
|
latestState = redraw();
|
|
1165
1209
|
return;
|
|
1166
1210
|
}
|
|
1167
1211
|
setSessionBinding(selected.conversation_id, latestState.activeChatSession);
|
|
1168
|
-
setStatus(`
|
|
1212
|
+
setStatus(`Attached chat link ${selected.conversation_id} -> ${latestState.activeChatSession}`, 'ok');
|
|
1169
1213
|
} else {
|
|
1170
|
-
setStatus('
|
|
1214
|
+
setStatus('Attach chat link cancelled.', 'warn');
|
|
1171
1215
|
}
|
|
1172
1216
|
latestState = redraw();
|
|
1173
1217
|
return;
|
|
@@ -1176,7 +1220,7 @@ async function runHostTui(identityPath, opts) {
|
|
|
1176
1220
|
const selected = (latestState.sessions || []).find((session) => session.session_key === uiState.selectedSessionKey);
|
|
1177
1221
|
if (!selected?.conversation_id) return;
|
|
1178
1222
|
removeSessionBinding(selected.conversation_id);
|
|
1179
|
-
setStatus(`
|
|
1223
|
+
setStatus(`Detached chat link for ${selected.conversation_id}`, 'ok');
|
|
1180
1224
|
latestState = redraw();
|
|
1181
1225
|
return;
|
|
1182
1226
|
}
|
|
@@ -3121,13 +3165,116 @@ program
|
|
|
3121
3165
|
else console.log(`Connected. conversation=${convo.data.conversation_id} did=${targetDid}`);
|
|
3122
3166
|
});
|
|
3123
3167
|
|
|
3168
|
+
async function runHostBootstrap(opts) {
|
|
3169
|
+
const validTemplates = new Set(['launchd', 'systemd', 'docker', 'pm2', 'supervisord']);
|
|
3170
|
+
const template = opts.template ? String(opts.template).trim() : '';
|
|
3171
|
+
if (template && !validTemplates.has(template)) {
|
|
3172
|
+
console.error(`Unsupported template: ${template}. Valid: ${Array.from(validTemplates).join(', ')}`);
|
|
3173
|
+
process.exit(1);
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
const steps = [];
|
|
3177
|
+
const runStep = (label, args) => {
|
|
3178
|
+
const result = runOpenClawInstall(args);
|
|
3179
|
+
steps.push({ label, args, result });
|
|
3180
|
+
return { label, args, result };
|
|
3181
|
+
};
|
|
3182
|
+
|
|
3183
|
+
const installStep = runStep('install', []);
|
|
3184
|
+
const hooksStep = installStep.result.ok ? runStep('hooks repair', ['fix-hooks']) : null;
|
|
3185
|
+
const verifyStep = installStep.result.ok && hooksStep?.result.ok
|
|
3186
|
+
? runStep('runtime verify', ['verify-runtime', '--fix-hooks'])
|
|
3187
|
+
: null;
|
|
3188
|
+
|
|
3189
|
+
let runnerStep = null;
|
|
3190
|
+
if (verifyStep?.result.ok && opts.write) {
|
|
3191
|
+
const runnerArgs = ['init-runner', '--ingress', '--panel'];
|
|
3192
|
+
if (template) runnerArgs.push('--template', template);
|
|
3193
|
+
const supportsWrite = !template || template === 'launchd' || template === 'systemd';
|
|
3194
|
+
if (supportsWrite) runnerArgs.push('--write');
|
|
3195
|
+
runnerStep = runStep(supportsWrite ? 'runner setup' : 'runner template', runnerArgs);
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
if (runnerStep?.result.stdout && opts.write) {
|
|
3199
|
+
console.log(runnerStep.result.stdout.trim());
|
|
3200
|
+
if (runnerStep.result.stderr.trim()) console.error(runnerStep.result.stderr.trim());
|
|
3201
|
+
console.log('');
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
const failed = steps.find((step) => !step.result.ok) ?? null;
|
|
3205
|
+
const formatStepStatus = (step, fallback = 'skipped') => (step ? (step.result.ok ? 'ok' : 'failed') : fallback);
|
|
3206
|
+
const runnerStatus = !opts.write
|
|
3207
|
+
? 'not_written'
|
|
3208
|
+
: !runnerStep
|
|
3209
|
+
? 'skipped'
|
|
3210
|
+
: !runnerStep.result.ok
|
|
3211
|
+
? 'failed'
|
|
3212
|
+
: (template && template !== 'launchd' && template !== 'systemd')
|
|
3213
|
+
? 'template_printed_not_started'
|
|
3214
|
+
: 'written_not_started';
|
|
3215
|
+
const installerSource = installStep.result.source || 'unknown';
|
|
3216
|
+
|
|
3217
|
+
console.log('PingAgent Host Bootstrap');
|
|
3218
|
+
console.log('========================');
|
|
3219
|
+
console.log(`install=${formatStepStatus(installStep)}`);
|
|
3220
|
+
console.log(`hooks_repair=${formatStepStatus(hooksStep)}`);
|
|
3221
|
+
console.log(`runtime_verify=${formatStepStatus(verifyStep)}`);
|
|
3222
|
+
console.log(`installer_source=${installerSource}`);
|
|
3223
|
+
console.log(`runner=${runnerStatus}`);
|
|
3224
|
+
console.log(`host_panel_url=${getHostPanelSurfaceUrl()}`);
|
|
3225
|
+
console.log('host_panel_started_by_bootstrap=false');
|
|
3226
|
+
console.log('host_panel_start=npx @pingagent/sdk web');
|
|
3227
|
+
console.log('host_panel_start_local=pingagent web');
|
|
3228
|
+
console.log('tui=npx @pingagent/sdk host tui');
|
|
3229
|
+
console.log('tui_local=pingagent host tui');
|
|
3230
|
+
console.log('');
|
|
3231
|
+
console.log('Control surfaces:');
|
|
3232
|
+
for (const line of getSurfaceRecommendationLines('npx @pingagent/sdk', 'pingagent')) console.log(line);
|
|
3233
|
+
console.log('');
|
|
3234
|
+
if (!opts.write) {
|
|
3235
|
+
console.log('Next steps:');
|
|
3236
|
+
console.log(' Bootstrap validates and repairs config, but it does not start long-lived daemons.');
|
|
3237
|
+
console.log(' Start the Host Panel now with: npx @pingagent/sdk web (or pingagent web)');
|
|
3238
|
+
console.log(' Use the headless surface now with: npx @pingagent/sdk host tui (or pingagent host tui)');
|
|
3239
|
+
console.log(` Re-run with: npx @pingagent/sdk host bootstrap --write${template ? ` --template ${template}` : ''}`);
|
|
3240
|
+
console.log(' Manual path: npx @pingagent/openclaw-install init-runner --ingress --panel');
|
|
3241
|
+
} else if (runnerStep?.result.ok) {
|
|
3242
|
+
console.log('Next steps:');
|
|
3243
|
+
console.log(' Runner files/templates were generated, but bootstrap did not start those services.');
|
|
3244
|
+
if (!template || template === 'launchd' || template === 'systemd') {
|
|
3245
|
+
console.log(' Follow the printed launchctl/systemctl instructions to start them.');
|
|
3246
|
+
} else {
|
|
3247
|
+
console.log(' Start the generated runner with your chosen process manager.');
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
if (failed) {
|
|
3252
|
+
const stdout = failed.result.stdout.trim();
|
|
3253
|
+
const stderr = failed.result.stderr.trim();
|
|
3254
|
+
console.error('');
|
|
3255
|
+
console.error(`Bootstrap failed during ${failed.label}.`);
|
|
3256
|
+
if (stdout) console.error(stdout);
|
|
3257
|
+
if (stderr) console.error(stderr);
|
|
3258
|
+
process.exit(1);
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3124
3262
|
const host = program
|
|
3125
3263
|
.command('host')
|
|
3126
|
-
.description('
|
|
3264
|
+
.description('OpenClaw host activation and operator control surfaces for GUI, headless, and low-token workflows');
|
|
3265
|
+
|
|
3266
|
+
host
|
|
3267
|
+
.command('bootstrap')
|
|
3268
|
+
.description('Run the idempotent OpenClaw activation flow and print the recommended Host Panel / TUI surfaces')
|
|
3269
|
+
.option('--write', 'Write launchd/systemd runner files when supported')
|
|
3270
|
+
.option('--template <name>', 'Runner template: launchd, systemd, docker, pm2, or supervisord')
|
|
3271
|
+
.action(async (opts) => {
|
|
3272
|
+
await runHostBootstrap(opts);
|
|
3273
|
+
});
|
|
3127
3274
|
|
|
3128
3275
|
host
|
|
3129
3276
|
.command('tui')
|
|
3130
|
-
.description('Start
|
|
3277
|
+
.description('Start the headless / low-token terminal UI for runtime, sessions, chat links, and repair actions')
|
|
3131
3278
|
.option('--once', 'Print one snapshot and exit')
|
|
3132
3279
|
.option('--refresh-ms <ms>', 'Refresh interval in interactive mode', '2000')
|
|
3133
3280
|
.option('--profile <name>', 'Use profile from ~/.pingagent/<name>')
|
|
@@ -3142,7 +3289,7 @@ host
|
|
|
3142
3289
|
|
|
3143
3290
|
program
|
|
3144
3291
|
.command('web')
|
|
3145
|
-
.description('Start
|
|
3292
|
+
.description('Start the Host Panel, the primary GUI surface for runtime inspection, trust policy, and repair. Use pingagent host tui for headless or low-token operation.')
|
|
3146
3293
|
.option('--port <port>', 'Port for the web server', '3846')
|
|
3147
3294
|
.action(async (opts) => {
|
|
3148
3295
|
const serverUrl = process.env.PINGAGENT_SERVER_URL || DEFAULT_SERVER;
|