@swarmclawai/swarmclaw 1.9.13 → 1.9.15

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.
@@ -0,0 +1,171 @@
1
+ import type { EvidenceArtifact, RunBrief, RunHandoffPacket, RunHandoffReadinessStatus, SessionRunRecord } from '@/types'
2
+
3
+ const MAX_TEXT = 900
4
+ const MAX_EVIDENCE = 12
5
+ const MAX_ARTIFACTS = 16
6
+
7
+ function compactText(value: string | null | undefined, maxChars = MAX_TEXT): string | null {
8
+ const text = (value || '').split(/\s+/).filter(Boolean).join(' ').trim()
9
+ if (!text) return null
10
+ return text.length > maxChars ? `${text.slice(0, maxChars - 3)}...` : text
11
+ }
12
+
13
+ function toIso(value: number | null | undefined): string {
14
+ return value && Number.isFinite(value) ? new Date(value).toISOString() : 'n/a'
15
+ }
16
+
17
+ function durationMs(run: SessionRunRecord, now: number): number | null {
18
+ if (!run.startedAt) return null
19
+ const end = run.endedAt || now
20
+ if (!Number.isFinite(end) || end < run.startedAt) return null
21
+ return Math.max(0, Math.trunc(end - run.startedAt))
22
+ }
23
+
24
+ function readinessStatus(run: SessionRunRecord, brief: RunBrief, artifacts: EvidenceArtifact[]): RunHandoffReadinessStatus {
25
+ if (run.status === 'failed') return 'blocked'
26
+ if (run.status === 'cancelled') return 'needs_attention'
27
+ if (run.status === 'queued' || run.status === 'running') return 'needs_attention'
28
+ if (brief.warnings.length > 0) return 'needs_attention'
29
+ if (!compactText(brief.result || run.resultPreview)) return 'needs_attention'
30
+ if (brief.evidence.length === 0 && artifacts.length === 0) return 'needs_attention'
31
+ return 'ready'
32
+ }
33
+
34
+ function recommendedActions(run: SessionRunRecord, brief: RunBrief, artifacts: EvidenceArtifact[]): string[] {
35
+ const actions: string[] = []
36
+ if (run.status === 'failed') actions.push('Review the run error, fix the cause, then rerun from the source session or owner.')
37
+ if (run.status === 'cancelled') actions.push('Review why the run was cancelled before continuing the handoff.')
38
+ if (run.status === 'queued' || run.status === 'running') actions.push('Wait for the run to finish or cancel it before using the result as final.')
39
+ if (!compactText(brief.result || run.resultPreview) && run.status === 'completed') actions.push('Record a result summary before sharing this run.')
40
+ if (brief.evidence.length === 0 && artifacts.length === 0 && run.status === 'completed') {
41
+ actions.push('Attach evidence, artifacts, or a task report if another operator will continue from this run.')
42
+ }
43
+ for (const warning of brief.warnings) {
44
+ actions.push(warning)
45
+ }
46
+ return actions.length > 0 ? Array.from(new Set(actions)).slice(0, 8) : ['Handoff packet is ready to share.']
47
+ }
48
+
49
+ function resumeCommands(run: SessionRunRecord): string[] {
50
+ return [
51
+ `swarmclaw runs handoff ${run.id} --query format=markdown`,
52
+ `swarmclaw runs brief ${run.id}`,
53
+ `swarmclaw chats context-pack ${run.sessionId} --query format=markdown`,
54
+ ]
55
+ }
56
+
57
+ export function buildRunHandoffPacket(
58
+ run: SessionRunRecord,
59
+ brief: RunBrief,
60
+ artifacts: EvidenceArtifact[] = [],
61
+ now = Date.now(),
62
+ ): RunHandoffPacket {
63
+ const limitedArtifacts = artifacts.slice(0, MAX_ARTIFACTS)
64
+ return {
65
+ schemaVersion: 1,
66
+ runId: run.id,
67
+ sessionId: run.sessionId,
68
+ title: compactText(brief.title || run.messagePreview, 160) || run.id,
69
+ objective: compactText(brief.objective || run.messagePreview, 1400) || run.source,
70
+ source: run.source,
71
+ mode: run.mode,
72
+ status: run.status,
73
+ owner: brief.owner || (run.ownerType && run.ownerId ? { type: run.ownerType, id: run.ownerId } : null),
74
+ generatedAt: now,
75
+ timing: {
76
+ queuedAt: run.queuedAt,
77
+ startedAt: run.startedAt || null,
78
+ endedAt: run.endedAt || null,
79
+ durationMs: durationMs(run, now),
80
+ },
81
+ outcome: {
82
+ result: compactText(brief.result || run.resultPreview, 1400),
83
+ error: compactText(brief.error || run.error, 1400),
84
+ warnings: brief.warnings.slice(0, 12),
85
+ },
86
+ usage: brief.usage,
87
+ timeline: brief.timeline.slice(0, 20),
88
+ evidence: brief.evidence.slice(0, MAX_EVIDENCE),
89
+ artifacts: limitedArtifacts,
90
+ resume: {
91
+ sessionId: run.sessionId,
92
+ commands: resumeCommands(run),
93
+ links: [
94
+ { label: 'Run events', href: `/api/runs/${encodeURIComponent(run.id)}/events` },
95
+ { label: 'Run brief', href: `/api/runs/${encodeURIComponent(run.id)}/brief` },
96
+ { label: 'Session context pack', href: `/api/chats/${encodeURIComponent(run.sessionId)}/context-pack?format=markdown` },
97
+ ],
98
+ },
99
+ readiness: {
100
+ status: readinessStatus(run, brief, limitedArtifacts),
101
+ recommendedActions: recommendedActions(run, brief, limitedArtifacts),
102
+ },
103
+ }
104
+ }
105
+
106
+ function appendSection(lines: string[], title: string, body: string[] = []) {
107
+ lines.push('', `## ${title}`)
108
+ if (body.length === 0) lines.push('None.')
109
+ else lines.push(...body)
110
+ }
111
+
112
+ function artifactLine(artifact: EvidenceArtifact): string {
113
+ const target = artifact.url || artifact.href || ''
114
+ const preview = compactText(artifact.preview || artifact.description, 280)
115
+ return `- ${artifact.title} (${artifact.kind})${target ? ` ${target}` : ''}${preview ? `: ${preview}` : ''}`
116
+ }
117
+
118
+ export function formatRunHandoffMarkdown(packet: RunHandoffPacket): string {
119
+ const owner = packet.owner ? `${packet.owner.type}:${packet.owner.id}` : 'unassigned'
120
+ const duration = packet.timing.durationMs == null ? 'n/a' : `${Math.round(packet.timing.durationMs / 1000)}s`
121
+ const lines = [
122
+ `# Run Handoff: ${packet.title}`,
123
+ '',
124
+ `Generated: ${toIso(packet.generatedAt)}`,
125
+ `Run ID: ${packet.runId}`,
126
+ `Session ID: ${packet.sessionId}`,
127
+ `Status: ${packet.status}`,
128
+ `Readiness: ${packet.readiness.status}`,
129
+ `Source: ${packet.source}`,
130
+ `Owner: ${owner}`,
131
+ `Duration: ${duration}`,
132
+ ]
133
+
134
+ appendSection(lines, 'Objective', [packet.objective])
135
+
136
+ appendSection(lines, 'Outcome', [
137
+ packet.outcome.result ? `- Result: ${packet.outcome.result}` : '',
138
+ packet.outcome.error ? `- Error: ${packet.outcome.error}` : '',
139
+ ...packet.outcome.warnings.map((warning) => `- Warning: ${warning}`),
140
+ ].filter(Boolean))
141
+
142
+ appendSection(lines, 'Timeline', packet.timeline.map((item) => {
143
+ const status = item.status ? ` (${item.status})` : ''
144
+ const detail = item.detail ? `: ${compactText(item.detail, 260)}` : ''
145
+ return `- ${item.label}${status} at ${toIso(item.at)}${detail}`
146
+ }))
147
+
148
+ appendSection(lines, 'Evidence', packet.evidence.map((item) => {
149
+ const url = item.url ? ` ${item.url}` : ''
150
+ return `- ${item.title} (${item.kind})${url}: ${item.summary}`
151
+ }))
152
+
153
+ appendSection(lines, 'Artifacts', packet.artifacts.map(artifactLine))
154
+
155
+ appendSection(lines, 'Usage', [
156
+ `- Input tokens: ${packet.usage.inputTokens ?? 0}`,
157
+ `- Output tokens: ${packet.usage.outputTokens ?? 0}`,
158
+ packet.usage.estimatedCost != null ? `- Estimated cost: $${packet.usage.estimatedCost.toFixed(4)}` : '',
159
+ `- Citations: ${packet.usage.citationCount}`,
160
+ packet.usage.sourceIds.length > 0 ? `- Sources: ${packet.usage.sourceIds.join(', ')}` : '',
161
+ ].filter(Boolean))
162
+
163
+ appendSection(lines, 'Resume', [
164
+ ...packet.resume.commands.map((command) => `- \`${command}\``),
165
+ ...packet.resume.links.map((link) => `- ${link.label}: ${link.href}`),
166
+ ])
167
+
168
+ appendSection(lines, 'Recommended Actions', packet.readiness.recommendedActions.map((action) => `- ${action}`))
169
+
170
+ return `${lines.join('\n')}\n`
171
+ }
@@ -3,9 +3,6 @@
3
3
  *
