@ziggs-ai/agent-sdk 0.1.3 → 0.1.5

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.
Files changed (98) hide show
  1. package/README.md +3 -1
  2. package/package.json +9 -4
  3. package/src/AgentHost.ts +495 -0
  4. package/src/adapters/OpenAIAdapter.ts +146 -0
  5. package/src/agent/Agent.ts +101 -0
  6. package/src/cognition/validateContext.ts +95 -0
  7. package/src/context/applyEffects.ts +80 -0
  8. package/src/context/batch.ts +17 -0
  9. package/src/context/classifyEnvelope.ts +38 -0
  10. package/src/context/routingLabels.ts +46 -0
  11. package/src/defineAgent.ts +62 -0
  12. package/src/formatters/AgreementFormatter.ts +111 -0
  13. package/src/formatters/HistoryFormatter.ts +166 -0
  14. package/src/formatters/index.ts +2 -0
  15. package/src/index.ts +105 -0
  16. package/src/ingress/normalizeIncoming.ts +162 -0
  17. package/src/memory/MemoryStore.ts +104 -0
  18. package/src/pricing/fleetDefaults.ts +218 -0
  19. package/src/pricing/fleetEvalFree.ts +24 -0
  20. package/src/pricing/fleetFreeTierA.gen.ts +12 -0
  21. package/src/pricing/fleetTierByAgentId.gen.ts +1022 -0
  22. package/src/runtime/AgentMachine.ts +364 -0
  23. package/src/runtime/PromptBuilder.ts +463 -0
  24. package/src/runtime/buildOutcome.ts +518 -0
  25. package/src/runtime/defaults.ts +75 -0
  26. package/src/runtime/runTurn.ts +691 -0
  27. package/src/runtime/validateWorkflow.ts +181 -0
  28. package/src/server/ConnectionPool.ts +155 -0
  29. package/src/server/EventQueue.ts +133 -0
  30. package/src/server/InboxCatchUp.ts +251 -0
  31. package/src/server/OutboxBuffer.ts +90 -0
  32. package/src/server/SeenMessages.ts +27 -0
  33. package/src/server/ZiggsEffectHandler.ts +409 -0
  34. package/src/server/agreements/AgreementService.ts +117 -0
  35. package/src/server/createHealthServer.ts +85 -0
  36. package/src/server/proactive/ProactiveTrigger.ts +83 -0
  37. package/src/server/runLauncher.ts +146 -0
  38. package/src/server/tasks/TaskService.ts +110 -0
  39. package/src/server/tasks/index.ts +1 -0
  40. package/src/server/telemetryIngest.ts +91 -0
  41. package/src/server/tools/index.ts +46 -0
  42. package/src/server/tools/tier1/protocolRunner.ts +133 -0
  43. package/src/server/tools/tier1/protocolTools.ts +99 -0
  44. package/src/server/tools/tier2/connectionTools.ts +75 -0
  45. package/src/server/tools/tier2/contextTools.ts +74 -0
  46. package/src/server/tools/tier2/discoveryTools.ts +34 -0
  47. package/src/server/tools/tier2/marketplaceTools.ts +25 -0
  48. package/src/server/tools/tier2/paymentTools.ts +193 -0
  49. package/src/server/ziggsconnect/ZiggsConnectClient.ts +126 -0
  50. package/src/server/ziggscontext/ZiggsContextClient.ts +137 -0
  51. package/src/server/ziggspay/ZiggsPayClient.ts +193 -0
  52. package/src/shared/ids.ts +3 -0
  53. package/src/shared/runtimeLog.ts +72 -0
  54. package/src/shared/types.ts +29 -0
  55. package/src/tasks/protocolRegistry.ts +25 -0
  56. package/src/tools/ToolManager.ts +95 -0
  57. package/src/tools/{ToolProvider.js → ToolProvider.ts} +5 -15
  58. package/src/tools/defineTool.ts +90 -0
  59. package/src/tools/index.ts +5 -0
  60. package/src/types.ts +407 -0
  61. package/src/utils/jsonExtractor.ts +100 -0
  62. package/src/ConnectionPool.js +0 -133
  63. package/src/adapters/OpenAIAdapter.js +0 -73
  64. package/src/agent/Agent.js +0 -121
  65. package/src/agent/EventQueue.js +0 -68
  66. package/src/agent/OutboxBuffer.js +0 -62
  67. package/src/cognition/PromptBuilder.js +0 -312
  68. package/src/cognition/resolveActionTool.js +0 -12
  69. package/src/cognition/runTurn.js +0 -578
  70. package/src/context/applyEffects.js +0 -133
  71. package/src/context/batch.js +0 -25
  72. package/src/context/classifyEnvelope.js +0 -82
  73. package/src/context/routingLabels.js +0 -54
  74. package/src/createHealthServer.js +0 -28
  75. package/src/formatters/HistoryFormatter.js +0 -257
  76. package/src/formatters/TaskFormatter.js +0 -180
  77. package/src/formatters/index.js +0 -9
  78. package/src/index.js +0 -76
  79. package/src/ingress/normalizeIncoming.js +0 -70
  80. package/src/runLauncher.js +0 -159
  81. package/src/shared/ids.js +0 -7
  82. package/src/shared/types.js +0 -86
  83. package/src/tasks/TaskService.js +0 -247
  84. package/src/tasks/index.js +0 -9
  85. package/src/tasks/taskCore.js +0 -229
  86. package/src/tasks/taskProtocolRegistry.js +0 -22
  87. package/src/tasks/taskProtocolRunner.js +0 -107
  88. package/src/tasks/taskProtocolTools.js +0 -87
  89. package/src/tools/ToolManager.js +0 -79
  90. package/src/tools/defineTool.js +0 -82
  91. package/src/tools/index.js +0 -11
  92. package/src/utils/jsonExtractor.js +0 -139
  93. package/src/workflow/AgentMachine.js +0 -250
  94. package/src/workflow/WorkflowRuntime.js +0 -63
  95. package/src/workflow/dsl.js +0 -287
  96. package/src/workflow/motifs.js +0 -435
  97. package/src/ziggs/runtime.js +0 -192
  98. /package/src/adapters/{index.js → index.ts} +0 -0
