a2acalling 0.6.42 → 0.6.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -5
- package/bin/cli.js +146 -34
- package/package.json +1 -1
- package/src/lib/claude-subagent.js +485 -0
- package/src/lib/conversation-driver.js +109 -28
- package/src/lib/disclosure.js +5 -6
- package/src/lib/runtime-adapter.js +221 -437
- package/src/routes/dashboard.js +5 -5
- package/src/server.js +5 -10
package/README.md
CHANGED
|
@@ -410,11 +410,7 @@ app.listen(3001);
|
|
|
410
410
|
| `A2A_PORT` | Server port (default: 3001) |
|
|
411
411
|
| `A2A_CONFIG_DIR` | Config directory (default: `~/.config/openclaw`) |
|
|
412
412
|
| `A2A_WORKSPACE` | Workspace root for context files like `USER.md` (default: current directory) |
|
|
413
|
-
| `A2A_RUNTIME` | Runtime mode: `auto` (default), `openclaw`, or `
|
|
414
|
-
| `A2A_RUNTIME_FAILOVER` | Fallback to generic runtime if OpenClaw runtime errors (default: `true`) |
|
|
415
|
-
| `A2A_AGENT_COMMAND` | Generic runtime command for inbound turn handling (reads JSON from stdin) |
|
|
416
|
-
| `A2A_SUMMARY_COMMAND` | Generic runtime command for call summaries (reads JSON from stdin) |
|
|
417
|
-
| `A2A_NOTIFY_COMMAND` | Generic runtime command for owner notifications (reads JSON from stdin) |
|
|
413
|
+
| `A2A_RUNTIME` | Runtime mode: `auto` (default), `openclaw`, or `claude` |
|
|
418
414
|
| `A2A_AGENT_NAME` | Override local agent display name |
|
|
419
415
|
| `A2A_OWNER_NAME` | Override owner display name |
|
|
420
416
|
| `A2A_COLLAB_MODE` | Conversation style: `adaptive` (default) or `deep_dive` |
|
package/bin/cli.js
CHANGED
|
@@ -518,8 +518,7 @@ async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
|
|
|
518
518
|
return tierData.topics.map(t => String(t && t.topic || '').trim()).filter(Boolean);
|
|
519
519
|
}
|
|
520
520
|
|
|
521
|
-
|
|
522
|
-
const tiersData = manifest.tiers || manifest.topics || {};
|
|
521
|
+
const tiersData = manifest.tiers || {};
|
|
523
522
|
|
|
524
523
|
try {
|
|
525
524
|
config.setTier('public', {
|
|
@@ -2037,60 +2036,173 @@ a2a add "${inviteUrl}" "${ownerText || 'friend'}" && a2a call "${ownerText || 'f
|
|
|
2037
2036
|
}
|
|
2038
2037
|
}
|
|
2039
2038
|
|
|
2040
|
-
//
|
|
2041
|
-
function
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
const
|
|
2046
|
-
|
|
2047
|
-
|
|
2039
|
+
// Check if a TCP port is currently occupied (async, fast)
|
|
2040
|
+
function isPortOccupied(port) {
|
|
2041
|
+
if (!port) return false;
|
|
2042
|
+
const net = require('net');
|
|
2043
|
+
return new Promise((resolve) => {
|
|
2044
|
+
const socket = net.connect({ host: '127.0.0.1', port });
|
|
2045
|
+
let settled = false;
|
|
2046
|
+
const finish = (result) => {
|
|
2047
|
+
if (settled) return;
|
|
2048
|
+
settled = true;
|
|
2049
|
+
try { socket.destroy(); } catch (e) {}
|
|
2050
|
+
resolve(result);
|
|
2051
|
+
};
|
|
2052
|
+
socket.setTimeout(500, () => finish(false));
|
|
2053
|
+
socket.once('connect', () => finish(true));
|
|
2054
|
+
socket.once('error', () => finish(false));
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2048
2057
|
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2058
|
+
// Find PID listening on a given port by parsing /proc/net/tcp6 (Linux)
|
|
2059
|
+
function findPidOnPort(port) {
|
|
2060
|
+
try {
|
|
2061
|
+
// Try fuser first (most reliable)
|
|
2062
|
+
const fuserResult = spawnSync('fuser', [`${port}/tcp`], {
|
|
2063
|
+
encoding: 'utf8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe']
|
|
2064
|
+
});
|
|
2065
|
+
if (fuserResult.status === 0 && fuserResult.stdout) {
|
|
2066
|
+
const pids = fuserResult.stdout.trim().split(/\s+/).map(Number).filter(p => p > 0);
|
|
2067
|
+
if (pids.length > 0) return pids;
|
|
2055
2068
|
}
|
|
2069
|
+
} catch (e) { /* fuser not available, fall through */ }
|
|
2056
2070
|
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
const
|
|
2062
|
-
|
|
2063
|
-
try {
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2071
|
+
try {
|
|
2072
|
+
// Fallback: parse /proc/net/tcp and /proc/net/tcp6
|
|
2073
|
+
const portHex = port.toString(16).toUpperCase().padStart(4, '0');
|
|
2074
|
+
const pids = [];
|
|
2075
|
+
for (const proto of ['/proc/net/tcp', '/proc/net/tcp6']) {
|
|
2076
|
+
let content;
|
|
2077
|
+
try { content = fs.readFileSync(proto, 'utf8'); } catch (e) { continue; }
|
|
2078
|
+
const lines = content.split('\n');
|
|
2079
|
+
for (const line of lines) {
|
|
2080
|
+
const parts = line.trim().split(/\s+/);
|
|
2081
|
+
if (parts.length < 10) continue;
|
|
2082
|
+
const localAddr = parts[1]; // e.g., 00000000:1F90
|
|
2083
|
+
const localPort = localAddr.split(':')[1];
|
|
2084
|
+
if (localPort && localPort.toUpperCase() === portHex) {
|
|
2085
|
+
const inode = parts[9];
|
|
2086
|
+
// Search /proc/*/fd/* for socket inodes
|
|
2087
|
+
try {
|
|
2088
|
+
const procDirs = fs.readdirSync('/proc').filter(d => /^\d+$/.test(d));
|
|
2089
|
+
for (const pid of procDirs) {
|
|
2090
|
+
try {
|
|
2091
|
+
const fdDir = `/proc/${pid}/fd`;
|
|
2092
|
+
const fds = fs.readdirSync(fdDir);
|
|
2093
|
+
for (const fd of fds) {
|
|
2094
|
+
try {
|
|
2095
|
+
const link = fs.readlinkSync(`${fdDir}/${fd}`);
|
|
2096
|
+
if (link === `socket:[${inode}]`) {
|
|
2097
|
+
pids.push(Number(pid));
|
|
2098
|
+
}
|
|
2099
|
+
} catch (e) {}
|
|
2100
|
+
}
|
|
2101
|
+
} catch (e) {}
|
|
2102
|
+
}
|
|
2103
|
+
} catch (e) {}
|
|
2104
|
+
}
|
|
2069
2105
|
}
|
|
2070
2106
|
}
|
|
2107
|
+
if (pids.length > 0) return [...new Set(pids)];
|
|
2108
|
+
} catch (e) { /* /proc parsing failed */ }
|
|
2071
2109
|
|
|
2072
|
-
|
|
2110
|
+
return [];
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
// Kill a specific PID with SIGTERM, wait, then SIGKILL if needed
|
|
2114
|
+
function killPidSync(pid) {
|
|
2115
|
+
try {
|
|
2116
|
+
process.kill(pid, 0); // existence check
|
|
2117
|
+
} catch (e) {
|
|
2118
|
+
return { ok: true, skipped: true };
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
process.kill(pid, 'SIGTERM');
|
|
2122
|
+
const start = Date.now();
|
|
2123
|
+
while (Date.now() - start < 3000) {
|
|
2073
2124
|
try {
|
|
2074
|
-
process.kill(pid,
|
|
2075
|
-
|
|
2125
|
+
process.kill(pid, 0);
|
|
2126
|
+
spawnSync('sleep', ['0.1'], { timeout: 500 });
|
|
2076
2127
|
} catch (e) {
|
|
2077
2128
|
return { ok: true, pid };
|
|
2078
2129
|
}
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
// Still alive — force kill
|
|
2133
|
+
try {
|
|
2134
|
+
process.kill(pid, 'SIGKILL');
|
|
2135
|
+
// Brief wait for SIGKILL to take effect
|
|
2136
|
+
spawnSync('sleep', ['0.2'], { timeout: 500 });
|
|
2137
|
+
try {
|
|
2138
|
+
process.kill(pid, 0);
|
|
2139
|
+
return { ok: false, pid, error: `PID ${pid} survived SIGKILL` };
|
|
2140
|
+
} catch (e) {
|
|
2141
|
+
return { ok: true, pid, forced: true };
|
|
2142
|
+
}
|
|
2143
|
+
} catch (e) {
|
|
2144
|
+
return { ok: true, pid };
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
// Kill server by PID from config (detached process started by quickstart)
|
|
2149
|
+
// Then verify the port is actually freed; if not, find and kill whatever holds it.
|
|
2150
|
+
async function killServerPid() {
|
|
2151
|
+
let pid, serverPort;
|
|
2152
|
+
try {
|
|
2153
|
+
const { A2AConfig } = require('../src/lib/config');
|
|
2154
|
+
const cfg = new A2AConfig();
|
|
2155
|
+
const onboarding = cfg.getOnboarding();
|
|
2156
|
+
pid = onboarding.server_pid;
|
|
2157
|
+
serverPort = onboarding.server_port;
|
|
2079
2158
|
} catch (err) {
|
|
2080
2159
|
// Config read failed — not fatal, continue with pm2 path
|
|
2081
2160
|
return { ok: true, skipped: true };
|
|
2082
2161
|
}
|
|
2162
|
+
|
|
2163
|
+
// Step 1: Try to kill the PID from config
|
|
2164
|
+
if (pid) {
|
|
2165
|
+
killPidSync(pid);
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
// Step 2: Verify the port is freed
|
|
2169
|
+
if (serverPort) {
|
|
2170
|
+
const stillOccupied = await isPortOccupied(serverPort);
|
|
2171
|
+
if (stillOccupied) {
|
|
2172
|
+
// Port is still held — find and kill whatever is on it
|
|
2173
|
+
const pids = findPidOnPort(serverPort);
|
|
2174
|
+
let killedAny = false;
|
|
2175
|
+
for (const p of pids) {
|
|
2176
|
+
const result = killPidSync(p);
|
|
2177
|
+
if (result.ok && !result.skipped) killedAny = true;
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
// Final check
|
|
2181
|
+
const stillUp = await isPortOccupied(serverPort);
|
|
2182
|
+
if (stillUp) {
|
|
2183
|
+
return { ok: false, pid, port: serverPort, error: `Port ${serverPort} is still occupied after kill attempts` };
|
|
2184
|
+
}
|
|
2185
|
+
return { ok: true, pid, port: serverPort, portKill: killedAny };
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
return { ok: true, pid, port: serverPort, skipped: !pid };
|
|
2083
2190
|
}
|
|
2084
2191
|
|
|
2085
2192
|
process.stdout.write('Stopping server... ');
|
|
2086
|
-
const pidResult = killServerPid();
|
|
2193
|
+
const pidResult = await killServerPid();
|
|
2087
2194
|
const stopped = pm2StopAndDelete('a2a');
|
|
2088
2195
|
if (!pidResult.ok && !stopped.ok) {
|
|
2089
2196
|
console.log('❌');
|
|
2090
|
-
console.error(` ${stopped.error}`);
|
|
2197
|
+
console.error(` ${pidResult.error || stopped.error}`);
|
|
2091
2198
|
process.exit(1);
|
|
2092
2199
|
}
|
|
2093
|
-
|
|
2200
|
+
if (!pidResult.ok) {
|
|
2201
|
+
console.log('⚠️');
|
|
2202
|
+
console.error(` Warning: ${pidResult.error}`);
|
|
2203
|
+
} else {
|
|
2204
|
+
console.log('✅');
|
|
2205
|
+
}
|
|
2094
2206
|
|
|
2095
2207
|
let configOk = true;
|
|
2096
2208
|
let dbOk = true;
|