4
4
  * Replaces the implicit `source: 'heartbeat' | 'heartbeat-wake'` convention
5
5
  * with a formal enum that determines routing, priority, and isolation behavior.
6
- *
7
- * Inspired by OpenClaw's separation of "run now" vs "queue next heartbeat" vs
8
- * scheduled execution with proper isolation.
9
6
  */
10
7
 
11
8
  // ── WakeMode enum ───────────────────────────────────────────────────────
@@ -84,8 +84,8 @@ export function budgetSkillsForPrompt(
84
84
 
85
85
  /**
86
86
  * Prescriptive skill adherence header.
87
- * This tells the model exactly when and how to use skills the key difference
88
- * vs OpenClaw's superior skill following (1-2 tool calls vs 3-5).
87
+ * This tells the model exactly when and how to use skills so it can keep
88
+ * skill-backed turns focused and economical.
89
89
  */
90
90
  const SKILL_ADHERENCE_HEADER = `## Skills
91
91
 
@@ -1,9 +1,6 @@
1
1
  /**
2
2
  * Workspace context injection — injects workspace files into the agent's system prompt.
3
3
  *
4
- * Inspired by OpenClaw's pattern of injecting HEARTBEAT.md, IDENTITY.md, AGENTS.md,
5
- * SOUL.md, TOOLS.md, USER.md, and BOOTSTRAP.md into every agent turn.
6
- *
7
4
  * This gives agents self-awareness, goals, and context about their operating environment
8
5
  * without requiring the user to manually configure everything.
9
6
  */
@@ -17,6 +17,7 @@ export * from './goal'
17
17
  export * from './mission'
18
18
  export * from './operations'
19
19
  export * from './run-brief'
20
+ export * from './run-handoff'
20
21
  export * from './artifact'
21
22
  export * from './swarmdock'
22
23
  export * from './dream'
@@ -0,0 +1,48 @@
1
+ import type { EvidenceArtifact } from './artifact'
2
+ import type { ExecutionOwnerType, SessionRunStatus } from './run'
3
+ import type { RunBriefEvidenceItem, RunBriefTimelineItem } from './run-brief'
4
+
5
+ export type RunHandoffReadinessStatus = 'ready' | 'needs_attention' | 'blocked'
6
+
7
+ export interface RunHandoffPacket {
8
+ schemaVersion: 1
9
+ runId: string
10
+ sessionId: string
11
+ title: string
12
+ objective: string
13
+ source: string
14
+ mode: string
15
+ status: SessionRunStatus
16
+ owner: { type: ExecutionOwnerType; id: string } | null
17
+ generatedAt: number
18
+ timing: {
19
+ queuedAt: number
20
+ startedAt: number | null
21
+ endedAt: number | null
22
+ durationMs: number | null
23
+ }
24
+ outcome: {
25
+ result: string | null
26
+ error: string | null
27
+ warnings: string[]
28
+ }
29
+ usage: {
30
+ inputTokens: number | null
31
+ outputTokens: number | null
32
+ estimatedCost: number | null
33
+ citationCount: number
34
+ sourceIds: string[]
35
+ }
36
+ timeline: RunBriefTimelineItem[]
37
+ evidence: RunBriefEvidenceItem[]
38
+ artifacts: EvidenceArtifact[]
39
+ resume: {
40
+ sessionId: string
41
+ commands: string[]
42
+ links: Array<{ label: string; href: string }>
43
+ }
44
+ readiness: {
45
+ status: RunHandoffReadinessStatus
46
+ recommendedActions: string[]
47
+ }
48
+ }