@@ -0,0 +1,101 @@
1
+ import type { Ctx, Identity, Workflow, EffectHandler } from '../types.js';
2
+ import { AgentMachine, type TransitionEvent } from '../runtime/AgentMachine.js';
3
+ import { runTurn } from '../runtime/runTurn.js';
4
+ import { PromptBuilder } from '../runtime/PromptBuilder.js';
5
+ import type { MemoryStore } from '../memory/MemoryStore.js';
6
+ import type { ToolManager } from '../tools/ToolManager.js';
7
+ import type { RawEvent } from '../runtime/buildOutcome.js';
8
+
9
+ export interface AgentOptions {
10
+ workflow: Workflow;
11
+ toolManager: ToolManager;
12
+ promptBuilder?: PromptBuilder;
13
+ }
14
+
15
+ export interface TickInput {
16
+ /** Wire event (already normalized: a `message` or `task_result`-style envelope). */
17
+ event: unknown;
18
+ /** Server-provided per-session state. Omit on the first tick — a fresh ctx is constructed. */
19
+ ctx?: Ctx;
20
+ /** Server-provided workflow state name. Omit on the first tick — defaults to `workflow.initial`. */
21
+ state?: string;
22
+ /** Who am I and what session am I running in. The Agent carries this through Ctx. */
23
+ identity: Identity;
24
+ /** The single side channel for I/O — LLM, context, tools, send, record. */
25
+ eff: EffectHandler;
26
+ /**
27
+ * Agent-private long-term memory. Operator-provided (default
28
+ * `InMemoryStore` when omitted by `AgentHost`). Key shape is the agent's
29
+ * choice — `counterparty:<id>`, `agreement:<id>:notes`, etc.
30
+ */
31
+ memory?: MemoryStore;
32
+ /** Optional tap for FSM transition events (ZIG-442). */
33
+ transitionTap?: (event: TransitionEvent) => void;
34
+ }
35
+
36
+ export interface TickOutput {
37
+ /** Updated ctx — Server persists this against the session. */
38
+ ctx: Ctx;
39
+ /** Workflow state name after the tick — Server persists this against the session. */
40
+ state: string;
41
+ /** Whether the machine settled at a parked state. */
42
+ parked: boolean;
43
+ /** Accumulated LLM call count and token usage for this tick. */
44
+ metrics: { llmCalls: number; tokens: { total: number; input: number; output: number } };
45
+ }
46
+
47
+ /**
48
+ * Pure agent: workflow definition + cognition machinery. No I/O, no transport,
49
+ * no services. The Agent's only side channel is the `eff` handler it receives
50
+ * on each `tick`. Server-side state (sessions, lanes, queue, outbox) lives in
51
+ * `AgentHost` — not here.
52
+ *
53
+ * The contract:
54
+ *
55
+ * `tick({ event, ctx, state, identity, eff })` →
56
+ * `{ ctx, state, parked }`
57
+ *
58
+ * Reads top-to-bottom like a synchronous function. Every `await eff(...)`
59
+ * inside the cognition core is a yield to the Server, which performs the
60
+ * impure operation and resumes the Agent with the result.
61
+ */
62
+ export class Agent {
63
+ workflow: Workflow;
64
+ toolManager: ToolManager;
65
+ promptBuilder: PromptBuilder;
66
+
67
+ constructor({ workflow, toolManager, promptBuilder }: AgentOptions) {
68
+ if (!workflow?.states) throw new Error('Agent: workflow with states is required');
69
+ if (!toolManager) throw new Error('Agent: toolManager is required');
70
+ this.workflow = workflow;
71
+ this.toolManager = toolManager;
72
+ this.promptBuilder = promptBuilder || new PromptBuilder();
73
+ }
74
+
75
+ async tick({ event, ctx, state, identity, eff, memory, transitionTap }: TickInput): Promise<TickOutput> {
76
+ if (!identity?.sessionId) throw new Error('Agent.tick: identity.sessionId is required');
77
+ if (!eff) throw new Error('Agent.tick: eff is required');
78
+
79
+ const machine = new AgentMachine({
80
+ workflow: this.workflow,
81
+ executionCore: runTurn,
82
+ eff,
83
+ toolManager: this.toolManager,
84
+ promptBuilder: this.promptBuilder,
85
+ memory,
86
+ input: { identity, ctx, state },
87
+ transitionTap,
88
+ });
89
+
90
+ // `send` awaits the recursive _step until the machine settles at a
91
+ // parked state (or stops). When it returns, we have the final state.
92
+ await machine.send({ event: event as RawEvent | undefined, identity });
93
+
94
+ return {
95
+ ctx: machine.ctx,
96
+ state: machine.state,
97
+ parked: machine.isParked,
98
+ metrics: { llmCalls: machine.llmCalls, tokens: machine.tokens },
99
+ };
100
+ }
101
+ }
@@ -0,0 +1,95 @@
1
+ export class ContextWarning {
2
+ constructor(public path: string, public message: string) {}
3
+ toString(): string { return `[context] ${this.path}: ${this.message}`; }
4
+ }
5
+
6
+ type WarnFn = (path: string, msg: string) => void;
7
+
8
+ export function validateContext(
9
+ ctx: unknown,
10
+ { logger }: { logger?: { warn: (msg: string) => void } } = {},
11
+ ): ContextWarning[] {
12
+ const warnings: ContextWarning[] = [];
13
+ const warn: WarnFn = (path, msg) => warnings.push(new ContextWarning(path, msg));
14
+
15
+ if (!ctx || typeof ctx !== 'object') {
16
+ warn('context', 'missing or not an object');
17
+ return warnings;
18
+ }
19
+
20
+ const c = ctx as Record<string, unknown>;
21
+ validateAgreements(c['agreements'], warn);
22
+ validateAgents(c['agents'], warn);
23
+ validateHistory(c['history'], warn);
24
+ validateUsers(c['users'], warn);
25
+
26
+ if (logger && warnings.length > 0) {
27
+ for (const w of warnings) logger.warn(w.toString());
28
+ }
29
+ return warnings;
30
+ }
31
+
32
+ function validateAgreements(agreements: unknown, warn: WarnFn): void {
33
+ if (!Array.isArray(agreements)) return;
34
+ for (let i = 0; i < agreements.length; i++) {
35
+ const ag = agreements[i] as Record<string, unknown>;
36
+ const path = `agreements[${i}]`;
37
+ if (!ag || typeof ag !== 'object') { warn(path, 'not an object'); continue; }
38
+ for (const key of ['agreementId', 'parties']) {
39
+ if (ag[key] == null) warn(`${path}.${key}`, 'missing');
40
+ }
41
+ if (ag['parties'] && typeof ag['parties'] === 'object') {
42
+ const parties = ag['parties'] as Record<string, unknown>;
43
+ for (const key of ['creator', 'provider']) {
44
+ if (!parties[key]) warn(`${path}.parties.${key}`, 'missing');
45
+ }
46
+ }
47
+ if (ag['proposal'] && typeof ag['proposal'] === 'object') {
48
+ if (!(ag['proposal'] as Record<string, unknown>)['status']) warn(`${path}.proposal.status`, 'missing');
49
+ }
50
+ if (ag['task'] && typeof ag['task'] === 'object') {
51
+ const t = ag['task'] as Record<string, unknown>;
52
+ if (!t['taskId']) warn(`${path}.task.taskId`, 'missing');
53
+ if (!t['state']) warn(`${path}.task.state`, 'missing');
54
+ }
55
+ }
56
+ }
57
+
58
+ function validateAgents(agents: unknown, warn: WarnFn): void {
59
+ if (!Array.isArray(agents)) return;
60
+ let youCount = 0;
61
+ for (let i = 0; i < agents.length; i++) {
62
+ const agent = agents[i] as Record<string, unknown>;
63
+ const path = `agents[${i}]`;
64
+ if (!agent || typeof agent !== 'object') { warn(path, 'not an object'); continue; }
65
+ if (!agent['agentId']) warn(`${path}.agentId`, 'missing');
66
+ if (agent['isYou']) youCount++;
67
+ }
68
+ if (agents.length > 0 && youCount === 0) warn('agents', 'no agent has isYou=true — agent identity will be unknown');
69
+ if (youCount > 1) warn('agents', `${youCount} agents have isYou=true — should be exactly 1`);
70
+ }
71
+
72
+ function validateHistory(history: unknown, warn: WarnFn): void {
73
+ if (!Array.isArray(history)) return;
74
+ for (let i = 0; i < history.length; i++) {
75
+ const entry = history[i] as Record<string, unknown>;
76
+ const path = `history[${i}]`;
77
+ if (!entry || typeof entry !== 'object') { warn(path, 'not an object'); continue; }
78
+ if (!entry['entryType'] && entry['entryType'] !== 0) warn(`${path}.entryType`, 'missing');
79
+ if (entry['entryType'] === 'message' && !entry['sender']) warn(`${path}.sender`, 'missing for message entry');
80
+ if (entry['entryType'] === 'task_history') {
81
+ const svc = entry['service'] as Record<string, unknown> | undefined;
82
+ if (!svc?.['task']) warn(`${path}.service.task`, 'missing for task_history entry');
83
+ }
84
+ }
85
+ }
86
+
87
+ function validateUsers(users: unknown, warn: WarnFn): void {
88
+ if (!Array.isArray(users)) return;
89
+ for (let i = 0; i < users.length; i++) {
90
+ const user = users[i] as Record<string, unknown>;
91
+ const path = `users[${i}]`;
92
+ if (!user || typeof user !== 'object') { warn(path, 'not an object'); continue; }
93
+ if (!user['userId']) warn(`${path}.userId`, 'missing');
94
+ }
95
+ }
@@ -0,0 +1,80 @@
1
+ import { isProtocolToolName, mapProtocolToolToOperation } from '../tasks/protocolRegistry.js';
2
+
3
+ function applyTaskOperationResultToContext(updates: Record<string, unknown>, operation: string | null, result: Record<string, unknown>): void {
4
+ const r = result || {};
5
+ const agreement = (r['agreement'] as Record<string, unknown>) ||
6
+ (r['agreementId'] ? r : null);
7
+ const task = (r['task'] as Record<string, unknown>) || (r['taskId'] ? r : null);
8
+
9
+ if (operation === 'agreement-propose' || operation === 'agreement-subcontract') {
10
+ // Agreement creation no longer spawns a task. The caller has made a
11
+ // proposal that the proposedTo party still needs to approve.
12
+ if (agreement) updates['proposal'] = r;
13
+ }
14
+ if (operation === 'task-spawn') {
15
+ if (task) {
16
+ updates['delegatedTask'] = r;
17
+ if (task['taskId']) {
18
+ if (!updates['_delegatedTaskIds']) updates['_delegatedTaskIds'] = [];
19
+ (updates['_delegatedTaskIds'] as string[]).push(task['taskId'] as string);
20
+ }
21
+ }
22
+ }
23
+ if (operation === 'task-update') {
24
+ const status = (task?.['state'] || r['state'] || r['status']) as string | undefined;
25
+ if (status === 'completed') updates['taskCompleted'] = true;
26
+ if (status === 'failed') updates['taskFailed'] = true;
27
+ }
28
+ if (operation === 'agreement-respond') updates['respondedProposal'] = r;
29
+ }
30
+
31
+ export const CONTEXT_RESET: Record<string, unknown> = Object.freeze({
32
+ messageSent: false, activeWait: false, proposal: null, taskCompleted: false,
33
+ taskFailed: false, subtaskResult: null, subtaskFailed: false, toolResults: null,
34
+ lastError: null, lastAction: null, lastActionResult: null, approval: false,
35
+ rejection: false, taskAssignment: null, incomingMessage: false,
36
+ respondedProposal: null, searchCompleted: false, boardResults: null, taskPublished: false,
37
+ });
38
+
39
+ export function buildContextUpdates(actionName: string, emittedEvents: unknown[]): Record<string, unknown> {
40
+ const updates: Record<string, unknown> = { ...CONTEXT_RESET, lastAction: actionName };
41
+ if (!Array.isArray(emittedEvents)) return updates;
42
+ const toolResults: unknown[] = [];
43
+
44
+ for (const ev of emittedEvents) {
45
+ if (!ev || typeof ev !== 'object') continue;
46
+ const events = Array.isArray(ev) ? ev : [ev];
47
+ for (const e of events) {
48
+ const event = e as Record<string, unknown>;
49
+ switch (event['type']) {
50
+ case 'tool_result':
51
+ toolResults.push({ tool: event['tool'], result: event['result'] });
52
+ updates['lastActionResult'] = event;
53
+ if (isProtocolToolName(event['tool'] as string)) {
54
+ applyTaskOperationResultToContext(updates, mapProtocolToolToOperation(event['tool'] as string), (event['result'] as Record<string, unknown>) || {});
55
+ }
56
+ break;
57
+ case 'tool_error':
58
+ toolResults.push({ tool: event['tool'], error: event['error'] });
59
+ updates['lastActionResult'] = event;
60
+ updates['lastError'] = event['error'];
61
+ break;
62
+ case 'message_sent':
63
+ updates['messageSent'] = true; updates['lastActionResult'] = event; break;
64
+ case 'message_duplicate_skipped':
65
+ updates['messageSent'] = true; break;
66
+ case 'waited':
67
+ updates['activeWait'] = true; break;
68
+ case 'task_result': {
69
+ updates['lastActionResult'] = event;
70
+ applyTaskOperationResultToContext(updates, (event['operation'] as string) || null, (event['result'] as Record<string, unknown>) || {});
71
+ break;
72
+ }
73
+ case 'task_error':
74
+ updates['lastError'] = event['error']; updates['lastActionResult'] = event; break;
75
+ }
76
+ }
77
+ }
78
+ if (toolResults.length > 0) updates['toolResults'] = toolResults;
79
+ return updates;
80
+ }
@@ -0,0 +1,17 @@
1
+ export function getBatchEvents(rawEvent: unknown): unknown[] {
2
+ if (!rawEvent) return [];
3
+ const e = rawEvent as Record<string, unknown>;
4
+ if (e['type'] === 'batch' && Array.isArray(e['events'])) return e['events'] as unknown[];
5
+ return [rawEvent];
6
+ }
7
+
8
+ export function isTaskResultRelevantToAgent(result: Record<string, unknown>, ownAgentId: string | null): boolean {
9
+ if (!ownAgentId) return true;
10
+ const parties = (result['agreement'] as Record<string, unknown> | undefined)?.['parties'] as Record<string, unknown> | undefined ?? {};
11
+ return !!(
12
+ result['creatorIsYou'] || result['providerIsYou'] || result['payerIsYou'] || result['proposedToIsYou'] ||
13
+ parties['creator'] === ownAgentId || parties['provider'] === ownAgentId ||
14
+ parties['payer'] === ownAgentId || parties['proposedTo'] === ownAgentId ||
15
+ parties['proposedTo'] === 'everyone'
16
+ );
17
+ }
@@ -0,0 +1,38 @@
1
+ import { CONTEXT_RESET } from './applyEffects.js';
2
+ import { getBatchEvents, isTaskResultRelevantToAgent } from './batch.js';
3
+
4
+ function isTaskAssignment(result: Record<string, unknown>, ownAgentId: string | null): boolean {
5
+ const parties = (result['agreement'] as Record<string, unknown> | undefined)?.['parties'] as Record<string, unknown> | undefined ?? {};
6
+ const isProvider = result['providerIsYou'] || parties['provider'] === ownAgentId;
7
+ return !!(isProvider && (result['state'] === 'active' || result['state'] === 'in-progress'));
8
+ }
9
+
10
+ export function classifyIncomingEvent(rawEvent: unknown, ownAgentId: string | null): Record<string, unknown> {
11
+ const flags: Record<string, unknown> = { ...CONTEXT_RESET };
12
+ if (!rawEvent) return flags;
13
+
14
+ for (const ev of getBatchEvents(rawEvent)) {
15
+ if (!ev) continue;
16
+ const e = ev as Record<string, unknown>;
17
+ if (e['type'] !== 'task_result') { flags['incomingMessage'] = true; continue; }
18
+
19
+ const result = (e['result'] as Record<string, unknown>) || {};
20
+ if (ownAgentId && !isTaskResultRelevantToAgent(result, ownAgentId) && e['receiverId'] !== ownAgentId) continue;
21
+
22
+ const state = result['state'] as string | undefined;
23
+ const ag = result['agreement'] as Record<string, unknown> | undefined;
24
+ const parties = (ag?.['parties'] as Record<string, unknown>) || {};
25
+ const proposalStatus = (ag?.['proposal'] as Record<string, unknown> | undefined)?.['status'];
26
+
27
+ if (state === 'active' && proposalStatus === 'approved' && ownAgentId && parties['creator'] === ownAgentId) {
28
+ flags['approval'] = true; flags['taskAssignment'] = result; continue;
29
+ }
30
+ if (isTaskAssignment(result, ownAgentId)) { flags['taskAssignment'] = result; flags['approval'] = true; continue; }
31
+ if (proposalStatus === 'approved' || (state === 'active' && proposalStatus === 'pending')) { flags['approval'] = true; flags['taskAssignment'] = result; continue; }
32
+ if (proposalStatus === 'rejected' || state === 'cancelled') { flags['rejection'] = true; continue; }
33
+ if (state === 'completed') { flags['subtaskResult'] = result; continue; }
34
+ if (state === 'failed') { flags['subtaskResult'] = result; flags['subtaskFailed'] = true; continue; }
35
+ flags['subtaskResult'] = result;
36
+ }
37
+ return flags;
38
+ }
@@ -0,0 +1,46 @@
1
+ import { isProtocolToolName } from '../tasks/protocolRegistry.js';
2
+ import { getBatchEvents, isTaskResultRelevantToAgent } from './batch.js';
3
+
4
+ export function unwrapBatchEvent(event: unknown): unknown {
5
+ const e = event as Record<string, unknown> | null | undefined;
6
+ if (e?.['type'] === 'batch' && Array.isArray(e['events']) && e['events'][0]) return e['events'][0];
7
+ return event;
8
+ }
9
+
10
+ export function classifyWorkflowEvent(event: unknown, ownAgentId: string | null = null): string {
11
+ const events = getBatchEvents(event);
12
+ if (events.length === 0) return 'message';
13
+
14
+ let sawTaskResult = false, sawOwnTaskResult = false;
15
+
16
+ for (const ev of events) {
17
+ const e = ev as Record<string, unknown> | null | undefined;
18
+ if (e?.['type'] !== 'task_result') continue;
19
+ sawTaskResult = true;
20
+ const result = (e['result'] as Record<string, unknown>) || {};
21
+ if (!isTaskResultRelevantToAgent(result, ownAgentId)) continue;
22
+ sawOwnTaskResult = true;
23
+ const state = result['state'] as string | undefined;
24
+ const ag = result['agreement'] as Record<string, unknown> | undefined;
25
+ const parties = (ag?.['parties'] as Record<string, unknown>) || {};
26
+ const proposalStatus = ag?.['proposal'] ? (ag['proposal'] as Record<string, unknown>)['status'] : undefined;
27
+ const isProvider = result['providerIsYou'] || parties['provider'] === ownAgentId;
28
+ if (isProvider && (state === 'active' || state === 'in-progress')) return 'task_assignment';
29
+ if (proposalStatus === 'approved' || (state === 'active' && proposalStatus === 'pending')) return 'task_approved';
30
+ if (proposalStatus === 'rejected' || state === 'cancelled') return 'task_rejected';
31
+ }
32
+ if (sawOwnTaskResult || sawTaskResult) return 'task_update';
33
+ return 'message';
34
+ }
35
+
36
+ export function findTaskResult(output: Record<string, unknown> | null | undefined): unknown {
37
+ const events = (output?.['emittedEvents'] as unknown[]) ?? [];
38
+ return events.find(e => {
39
+ const ev = e as Record<string, unknown>;
40
+ return ev['type'] === 'task_result' || (ev['type'] === 'tool_result' && isProtocolToolName(ev['tool'] as string));
41
+ }) ?? null;
42
+ }
43
+
44
+ export function findIncomingTaskResult(incomingEvent: unknown): unknown {
45
+ return getBatchEvents(incomingEvent).find(e => (e as Record<string, unknown>)?.['type'] === 'task_result') ?? null;
46
+ }
@@ -0,0 +1,62 @@
1
+ import type { AgentConfig, DefineAgentInput, Workflow } from './types.js';
2
+ import { validateWorkflow } from './runtime/validateWorkflow.js';
3
+ import { runtimeLog } from './shared/runtimeLog.js';
4
+
5
+ /**
6
+ * Validates the workflow synchronously, resolves merged tools and model, and
7
+ * returns a frozen AgentConfig. Does NOT mutate the input — `wait` action and
8
+ * `activeWait` transitions are the workflow author's responsibility (visible
9
+ * spread of `thinkingDefaults`).
10
+ *
11
+ * Validation throws WorkflowValidationError on hard problems and logs warnings
12
+ * on soft ones (e.g. missing wait fallthrough).
13
+ */
14
+ export function defineAgent(input: DefineAgentInput): AgentConfig {
15
+ if (!input?.agentId) throw new Error('[defineAgent] agentId is required');
16
+ if (!input?.description) throw new Error('[defineAgent] description is required');
17
+ if (!input?.states) throw new Error('[defineAgent] states is required');
18
+ if (!input?.initial) throw new Error('[defineAgent] initial state is required');
19
+
20
+ const workflow: Workflow = {
21
+ id: input.agentId,
22
+ description: input.description,
23
+ initial: input.initial,
24
+ states: input.states,
25
+ };
26
+
27
+ const { warnings } = validateWorkflow(workflow);
28
+ for (const w of warnings) runtimeLog.warn('defineAgent', w);
29
+
30
+ const tools = [
31
+ ...(Array.isArray(input.tools) ? input.tools : []),
32
+ ...(Array.isArray(input.services?.tools) ? input.services!.tools! : []),
33
+ ];
34
+
35
+ const model =
36
+ input.services?.llm?.model ||
37
+ process.env.OPENAI_MODEL ||
38
+ 'gpt-5.4';
39
+
40
+ // Strip the structural fields we already consumed; pass through everything
41
+ // else (operatorKey, taskTools, custom keys consumed by AgentHost).
42
+ const {
43
+ agentId, description, specialization, services: _services, initial: _initial,
44
+ states: _states, tools: _tools, operatorKey, taskTools, ...rest
45
+ } = input;
46
+
47
+ const config: AgentConfig = {
48
+ openaiKey: process.env.OPENAI_API_KEY,
49
+ ...(operatorKey ? { operatorKey } : {}),
50
+ agentId,
51
+ description,
52
+ ...(specialization ? { specialization } : {}),
53
+ tools,
54
+ model,
55
+ workflow,
56
+ ...(input.services ? { services: input.services } : {}),
57
+ ...(taskTools !== undefined ? { taskTools } : {}),
58
+ ...rest,
59
+ };
60
+
61
+ return Object.freeze(config) as AgentConfig;
62
+ }
@@ -0,0 +1,111 @@
1
+ interface AgreementFormatterOptions {
2
+ shortIds?: boolean;
3
+ indentSize?: number;
4
+ }
5
+
6
+ type AnyObj = Record<string, unknown>;
7
+
8
+ export class AgreementFormatter {
9
+ private shortIds: boolean;
10
+ private indentSize: number;
11
+
12
+ constructor(options: AgreementFormatterOptions = {}) {
13
+ this.shortIds = options.shortIds ?? false;
14
+ this.indentSize = options.indentSize ?? 2;
15
+ }
16
+
17
+ format(agreements: AnyObj[] | null | undefined): string {
18
+ if (!agreements?.length) return 'No active agreements.';
19
+ const pending = agreements.filter(a => (a['proposal'] as AnyObj | undefined)?.['status'] === 'pending' && a['proposedToIsYou']);
20
+ const standing = agreements.filter(a => !a['task']);
21
+ const active = agreements.filter(a => a['task'] && !((a['proposal'] as AnyObj | undefined)?.['status'] === 'pending' && a['proposedToIsYou']));
22
+ const sections: string[] = [];
23
+ if (pending.length > 0) { sections.push('Pending Proposals (awaiting your response):'); for (const ag of pending) sections.push(this.formatAgreement(ag, this._indent(1))); }
24
+ if (active.length > 0) { if (sections.length > 0) sections.push(''); sections.push('Active Agreements:'); for (const ag of active) sections.push(this.formatAgreement(ag, this._indent(1))); }
25
+ if (standing.length > 0) { if (sections.length > 0) sections.push(''); sections.push('Standing Agreements (no execution running):'); for (const ag of standing) sections.push(this.formatAgreement(ag, this._indent(1))); }
26
+ return sections.join('\n');
27
+ }
28
+
29
+ formatAgreement(ag: AnyObj, indent = ''): string {
30
+ const status = ((ag['status'] as string) || 'unknown').toUpperCase();
31
+ const agId = this.shortId(ag['agreementId'] as string);
32
+ const parties = (ag['parties'] as AnyObj) || {};
33
+ const lines = [
34
+ `${indent}[${status}] Agreement ${agId}`,
35
+ `${indent} Creator: ${this._labelParty(parties['creator'] as string, ag['creatorIsYou'] as boolean)}`,
36
+ `${indent} Provider: ${this._labelParty(parties['provider'] as string, ag['providerIsYou'] as boolean)}`,
37
+ `${indent} Payer: ${this._labelParty(parties['payer'] as string, ag['payerIsYou'] as boolean)}`,
38
+ ];
39
+ if (parties['proposedTo']) lines.push(`${indent} Proposed to: ${this._labelParty(parties['proposedTo'] as string, ag['proposedToIsYou'] as boolean)}`);
40
+ const proposal = ag['proposal'] as AnyObj | undefined;
41
+ if (proposal?.['status']) lines.push(`${indent} Proposal: ${proposal['status']}${proposal['respondedBy'] ? ` (by ${proposal['respondedBy']})` : ''}`);
42
+ const money = ag['money'] as AnyObj | undefined;
43
+ if (typeof money?.['price'] === 'number' && (money['price'] as number) > 0) {
44
+ const parts = [`${money['price']} cents`];
45
+ if (money['paymentStatus'] && money['paymentStatus'] !== 'none') parts.push(money['paymentStatus'] as string);
46
+ lines.push(`${indent} Money: ${parts.join(', ')}`);
47
+ }
48
+ const terms = ag['terms'] as AnyObj | undefined;
49
+ if (terms) {
50
+ const tparts: string[] = [];
51
+ if (terms['lifecycle'] && terms['lifecycle'] !== 'open') tparts.push(`lifecycle: ${terms['lifecycle']}`);
52
+ if (terms['maxExecutions'] != null) tparts.push(`maxExecutions: ${terms['maxExecutions']}`);
53
+ if (terms['expiresAt']) tparts.push(`expiresAt: ${terms['expiresAt']}`);
54
+ if (terms['description']) tparts.push(`"${terms['description']}"`);
55
+ if (tparts.length > 0) lines.push(`${indent} Terms: ${tparts.join(', ')}`);
56
+ }
57
+ if (ag['task']) { lines.push(''); lines.push(`${indent} Execution:`); lines.push(this._renderTaskExecution(ag['task'] as AnyObj, indent + ' ')); }
58
+ else lines.push(`${indent} Execution: (none yet)`);
59
+ return lines.join('\n');
60
+ }
61
+
62
+ _renderTaskExecution(task: AnyObj, indent = ''): string {
63
+ const state = ((task['state'] as string) || 'unknown').toUpperCase();
64
+ const id = this.shortId(task['taskId'] as string);
65
+ const lines = [`${indent}[${state}] ${task['description'] || 'No description'}`, `${indent} Task ID: ${id}`];
66
+ if (task['parentTaskId']) lines.push(`${indent} Parent task: ${task['parentTaskId']}`);
67
+ if (task['result'] && (task['state'] === 'completed' || task['state'] === 'failed')) {
68
+ const r = String(task['result']);
69
+ lines.push(`${indent} Result: ${r.length > 100 ? r.slice(0, 100) + '...' : r}`);
70
+ }
71
+ const plan = task['plan'] as AnyObj | undefined;
72
+ const steps = plan?.['steps'] as AnyObj[] | undefined;
73
+ if (steps?.length) {
74
+ const sorted = [...steps].sort((a, b) => (a['order'] as number) - (b['order'] as number));
75
+ const done = sorted.filter(s => s['status'] === 'completed').length;
76
+ lines.push(`${indent} Plan (${done}/${sorted.length} done):`);
77
+ for (const step of sorted) {
78
+ const icon = ({ pending: '○', in_progress: '●', completed: '✓', skipped: '–' } as Record<string, string>)[step['status'] as string] || '?';
79
+ const note = step['status'] === 'completed' && step['result'] ? ` → ${String(step['result']).slice(0, 60)}${String(step['result']).length > 60 ? '...' : ''}` : '';
80
+ lines.push(`${indent} ${step['order']}. [${icon}] ${step['description'] || step['stepId']}${note}`);
81
+ }
82
+ }
83
+ const subtasks = task['subtasks'] as AnyObj[] | undefined;
84
+ if (subtasks?.length) {
85
+ lines.push(`${indent} Subtasks (${subtasks.length}):`);
86
+ for (const st of subtasks) {
87
+ const sstate = ((st['state'] as string) || 'unknown').toUpperCase();
88
+ const sid = this.shortId(st['taskId'] as string);
89
+ lines.push(`${indent} [${sstate}] ${st['description'] || sid} (${sid}, provider: ${st['providerIsYou'] ? 'You' : (st['provider'] ?? 'unassigned')})`);
90
+ }
91
+ }
92
+ return lines.join('\n');
93
+ }
94
+
95
+ _labelParty(id: string | null | undefined, isYou: boolean | undefined): string {
96
+ if (!id) return 'N/A';
97
+ return isYou ? `You (${id})` : id;
98
+ }
99
+
100
+ shortId(id: string | null | undefined): string {
101
+ if (!id) return '?';
102
+ if (!this.shortIds) return id;
103
+ return id.length > 20 ? id.slice(0, 20) + '...' : id;
104
+ }
105
+
106
+ private _indent(level: number): string {
107
+ return ' '.repeat(level * this.indentSize);
108
+ }
109
+ }
110
+
111
+ export const agreementFormatter = new AgreementFormatter();