@ziggs-ai/agent-sdk 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -0
- package/package.json +26 -0
- package/src/ConnectionPool.js +133 -0
- package/src/adapters/OpenAIAdapter.js +73 -0
- package/src/adapters/index.js +1 -0
- package/src/agent/Agent.js +121 -0
- package/src/agent/EventQueue.js +68 -0
- package/src/agent/OutboxBuffer.js +62 -0
- package/src/cognition/PromptBuilder.js +312 -0
- package/src/cognition/resolveActionTool.js +12 -0
- package/src/cognition/runTurn.js +578 -0
- package/src/context/applyEffects.js +133 -0
- package/src/context/batch.js +25 -0
- package/src/context/classifyEnvelope.js +82 -0
- package/src/context/routingLabels.js +54 -0
- package/src/createHealthServer.js +28 -0
- package/src/formatters/HistoryFormatter.js +257 -0
- package/src/formatters/TaskFormatter.js +180 -0
- package/src/formatters/index.js +9 -0
- package/src/index.js +76 -0
- package/src/ingress/normalizeIncoming.js +70 -0
- package/src/runLauncher.js +159 -0
- package/src/shared/ids.js +7 -0
- package/src/shared/types.js +86 -0
- package/src/tasks/TaskService.js +247 -0
- package/src/tasks/index.js +9 -0
- package/src/tasks/taskCore.js +229 -0
- package/src/tasks/taskProtocolRegistry.js +22 -0
- package/src/tasks/taskProtocolRunner.js +107 -0
- package/src/tasks/taskProtocolTools.js +87 -0
- package/src/tools/ToolManager.js +79 -0
- package/src/tools/ToolProvider.js +29 -0
- package/src/tools/defineTool.js +82 -0
- package/src/tools/index.js +11 -0
- package/src/utils/jsonExtractor.js +139 -0
- package/src/workflow/AgentMachine.js +250 -0
- package/src/workflow/WorkflowRuntime.js +63 -0
- package/src/workflow/dsl.js +287 -0
- package/src/workflow/motifs.js +435 -0
- package/src/ziggs/runtime.js +192 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TaskFormatter - Pluggable formatter for converting raw task data to readable text.
|
|
3
|
+
*
|
|
4
|
+
* This formatter is external and pluggable - can be swapped with custom implementations
|
|
5
|
+
* by passing a different formatter to PromptBuilder.
|
|
6
|
+
*/
|
|
7
|
+
export class TaskFormatter {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
this.shortIds = options.shortIds ?? false; // Always show full IDs by default
|
|
11
|
+
this.indentSize = options.indentSize ?? 2;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Format an array of tasks into readable text
|
|
16
|
+
* @param {Array} tasks - Array of tasks from context
|
|
17
|
+
* @param {string} agentId - The current agent's ID (for "You" labels)
|
|
18
|
+
* @returns {string} Formatted tasks text
|
|
19
|
+
*/
|
|
20
|
+
format(tasks, agentId) {
|
|
21
|
+
if (!tasks?.length) return 'No active tasks.';
|
|
22
|
+
|
|
23
|
+
// Separate pending proposals from active/other tasks
|
|
24
|
+
const pending = tasks.filter(t =>
|
|
25
|
+
t.state === 'proposal' && t.proposal?.status === 'pending'
|
|
26
|
+
);
|
|
27
|
+
const active = tasks.filter(t =>
|
|
28
|
+
t.state !== 'proposal' || t.proposal?.status !== 'pending'
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const sections = [];
|
|
32
|
+
|
|
33
|
+
// Show pending proposals first (needs attention)
|
|
34
|
+
if (pending.length > 0) {
|
|
35
|
+
sections.push('Pending Proposals (waiting for approval):');
|
|
36
|
+
pending.forEach(t => {
|
|
37
|
+
sections.push(this.formatTask(t, agentId, this._indent(1)));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Show active tasks
|
|
42
|
+
if (active.length > 0) {
|
|
43
|
+
if (sections.length > 0) sections.push('');
|
|
44
|
+
sections.push('Active Tasks:');
|
|
45
|
+
active.forEach(t => {
|
|
46
|
+
sections.push(this.formatTask(t, agentId, this._indent(1)));
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return sections.join('\n');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Format a single task
|
|
55
|
+
*/
|
|
56
|
+
formatTask(task, agentId, indent = '') {
|
|
57
|
+
const state = (task.state || 'unknown').toUpperCase();
|
|
58
|
+
const id = this.shortIds ? this.shortId(task.taskId) : task.taskId;
|
|
59
|
+
const desc = task.description || 'No description';
|
|
60
|
+
|
|
61
|
+
const isOwner = task.agentId === agentId;
|
|
62
|
+
const isExecutor = task.executorId === agentId;
|
|
63
|
+
const proposedToYou = task.proposal?.proposedTo === agentId;
|
|
64
|
+
|
|
65
|
+
const lines = [
|
|
66
|
+
`${indent}[${state}] ${desc}`,
|
|
67
|
+
`${indent} ID: ${id}`,
|
|
68
|
+
`${indent} Owner: ${isOwner ? 'You' : task.agentId}`,
|
|
69
|
+
`${indent} Executor: ${isExecutor ? 'You' : task.executorId}`,
|
|
70
|
+
`${indent} Payer: ${task.payerId || 'N/A'}`
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
// Add proposal info if present
|
|
74
|
+
if (task.proposal) {
|
|
75
|
+
const proposalStatus = task.proposal.status || 'unknown';
|
|
76
|
+
const proposedTo = proposedToYou ? 'You' : task.proposal.proposedTo;
|
|
77
|
+
lines.push(`${indent} Proposal: ${proposalStatus} (to ${proposedTo})`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Add parent task reference if this is a subtask
|
|
81
|
+
if (task.parentTaskId) {
|
|
82
|
+
const parentId = this.shortIds ? this.shortId(task.parentTaskId) : task.parentTaskId;
|
|
83
|
+
lines.push(`${indent} Parent: ${parentId}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Orchestration uses contract.specialistAgentId without lifecycle
|
|
87
|
+
const c = task.contract;
|
|
88
|
+
if (c && typeof c === 'object') {
|
|
89
|
+
const contractParts = [];
|
|
90
|
+
if (c.specialistAgentId) contractParts.push(`specialistAgentId: ${c.specialistAgentId}`);
|
|
91
|
+
if (Array.isArray(c.specialistAgentIds) && c.specialistAgentIds.length > 0) {
|
|
92
|
+
contractParts.push(`specialistAgentIds: [${c.specialistAgentIds.join(', ')}]`);
|
|
93
|
+
}
|
|
94
|
+
if (c.lifecycle && c.lifecycle !== 'open') {
|
|
95
|
+
contractParts.push(`lifecycle: ${c.lifecycle}`);
|
|
96
|
+
if (c.maxExecutions != null) contractParts.push(`maxExecutions: ${c.maxExecutions}`);
|
|
97
|
+
if (c.expiresAt) contractParts.push(`expiresAt: ${c.expiresAt}`);
|
|
98
|
+
}
|
|
99
|
+
if (contractParts.length > 0) {
|
|
100
|
+
lines.push(`${indent} Contract: ${contractParts.join(', ')}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Add perspective info if present (cross-chat reporting)
|
|
105
|
+
if (task.perspective?.ownerChatId) {
|
|
106
|
+
lines.push(`${indent} Report to: ${task.perspective.ownerChatId}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
// Add result if completed/failed
|
|
111
|
+
if (task.result && (task.state === 'completed' || task.state === 'failed')) {
|
|
112
|
+
const resultPreview = task.result.length > 100
|
|
113
|
+
? task.result.slice(0, 100) + '...'
|
|
114
|
+
: task.result;
|
|
115
|
+
lines.push(`${indent} Result: ${resultPreview}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Add plan steps if present
|
|
119
|
+
if (task.plan?.steps?.length > 0) {
|
|
120
|
+
const sorted = [...task.plan.steps].sort((a, b) => a.order - b.order);
|
|
121
|
+
lines.push(`${indent} Plan (${sorted.filter(s => s.status === 'completed').length}/${sorted.length} completed):`);
|
|
122
|
+
sorted.forEach(step => {
|
|
123
|
+
const icon = { pending: '○', in_progress: '●', completed: '✓', skipped: '–' }[step.status] || '?';
|
|
124
|
+
const resultNote = step.status === 'completed' && step.result
|
|
125
|
+
? ` → ${String(step.result).slice(0, 60)}${String(step.result).length > 60 ? '...' : ''}`
|
|
126
|
+
: '';
|
|
127
|
+
lines.push(`${indent} ${step.order}. [${icon}] ${step.description || step.stepId}${resultNote}`);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Add subtasks if present
|
|
132
|
+
if (task.subtasks?.length > 0) {
|
|
133
|
+
lines.push(`${indent} Subtasks (${task.subtasks.length}):`);
|
|
134
|
+
task.subtasks.forEach(subtask => {
|
|
135
|
+
lines.push(this.formatSubtask(subtask, agentId, indent + ' '));
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return lines.join('\n');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Format a subtask (simplified version)
|
|
144
|
+
*/
|
|
145
|
+
formatSubtask(subtask, agentId, indent = '') {
|
|
146
|
+
const state = (subtask.state || 'unknown').toUpperCase();
|
|
147
|
+
const id = this.shortIds ? this.shortId(subtask.taskId) : subtask.taskId;
|
|
148
|
+
const desc = subtask.description || 'No description';
|
|
149
|
+
const isExecutor = subtask.executorId === agentId;
|
|
150
|
+
|
|
151
|
+
return `${indent}[${state}] ${desc} (${id}, executor: ${isExecutor ? 'You' : subtask.executorId})`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Return full task ID (or shortened if shortIds option is enabled)
|
|
156
|
+
*/
|
|
157
|
+
shortId(taskId) {
|
|
158
|
+
if (!taskId) return '?';
|
|
159
|
+
|
|
160
|
+
// If shortIds is disabled, always return full ID
|
|
161
|
+
if (!this.shortIds) return taskId;
|
|
162
|
+
|
|
163
|
+
// Otherwise, shorten it
|
|
164
|
+
const parts = taskId.split('_');
|
|
165
|
+
if (parts.length >= 3) {
|
|
166
|
+
return `task_...${parts[parts.length - 1]}`;
|
|
167
|
+
}
|
|
168
|
+
return taskId.length > 20 ? taskId.slice(0, 20) + '...' : taskId;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Generate indentation string
|
|
173
|
+
*/
|
|
174
|
+
_indent(level) {
|
|
175
|
+
return ' '.repeat(level * this.indentSize);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Default instance for easy import
|
|
180
|
+
export const taskFormatter = new TaskFormatter();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatters - Pluggable formatting utilities for agent prompts
|
|
3
|
+
*
|
|
4
|
+
* These formatters convert raw data into readable text for the prompt.
|
|
5
|
+
* They are external and pluggable - can be swapped with custom implementations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { HistoryFormatter, historyFormatter } from './HistoryFormatter.js';
|
|
9
|
+
export { TaskFormatter, taskFormatter } from './TaskFormatter.js';
|
package/src/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package entry. Domain layout (imports flow downward only):
|
|
3
|
+
*
|
|
4
|
+
* ziggs/ WebSocket + DI + ZiggsAgent / createAgent / defineAgent
|
|
5
|
+
* agent/ Process: Agent, EventQueue, OutboxBuffer
|
|
6
|
+
* workflow/ AgentMachine, WorkflowRuntime
|
|
7
|
+
* cognition/ runTurn, PromptBuilder, resolveActionTool
|
|
8
|
+
* ingress/ normalizeIncomingEvent (wire → envelope)
|
|
9
|
+
* context/ batch, routing flags, applyEffects, workflow labels
|
|
10
|
+
* tasks/ TaskService + Ziggs task protocol tools
|
|
11
|
+
* tools/ ToolManager, defineTool
|
|
12
|
+
* adapters/ OpenAIAdapter
|
|
13
|
+
* formatters/ history + task text for prompts
|
|
14
|
+
* utils/ jsonExtractor
|
|
15
|
+
* shared/ types, ids
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
ZiggsAgent,
|
|
20
|
+
createAgent,
|
|
21
|
+
defineAgent,
|
|
22
|
+
} from './ziggs/runtime.js';
|
|
23
|
+
|
|
24
|
+
export { Agent } from './agent/Agent.js';
|
|
25
|
+
export { EventQueue } from './agent/EventQueue.js';
|
|
26
|
+
export { OutboxBuffer } from './agent/OutboxBuffer.js';
|
|
27
|
+
export { normalizeIncomingEvent } from './ingress/normalizeIncoming.js';
|
|
28
|
+
export * from './shared/types.js';
|
|
29
|
+
|
|
30
|
+
export { AgentMachine } from './workflow/AgentMachine.js';
|
|
31
|
+
export { WorkflowRuntime } from './workflow/WorkflowRuntime.js';
|
|
32
|
+
export { agent, parseDsl } from './workflow/dsl.js';
|
|
33
|
+
export { runTurn } from './cognition/runTurn.js';
|
|
34
|
+
export { resolveActionTool } from './cognition/resolveActionTool.js';
|
|
35
|
+
export { PromptBuilder } from './cognition/PromptBuilder.js';
|
|
36
|
+
export {
|
|
37
|
+
TASK_PROTOCOL_TOOLS,
|
|
38
|
+
taskMakeTaskTool,
|
|
39
|
+
taskMakeSubTasksTool,
|
|
40
|
+
taskUpdateTaskTool,
|
|
41
|
+
taskRespondProposalTool,
|
|
42
|
+
taskUpdatePlanStepTool,
|
|
43
|
+
} from './tasks/taskProtocolTools.js';
|
|
44
|
+
export {
|
|
45
|
+
TASK_PROTOCOL_TOOL_NAMES,
|
|
46
|
+
TASK_PROTOCOL_TOOL_TO_OPERATION,
|
|
47
|
+
mapTaskProtocolToolToOperation,
|
|
48
|
+
isTaskProtocolToolName,
|
|
49
|
+
} from './tasks/taskProtocolRegistry.js';
|
|
50
|
+
export { executeTaskPayload } from './tasks/taskProtocolRunner.js';
|
|
51
|
+
export { classifyIncomingEvent } from './context/classifyEnvelope.js';
|
|
52
|
+
export { buildContextUpdates, CONTEXT_RESET } from './context/applyEffects.js';
|
|
53
|
+
export { getBatchEvents, isTaskResultRelevantToAgent } from './context/batch.js';
|
|
54
|
+
export {
|
|
55
|
+
classifyWorkflowEvent,
|
|
56
|
+
findTaskResult,
|
|
57
|
+
unwrapBatchEvent,
|
|
58
|
+
findIncomingTaskResult,
|
|
59
|
+
} from './context/routingLabels.js';
|
|
60
|
+
|
|
61
|
+
export { TaskService } from './tasks/TaskService.js';
|
|
62
|
+
export { ToolManager } from './tools/ToolManager.js';
|
|
63
|
+
export { ToolProvider } from './tools/ToolProvider.js';
|
|
64
|
+
export { defineTool } from './tools/defineTool.js';
|
|
65
|
+
export { OpenAIAdapter } from './adapters/OpenAIAdapter.js';
|
|
66
|
+
export { extractJSON, safeParseJSON } from './utils/jsonExtractor.js';
|
|
67
|
+
export { HistoryFormatter, historyFormatter, TaskFormatter, taskFormatter } from './formatters/index.js';
|
|
68
|
+
export { WebSocketClient, ContextReader, ContextWriter, AgentSearchClient, getBackendUrl, getWebSocketUrl } from '@ziggs-ai/api-client';
|
|
69
|
+
|
|
70
|
+
export { default } from './ziggs/runtime.js';
|
|
71
|
+
|
|
72
|
+
// ConnectionPool — opt-in lazy WebSocket pool for large-scale dynamic agent fleets.
|
|
73
|
+
// Use via createAgent(configs, { lazy: true }) or by direct construction.
|
|
74
|
+
export { ConnectionPool } from './ConnectionPool.js';
|
|
75
|
+
export { createHealthServer } from './createHealthServer.js';
|
|
76
|
+
export { runLauncher } from './runLauncher.js';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts external metadata + text into canonical event payloads and routing hints.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { OPEN_PROPOSAL_TARGET } from '../tasks/taskCore.js';
|
|
6
|
+
|
|
7
|
+
function normalizeChatId(metadata, taskData) {
|
|
8
|
+
return metadata.chatId || metadata.chat_id || taskData?.chatId || taskData?.perspective?.ownerChatId || null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeReceiverId(metadata, taskData) {
|
|
12
|
+
return metadata.receiver?.id || metadata.receiverId || metadata.to?.id || taskData?.executorId || taskData?.proposedTo || null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isTaskRelated(metadata, taskData) {
|
|
16
|
+
if (!taskData?.taskId) return false;
|
|
17
|
+
const entryType = metadata.entryType || 'message';
|
|
18
|
+
const contentType = metadata.content_type || metadata.contentType || 'text';
|
|
19
|
+
return (
|
|
20
|
+
entryType === 'notification' || entryType === 'task_history'
|
|
21
|
+
|| contentType === 'task' || contentType === 'task_update'
|
|
22
|
+
|| Boolean(taskData.state || taskData.proposal || taskData.parentTaskId || taskData.executorId || taskData.agentId || taskData.payerId || taskData.createdBy)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isRelevantForAgent(ids, ownAgentId) {
|
|
27
|
+
if (!ownAgentId) return true;
|
|
28
|
+
return ids.includes(ownAgentId) || ids.includes(OPEN_PROPOSAL_TARGET);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function normalizeIncomingEvent({ text, metadata = {}, ownAgentId = null }) {
|
|
32
|
+
const taskData = metadata.service?.task || metadata.task || null;
|
|
33
|
+
const chatId = normalizeChatId(metadata, taskData);
|
|
34
|
+
const receiverId = normalizeReceiverId(metadata, taskData);
|
|
35
|
+
const senderType = metadata.sender?.type ? String(metadata.sender.type).toUpperCase() : undefined;
|
|
36
|
+
|
|
37
|
+
const base = {
|
|
38
|
+
senderId: metadata.sender?.id,
|
|
39
|
+
senderType,
|
|
40
|
+
receiverId: receiverId || undefined,
|
|
41
|
+
timestamp: Date.now(),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (isTaskRelated(metadata, taskData)) {
|
|
45
|
+
const receiverFromMeta = metadata.receiver?.id || metadata.receiverId || null;
|
|
46
|
+
const candidateIds = [
|
|
47
|
+
taskData.agentId,
|
|
48
|
+
taskData.executorId,
|
|
49
|
+
taskData.payerId,
|
|
50
|
+
taskData.proposedTo,
|
|
51
|
+
taskData.createdBy,
|
|
52
|
+
receiverFromMeta,
|
|
53
|
+
].filter(Boolean);
|
|
54
|
+
const relevant = isRelevantForAgent(candidateIds, ownAgentId);
|
|
55
|
+
return {
|
|
56
|
+
chatId,
|
|
57
|
+
event: { ...base, type: 'task_result', result: taskData, text },
|
|
58
|
+
shouldProcess: relevant,
|
|
59
|
+
reason: relevant ? null : 'task_not_targeted_to_agent',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const relevant = !ownAgentId || !receiverId || receiverId === ownAgentId || receiverId === OPEN_PROPOSAL_TARGET;
|
|
64
|
+
return {
|
|
65
|
+
chatId,
|
|
66
|
+
event: { ...base, type: 'message', text },
|
|
67
|
+
shouldProcess: relevant,
|
|
68
|
+
reason: relevant ? null : 'message_not_targeted_to_agent',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runLauncher — one entry point to run an agent or a fleet.
|
|
3
|
+
*
|
|
4
|
+
* runLauncher(config, opts) → single eager agent (hello-world)
|
|
5
|
+
* runLauncher([c1, c2, …], opts) → fleet: ConnectionPool + control socket + health server
|
|
6
|
+
*
|
|
7
|
+
* For a single config, the agent connects directly and stays online. No pool,
|
|
8
|
+
* no wake-on-demand, no yellow-dot "available" state — just one green socket.
|
|
9
|
+
*
|
|
10
|
+
* For a fleet, agents register lazily: they show as "available" until messaged,
|
|
11
|
+
* then the backend pushes a wake frame over the one launcher control socket
|
|
12
|
+
* and the addressed agent's own socket opens on demand.
|
|
13
|
+
*
|
|
14
|
+
* Both shapes share: SIGTERM cleanup, optional health server, optional extra
|
|
15
|
+
* `onShutdown` hook. The fleet form additionally wires control socket,
|
|
16
|
+
* optional always-on orchestrator, and optional preWake.
|
|
17
|
+
*
|
|
18
|
+
* Usage (single):
|
|
19
|
+
* await runLauncher({ ...config, operatorKey, openaiKey });
|
|
20
|
+
*
|
|
21
|
+
* Usage (fleet):
|
|
22
|
+
* await runLauncher(configs, {
|
|
23
|
+
* operatorKey: process.env.ZIGGS_OPERATOR_KEY,
|
|
24
|
+
* label: 'council',
|
|
25
|
+
* orchestrator: orchConfig,
|
|
26
|
+
* preWake: ['newton', 'einstein'],
|
|
27
|
+
* onShutdown: async () => mongo.close(),
|
|
28
|
+
* });
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { ConnectionPool } from './ConnectionPool.js';
|
|
32
|
+
import { ZiggsAgent } from './ziggs/runtime.js';
|
|
33
|
+
import { createHealthServer } from './createHealthServer.js';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {object|object[]} configOrConfigs - a single defineAgent config or an array of them
|
|
37
|
+
* @param {object} [opts]
|
|
38
|
+
* @param {string} [opts.operatorKey] Credential for the control socket (fleet form only). Falls back to scanning configs.
|
|
39
|
+
* @param {string} [opts.wsUrl=process.env.WS_URL] Backend WebSocket URL
|
|
40
|
+
* @param {number} [opts.port=process.env.PORT||8080] Health check port (set to 0 to skip)
|
|
41
|
+
* @param {boolean} [opts.healthServer=true] Start a 200-OK HTTP server on `port`. Default true for both shapes; set false to skip.
|
|
42
|
+
* @param {string} [opts.label='launcher'] Log prefix
|
|
43
|
+
* @param {Array<object>} [opts.agentMeta] (fleet only) Metadata keyed by agentId for pool.query()
|
|
44
|
+
* @param {object|Function} [opts.orchestrator] (fleet only) Always-on extra agent config, or a builder (pool) => config
|
|
45
|
+
* @param {Array<string>} [opts.preWake=[]] (fleet only) AgentIds to wake on boot
|
|
46
|
+
* @param {object} [opts.poolOptions] (fleet only) { maxActive, idleTimeoutMs }
|
|
47
|
+
* @param {Function} [opts.onShutdown] Extra async cleanup on SIGTERM
|
|
48
|
+
* @returns {Promise<
|
|
49
|
+
* { agent: ZiggsAgent, healthServer?: import('http').Server } |
|
|
50
|
+
* { pool: ConnectionPool, orchestrator: ZiggsAgent|null, healthServer?: import('http').Server }
|
|
51
|
+
* >}
|
|
52
|
+
*/
|
|
53
|
+
export async function runLauncher(configOrConfigs, opts = {}) {
|
|
54
|
+
if (Array.isArray(configOrConfigs)) {
|
|
55
|
+
return runFleet(configOrConfigs, opts);
|
|
56
|
+
}
|
|
57
|
+
return runSingle(configOrConfigs, opts);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function runSingle(config, {
|
|
61
|
+
operatorKey,
|
|
62
|
+
label = 'agent',
|
|
63
|
+
port = process.env.PORT || 8080,
|
|
64
|
+
healthServer = true,
|
|
65
|
+
onShutdown,
|
|
66
|
+
} = {}) {
|
|
67
|
+
const effectiveKey = operatorKey ?? process.env.ZIGGS_OPERATOR_KEY;
|
|
68
|
+
const decorated = effectiveKey && !config.operatorKey
|
|
69
|
+
? { ...config, operatorKey: effectiveKey }
|
|
70
|
+
: config;
|
|
71
|
+
const agent = new ZiggsAgent(decorated);
|
|
72
|
+
await agent.connectAsync();
|
|
73
|
+
console.log(`[${label}] "${agent.options.ownAgentId ?? agent.options.name ?? 'agent'}" connected`);
|
|
74
|
+
|
|
75
|
+
const server = healthServer ? createHealthServer({ port, label }) : null;
|
|
76
|
+
|
|
77
|
+
process.on('SIGTERM', async () => {
|
|
78
|
+
console.log(`[${label}] SIGTERM — shutting down`);
|
|
79
|
+
agent.disconnect();
|
|
80
|
+
try { await onShutdown?.(); } catch (err) { console.warn(`[${label}] onShutdown error: ${err.message}`); }
|
|
81
|
+
server?.close();
|
|
82
|
+
process.exit(0);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return { agent, healthServer: server ?? undefined };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function runFleet(configs, {
|
|
89
|
+
operatorKey,
|
|
90
|
+
wsUrl = process.env.WS_URL,
|
|
91
|
+
port = process.env.PORT || 8080,
|
|
92
|
+
healthServer = true,
|
|
93
|
+
label = 'launcher',
|
|
94
|
+
agentMeta,
|
|
95
|
+
orchestrator: orchestratorConfig,
|
|
96
|
+
preWake = [],
|
|
97
|
+
poolOptions,
|
|
98
|
+
onShutdown,
|
|
99
|
+
} = {}) {
|
|
100
|
+
const pool = new ConnectionPool(poolOptions);
|
|
101
|
+
// Decorate each config with operatorKey so agent sockets pick it up when they connect.
|
|
102
|
+
const effectiveKey = operatorKey ?? process.env.ZIGGS_OPERATOR_KEY;
|
|
103
|
+
const decoratedConfigs = effectiveKey
|
|
104
|
+
? configs.map(c => (c.operatorKey ? c : { ...c, operatorKey: effectiveKey }))
|
|
105
|
+
: configs;
|
|
106
|
+
pool.register(decoratedConfigs, agentMeta);
|
|
107
|
+
const server = healthServer ? createHealthServer({ port, label }) : null;
|
|
108
|
+
|
|
109
|
+
// Optional always-on orchestrator (own socket; not managed by pool idle-eviction).
|
|
110
|
+
// Accept either a plain config or a builder (pool) => config — orchestrators
|
|
111
|
+
// whose tools reference the pool need the builder form.
|
|
112
|
+
let resolvedOrchConfig = typeof orchestratorConfig === 'function'
|
|
113
|
+
? orchestratorConfig(pool)
|
|
114
|
+
: orchestratorConfig;
|
|
115
|
+
if (resolvedOrchConfig && effectiveKey && !resolvedOrchConfig.operatorKey) {
|
|
116
|
+
resolvedOrchConfig = { ...resolvedOrchConfig, operatorKey: effectiveKey };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Control socket: single WS that registers all agentIds as "available" and
|
|
120
|
+
// receives launcher:wake frames from the backend. Prefer explicit opts.operatorKey;
|
|
121
|
+
// fall back to scanning configs so hello-world callers need no extra wiring.
|
|
122
|
+
const controlKey = effectiveKey
|
|
123
|
+
?? configs.find(c => c.operatorKey)?.operatorKey
|
|
124
|
+
?? resolvedOrchConfig?.operatorKey;
|
|
125
|
+
if (controlKey) {
|
|
126
|
+
pool.startControl({ wsUrl, operatorKey: controlKey });
|
|
127
|
+
} else {
|
|
128
|
+
console.warn(`[${label}] No operatorKey provided — skipping control socket`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let orchestrator = null;
|
|
132
|
+
if (resolvedOrchConfig) {
|
|
133
|
+
orchestrator = new ZiggsAgent(resolvedOrchConfig);
|
|
134
|
+
await orchestrator.connectAsync();
|
|
135
|
+
console.log(`[${label}] Orchestrator "${resolvedOrchConfig.name || resolvedOrchConfig.ownAgentId || 'orchestrator'}" connected`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (preWake.length > 0) {
|
|
139
|
+
await Promise.all(preWake.map(id =>
|
|
140
|
+
pool.wake(id)
|
|
141
|
+
.then(() => console.log(`[${label}] Pre-woke "${id}"`))
|
|
142
|
+
.catch(err => console.warn(`[${label}] Could not pre-wake "${id}": ${err.message}`))
|
|
143
|
+
));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
console.log(`[${label}] Ready — ${pool.size} agent(s) registered, ${pool.listActive().length} connected`);
|
|
147
|
+
|
|
148
|
+
process.on('SIGTERM', async () => {
|
|
149
|
+
console.log(`[${label}] SIGTERM — shutting down`);
|
|
150
|
+
pool.stopControl();
|
|
151
|
+
orchestrator?.disconnect();
|
|
152
|
+
await pool.disconnectAll();
|
|
153
|
+
try { await onShutdown?.(); } catch (err) { console.warn(`[${label}] onShutdown error: ${err.message}`); }
|
|
154
|
+
server?.close();
|
|
155
|
+
process.exit(0);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return { pool, orchestrator, healthServer: server ?? undefined };
|
|
159
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized type constants and magic strings used throughout the Agent system
|
|
3
|
+
*
|
|
4
|
+
* This file contains two main type systems:
|
|
5
|
+
* 1. Agent Types: Types used by the Agent core system (events, tasks, etc.)
|
|
6
|
+
* 2. Timeline Types: Re-exported from @ziggs-ai/api-client for convenience
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Re-export timeline types from api-client
|
|
10
|
+
export { EntryTypes, ContentTypes } from '@ziggs-ai/api-client';
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// AGENT TYPES SYSTEM
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
// Event Types
|
|
17
|
+
export const EventTypes = {
|
|
18
|
+
MESSAGE: 'message',
|
|
19
|
+
TOOL_RESULT: 'tool_result',
|
|
20
|
+
TOOL_ERROR: 'tool_error',
|
|
21
|
+
TASK_RESULT: 'task_result',
|
|
22
|
+
TASK_ERROR: 'task_error',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// ⚠️ SYNC: Task states re-exported from taskCore.js — see backend/src/tasks/task-core.ts
|
|
26
|
+
export { TaskState as TaskStates, TERMINAL_STATES, OPEN_PROPOSAL_TARGET } from '../tasks/taskCore.js';
|
|
27
|
+
|
|
28
|
+
// Task Operations
|
|
29
|
+
export const TaskOperations = {
|
|
30
|
+
MAKE_TASK: 'make-task',
|
|
31
|
+
MAKE_SUB_TASKS: 'make-sub-tasks',
|
|
32
|
+
UPDATE_TASK: 'update-task',
|
|
33
|
+
RESPOND_PROPOSAL: 'respond-proposal',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Context Properties
|
|
37
|
+
export const ContextProperties = {
|
|
38
|
+
HISTORY: 'history',
|
|
39
|
+
ACTIVE_TASKS: 'activeTasks',
|
|
40
|
+
AGENTS: 'agents',
|
|
41
|
+
USERS: 'users',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Decision Properties
|
|
45
|
+
export const DecisionProperties = {
|
|
46
|
+
TASK: 'task',
|
|
47
|
+
ACTION: 'action',
|
|
48
|
+
REASONING: 'reasoning',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Task Properties
|
|
52
|
+
export const TaskProperties = {
|
|
53
|
+
STATE: 'state',
|
|
54
|
+
SUBTASKS: 'subtasks',
|
|
55
|
+
TASK_ID: 'taskId',
|
|
56
|
+
DESCRIPTION: 'description',
|
|
57
|
+
OPERATION: 'operation',
|
|
58
|
+
STATUS: 'status',
|
|
59
|
+
RESULT: 'result',
|
|
60
|
+
EXECUTOR_ID: 'executorId',
|
|
61
|
+
TYPE: 'type',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Operation Types
|
|
65
|
+
export const OperationTypes = {
|
|
66
|
+
TOOL: 'tool',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Operation States
|
|
70
|
+
export const OperationStates = {
|
|
71
|
+
STARTED: 'started',
|
|
72
|
+
COMPLETED: 'completed',
|
|
73
|
+
ERROR: 'error',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Participant Types
|
|
77
|
+
// Sender IDs
|
|
78
|
+
export const SenderIds = {
|
|
79
|
+
SYSTEM: 'system',
|
|
80
|
+
AGENT: 'agent',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Task Types
|
|
84
|
+
export const TaskTypes = {
|
|
85
|
+
CONVERSATION: 'conversation',
|
|
86
|
+
};
|