a2acalling 0.6.49 → 0.6.50
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/cli.js +56 -2
- package/docs/protocol.md +42 -0
- package/package.json +1 -1
- package/scripts/install-skills.js +80 -0
- package/scripts/postinstall.js +12 -0
- package/src/lib/claude-subagent.js +6 -5
- package/src/lib/config.js +1 -0
- package/src/lib/conversation-driver.js +12 -3
- package/src/lib/runtime-adapter.js +8 -3
- package/src/lib/tokens.js +13 -1
- package/src/lib/turn-timeout.js +52 -0
- package/src/routes/a2a.js +1 -0
- package/src/server.js +16 -1
package/bin/cli.js
CHANGED
|
@@ -37,7 +37,8 @@ const ONBOARDING_EXEMPT = new Set([
|
|
|
37
37
|
'dashboard',
|
|
38
38
|
'server',
|
|
39
39
|
'setup',
|
|
40
|
-
'install'
|
|
40
|
+
'install',
|
|
41
|
+
'skills'
|
|
41
42
|
]);
|
|
42
43
|
|
|
43
44
|
function isOnboarded() {
|
|
@@ -710,6 +711,8 @@ const commands = {
|
|
|
710
711
|
|
|
711
712
|
// Get objectives from disclosure
|
|
712
713
|
const objectives = tierTopics.objectives || [];
|
|
714
|
+
const timeoutMsRaw = args.flags['timeout-ms'] || args.flags.timeout_ms;
|
|
715
|
+
const timeoutMs = timeoutMsRaw ? Number.parseInt(String(timeoutMsRaw), 10) : null;
|
|
713
716
|
|
|
714
717
|
const { token, record } = store.create({
|
|
715
718
|
name: args.flags.name || args.flags.n || 'unnamed',
|
|
@@ -720,7 +723,8 @@ const commands = {
|
|
|
720
723
|
notify: args.flags.notify || 'all',
|
|
721
724
|
maxCalls,
|
|
722
725
|
allowedTopics,
|
|
723
|
-
allowedGoals: objectives.map(o => o.objective || o)
|
|
726
|
+
allowedGoals: objectives.map(o => o.objective || o),
|
|
727
|
+
timeoutMs
|
|
724
728
|
});
|
|
725
729
|
|
|
726
730
|
const resolvedHost = await resolveInviteHostname();
|
|
@@ -759,6 +763,7 @@ const commands = {
|
|
|
759
763
|
console.log(`Disclosure: ${record.disclosure}`);
|
|
760
764
|
console.log(`Notify: ${record.notify}`);
|
|
761
765
|
console.log(`Max calls: ${record.max_calls || 'unlimited'}`);
|
|
766
|
+
if (record.timeout_ms) console.log(`Turn timeout: ${record.timeout_ms}ms`);
|
|
762
767
|
if (linkContact) console.log(`Linked to: ${linkContact}`);
|
|
763
768
|
console.log(`\nTo revoke: a2a revoke ${record.id}`);
|
|
764
769
|
console.log(`\n${'─'.repeat(50)}`);
|
|
@@ -1355,11 +1360,15 @@ a2a add "${inviteUrl}" "${ownerText || 'friend'}" && a2a call "${ownerText || 'f
|
|
|
1355
1360
|
|
|
1356
1361
|
// Build owner context from config for summarizer
|
|
1357
1362
|
let ownerContext = {};
|
|
1363
|
+
let configTurnTimeoutMs = null;
|
|
1358
1364
|
try {
|
|
1359
1365
|
const { A2AConfig } = require('../src/lib/config');
|
|
1360
1366
|
const config = new A2AConfig();
|
|
1361
1367
|
const configAll = config.getAll();
|
|
1362
1368
|
const tierGoals = configAll.tiers?.public?.goals || [];
|
|
1369
|
+
configTurnTimeoutMs = configAll.defaults?.turnTimeoutMs
|
|
1370
|
+
|| configAll.defaults?.turn_timeout_ms
|
|
1371
|
+
|| null;
|
|
1363
1372
|
ownerContext = {
|
|
1364
1373
|
goals: tierGoals,
|
|
1365
1374
|
agentName: agentContext.name,
|
|
@@ -1378,6 +1387,7 @@ a2a add "${inviteUrl}" "${ownerText || 'friend'}" && a2a call "${ownerText || 'f
|
|
|
1378
1387
|
disclosure,
|
|
1379
1388
|
minTurns,
|
|
1380
1389
|
maxTurns,
|
|
1390
|
+
configTurnTimeoutMs,
|
|
1381
1391
|
ownerContext,
|
|
1382
1392
|
onTurn: (info) => {
|
|
1383
1393
|
const preview = info.messagePreview.length >= 80
|
|
@@ -2625,6 +2635,46 @@ a2a add "${inviteUrl}" "${ownerText || 'friend'}" && a2a call "${ownerText || 'f
|
|
|
2625
2635
|
return commands.quickstart(args);
|
|
2626
2636
|
},
|
|
2627
2637
|
|
|
2638
|
+
skills: (args) => {
|
|
2639
|
+
const { installSkills, SKILL_FILES } = require('../scripts/install-skills');
|
|
2640
|
+
const check = args.flags.check || args.flags.c;
|
|
2641
|
+
const force = args.flags.force;
|
|
2642
|
+
const targetDir = process.cwd();
|
|
2643
|
+
|
|
2644
|
+
if (check) {
|
|
2645
|
+
console.log('A2A skills for this project:\n');
|
|
2646
|
+
for (const file of SKILL_FILES) {
|
|
2647
|
+
const destPath = path.join(targetDir, file.dest);
|
|
2648
|
+
const exists = fs.existsSync(destPath);
|
|
2649
|
+
const icon = exists ? ' \u2713' : ' \u2717';
|
|
2650
|
+
console.log(`${icon} ${file.dest}${exists ? ' (installed)' : ' (not installed)'}`);
|
|
2651
|
+
}
|
|
2652
|
+
console.log(`\nRun "a2a skills" to install missing files.`);
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
const result = installSkills(targetDir, { force });
|
|
2657
|
+
|
|
2658
|
+
if (result.installed.length) {
|
|
2659
|
+
console.log(`\n Installed ${result.installed.length} A2A skill file(s):\n`);
|
|
2660
|
+
result.installed.forEach(f => console.log(` + ${f}`));
|
|
2661
|
+
}
|
|
2662
|
+
if (result.skipped.length) {
|
|
2663
|
+
console.log(`\n Skipped ${result.skipped.length} unchanged file(s)`);
|
|
2664
|
+
}
|
|
2665
|
+
if (result.errors.length) {
|
|
2666
|
+
console.error(`\n Errors:`);
|
|
2667
|
+
result.errors.forEach(e => console.error(` ! ${e.file}: ${e.error}`));
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
if (result.installed.length === 0 && result.skipped.length > 0) {
|
|
2671
|
+
console.log('\n All skills already installed. Use --force to overwrite.\n');
|
|
2672
|
+
} else if (result.installed.length > 0) {
|
|
2673
|
+
console.log('\n Skills ready. In Claude Code, type /a2a- to see available commands.');
|
|
2674
|
+
console.log(' In Codex CLI, A2A instructions are in .codex/AGENTS.md\n');
|
|
2675
|
+
}
|
|
2676
|
+
},
|
|
2677
|
+
|
|
2628
2678
|
version: () => {
|
|
2629
2679
|
const pkg = require('../package.json');
|
|
2630
2680
|
console.log(pkg.version);
|
|
@@ -2645,6 +2695,7 @@ Commands:
|
|
|
2645
2695
|
--disclosure, -d Disclosure level (public, minimal, none)
|
|
2646
2696
|
--notify Owner notification (all, summary, none)
|
|
2647
2697
|
--max-calls Maximum invocations (default: 100)
|
|
2698
|
+
--timeout-ms Per-token Claude turn timeout in milliseconds
|
|
2648
2699
|
--link, -l Auto-link to contact name
|
|
2649
2700
|
|
|
2650
2701
|
list List active tokens
|
|
@@ -2710,6 +2761,9 @@ Server:
|
|
|
2710
2761
|
uninstall Stop server and remove local config/DB
|
|
2711
2762
|
--keep-config Preserve config/DB (for reinstall)
|
|
2712
2763
|
--force Skip confirmation prompt
|
|
2764
|
+
skills Install Claude Code + Codex CLI skills
|
|
2765
|
+
--check, -c Show what would be installed
|
|
2766
|
+
--force Overwrite existing files
|
|
2713
2767
|
version Show installed package version
|
|
2714
2768
|
|
|
2715
2769
|
Examples:
|
package/docs/protocol.md
CHANGED
|
@@ -388,6 +388,48 @@ module.exports = function (test, assert, helpers) {
|
|
|
388
388
|
};
|
|
389
389
|
```
|
|
390
390
|
|
|
391
|
+
## CLI Skills (Claude Code & Codex)
|
|
392
|
+
|
|
393
|
+
A2A ships with slash commands for Claude Code and agent instructions for Codex CLI.
|
|
394
|
+
|
|
395
|
+
### Installation
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
a2a skills # Install into current project
|
|
399
|
+
a2a skills --check # See what would be installed
|
|
400
|
+
a2a skills --force # Overwrite existing files
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Skills are also installed automatically on `npm install -g a2acalling`.
|
|
404
|
+
|
|
405
|
+
### Claude Code Commands
|
|
406
|
+
|
|
407
|
+
| Command | Description |
|
|
408
|
+
|---------|-------------|
|
|
409
|
+
| `/a2a-call <contact> <msg>` | Call another agent (multi-turn) |
|
|
410
|
+
| `/a2a-invite [name] [--tier]` | Create invite token |
|
|
411
|
+
| `/a2a-contacts [add\|show\|ping\|rm]` | Manage contacts |
|
|
412
|
+
| `/a2a-status` | Server and agent health dashboard |
|
|
413
|
+
| `/a2a-setup` | First-time setup and onboarding |
|
|
414
|
+
|
|
415
|
+
Files installed to: `.claude/commands/a2a-*.md`
|
|
416
|
+
|
|
417
|
+
### Codex CLI
|
|
418
|
+
|
|
419
|
+
A2A agent instructions are installed to `.codex/AGENTS.md`. Codex reads this file automatically to understand available A2A commands, permission tiers, and workflows.
|
|
420
|
+
|
|
421
|
+
### Manual Installation
|
|
422
|
+
|
|
423
|
+
If the automatic install didn't work, copy the files manually:
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
# Claude Code commands
|
|
427
|
+
cp node_modules/a2acalling/.claude/commands/a2a-*.md .claude/commands/
|
|
428
|
+
|
|
429
|
+
# Codex instructions
|
|
430
|
+
cp node_modules/a2acalling/.codex/AGENTS.md .codex/AGENTS.md
|
|
431
|
+
```
|
|
432
|
+
|
|
391
433
|
## Future Protocol Extensions (v1+)
|
|
392
434
|
|
|
393
435
|
- **Capability advertisement**: Agents declare what they can help with
|
package/package.json
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A Skill Installer
|
|
3
|
+
*
|
|
4
|
+
* Copies Claude Code commands and Codex AGENTS.md into a target project directory.
|
|
5
|
+
* Idempotent: skips files that already exist with identical content.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const PACKAGE_ROOT = path.join(__dirname, '..');
|
|
12
|
+
|
|
13
|
+
const SKILL_FILES = [
|
|
14
|
+
{ src: '.claude/commands/a2a-call.md', dest: '.claude/commands/a2a-call.md' },
|
|
15
|
+
{ src: '.claude/commands/a2a-invite.md', dest: '.claude/commands/a2a-invite.md' },
|
|
16
|
+
{ src: '.claude/commands/a2a-contacts.md', dest: '.claude/commands/a2a-contacts.md' },
|
|
17
|
+
{ src: '.claude/commands/a2a-status.md', dest: '.claude/commands/a2a-status.md' },
|
|
18
|
+
{ src: '.claude/commands/a2a-setup.md', dest: '.claude/commands/a2a-setup.md' },
|
|
19
|
+
{ src: '.codex/AGENTS.md', dest: '.codex/AGENTS.md' }
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
function installSkills(targetDir, options = {}) {
|
|
23
|
+
const result = { installed: [], skipped: [], errors: [] };
|
|
24
|
+
|
|
25
|
+
for (const file of SKILL_FILES) {
|
|
26
|
+
const srcPath = path.join(PACKAGE_ROOT, file.src);
|
|
27
|
+
const destPath = path.join(targetDir, file.dest);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
if (!fs.existsSync(srcPath)) {
|
|
31
|
+
result.errors.push({ file: file.src, error: 'Source file not found' });
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const srcContent = fs.readFileSync(srcPath, 'utf8');
|
|
36
|
+
|
|
37
|
+
// Check if identical file already exists
|
|
38
|
+
if (!options.force && fs.existsSync(destPath)) {
|
|
39
|
+
const existing = fs.readFileSync(destPath, 'utf8');
|
|
40
|
+
if (existing === srcContent) {
|
|
41
|
+
result.skipped.push(file.dest);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Create directory and write file
|
|
47
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
48
|
+
fs.writeFileSync(destPath, srcContent);
|
|
49
|
+
result.installed.push(file.dest);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
result.errors.push({ file: file.dest, error: err.message });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// CLI mode: node scripts/install-skills.js [targetDir] [--force]
|
|
59
|
+
if (require.main === module) {
|
|
60
|
+
const args = process.argv.slice(2);
|
|
61
|
+
const force = args.includes('--force');
|
|
62
|
+
const targetDir = args.find(a => !a.startsWith('-')) || process.cwd();
|
|
63
|
+
|
|
64
|
+
const result = installSkills(targetDir, { force });
|
|
65
|
+
|
|
66
|
+
if (result.installed.length) {
|
|
67
|
+
console.log(`Installed ${result.installed.length} A2A skill file(s):`);
|
|
68
|
+
result.installed.forEach(f => console.log(` + ${f}`));
|
|
69
|
+
}
|
|
70
|
+
if (result.skipped.length) {
|
|
71
|
+
console.log(`Skipped ${result.skipped.length} unchanged file(s)`);
|
|
72
|
+
}
|
|
73
|
+
if (result.errors.length) {
|
|
74
|
+
console.error(`Errors: ${result.errors.length}`);
|
|
75
|
+
result.errors.forEach(e => console.error(` ! ${e.file}: ${e.error}`));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { installSkills, SKILL_FILES };
|
package/scripts/postinstall.js
CHANGED
|
@@ -43,13 +43,25 @@ const result = spawnSync(process.execPath, [cliPath, 'quickstart'], {
|
|
|
43
43
|
|
|
44
44
|
if (result.error) {
|
|
45
45
|
// Don't fail the install — the agent will get onboarding when it runs `a2a`.
|
|
46
|
+
installSkillFiles();
|
|
46
47
|
installMacOSApp();
|
|
47
48
|
process.exit(0);
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
installSkillFiles();
|
|
50
52
|
installMacOSApp();
|
|
51
53
|
process.exit(result.status || 0);
|
|
52
54
|
|
|
55
|
+
// Best-effort: install Claude Code + Codex skills into the workspace
|
|
56
|
+
function installSkillFiles() {
|
|
57
|
+
try {
|
|
58
|
+
const { installSkills } = require('./install-skills');
|
|
59
|
+
installSkills(initCwd);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
// Silent — skills can be installed later with `a2a skills`
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
53
65
|
// Download and install the native macOS app from GitHub Releases
|
|
54
66
|
function installMacOSApp() {
|
|
55
67
|
const os = require('os');
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const { execSync, spawn } = require('child_process');
|
|
11
11
|
const { createLogger } = require('./logger');
|
|
12
|
+
const { HARD_FALLBACK_TURN_TIMEOUT_MS } = require('./turn-timeout');
|
|
12
13
|
|
|
13
14
|
const logger = createLogger({ component: 'a2a.claude-subagent' });
|
|
14
15
|
|
|
@@ -216,7 +217,7 @@ function parseSubagentResponse(resultText) {
|
|
|
216
217
|
* @param {number} timeoutMs - Timeout in milliseconds
|
|
217
218
|
* @returns {Promise<{ stdout: string, stderr: string }>}
|
|
218
219
|
*/
|
|
219
|
-
function spawnClaude(args, timeoutMs =
|
|
220
|
+
function spawnClaude(args, timeoutMs = HARD_FALLBACK_TURN_TIMEOUT_MS) {
|
|
220
221
|
return new Promise((resolve, reject) => {
|
|
221
222
|
const proc = spawn('claude', args, {
|
|
222
223
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -302,7 +303,7 @@ function extractResultFromJson(stdout) {
|
|
|
302
303
|
* @param {Array} options.activeThreads - Active conversation threads
|
|
303
304
|
* @param {Array} options.candidateCollaborations - Candidate collaboration ideas
|
|
304
305
|
* @param {boolean} options.closeSignal - Whether close has been signaled
|
|
305
|
-
* @param {number} [options.timeoutMs=
|
|
306
|
+
* @param {number} [options.timeoutMs=300000] - Timeout in milliseconds
|
|
306
307
|
* @returns {Promise<{ message: string, statePatch: object|null, flags: array, sessionId: string }>}
|
|
307
308
|
*/
|
|
308
309
|
async function runClaudeTurn(options) {
|
|
@@ -317,7 +318,7 @@ async function runClaudeTurn(options) {
|
|
|
317
318
|
activeThreads = [],
|
|
318
319
|
candidateCollaborations = [],
|
|
319
320
|
closeSignal = false,
|
|
320
|
-
timeoutMs =
|
|
321
|
+
timeoutMs = HARD_FALLBACK_TURN_TIMEOUT_MS
|
|
321
322
|
} = options;
|
|
322
323
|
|
|
323
324
|
const turnPrompt = buildTurnPrompt({
|
|
@@ -396,10 +397,10 @@ async function runClaudeTurn(options) {
|
|
|
396
397
|
*
|
|
397
398
|
* @param {string} sessionId - Session ID to resume
|
|
398
399
|
* @param {string} reason - Why the conversation is ending
|
|
399
|
-
* @param {number} [timeoutMs=
|
|
400
|
+
* @param {number} [timeoutMs=300000] - Timeout in milliseconds
|
|
400
401
|
* @returns {Promise<{ summary: string, ownerSummary: string, actionItems: array, flags: array }>}
|
|
401
402
|
*/
|
|
402
|
-
async function runClaudeSummary(sessionId, reason, timeoutMs =
|
|
403
|
+
async function runClaudeSummary(sessionId, reason, timeoutMs = HARD_FALLBACK_TURN_TIMEOUT_MS) {
|
|
403
404
|
if (!sessionId) {
|
|
404
405
|
throw new Error('Cannot summarize without a session ID');
|
|
405
406
|
}
|
package/src/lib/config.js
CHANGED
|
@@ -22,6 +22,7 @@ const {
|
|
|
22
22
|
const { getTopicsForTier, formatTopicsForPrompt, loadManifest } = require('./disclosure');
|
|
23
23
|
const { createLogger } = require('./logger');
|
|
24
24
|
const { buildUnifiedSummaryPrompt } = require('./summary-prompt');
|
|
25
|
+
const { resolveTokenTimeoutMs, resolveTurnTimeoutMs } = require('./turn-timeout');
|
|
25
26
|
|
|
26
27
|
const logger = createLogger({ component: 'a2a.conversation-driver' });
|
|
27
28
|
|
|
@@ -130,9 +131,16 @@ class ConversationDriver {
|
|
|
130
131
|
this.summarizer = options.summarizer || null;
|
|
131
132
|
this.ownerContext = options.ownerContext || {};
|
|
132
133
|
this.claudeMode = options.runtime?.mode === 'claude';
|
|
133
|
-
this.claudeTimeoutMs = options.claudeTimeoutMs || 180000;
|
|
134
134
|
|
|
135
|
-
const
|
|
135
|
+
const tokenTimeoutMs = options.tokenTimeoutMs
|
|
136
|
+
|| options.claudeTimeoutMs
|
|
137
|
+
|| resolveTokenTimeoutMs(options.token);
|
|
138
|
+
const configTimeoutMs = options.configTurnTimeoutMs;
|
|
139
|
+
this.claudeTimeoutMs = resolveTurnTimeoutMs({ tokenTimeoutMs, configTimeoutMs });
|
|
140
|
+
|
|
141
|
+
const clientTimeout = this.claudeMode
|
|
142
|
+
? Math.max(this.claudeTimeoutMs + 20000, 200000)
|
|
143
|
+
: 65000;
|
|
136
144
|
this.client = new A2AClient({ caller: this.caller, timeout: clientTimeout });
|
|
137
145
|
}
|
|
138
146
|
|
|
@@ -221,7 +229,8 @@ class ConversationDriver {
|
|
|
221
229
|
sessionId: `summary-${Date.now()}`,
|
|
222
230
|
prompt,
|
|
223
231
|
messages,
|
|
224
|
-
callerInfo: { name: agentContext.name, owner: agentContext.owner }
|
|
232
|
+
callerInfo: { name: agentContext.name, owner: agentContext.owner },
|
|
233
|
+
timeoutMs: this.claudeMode ? this.claudeTimeoutMs : 35000
|
|
225
234
|
});
|
|
226
235
|
} catch (err) {
|
|
227
236
|
logger.warn('Runtime summarizer failed, using default', {
|
|
@@ -14,6 +14,7 @@ const { execSync, spawnSync } = require('child_process');
|
|
|
14
14
|
const { createLogger } = require('./logger');
|
|
15
15
|
const { runClaudeTurn: invokeClaudeTurn, buildSubagentSystemPrompt, runClaudeSummary } = require('./claude-subagent');
|
|
16
16
|
const { getTopicsForTier, formatTopicsForPrompt, loadManifest } = require('./disclosure');
|
|
17
|
+
const { HARD_FALLBACK_TURN_TIMEOUT_MS } = require('./turn-timeout');
|
|
17
18
|
|
|
18
19
|
function commandExists(command) {
|
|
19
20
|
try {
|
|
@@ -208,7 +209,7 @@ function createRuntimeAdapter(options = {}) {
|
|
|
208
209
|
activeThreads: context?.activeThreads || [],
|
|
209
210
|
candidateCollaborations: context?.candidateCollaborations || [],
|
|
210
211
|
closeSignal: context?.closeSignal || false,
|
|
211
|
-
timeoutMs: timeoutMs ||
|
|
212
|
+
timeoutMs: timeoutMs || HARD_FALLBACK_TURN_TIMEOUT_MS
|
|
212
213
|
});
|
|
213
214
|
|
|
214
215
|
// Store session ID from first turn for subsequent --resume
|
|
@@ -379,7 +380,7 @@ function createRuntimeAdapter(options = {}) {
|
|
|
379
380
|
}
|
|
380
381
|
}
|
|
381
382
|
|
|
382
|
-
async function summarize({ sessionId, prompt, messages, callerInfo, traceId, conversationId }) {
|
|
383
|
+
async function summarize({ sessionId, prompt, messages, callerInfo, traceId, conversationId, timeoutMs }) {
|
|
383
384
|
const effectiveTraceId = traceId || callerInfo?.trace_id || callerInfo?.traceId;
|
|
384
385
|
const requestId = callerInfo?.request_id || callerInfo?.requestId;
|
|
385
386
|
const effectiveConversationId = conversationId || callerInfo?.conversation_id || callerInfo?.conversationId;
|
|
@@ -388,7 +389,11 @@ function createRuntimeAdapter(options = {}) {
|
|
|
388
389
|
if (modeInfo.mode === 'claude') {
|
|
389
390
|
const session = claudeSessions.get(sessionId);
|
|
390
391
|
if (session?.claudeSessionId) {
|
|
391
|
-
const result = await runClaudeSummary(
|
|
392
|
+
const result = await runClaudeSummary(
|
|
393
|
+
session.claudeSessionId,
|
|
394
|
+
'conversation ended',
|
|
395
|
+
timeoutMs || HARD_FALLBACK_TURN_TIMEOUT_MS
|
|
396
|
+
);
|
|
392
397
|
if (result && result.summary) {
|
|
393
398
|
return result;
|
|
394
399
|
}
|
package/src/lib/tokens.js
CHANGED
|
@@ -56,6 +56,11 @@ function sanitizeCustomFields(fields, options = {}) {
|
|
|
56
56
|
return cleaned;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
function parsePositiveTimeoutMs(value) {
|
|
60
|
+
const parsed = Number.parseInt(String(value ?? ''), 10);
|
|
61
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
59
64
|
class TokenStore {
|
|
60
65
|
constructor(configDir = DEFAULT_CONFIG_DIR) {
|
|
61
66
|
this.configDir = configDir;
|
|
@@ -196,7 +201,8 @@ class TokenStore {
|
|
|
196
201
|
// Snapshot of actual capabilities at creation time
|
|
197
202
|
allowedTopics = null, // Array of topic strings, e.g. ['chat', 'calendar.read']
|
|
198
203
|
allowedGoals = null, // Array of goal strings, e.g. ['grow-network', 'find-collaborators']
|
|
199
|
-
tierSettings = null
|
|
204
|
+
tierSettings = null, // Object with tier-specific settings
|
|
205
|
+
timeoutMs = null
|
|
200
206
|
} = options;
|
|
201
207
|
|
|
202
208
|
const tier = String(permissions || 'public').trim() || 'public';
|
|
@@ -255,6 +261,7 @@ class TokenStore {
|
|
|
255
261
|
capabilities: capabilities || defaultCapabilities,
|
|
256
262
|
allowed_topics: allowedTopics || defaultTopics[tier] || ['chat'],
|
|
257
263
|
allowed_goals: allowedGoals || defaultGoals[tier] || [],
|
|
264
|
+
timeout_ms: parsePositiveTimeoutMs(timeoutMs),
|
|
258
265
|
tier_settings: tierSettings || {}, // Snapshot of settings at creation
|
|
259
266
|
disclosure,
|
|
260
267
|
notify,
|
|
@@ -327,6 +334,10 @@ class TokenStore {
|
|
|
327
334
|
|| TokenStore.DEFAULT_CAPABILITIES[tier]
|
|
328
335
|
|| ['context-read'];
|
|
329
336
|
|
|
337
|
+
const timeoutMs = parsePositiveTimeoutMs(record.timeout_ms)
|
|
338
|
+
|| parsePositiveTimeoutMs(record.tier_settings?.timeout_ms)
|
|
339
|
+
|| parsePositiveTimeoutMs(record.tier_settings?.timeoutMs);
|
|
340
|
+
|
|
330
341
|
return {
|
|
331
342
|
valid: true,
|
|
332
343
|
id: record.id,
|
|
@@ -335,6 +346,7 @@ class TokenStore {
|
|
|
335
346
|
capabilities,
|
|
336
347
|
allowed_topics: record.allowed_topics || ['chat'],
|
|
337
348
|
allowed_goals: record.allowed_goals || [],
|
|
349
|
+
timeout_ms: timeoutMs,
|
|
338
350
|
tier_settings: record.tier_settings || {},
|
|
339
351
|
disclosure: record.disclosure,
|
|
340
352
|
notify: record.notify,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const HARD_FALLBACK_TURN_TIMEOUT_MS = 300000;
|
|
2
|
+
|
|
3
|
+
function parsePositiveInt(value) {
|
|
4
|
+
const parsed = Number.parseInt(String(value ?? ''), 10);
|
|
5
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function resolveTokenTimeoutMs(token) {
|
|
9
|
+
if (!token || typeof token !== 'object') {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const topLevel = parsePositiveInt(token.timeout_ms ?? token.timeoutMs);
|
|
14
|
+
if (topLevel) {
|
|
15
|
+
return topLevel;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const tierSettings = token.tier_settings || token.tierSettings;
|
|
19
|
+
if (!tierSettings || typeof tierSettings !== 'object') {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return parsePositiveInt(tierSettings.timeout_ms ?? tierSettings.timeoutMs);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveTurnTimeoutMs(options = {}) {
|
|
26
|
+
const tokenTimeoutMs = parsePositiveInt(options.tokenTimeoutMs);
|
|
27
|
+
if (tokenTimeoutMs) {
|
|
28
|
+
return tokenTimeoutMs;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const envTimeoutMs = parsePositiveInt(
|
|
32
|
+
options.envTimeoutMs !== undefined ? options.envTimeoutMs : process.env.A2A_TURN_TIMEOUT
|
|
33
|
+
);
|
|
34
|
+
if (envTimeoutMs) {
|
|
35
|
+
return envTimeoutMs;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const configTimeoutMs = parsePositiveInt(options.configTimeoutMs);
|
|
39
|
+
if (configTimeoutMs) {
|
|
40
|
+
return configTimeoutMs;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const fallbackTimeoutMs = parsePositiveInt(options.hardFallbackMs);
|
|
44
|
+
return fallbackTimeoutMs || HARD_FALLBACK_TURN_TIMEOUT_MS;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = {
|
|
48
|
+
HARD_FALLBACK_TURN_TIMEOUT_MS,
|
|
49
|
+
parsePositiveInt,
|
|
50
|
+
resolveTokenTimeoutMs,
|
|
51
|
+
resolveTurnTimeoutMs
|
|
52
|
+
};
|
package/src/routes/a2a.js
CHANGED
|
@@ -340,6 +340,7 @@ function createRoutes(options = {}) {
|
|
|
340
340
|
tier: validation.tier,
|
|
341
341
|
capabilities: validation.capabilities,
|
|
342
342
|
allowed_topics: validation.allowed_topics,
|
|
343
|
+
timeout_ms: validation.timeout_ms,
|
|
343
344
|
disclosure: validation.disclosure,
|
|
344
345
|
caller: sanitizedCaller,
|
|
345
346
|
conversation_id: conversation_id || `conv_${Date.now()}_${crypto.randomBytes(6).toString('hex')}`,
|
package/src/server.js
CHANGED
|
@@ -27,6 +27,7 @@ const { buildUnifiedSummaryPrompt } = require('./lib/summary-prompt');
|
|
|
27
27
|
const { A2AConfig } = require('./lib/config');
|
|
28
28
|
const { UpdateManager } = require('./lib/update-manager');
|
|
29
29
|
const { spawn } = require('child_process');
|
|
30
|
+
const { resolveTurnTimeoutMs } = require('./lib/turn-timeout');
|
|
30
31
|
|
|
31
32
|
const DEFAULT_PORTS = [80, 3001, 8080, 8443, 9001];
|
|
32
33
|
const requestedPort = process.env.PORT ? parseInt(process.env.PORT, 10)
|
|
@@ -120,6 +121,15 @@ function readPositiveIntEnv(name, fallback) {
|
|
|
120
121
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
function resolveConfiguredTurnTimeoutMs() {
|
|
125
|
+
try {
|
|
126
|
+
const defaults = config.getDefaults?.() || {};
|
|
127
|
+
return defaults.turnTimeoutMs ?? defaults.turn_timeout_ms ?? null;
|
|
128
|
+
} catch (err) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
123
133
|
function resolveCollabMode() {
|
|
124
134
|
const raw = String(process.env.A2A_COLLAB_MODE || 'adaptive').trim().toLowerCase();
|
|
125
135
|
if (raw === 'deep_dive' || raw === 'deep-dive') {
|
|
@@ -583,6 +593,10 @@ async function callAgent(message, a2aContext) {
|
|
|
583
593
|
: buildConnectionPrompt(promptOptions);
|
|
584
594
|
|
|
585
595
|
const sessionId = `a2a-${conversationId}`;
|
|
596
|
+
const claudeTurnTimeoutMs = resolveTurnTimeoutMs({
|
|
597
|
+
tokenTimeoutMs: a2aContext.timeout_ms,
|
|
598
|
+
configTimeoutMs: resolveConfiguredTurnTimeoutMs()
|
|
599
|
+
});
|
|
586
600
|
|
|
587
601
|
try {
|
|
588
602
|
callLogger.info('Handling inbound call turn', {
|
|
@@ -600,12 +614,13 @@ async function callAgent(message, a2aContext) {
|
|
|
600
614
|
prompt,
|
|
601
615
|
message,
|
|
602
616
|
caller: a2aContext.caller || {},
|
|
603
|
-
timeoutMs: 65000,
|
|
617
|
+
timeoutMs: runtime.mode === 'claude' ? claudeTurnTimeoutMs : 65000,
|
|
604
618
|
context: {
|
|
605
619
|
conversationId,
|
|
606
620
|
tier: tierInfo,
|
|
607
621
|
ownerName: agentContext.owner,
|
|
608
622
|
allowedTopics: a2aContext.allowed_topics || [],
|
|
623
|
+
timeoutMs: runtime.mode === 'claude' ? claudeTurnTimeoutMs : 65000,
|
|
609
624
|
traceId,
|
|
610
625
|
requestId
|
|
611
626
|
}
|