optimal-cli 1.0.0 → 1.0.1
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/bin/optimal.d.ts +2 -0
- package/dist/bin/optimal.js +1590 -0
- package/dist/lib/assets/index.d.ts +79 -0
- package/dist/lib/assets/index.js +153 -0
- package/dist/lib/assets.d.ts +20 -0
- package/dist/lib/assets.js +112 -0
- package/dist/lib/auth/index.d.ts +83 -0
- package/dist/lib/auth/index.js +146 -0
- package/dist/lib/board/index.d.ts +39 -0
- package/dist/lib/board/index.js +285 -0
- package/dist/lib/board/types.d.ts +111 -0
- package/dist/lib/board/types.js +1 -0
- package/dist/lib/bot/claim.d.ts +3 -0
- package/dist/lib/bot/claim.js +20 -0
- package/dist/lib/bot/coordinator.d.ts +27 -0
- package/dist/lib/bot/coordinator.js +178 -0
- package/dist/lib/bot/heartbeat.d.ts +6 -0
- package/dist/lib/bot/heartbeat.js +30 -0
- package/dist/lib/bot/index.d.ts +9 -0
- package/dist/lib/bot/index.js +6 -0
- package/dist/lib/bot/protocol.d.ts +12 -0
- package/dist/lib/bot/protocol.js +74 -0
- package/dist/lib/bot/reporter.d.ts +3 -0
- package/dist/lib/bot/reporter.js +27 -0
- package/dist/lib/bot/skills.d.ts +26 -0
- package/dist/lib/bot/skills.js +69 -0
- package/dist/lib/budget/projections.d.ts +115 -0
- package/dist/lib/budget/projections.js +384 -0
- package/dist/lib/budget/scenarios.d.ts +93 -0
- package/dist/lib/budget/scenarios.js +214 -0
- package/dist/lib/cms/publish-blog.d.ts +62 -0
- package/dist/lib/cms/publish-blog.js +74 -0
- package/dist/lib/cms/strapi-client.d.ts +123 -0
- package/dist/lib/cms/strapi-client.js +213 -0
- package/dist/lib/config/registry.d.ts +17 -0
- package/dist/lib/config/registry.js +182 -0
- package/dist/lib/config/schema.d.ts +31 -0
- package/dist/lib/config/schema.js +25 -0
- package/dist/lib/config.d.ts +55 -0
- package/dist/lib/config.js +206 -0
- package/dist/lib/errors.d.ts +25 -0
- package/dist/lib/errors.js +91 -0
- package/dist/lib/format.d.ts +28 -0
- package/dist/lib/format.js +98 -0
- package/dist/lib/infra/deploy.d.ts +29 -0
- package/dist/lib/infra/deploy.js +58 -0
- package/dist/lib/infra/migrate.d.ts +34 -0
- package/dist/lib/infra/migrate.js +103 -0
- package/dist/lib/newsletter/distribute.d.ts +52 -0
- package/dist/lib/newsletter/distribute.js +193 -0
- package/{lib/newsletter/generate-insurance.ts → dist/lib/newsletter/generate-insurance.d.ts} +7 -24
- package/dist/lib/newsletter/generate-insurance.js +36 -0
- package/dist/lib/newsletter/generate.d.ts +104 -0
- package/dist/lib/newsletter/generate.js +571 -0
- package/dist/lib/returnpro/anomalies.d.ts +64 -0
- package/dist/lib/returnpro/anomalies.js +166 -0
- package/dist/lib/returnpro/audit.d.ts +32 -0
- package/dist/lib/returnpro/audit.js +147 -0
- package/dist/lib/returnpro/diagnose.d.ts +52 -0
- package/dist/lib/returnpro/diagnose.js +281 -0
- package/dist/lib/returnpro/kpis.d.ts +32 -0
- package/dist/lib/returnpro/kpis.js +192 -0
- package/dist/lib/returnpro/templates.d.ts +48 -0
- package/dist/lib/returnpro/templates.js +229 -0
- package/dist/lib/returnpro/upload-income.d.ts +25 -0
- package/dist/lib/returnpro/upload-income.js +235 -0
- package/dist/lib/returnpro/upload-netsuite.d.ts +37 -0
- package/dist/lib/returnpro/upload-netsuite.js +566 -0
- package/dist/lib/returnpro/upload-r1.d.ts +48 -0
- package/dist/lib/returnpro/upload-r1.js +398 -0
- package/dist/lib/returnpro/validate.d.ts +37 -0
- package/dist/lib/returnpro/validate.js +124 -0
- package/dist/lib/social/meta.d.ts +90 -0
- package/dist/lib/social/meta.js +160 -0
- package/dist/lib/social/post-generator.d.ts +83 -0
- package/dist/lib/social/post-generator.js +333 -0
- package/dist/lib/social/publish.d.ts +66 -0
- package/dist/lib/social/publish.js +226 -0
- package/dist/lib/social/scraper.d.ts +67 -0
- package/dist/lib/social/scraper.js +361 -0
- package/dist/lib/supabase.d.ts +4 -0
- package/dist/lib/supabase.js +20 -0
- package/dist/lib/transactions/delete-batch.d.ts +60 -0
- package/dist/lib/transactions/delete-batch.js +203 -0
- package/dist/lib/transactions/ingest.d.ts +43 -0
- package/dist/lib/transactions/ingest.js +555 -0
- package/dist/lib/transactions/stamp.d.ts +51 -0
- package/dist/lib/transactions/stamp.js +524 -0
- package/package.json +3 -4
- package/bin/optimal.ts +0 -1731
- package/lib/assets/index.ts +0 -225
- package/lib/assets.ts +0 -124
- package/lib/auth/index.ts +0 -189
- package/lib/board/index.ts +0 -309
- package/lib/board/types.ts +0 -124
- package/lib/bot/claim.ts +0 -43
- package/lib/bot/coordinator.ts +0 -254
- package/lib/bot/heartbeat.ts +0 -37
- package/lib/bot/index.ts +0 -9
- package/lib/bot/protocol.ts +0 -99
- package/lib/bot/reporter.ts +0 -42
- package/lib/bot/skills.ts +0 -81
- package/lib/budget/projections.ts +0 -561
- package/lib/budget/scenarios.ts +0 -312
- package/lib/cms/publish-blog.ts +0 -129
- package/lib/cms/strapi-client.ts +0 -302
- package/lib/config/registry.ts +0 -228
- package/lib/config/schema.ts +0 -58
- package/lib/config.ts +0 -247
- package/lib/errors.ts +0 -129
- package/lib/format.ts +0 -120
- package/lib/infra/.gitkeep +0 -0
- package/lib/infra/deploy.ts +0 -70
- package/lib/infra/migrate.ts +0 -141
- package/lib/newsletter/.gitkeep +0 -0
- package/lib/newsletter/distribute.ts +0 -256
- package/lib/newsletter/generate.ts +0 -735
- package/lib/returnpro/.gitkeep +0 -0
- package/lib/returnpro/anomalies.ts +0 -258
- package/lib/returnpro/audit.ts +0 -194
- package/lib/returnpro/diagnose.ts +0 -400
- package/lib/returnpro/kpis.ts +0 -255
- package/lib/returnpro/templates.ts +0 -323
- package/lib/returnpro/upload-income.ts +0 -311
- package/lib/returnpro/upload-netsuite.ts +0 -696
- package/lib/returnpro/upload-r1.ts +0 -563
- package/lib/returnpro/validate.ts +0 -154
- package/lib/social/meta.ts +0 -228
- package/lib/social/post-generator.ts +0 -468
- package/lib/social/publish.ts +0 -301
- package/lib/social/scraper.ts +0 -503
- package/lib/supabase.ts +0 -25
- package/lib/transactions/delete-batch.ts +0 -258
- package/lib/transactions/ingest.ts +0 -659
- package/lib/transactions/stamp.ts +0 -654
package/lib/bot/coordinator.ts
DELETED
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
listTasks,
|
|
3
|
-
updateTask,
|
|
4
|
-
claimTask,
|
|
5
|
-
logActivity,
|
|
6
|
-
listActivity,
|
|
7
|
-
type Task,
|
|
8
|
-
} from '../board/index.js'
|
|
9
|
-
import { sendHeartbeat, getActiveAgents } from './heartbeat.js'
|
|
10
|
-
import { claimNextTask } from './claim.js'
|
|
11
|
-
import { getAgentProfiles, matchTasksToAgent, type AgentProfile } from './skills.js'
|
|
12
|
-
|
|
13
|
-
// --- Types ---
|
|
14
|
-
|
|
15
|
-
export interface CoordinatorConfig {
|
|
16
|
-
pollIntervalMs: number
|
|
17
|
-
maxAgents: number
|
|
18
|
-
autoAssign: boolean
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface CoordinatorStatus {
|
|
22
|
-
activeAgents: { agent: string; status: string; lastSeen: string }[]
|
|
23
|
-
idleAgents: AgentProfile[]
|
|
24
|
-
tasksInProgress: number
|
|
25
|
-
tasksReady: number
|
|
26
|
-
tasksBlocked: number
|
|
27
|
-
lastPollAt: string | null
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface RebalanceResult {
|
|
31
|
-
releasedTasks: Task[]
|
|
32
|
-
reassignedTasks: Task[]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// --- State ---
|
|
36
|
-
|
|
37
|
-
const DEFAULT_CONFIG: CoordinatorConfig = {
|
|
38
|
-
pollIntervalMs: 30_000,
|
|
39
|
-
maxAgents: 10,
|
|
40
|
-
autoAssign: true,
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
let lastPollAt: string | null = null
|
|
44
|
-
|
|
45
|
-
// --- Coordinator loop ---
|
|
46
|
-
|
|
47
|
-
export async function runCoordinatorLoop(
|
|
48
|
-
config?: Partial<CoordinatorConfig>,
|
|
49
|
-
): Promise<void> {
|
|
50
|
-
const cfg: CoordinatorConfig = { ...DEFAULT_CONFIG, ...config }
|
|
51
|
-
let running = true
|
|
52
|
-
|
|
53
|
-
const shutdown = () => {
|
|
54
|
-
running = false
|
|
55
|
-
console.log('\nCoordinator shutting down...')
|
|
56
|
-
}
|
|
57
|
-
process.on('SIGINT', shutdown)
|
|
58
|
-
|
|
59
|
-
console.log(
|
|
60
|
-
`Coordinator started — poll every ${cfg.pollIntervalMs}ms, max ${cfg.maxAgents} agents, autoAssign=${cfg.autoAssign}`,
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
while (running) {
|
|
64
|
-
try {
|
|
65
|
-
await pollOnce(cfg)
|
|
66
|
-
} catch (err) {
|
|
67
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
68
|
-
console.error(`Coordinator poll error: ${msg}`)
|
|
69
|
-
await logActivity({
|
|
70
|
-
actor: 'coordinator',
|
|
71
|
-
action: 'poll_error',
|
|
72
|
-
new_value: { error: msg },
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Wait for next poll or until interrupted
|
|
77
|
-
await new Promise<void>((resolve) => {
|
|
78
|
-
const timer = setTimeout(resolve, cfg.pollIntervalMs)
|
|
79
|
-
if (!running) {
|
|
80
|
-
clearTimeout(timer)
|
|
81
|
-
resolve()
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
process.removeListener('SIGINT', shutdown)
|
|
87
|
-
console.log('Coordinator stopped.')
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function pollOnce(cfg: CoordinatorConfig): Promise<void> {
|
|
91
|
-
lastPollAt = new Date().toISOString()
|
|
92
|
-
|
|
93
|
-
const profiles = getAgentProfiles().slice(0, cfg.maxAgents)
|
|
94
|
-
const readyTasks = await listTasks({ status: 'ready' })
|
|
95
|
-
const claimedTasks = await listTasks({ status: 'claimed' })
|
|
96
|
-
|
|
97
|
-
// Send heartbeats for all active agents that have claimed tasks
|
|
98
|
-
const activeAgentIds = new Set(
|
|
99
|
-
claimedTasks.map((t) => t.claimed_by).filter(Boolean) as string[],
|
|
100
|
-
)
|
|
101
|
-
for (const agentId of activeAgentIds) {
|
|
102
|
-
await sendHeartbeat(agentId, 'working')
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (!cfg.autoAssign) {
|
|
106
|
-
await logActivity({
|
|
107
|
-
actor: 'coordinator',
|
|
108
|
-
action: 'poll',
|
|
109
|
-
new_value: {
|
|
110
|
-
readyTasks: readyTasks.length,
|
|
111
|
-
activeAgents: activeAgentIds.size,
|
|
112
|
-
autoAssign: false,
|
|
113
|
-
ts: lastPollAt,
|
|
114
|
-
},
|
|
115
|
-
})
|
|
116
|
-
return
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Find idle agents and try to assign tasks
|
|
120
|
-
let assignedCount = 0
|
|
121
|
-
for (const agent of profiles) {
|
|
122
|
-
// Check if agent has capacity
|
|
123
|
-
const agentClaimed = claimedTasks.filter((t) => t.claimed_by === agent.id)
|
|
124
|
-
if (agentClaimed.length >= agent.maxConcurrent) continue
|
|
125
|
-
|
|
126
|
-
// Match available tasks to this agent
|
|
127
|
-
const matched = matchTasksToAgent(agent, readyTasks)
|
|
128
|
-
if (matched.length === 0) continue
|
|
129
|
-
|
|
130
|
-
// Claim the top-priority matched task
|
|
131
|
-
const task = await claimNextTask(agent.id, agent.skills)
|
|
132
|
-
if (task) {
|
|
133
|
-
assignedCount++
|
|
134
|
-
console.log(` Assigned "${task.title}" -> ${agent.id}`)
|
|
135
|
-
// Remove from local readyTasks to avoid double-assign
|
|
136
|
-
const idx = readyTasks.findIndex((t) => t.id === task.id)
|
|
137
|
-
if (idx >= 0) readyTasks.splice(idx, 1)
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
await logActivity({
|
|
142
|
-
actor: 'coordinator',
|
|
143
|
-
action: 'poll',
|
|
144
|
-
new_value: {
|
|
145
|
-
readyTasks: readyTasks.length,
|
|
146
|
-
activeAgents: activeAgentIds.size,
|
|
147
|
-
assigned: assignedCount,
|
|
148
|
-
ts: lastPollAt,
|
|
149
|
-
},
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
if (assignedCount > 0) {
|
|
153
|
-
console.log(`Poll complete: assigned ${assignedCount} task(s)`)
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// --- Status ---
|
|
158
|
-
|
|
159
|
-
export async function getCoordinatorStatus(): Promise<CoordinatorStatus> {
|
|
160
|
-
const profiles = getAgentProfiles()
|
|
161
|
-
const activeAgents = await getActiveAgents()
|
|
162
|
-
const readyTasks = await listTasks({ status: 'ready' })
|
|
163
|
-
const claimedTasks = await listTasks({ status: 'claimed' })
|
|
164
|
-
const inProgressTasks = await listTasks({ status: 'in_progress' })
|
|
165
|
-
const blockedTasks = await listTasks({ status: 'blocked' })
|
|
166
|
-
|
|
167
|
-
const activeIds = new Set(activeAgents.map((a) => a.agent))
|
|
168
|
-
const idleAgents = profiles.filter((p) => !activeIds.has(p.id))
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
activeAgents,
|
|
172
|
-
idleAgents,
|
|
173
|
-
tasksInProgress: claimedTasks.length + inProgressTasks.length,
|
|
174
|
-
tasksReady: readyTasks.length,
|
|
175
|
-
tasksBlocked: blockedTasks.length,
|
|
176
|
-
lastPollAt,
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// --- Manual assignment ---
|
|
181
|
-
|
|
182
|
-
export async function assignTask(
|
|
183
|
-
taskId: string,
|
|
184
|
-
agentId: string,
|
|
185
|
-
): Promise<Task> {
|
|
186
|
-
const task = await claimTask(taskId, agentId)
|
|
187
|
-
|
|
188
|
-
await logActivity({
|
|
189
|
-
actor: 'coordinator',
|
|
190
|
-
action: 'manual_assign',
|
|
191
|
-
task_id: taskId,
|
|
192
|
-
new_value: { agentId, title: task.title },
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
console.log(`Manually assigned "${task.title}" -> ${agentId}`)
|
|
196
|
-
return task
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// --- Rebalance ---
|
|
200
|
-
|
|
201
|
-
export async function rebalance(): Promise<RebalanceResult> {
|
|
202
|
-
const claimedTasks = await listTasks({ status: 'claimed' })
|
|
203
|
-
const now = Date.now()
|
|
204
|
-
const staleThreshold = 60 * 60 * 1000 // 1 hour
|
|
205
|
-
|
|
206
|
-
const releasedTasks: Task[] = []
|
|
207
|
-
const reassignedTasks: Task[] = []
|
|
208
|
-
|
|
209
|
-
for (const task of claimedTasks) {
|
|
210
|
-
if (!task.claimed_at) continue
|
|
211
|
-
|
|
212
|
-
const claimedAge = now - new Date(task.claimed_at).getTime()
|
|
213
|
-
if (claimedAge < staleThreshold) continue
|
|
214
|
-
|
|
215
|
-
// Check for recent activity on this task
|
|
216
|
-
const activity = await listActivity({ task_id: task.id, limit: 5 })
|
|
217
|
-
const recentActivity = activity.some((a) => {
|
|
218
|
-
const age = now - new Date(a.created_at).getTime()
|
|
219
|
-
return age < staleThreshold && a.action !== 'poll'
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
if (recentActivity) continue
|
|
223
|
-
|
|
224
|
-
// Release stale task
|
|
225
|
-
const released = await updateTask(
|
|
226
|
-
task.id,
|
|
227
|
-
{
|
|
228
|
-
status: 'ready',
|
|
229
|
-
claimed_by: null,
|
|
230
|
-
claimed_at: null,
|
|
231
|
-
},
|
|
232
|
-
'coordinator',
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
releasedTasks.push(released)
|
|
236
|
-
|
|
237
|
-
await logActivity({
|
|
238
|
-
actor: 'coordinator',
|
|
239
|
-
action: 'rebalance_release',
|
|
240
|
-
task_id: task.id,
|
|
241
|
-
new_value: {
|
|
242
|
-
previousAgent: task.claimed_by,
|
|
243
|
-
claimedAt: task.claimed_at,
|
|
244
|
-
reason: 'stale_claim',
|
|
245
|
-
},
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
console.log(
|
|
249
|
-
`Released stale task "${task.title}" (was claimed by ${task.claimed_by})`,
|
|
250
|
-
)
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return { releasedTasks, reassignedTasks }
|
|
254
|
-
}
|
package/lib/bot/heartbeat.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { logActivity, listActivity } from '../board/index.js'
|
|
2
|
-
|
|
3
|
-
export async function sendHeartbeat(
|
|
4
|
-
agentId: string,
|
|
5
|
-
status: 'idle' | 'working' | 'error',
|
|
6
|
-
): Promise<void> {
|
|
7
|
-
await logActivity({
|
|
8
|
-
actor: agentId,
|
|
9
|
-
action: 'heartbeat',
|
|
10
|
-
new_value: { status, ts: new Date().toISOString() },
|
|
11
|
-
})
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export async function getActiveAgents(): Promise<
|
|
15
|
-
{ agent: string; status: string; lastSeen: string }[]
|
|
16
|
-
> {
|
|
17
|
-
const entries = await listActivity({ limit: 200 })
|
|
18
|
-
const cutoff = Date.now() - 5 * 60 * 1000
|
|
19
|
-
|
|
20
|
-
const latest = new Map<string, { status: string; lastSeen: string }>()
|
|
21
|
-
|
|
22
|
-
for (const e of entries) {
|
|
23
|
-
if (e.action !== 'heartbeat') continue
|
|
24
|
-
if (new Date(e.created_at).getTime() < cutoff) continue
|
|
25
|
-
if (latest.has(e.actor)) continue
|
|
26
|
-
const nv = e.new_value as { status?: string } | null
|
|
27
|
-
latest.set(e.actor, {
|
|
28
|
-
status: nv?.status ?? 'unknown',
|
|
29
|
-
lastSeen: e.created_at,
|
|
30
|
-
})
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return Array.from(latest.entries()).map(([agent, info]) => ({
|
|
34
|
-
agent,
|
|
35
|
-
...info,
|
|
36
|
-
}))
|
|
37
|
-
}
|
package/lib/bot/index.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export { sendHeartbeat, getActiveAgents } from './heartbeat.js'
|
|
2
|
-
export { claimNextTask, releaseTask } from './claim.js'
|
|
3
|
-
export { reportProgress, reportCompletion, reportBlocked } from './reporter.js'
|
|
4
|
-
export { getAgentProfiles, matchTasksToAgent, findBestAgent } from './skills.js'
|
|
5
|
-
export type { AgentProfile } from './skills.js'
|
|
6
|
-
export { runCoordinatorLoop, getCoordinatorStatus, assignTask, rebalance } from './coordinator.js'
|
|
7
|
-
export type { CoordinatorConfig, CoordinatorStatus, RebalanceResult } from './coordinator.js'
|
|
8
|
-
export { processAgentMessage } from './protocol.js'
|
|
9
|
-
export type { AgentMessage, AgentResponse } from './protocol.js'
|
package/lib/bot/protocol.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { sendHeartbeat } from './heartbeat.js'
|
|
2
|
-
import { claimNextTask, releaseTask } from './claim.js'
|
|
3
|
-
import { reportProgress, reportCompletion, reportBlocked } from './reporter.js'
|
|
4
|
-
|
|
5
|
-
// --- Types ---
|
|
6
|
-
|
|
7
|
-
export interface AgentMessage {
|
|
8
|
-
type: 'heartbeat' | 'claim' | 'progress' | 'complete' | 'blocked' | 'release'
|
|
9
|
-
agentId: string
|
|
10
|
-
taskId?: string
|
|
11
|
-
payload?: Record<string, unknown>
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface AgentResponse {
|
|
15
|
-
success: boolean
|
|
16
|
-
data?: unknown
|
|
17
|
-
error?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// --- Message processor ---
|
|
21
|
-
|
|
22
|
-
export async function processAgentMessage(
|
|
23
|
-
msg: AgentMessage,
|
|
24
|
-
): Promise<AgentResponse> {
|
|
25
|
-
try {
|
|
26
|
-
switch (msg.type) {
|
|
27
|
-
case 'heartbeat':
|
|
28
|
-
return await handleHeartbeat(msg)
|
|
29
|
-
case 'claim':
|
|
30
|
-
return await handleClaim(msg)
|
|
31
|
-
case 'progress':
|
|
32
|
-
return await handleProgress(msg)
|
|
33
|
-
case 'complete':
|
|
34
|
-
return await handleComplete(msg)
|
|
35
|
-
case 'blocked':
|
|
36
|
-
return await handleBlocked(msg)
|
|
37
|
-
case 'release':
|
|
38
|
-
return await handleRelease(msg)
|
|
39
|
-
default:
|
|
40
|
-
return { success: false, error: `Unknown message type: ${(msg as AgentMessage).type}` }
|
|
41
|
-
}
|
|
42
|
-
} catch (err) {
|
|
43
|
-
const errorMsg = err instanceof Error ? err.message : String(err)
|
|
44
|
-
return { success: false, error: errorMsg }
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// --- Handlers ---
|
|
49
|
-
|
|
50
|
-
async function handleHeartbeat(msg: AgentMessage): Promise<AgentResponse> {
|
|
51
|
-
const status = (msg.payload?.status as 'idle' | 'working' | 'error') ?? 'idle'
|
|
52
|
-
await sendHeartbeat(msg.agentId, status)
|
|
53
|
-
return { success: true, data: { agentId: msg.agentId, status } }
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function handleClaim(msg: AgentMessage): Promise<AgentResponse> {
|
|
57
|
-
const skills = msg.payload?.skills as string[] | undefined
|
|
58
|
-
const task = await claimNextTask(msg.agentId, skills)
|
|
59
|
-
if (!task) {
|
|
60
|
-
return { success: true, data: null }
|
|
61
|
-
}
|
|
62
|
-
return { success: true, data: { taskId: task.id, title: task.title } }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function handleProgress(msg: AgentMessage): Promise<AgentResponse> {
|
|
66
|
-
if (!msg.taskId) {
|
|
67
|
-
return { success: false, error: 'taskId is required for progress messages' }
|
|
68
|
-
}
|
|
69
|
-
const message = (msg.payload?.message as string) ?? 'Progress update'
|
|
70
|
-
await reportProgress(msg.taskId, msg.agentId, message)
|
|
71
|
-
return { success: true, data: { taskId: msg.taskId } }
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function handleComplete(msg: AgentMessage): Promise<AgentResponse> {
|
|
75
|
-
if (!msg.taskId) {
|
|
76
|
-
return { success: false, error: 'taskId is required for complete messages' }
|
|
77
|
-
}
|
|
78
|
-
const summary = (msg.payload?.summary as string) ?? 'Task completed'
|
|
79
|
-
await reportCompletion(msg.taskId, msg.agentId, summary)
|
|
80
|
-
return { success: true, data: { taskId: msg.taskId, status: 'done' } }
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async function handleBlocked(msg: AgentMessage): Promise<AgentResponse> {
|
|
84
|
-
if (!msg.taskId) {
|
|
85
|
-
return { success: false, error: 'taskId is required for blocked messages' }
|
|
86
|
-
}
|
|
87
|
-
const reason = (msg.payload?.reason as string) ?? 'No reason given'
|
|
88
|
-
await reportBlocked(msg.taskId, msg.agentId, reason)
|
|
89
|
-
return { success: true, data: { taskId: msg.taskId, status: 'blocked' } }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function handleRelease(msg: AgentMessage): Promise<AgentResponse> {
|
|
93
|
-
if (!msg.taskId) {
|
|
94
|
-
return { success: false, error: 'taskId is required for release messages' }
|
|
95
|
-
}
|
|
96
|
-
const reason = msg.payload?.reason as string | undefined
|
|
97
|
-
const task = await releaseTask(msg.taskId, msg.agentId, reason)
|
|
98
|
-
return { success: true, data: { taskId: task.id, status: 'ready' } }
|
|
99
|
-
}
|
package/lib/bot/reporter.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { addComment, updateTask, completeTask } from '../board/index.js'
|
|
2
|
-
|
|
3
|
-
export async function reportProgress(
|
|
4
|
-
taskId: string,
|
|
5
|
-
agentId: string,
|
|
6
|
-
message: string,
|
|
7
|
-
): Promise<void> {
|
|
8
|
-
await addComment({
|
|
9
|
-
task_id: taskId,
|
|
10
|
-
author: agentId,
|
|
11
|
-
body: message,
|
|
12
|
-
comment_type: 'comment',
|
|
13
|
-
})
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function reportCompletion(
|
|
17
|
-
taskId: string,
|
|
18
|
-
agentId: string,
|
|
19
|
-
summary: string,
|
|
20
|
-
): Promise<void> {
|
|
21
|
-
await completeTask(taskId, agentId)
|
|
22
|
-
await addComment({
|
|
23
|
-
task_id: taskId,
|
|
24
|
-
author: agentId,
|
|
25
|
-
body: summary,
|
|
26
|
-
comment_type: 'status_change',
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export async function reportBlocked(
|
|
31
|
-
taskId: string,
|
|
32
|
-
agentId: string,
|
|
33
|
-
reason: string,
|
|
34
|
-
): Promise<void> {
|
|
35
|
-
await updateTask(taskId, { status: 'blocked' }, agentId)
|
|
36
|
-
await addComment({
|
|
37
|
-
task_id: taskId,
|
|
38
|
-
author: agentId,
|
|
39
|
-
body: `Blocked: ${reason}`,
|
|
40
|
-
comment_type: 'status_change',
|
|
41
|
-
})
|
|
42
|
-
}
|
package/lib/bot/skills.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { readFileSync, existsSync } from 'node:fs'
|
|
2
|
-
import { resolve, dirname } from 'node:path'
|
|
3
|
-
import { fileURLToPath } from 'node:url'
|
|
4
|
-
import type { Task } from '../board/types.js'
|
|
5
|
-
|
|
6
|
-
export interface AgentProfile {
|
|
7
|
-
id: string
|
|
8
|
-
skills: string[]
|
|
9
|
-
maxConcurrent: number
|
|
10
|
-
status: 'idle' | 'working' | 'error'
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const DEFAULT_PROFILE: AgentProfile = {
|
|
14
|
-
id: 'default',
|
|
15
|
-
skills: ['*'],
|
|
16
|
-
maxConcurrent: 1,
|
|
17
|
-
status: 'idle',
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Reads agent profiles from agents/profiles.json.
|
|
22
|
-
* Falls back to a single default wildcard profile if the file doesn't exist.
|
|
23
|
-
*/
|
|
24
|
-
export function getAgentProfiles(): AgentProfile[] {
|
|
25
|
-
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
26
|
-
const profilesPath = resolve(root, 'agents', 'profiles.json')
|
|
27
|
-
|
|
28
|
-
if (!existsSync(profilesPath)) {
|
|
29
|
-
return [DEFAULT_PROFILE]
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
const raw = readFileSync(profilesPath, 'utf-8')
|
|
34
|
-
const parsed = JSON.parse(raw) as AgentProfile[]
|
|
35
|
-
return parsed
|
|
36
|
-
} catch {
|
|
37
|
-
return [DEFAULT_PROFILE]
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Filters and ranks tasks an agent can work on.
|
|
43
|
-
*
|
|
44
|
-
* - Includes tasks whose skill_required is in the agent's skills list,
|
|
45
|
-
* or tasks with no skill_required, or if agent has wildcard '*'.
|
|
46
|
-
* - Sorts by priority (P1 first), then sort_order.
|
|
47
|
-
* - Returns at most agent.maxConcurrent tasks.
|
|
48
|
-
*/
|
|
49
|
-
export function matchTasksToAgent(agent: AgentProfile, tasks: Task[]): Task[] {
|
|
50
|
-
const hasWildcard = agent.skills.includes('*')
|
|
51
|
-
|
|
52
|
-
const matched = tasks.filter((t) => {
|
|
53
|
-
if (hasWildcard) return true
|
|
54
|
-
if (!t.skill_required) return true
|
|
55
|
-
return agent.skills.includes(t.skill_required)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
matched.sort((a, b) => {
|
|
59
|
-
if (a.priority !== b.priority) return a.priority - b.priority
|
|
60
|
-
return a.sort_order - b.sort_order
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
return matched.slice(0, agent.maxConcurrent)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Finds the first idle agent whose skills match the task's skill_required.
|
|
68
|
-
* Returns null if no suitable idle agent exists.
|
|
69
|
-
*/
|
|
70
|
-
export function findBestAgent(
|
|
71
|
-
profiles: AgentProfile[],
|
|
72
|
-
task: Task,
|
|
73
|
-
): AgentProfile | null {
|
|
74
|
-
for (const agent of profiles) {
|
|
75
|
-
if (agent.status !== 'idle') continue
|
|
76
|
-
if (agent.skills.includes('*')) return agent
|
|
77
|
-
if (!task.skill_required) return agent
|
|
78
|
-
if (agent.skills.includes(task.skill_required)) return agent
|
|
79
|
-
}
|
|
80
|
-
return null
|
|
81
|
-
}
|