@compilr-dev/sdk 0.10.7 → 0.10.9
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/compressors/bash.js +223 -0
- package/dist/detection/common.js +13 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/team/delegation-tools.d.ts +57 -0
- package/dist/team/delegation-tools.js +224 -0
- package/dist/team/index.d.ts +2 -0
- package/dist/team/index.js +2 -0
- package/package.json +1 -1
package/dist/compressors/bash.js
CHANGED
|
@@ -40,6 +40,27 @@ export function compressBashOutput(command, stdout) {
|
|
|
40
40
|
// curl / wget (HTTP responses)
|
|
41
41
|
if (cmd.match(/^(curl|wget)\b/))
|
|
42
42
|
return compressCurlOutput(stdout);
|
|
43
|
+
// GitHub CLI (verbose detail commands — pr/issue view, run view)
|
|
44
|
+
// List commands (pr list, issue list, run list) are already tabular and compact;
|
|
45
|
+
// pass them through unchanged.
|
|
46
|
+
if (cmd.match(/^gh\s+(pr|issue|run)\s+view\b/))
|
|
47
|
+
return compressGhView(stdout);
|
|
48
|
+
// Docker
|
|
49
|
+
// `docker logs` — cap to last N lines (logs blow up the context fast)
|
|
50
|
+
if (cmd.match(/^docker\s+logs\b/))
|
|
51
|
+
return compressLogsOutput(stdout);
|
|
52
|
+
// `docker pull/push/build` — strip noisy progress lines, keep summary
|
|
53
|
+
if (cmd.match(/^docker\s+(pull|push|build)\b/))
|
|
54
|
+
return compressDockerProgress(stdout);
|
|
55
|
+
// `docker ps`, `docker images`, `docker stats` — already tabular and compact, pass through
|
|
56
|
+
// kubectl
|
|
57
|
+
// `kubectl logs` — same as docker logs
|
|
58
|
+
if (cmd.match(/^kubectl\s+logs\b/))
|
|
59
|
+
return compressLogsOutput(stdout);
|
|
60
|
+
// `kubectl describe` — verbose, often deeply indented; strip noise + cap repeated events
|
|
61
|
+
if (cmd.match(/^kubectl\s+describe\b/))
|
|
62
|
+
return compressKubectlDescribe(stdout);
|
|
63
|
+
// `kubectl get` — already tabular and compact, pass through
|
|
43
64
|
return null; // No compressor matched
|
|
44
65
|
}
|
|
45
66
|
// ─── Git Status ─────────────────────────────────────────────────────────────
|
|
@@ -296,6 +317,208 @@ function compressLsOutput(output) {
|
|
|
296
317
|
}
|
|
297
318
|
return result.join('\n');
|
|
298
319
|
}
|
|
320
|
+
// ─── GitHub CLI: pr/issue/run view ──────────────────────────────────────────
|
|
321
|
+
/**
|
|
322
|
+
* Compress `gh pr view`, `gh issue view`, `gh run view` output.
|
|
323
|
+
*
|
|
324
|
+
* Strips noise that has zero signal for the agent:
|
|
325
|
+
* - HTML comments (often left over from PR/issue templates)
|
|
326
|
+
* - Markdown badge images and image-only lines
|
|
327
|
+
* - Empty metadata lines (`labels:`, `projects:`, `milestone:` with no value)
|
|
328
|
+
* - "🤖 Generated with [Claude Code]" / "Co-Authored-By: ..." footers
|
|
329
|
+
* (only the duplicated trailing block — preserves any in-body co-authors
|
|
330
|
+
* that appear before the body's last paragraph)
|
|
331
|
+
* - The trailing "View this pull request on GitHub: <url>" footer
|
|
332
|
+
* - Runs of 3+ blank lines collapsed to a single blank
|
|
333
|
+
*
|
|
334
|
+
* Pass-through: keeps title, status, body, labels with values, code blocks.
|
|
335
|
+
*/
|
|
336
|
+
function compressGhView(output) {
|
|
337
|
+
// Drop HTML comments first (multiline regex)
|
|
338
|
+
const cleaned = output.replace(/<!--[\s\S]*?-->/g, '');
|
|
339
|
+
const lines = cleaned.split('\n');
|
|
340
|
+
const kept = [];
|
|
341
|
+
let consecutiveBlanks = 0;
|
|
342
|
+
for (const line of lines) {
|
|
343
|
+
const trimmed = line.trim();
|
|
344
|
+
// Trailing "View this pull request on GitHub: ..." / "View this issue on GitHub: ..."
|
|
345
|
+
if (/^View this (pull request|issue|workflow run) on GitHub:/i.test(trimmed))
|
|
346
|
+
continue;
|
|
347
|
+
// Empty metadata lines: `labels:`, `projects:`, `milestone:`, `assignees:`,
|
|
348
|
+
// `reviewers:` with no value after the colon.
|
|
349
|
+
if (/^(labels|projects|milestone|assignees|reviewers|tags):\s*$/i.test(trimmed)) {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
// Badge image / image-only markdown lines
|
|
353
|
+
if (/^\[!\[[^\]]*\]\([^)]*\)\]\([^)]*\)$/.test(trimmed))
|
|
354
|
+
continue; // [](link)
|
|
355
|
+
if (/^!\[[^\]]*\]\([^)]*\)$/.test(trimmed))
|
|
356
|
+
continue; // 
|
|
357
|
+
// Collapse runs of blank lines (max 1 consecutive)
|
|
358
|
+
if (trimmed === '') {
|
|
359
|
+
consecutiveBlanks++;
|
|
360
|
+
if (consecutiveBlanks > 1)
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
consecutiveBlanks = 0;
|
|
365
|
+
}
|
|
366
|
+
kept.push(line);
|
|
367
|
+
}
|
|
368
|
+
// Strip a trailing "🤖 Generated with [Claude Code]" / "Co-Authored-By:" footer
|
|
369
|
+
// if it's the last non-blank block. We only strip if it's clearly the tail —
|
|
370
|
+
// we don't want to lose co-authors that appear inside the body.
|
|
371
|
+
while (kept.length > 0) {
|
|
372
|
+
const last = kept[kept.length - 1].trim();
|
|
373
|
+
if (last === '') {
|
|
374
|
+
kept.pop();
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (last.startsWith('🤖 Generated with [Claude Code]') || /^Co-Authored-By:/i.test(last)) {
|
|
378
|
+
kept.pop();
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
return kept.join('\n');
|
|
384
|
+
}
|
|
385
|
+
// ─── Container logs (docker logs / kubectl logs) ────────────────────────────
|
|
386
|
+
const LOGS_MAX_LINES = 100;
|
|
387
|
+
/**
|
|
388
|
+
* Cap container logs to the last N lines. Logs typically blow up token
|
|
389
|
+
* usage fast (a single container can produce thousands of lines), and the
|
|
390
|
+
* agent almost always wants the tail (most-recent failure / state).
|
|
391
|
+
*
|
|
392
|
+
* Drops a single banner line at the top noting how many lines were truncated
|
|
393
|
+
* so the agent knows the output was clipped.
|
|
394
|
+
*/
|
|
395
|
+
function compressLogsOutput(output) {
|
|
396
|
+
const lines = output.split('\n');
|
|
397
|
+
// Don't compress small outputs — the cap is the only thing this compressor does.
|
|
398
|
+
if (lines.length <= LOGS_MAX_LINES)
|
|
399
|
+
return output;
|
|
400
|
+
const truncated = lines.length - LOGS_MAX_LINES;
|
|
401
|
+
const tail = lines.slice(-LOGS_MAX_LINES);
|
|
402
|
+
return `... (${String(truncated)} earlier log lines truncated — showing last ${String(LOGS_MAX_LINES)})\n${tail.join('\n')}`;
|
|
403
|
+
}
|
|
404
|
+
// ─── docker pull / push / build (strip progress noise) ──────────────────────
|
|
405
|
+
/**
|
|
406
|
+
* Strip per-layer progress lines from `docker pull/push/build`. Keeps the
|
|
407
|
+
* digest / "Status: Image is up to date" / "Successfully built" summary lines.
|
|
408
|
+
*
|
|
409
|
+
* Patterns dropped (all common Docker progress chatter):
|
|
410
|
+
* - "<hash>: Pulling fs layer"
|
|
411
|
+
* - "<hash>: Waiting"
|
|
412
|
+
* - "<hash>: Verifying Checksum"
|
|
413
|
+
* - "<hash>: Downloading [====> ] 12.3MB/45.6MB"
|
|
414
|
+
* - "<hash>: Pull complete" / "Extracting" / "Download complete"
|
|
415
|
+
* - BuildKit progress lines: "#5 [internal] load build context"
|
|
416
|
+
* - Build context transfer lines
|
|
417
|
+
*/
|
|
418
|
+
function compressDockerProgress(output) {
|
|
419
|
+
const lines = output.split('\n');
|
|
420
|
+
const kept = [];
|
|
421
|
+
for (const line of lines) {
|
|
422
|
+
const trimmed = line.trim();
|
|
423
|
+
// Per-layer progress patterns (hash prefix + colon + status)
|
|
424
|
+
if (/^[a-f0-9]{12}:\s+(Pulling fs layer|Waiting|Verifying Checksum|Download complete|Pull complete|Extracting|Downloading|Pushing|Pushed|Mounted from|Layer already exists|Preparing|Already exists)/i.test(trimmed)) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
// BuildKit transfer lines: "#N transferring context: 1.23MB"
|
|
428
|
+
if (/^#\d+\s+(transferring|sha256:|extracting|naming to|exporting layers|writing image)/.test(trimmed))
|
|
429
|
+
continue;
|
|
430
|
+
// Plain "Downloading [====> ] 12MB/45MB" without hash prefix
|
|
431
|
+
if (/^\s*Downloading\s+\[/.test(line))
|
|
432
|
+
continue;
|
|
433
|
+
kept.push(line);
|
|
434
|
+
}
|
|
435
|
+
return kept.join('\n');
|
|
436
|
+
}
|
|
437
|
+
// ─── kubectl describe (collapse indentation noise, dedupe events) ───────────
|
|
438
|
+
/**
|
|
439
|
+
* Compress `kubectl describe` output. The verbose describe format is highly
|
|
440
|
+
* indented and includes long Events tables that often duplicate the same
|
|
441
|
+
* message N times ("Back-off restarting failed container").
|
|
442
|
+
*
|
|
443
|
+
* Strategy:
|
|
444
|
+
* - Trim trailing whitespace on every line (huge amount of padding)
|
|
445
|
+
* - Collapse 3+ consecutive blank lines to a single blank
|
|
446
|
+
* - In the Events: section, dedupe identical messages — keep first occurrence
|
|
447
|
+
* plus a count of repeats ("(x42)")
|
|
448
|
+
*/
|
|
449
|
+
function compressKubectlDescribe(output) {
|
|
450
|
+
const lines = output.split('\n');
|
|
451
|
+
const kept = [];
|
|
452
|
+
let inEvents = false;
|
|
453
|
+
let consecutiveBlanks = 0;
|
|
454
|
+
// Track event message → count (only used inside Events: section)
|
|
455
|
+
const eventCounts = new Map();
|
|
456
|
+
const eventOrder = [];
|
|
457
|
+
for (const line of lines) {
|
|
458
|
+
const rstripped = line.replace(/\s+$/, '');
|
|
459
|
+
const trimmed = rstripped.trim();
|
|
460
|
+
// Section header transitions
|
|
461
|
+
if (/^Events:\s*$/.test(trimmed)) {
|
|
462
|
+
inEvents = true;
|
|
463
|
+
kept.push(rstripped);
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
if (inEvents && /^\S/.test(rstripped) && !/^Events:/.test(trimmed)) {
|
|
467
|
+
// A non-indented, non-Events: line means we've left the Events section.
|
|
468
|
+
// Flush deduped events first.
|
|
469
|
+
flushEvents(kept, eventCounts, eventOrder);
|
|
470
|
+
inEvents = false;
|
|
471
|
+
}
|
|
472
|
+
if (inEvents) {
|
|
473
|
+
// Inside Events table — collect by "type + reason + message" (drop timestamp/age columns)
|
|
474
|
+
// Lines look like:
|
|
475
|
+
// " Type Reason Age From Message"
|
|
476
|
+
// " ---- ------ --- ---- -------"
|
|
477
|
+
// " Warning BackOff 1m kubelet Back-off restarting failed container"
|
|
478
|
+
if (trimmed === '' || /^(Type|----)/.test(trimmed)) {
|
|
479
|
+
kept.push(rstripped);
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
// Extract the message (everything after the first 4 whitespace-separated columns)
|
|
483
|
+
const parts = trimmed.split(/\s{2,}/); // 2+ spaces between columns
|
|
484
|
+
const key = parts.length > 4 ? parts.slice(-1)[0] : trimmed;
|
|
485
|
+
const count = eventCounts.get(key) ?? 0;
|
|
486
|
+
if (count === 0)
|
|
487
|
+
eventOrder.push(rstripped);
|
|
488
|
+
eventCounts.set(key, count + 1);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
// Collapse runs of blank lines
|
|
492
|
+
if (trimmed === '') {
|
|
493
|
+
consecutiveBlanks++;
|
|
494
|
+
if (consecutiveBlanks > 1)
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
consecutiveBlanks = 0;
|
|
499
|
+
}
|
|
500
|
+
kept.push(rstripped);
|
|
501
|
+
}
|
|
502
|
+
// Flush any pending events at the end
|
|
503
|
+
if (inEvents)
|
|
504
|
+
flushEvents(kept, eventCounts, eventOrder);
|
|
505
|
+
return kept.join('\n');
|
|
506
|
+
}
|
|
507
|
+
function flushEvents(kept, counts, order) {
|
|
508
|
+
for (const line of order) {
|
|
509
|
+
const parts = line.trim().split(/\s{2,}/);
|
|
510
|
+
const key = parts.length > 4 ? parts.slice(-1)[0] : line.trim();
|
|
511
|
+
const count = counts.get(key) ?? 1;
|
|
512
|
+
if (count > 1) {
|
|
513
|
+
kept.push(`${line} (x${String(count)})`);
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
kept.push(line);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
counts.clear();
|
|
520
|
+
order.length = 0;
|
|
521
|
+
}
|
|
299
522
|
// ─── curl / wget ────────────────────────────────────────────────────────────
|
|
300
523
|
function compressCurlOutput(output) {
|
|
301
524
|
const lines = output.split('\n');
|
package/dist/detection/common.js
CHANGED
|
@@ -79,6 +79,11 @@ function readGitRemote(projectPath) {
|
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
81
|
* Read description from README.md or COMPILR.md (first non-heading paragraph).
|
|
82
|
+
*
|
|
83
|
+
* Skips noise commonly found above the first prose paragraph in auto-generated
|
|
84
|
+
* READMEs: headings, badge images, HTML comments, block-quote callouts, URL-only
|
|
85
|
+
* lines, and `**Key**: value` bold-metadata lines (e.g. Lovable's `**URL**: ...`,
|
|
86
|
+
* v0/Vercel templates with `**Demo**: ...`).
|
|
82
87
|
*/
|
|
83
88
|
function readDescription(projectPath) {
|
|
84
89
|
const candidates = ['README.md', 'readme.md', 'COMPILR.md'];
|
|
@@ -89,7 +94,7 @@ function readDescription(projectPath) {
|
|
|
89
94
|
try {
|
|
90
95
|
const content = readFileSync(filePath, 'utf-8');
|
|
91
96
|
const lines = content.split('\n');
|
|
92
|
-
// Find first non-empty, non-heading line
|
|
97
|
+
// Find first non-empty, non-heading, non-metadata line
|
|
93
98
|
for (const line of lines) {
|
|
94
99
|
const trimmed = line.trim();
|
|
95
100
|
if (!trimmed)
|
|
@@ -99,7 +104,13 @@ function readDescription(projectPath) {
|
|
|
99
104
|
if (trimmed.startsWith('!['))
|
|
100
105
|
continue; // badge images
|
|
101
106
|
if (trimmed.startsWith('<!--'))
|
|
102
|
-
continue;
|
|
107
|
+
continue; // HTML comments
|
|
108
|
+
if (trimmed.startsWith('>'))
|
|
109
|
+
continue; // block-quote callouts / "Note:" disclaimers
|
|
110
|
+
if (/^\*\*[\w\s/-]+\*\*\s*:/.test(trimmed))
|
|
111
|
+
continue; // bold-key metadata: **URL**: ..., **Live Demo**: ...
|
|
112
|
+
if (/^https?:\/\/\S+\s*$/.test(trimmed))
|
|
113
|
+
continue; // URL-only lines
|
|
103
114
|
// Found a content line — take up to 200 chars
|
|
104
115
|
return trimmed.length > 200 ? trimmed.slice(0, 200) + '...' : trimmed;
|
|
105
116
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
*/
|
|
36
36
|
export { createCompilrAgent } from './agent.js';
|
|
37
37
|
export type { CompilrAgentConfig, CompilrAgent, RunOptions, RunResult, ToolCallRecord, ToolConfig, UsageInfo, ProviderType, PermissionCallback, GuardrailConfig, ContextConfig, CapabilitiesConfig, } from './config.js';
|
|
38
|
-
export { AgentTeam, TeamAgent, SharedContextManager, ArtifactStore, DelegationTracker, ContextResolver, } from './team/index.js';
|
|
38
|
+
export { AgentTeam, TeamAgent, SharedContextManager, ArtifactStore, DelegationTracker, ContextResolver, createDelegationStatusTool, createHandoffTool, } from './team/index.js';
|
|
39
39
|
export type { AgentTeamConfig, TeamAgentConfig, ITeamPersistence, IArtifactStorage, ISessionRegistry, CustomAgentDefinition, AgentTemplate, AgentWorkshopData, WorkshopRoleDef, WorkshopToolProfile, WorkshopModelTier, WorkshopSkillDef, PlanSubmitInfo, PlanSubmitResult, PlanModeExitInfo, PlanModeCallbacks, ToolConfig as TeamToolConfig, ToolTier, ToolGroup, ProfileInfo, } from './team/index.js';
|
|
40
|
-
export type { AgentRole, RoleMetadata, ToolProfile, MascotExpression, BackgroundSessionInfo, SerializedTeam, SerializedTeamAgent, TeamMetadata, TeamEvent, TeamEventType, TeamEventHandler, Artifact, ArtifactType as TeamArtifactType, ArtifactSummary as TeamArtifactSummary, CreateArtifactOptions, UpdateArtifactOptions, SerializedArtifact, SharedContext, SharedProjectInfo, SharedTeamInfo, TeamRosterEntry, TeamActivity, TeamActivityType, SharedDecision, TokenBudget, SerializedSharedContext, ParsedMention, ParsedInput, ResolvedMention, ResolveOptions, ResolutionSource, Delegation, DelegationStatus, DelegationResult, CompletionEvent, CreateDelegationOptions, DelegationStats, DelegationTrackerEvents, SkillToolRequirement, } from './team/index.js';
|
|
40
|
+
export type { AgentRole, RoleMetadata, ToolProfile, MascotExpression, BackgroundSessionInfo, SerializedTeam, SerializedTeamAgent, TeamMetadata, TeamEvent, TeamEventType, TeamEventHandler, Artifact, ArtifactType as TeamArtifactType, ArtifactSummary as TeamArtifactSummary, CreateArtifactOptions, UpdateArtifactOptions, SerializedArtifact, SharedContext, SharedProjectInfo, SharedTeamInfo, TeamRosterEntry, TeamActivity, TeamActivityType, SharedDecision, TokenBudget, SerializedSharedContext, ParsedMention, ParsedInput, ResolvedMention, ResolveOptions, ResolutionSource, Delegation, DelegationStatus, DelegationResult, CompletionEvent, CreateDelegationOptions, DelegationStats, DelegationTrackerEvents, HandoffResult, HandoffToolConfig, SkillToolRequirement, } from './team/index.js';
|
|
41
41
|
export { ROLE_METADATA, ROLE_EXPERTISE, ROLE_GROUPS, PREDEFINED_ROLE_IDS, TOOL_GROUPS, TOOL_PROFILES, PROFILE_INFO, SKILL_REQUIREMENTS, CUSTOM_MASCOTS, buildAgentWorkshopData, buildSuggestedRolesMap, PLAN_MODE_BLOCKED_TOOLS, PLAN_MODE_DENIAL_MESSAGE, PLAN_MODE_PROMPT, isToolAllowedInPlanMode, getPlanModePrompt, } from './team/index.js';
|
|
42
42
|
export { getToolsForProfile, detectProfileFromTools, isProfileReadOnly, generateToolAwarenessPrompt, generateCoordinatorGuidance, generateSpecialistGuidance, createDefaultToolConfig, validateToolConfig, getAllGroupIds, getGroupInfo, getGroupsByTier, getGroupsForProfile, assignMascot, generateCustomAgentSystemPrompt, getCustomAgentToolFilter, getCustomAgentProfileLabel, validateAgentId, isAgentIdTaken, createCustomAgentDefinition, listTemplates, getTemplate, saveTemplate, updateTemplate, deleteTemplate, createAgentFromTemplate, parseInputForMentions, getReferencedAgents, hasReferences, buildMessageWithContext, buildContextMap, findAgentForRole, findAgentById, getAvailableSpecialists, getSpecialistsSummary, hasSpecialists, suggestOwner, suggestOwners, matchesAgentExpertise, wouldCreateLoop, recordAssignment, getAssignmentHistory, clearAssignmentHistory, clearAllAssignmentHistory, canReassign, resolveAgentIdCollision, setActiveSharedContext, getActiveSharedContext, recordTeamActivity, getDefinedSkillNames, getSkillRequirements, checkSkillCompatibility, getCompatibleSkills, getAllRequiredTools, getSkillsByCategory, } from './team/index.js';
|
|
43
43
|
export { codingPreset, readOnlyPreset, resolvePreset } from './presets/index.js';
|
package/dist/index.js
CHANGED
|
@@ -41,7 +41,7 @@ export { createCompilrAgent } from './agent.js';
|
|
|
41
41
|
// Multi-Agent Team Orchestration
|
|
42
42
|
// =============================================================================
|
|
43
43
|
// Core classes
|
|
44
|
-
export { AgentTeam, TeamAgent, SharedContextManager, ArtifactStore, DelegationTracker, ContextResolver, } from './team/index.js';
|
|
44
|
+
export { AgentTeam, TeamAgent, SharedContextManager, ArtifactStore, DelegationTracker, ContextResolver, createDelegationStatusTool, createHandoffTool, } from './team/index.js';
|
|
45
45
|
// Constants
|
|
46
46
|
export { ROLE_METADATA, ROLE_EXPERTISE, ROLE_GROUPS, PREDEFINED_ROLE_IDS, TOOL_GROUPS, TOOL_PROFILES, PROFILE_INFO, SKILL_REQUIREMENTS, CUSTOM_MASCOTS, buildAgentWorkshopData, buildSuggestedRolesMap,
|
|
47
47
|
// Plan mode
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegation & Handoff Tools — SDK Factories
|
|
3
|
+
*
|
|
4
|
+
* Factory functions for creating delegation_status and handoff tools.
|
|
5
|
+
* Both CLI and Desktop call these with platform-specific dependencies.
|
|
6
|
+
*/
|
|
7
|
+
import { type Tool } from '@compilr-dev/agents';
|
|
8
|
+
import type { DelegationTracker } from './delegation-tracker.js';
|
|
9
|
+
import type { AgentTeam } from './team.js';
|
|
10
|
+
interface DelegationStatusInput {
|
|
11
|
+
delegation_id?: string;
|
|
12
|
+
agent_id?: string;
|
|
13
|
+
status?: 'active' | 'completed' | 'failed' | 'all';
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a delegation_status tool that queries the given tracker.
|
|
17
|
+
* Read-only — safe for parallel execution.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createDelegationStatusTool(tracker: DelegationTracker): Tool<DelegationStatusInput>;
|
|
20
|
+
interface HandoffInput {
|
|
21
|
+
agentId: string;
|
|
22
|
+
task: string;
|
|
23
|
+
reason?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Result from the platform's handoff handler.
|
|
27
|
+
*/
|
|
28
|
+
export interface HandoffResult {
|
|
29
|
+
/** Whether the handoff was executed */
|
|
30
|
+
success: boolean;
|
|
31
|
+
/** Whether the user declined the handoff */
|
|
32
|
+
declined?: boolean;
|
|
33
|
+
/** Error message (if any) */
|
|
34
|
+
error?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Configuration for creating a handoff tool.
|
|
38
|
+
*/
|
|
39
|
+
export interface HandoffToolConfig {
|
|
40
|
+
/** The team instance for roster validation and one-hop tracking */
|
|
41
|
+
team: AgentTeam;
|
|
42
|
+
/** The current agent's ID (for one-hop rule enforcement) */
|
|
43
|
+
currentAgentId: string;
|
|
44
|
+
/**
|
|
45
|
+
* Platform-specific handoff handler. Called after validation.
|
|
46
|
+
* Should show approval UI if needed and execute the switch.
|
|
47
|
+
*/
|
|
48
|
+
onHandoff: (targetAgentId: string, task: string, reason?: string) => Promise<HandoffResult>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Create a handoff tool for a specific agent.
|
|
52
|
+
*
|
|
53
|
+
* The tool enforces the one-hop rule: if this agent was itself handed a task,
|
|
54
|
+
* it can only hand back to the coordinator (default agent), not to another specialist.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createHandoffTool(config: HandoffToolConfig): Tool<HandoffInput>;
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegation & Handoff Tools — SDK Factories
|
|
3
|
+
*
|
|
4
|
+
* Factory functions for creating delegation_status and handoff tools.
|
|
5
|
+
* Both CLI and Desktop call these with platform-specific dependencies.
|
|
6
|
+
*/
|
|
7
|
+
import { defineTool } from '@compilr-dev/agents';
|
|
8
|
+
function formatDuration(from, to) {
|
|
9
|
+
const endTime = to ?? new Date();
|
|
10
|
+
const ms = endTime.getTime() - from.getTime();
|
|
11
|
+
const seconds = Math.floor(ms / 1000);
|
|
12
|
+
const minutes = Math.floor(seconds / 60);
|
|
13
|
+
const hours = Math.floor(minutes / 60);
|
|
14
|
+
if (hours > 0) {
|
|
15
|
+
return `${String(hours)}h ${String(minutes % 60)}m`;
|
|
16
|
+
}
|
|
17
|
+
else if (minutes > 0) {
|
|
18
|
+
return `${String(minutes)}m ${String(seconds % 60)}s`;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
return `${String(seconds)}s`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function formatDelegation(d) {
|
|
25
|
+
const entry = {
|
|
26
|
+
id: d.id,
|
|
27
|
+
targetAgent: d.targetAgentId,
|
|
28
|
+
task: d.task.length > 100 ? d.task.slice(0, 100) + '...' : d.task,
|
|
29
|
+
status: d.status,
|
|
30
|
+
duration: formatDuration(d.createdAt, d.completedAt),
|
|
31
|
+
};
|
|
32
|
+
if (d.todoIndex !== undefined) {
|
|
33
|
+
entry.todoIndex = d.todoIndex;
|
|
34
|
+
}
|
|
35
|
+
if (d.result) {
|
|
36
|
+
entry.result = {
|
|
37
|
+
success: d.result.success,
|
|
38
|
+
summary: d.result.summary,
|
|
39
|
+
artifactIds: d.result.artifactIds,
|
|
40
|
+
...(d.result.error ? { error: d.result.error } : {}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return entry;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create a delegation_status tool that queries the given tracker.
|
|
47
|
+
* Read-only — safe for parallel execution.
|
|
48
|
+
*/
|
|
49
|
+
export function createDelegationStatusTool(tracker) {
|
|
50
|
+
return defineTool({
|
|
51
|
+
name: 'delegation_status',
|
|
52
|
+
description: 'Query the status of tasks delegated via delegate_background. ' +
|
|
53
|
+
'Returns active delegations by default (pending and running). ' +
|
|
54
|
+
'Use delegation_id for a specific delegation, agent_id to filter by agent, ' +
|
|
55
|
+
'or status to see completed/failed/all delegations.',
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
delegation_id: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description: 'Specific delegation ID (e.g., "del_abc12345")',
|
|
62
|
+
},
|
|
63
|
+
agent_id: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
description: 'Filter by target agent (e.g., "dev", "arch")',
|
|
66
|
+
},
|
|
67
|
+
status: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
enum: ['active', 'completed', 'failed', 'all'],
|
|
70
|
+
description: 'Filter by status. Default: "active" (pending + running)',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
execute: (input) => {
|
|
75
|
+
return Promise.resolve(executeDelegationStatus(tracker, input));
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function executeDelegationStatus(tracker, input) {
|
|
80
|
+
// Single delegation lookup
|
|
81
|
+
if (input.delegation_id) {
|
|
82
|
+
const delegation = tracker.getDelegation(input.delegation_id);
|
|
83
|
+
if (!delegation) {
|
|
84
|
+
return {
|
|
85
|
+
success: false,
|
|
86
|
+
error: `Delegation ${input.delegation_id} not found`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
result: {
|
|
92
|
+
delegations: [formatDelegation(delegation)],
|
|
93
|
+
stats: tracker.getStats(),
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// Get all delegations then filter
|
|
98
|
+
let delegations;
|
|
99
|
+
if (input.agent_id) {
|
|
100
|
+
delegations = tracker.getByAgent(input.agent_id);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
delegations = tracker.getAll();
|
|
104
|
+
}
|
|
105
|
+
// Apply status filter
|
|
106
|
+
const filter = input.status ?? 'active';
|
|
107
|
+
if (filter !== 'all') {
|
|
108
|
+
delegations = delegations.filter((d) => {
|
|
109
|
+
switch (filter) {
|
|
110
|
+
case 'active':
|
|
111
|
+
return d.status === 'pending' || d.status === 'running';
|
|
112
|
+
case 'completed':
|
|
113
|
+
return d.status === 'completed';
|
|
114
|
+
case 'failed':
|
|
115
|
+
return d.status === 'failed';
|
|
116
|
+
default:
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
success: true,
|
|
123
|
+
result: {
|
|
124
|
+
delegations: delegations.map(formatDelegation),
|
|
125
|
+
stats: tracker.getStats(),
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Create a handoff tool for a specific agent.
|
|
131
|
+
*
|
|
132
|
+
* The tool enforces the one-hop rule: if this agent was itself handed a task,
|
|
133
|
+
* it can only hand back to the coordinator (default agent), not to another specialist.
|
|
134
|
+
*/
|
|
135
|
+
export function createHandoffTool(config) {
|
|
136
|
+
const { team, currentAgentId, onHandoff } = config;
|
|
137
|
+
return defineTool({
|
|
138
|
+
name: 'handoff',
|
|
139
|
+
description: 'Hand off the current task to another team agent. ' +
|
|
140
|
+
'Use this when a task is outside your expertise or would benefit from another specialist. ' +
|
|
141
|
+
'The user will be asked to approve the handoff before switching. ' +
|
|
142
|
+
'Example: hand off architecture questions to $arch, implementation to $dev, testing to $qa.',
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: 'object',
|
|
145
|
+
properties: {
|
|
146
|
+
agentId: {
|
|
147
|
+
type: 'string',
|
|
148
|
+
description: 'Target agent ID (e.g., "arch", "dev", "qa", "default")',
|
|
149
|
+
},
|
|
150
|
+
task: {
|
|
151
|
+
type: 'string',
|
|
152
|
+
description: 'Task description for the target agent - be specific and clear',
|
|
153
|
+
},
|
|
154
|
+
reason: {
|
|
155
|
+
type: 'string',
|
|
156
|
+
description: 'Why this agent is better suited for the task (shown to user)',
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
required: ['agentId', 'task'],
|
|
160
|
+
},
|
|
161
|
+
execute: async (input) => {
|
|
162
|
+
// Validate input
|
|
163
|
+
if (!input.agentId || input.agentId.trim().length === 0) {
|
|
164
|
+
return { success: false, error: 'Agent ID is required' };
|
|
165
|
+
}
|
|
166
|
+
if (!input.task || input.task.trim().length === 0) {
|
|
167
|
+
return { success: false, error: 'Task description is required' };
|
|
168
|
+
}
|
|
169
|
+
const targetId = input.agentId.trim();
|
|
170
|
+
// Validate target agent exists in team
|
|
171
|
+
const allAgents = team.getAll();
|
|
172
|
+
const targetExists = allAgents.some((a) => a.id === targetId);
|
|
173
|
+
if (!targetExists) {
|
|
174
|
+
const available = allAgents.map((a) => a.id).join(', ');
|
|
175
|
+
return {
|
|
176
|
+
success: false,
|
|
177
|
+
error: `Agent "${targetId}" not found in team. Available: ${available}`,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
// Enforce one-hop rule
|
|
181
|
+
if (team.wasHandedTo(currentAgentId) && targetId !== 'default') {
|
|
182
|
+
const source = team.getHandoffSource(currentAgentId);
|
|
183
|
+
return {
|
|
184
|
+
success: false,
|
|
185
|
+
error: `One-hop rule: you were handed this task by $${source ?? 'unknown'} ` +
|
|
186
|
+
`and cannot re-hand it to another specialist. ` +
|
|
187
|
+
`You can only hand back to $default (coordinator). ` +
|
|
188
|
+
`Either complete the task yourself or hand back to the coordinator.`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
// Execute handoff via platform callback
|
|
192
|
+
try {
|
|
193
|
+
const result = await onHandoff(targetId, input.task.trim(), input.reason?.trim());
|
|
194
|
+
if (result.error) {
|
|
195
|
+
return { success: false, error: result.error };
|
|
196
|
+
}
|
|
197
|
+
if (result.declined) {
|
|
198
|
+
return {
|
|
199
|
+
success: true,
|
|
200
|
+
result: {
|
|
201
|
+
handedOff: false,
|
|
202
|
+
message: 'User declined the handoff. Continue handling the task yourself.',
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// Record the handoff for one-hop tracking
|
|
207
|
+
team.recordHandoff(targetId, currentAgentId);
|
|
208
|
+
return {
|
|
209
|
+
success: true,
|
|
210
|
+
result: {
|
|
211
|
+
handedOff: true,
|
|
212
|
+
message: `Task handed off to $${targetId}. They will handle it from here.`,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
return {
|
|
218
|
+
success: false,
|
|
219
|
+
error: `Handoff failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
}
|
package/dist/team/index.d.ts
CHANGED
|
@@ -38,3 +38,5 @@ export { setActiveSharedContext, getActiveSharedContext, recordTeamActivity } fr
|
|
|
38
38
|
export type { SkillToolRequirement } from './skill-requirements.js';
|
|
39
39
|
export { SKILL_REQUIREMENTS, getDefinedSkillNames, getSkillRequirements, checkSkillCompatibility, getCompatibleSkills, getAllRequiredTools, getSkillsByCategory, } from './skill-requirements.js';
|
|
40
40
|
export { resolveAgentIdCollision } from './collision-utils.js';
|
|
41
|
+
export { createDelegationStatusTool, createHandoffTool } from './delegation-tools.js';
|
|
42
|
+
export type { HandoffResult, HandoffToolConfig } from './delegation-tools.js';
|
package/dist/team/index.js
CHANGED
|
@@ -31,3 +31,5 @@ export { setActiveSharedContext, getActiveSharedContext, recordTeamActivity } fr
|
|
|
31
31
|
export { SKILL_REQUIREMENTS, getDefinedSkillNames, getSkillRequirements, checkSkillCompatibility, getCompatibleSkills, getAllRequiredTools, getSkillsByCategory, } from './skill-requirements.js';
|
|
32
32
|
// Collision utils
|
|
33
33
|
export { resolveAgentIdCollision } from './collision-utils.js';
|
|
34
|
+
// Delegation & Handoff tools (factory functions)
|
|
35
|
+
export { createDelegationStatusTool, createHandoffTool } from './delegation-tools.js';
|