@hanfani/core 0.1.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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/dist/conformance.d.ts +25 -0
  4. package/dist/conformance.d.ts.map +1 -0
  5. package/dist/conformance.js +90 -0
  6. package/dist/conformance.js.map +1 -0
  7. package/dist/defineAgent.d.ts +76 -0
  8. package/dist/defineAgent.d.ts.map +1 -0
  9. package/dist/defineAgent.js +59 -0
  10. package/dist/defineAgent.js.map +1 -0
  11. package/dist/definePrompt.d.ts +15 -0
  12. package/dist/definePrompt.d.ts.map +1 -0
  13. package/dist/definePrompt.js +23 -0
  14. package/dist/definePrompt.js.map +1 -0
  15. package/dist/defineWorkflow.d.ts +10 -0
  16. package/dist/defineWorkflow.d.ts.map +1 -0
  17. package/dist/defineWorkflow.js +38 -0
  18. package/dist/defineWorkflow.js.map +1 -0
  19. package/dist/delivery.d.ts +8 -0
  20. package/dist/delivery.d.ts.map +1 -0
  21. package/dist/delivery.js +21 -0
  22. package/dist/delivery.js.map +1 -0
  23. package/dist/fold.d.ts +8 -0
  24. package/dist/fold.d.ts.map +1 -0
  25. package/dist/fold.js +98 -0
  26. package/dist/fold.js.map +1 -0
  27. package/dist/gate.d.ts +25 -0
  28. package/dist/gate.d.ts.map +1 -0
  29. package/dist/gate.js +22 -0
  30. package/dist/gate.js.map +1 -0
  31. package/dist/handoff.d.ts +77 -0
  32. package/dist/handoff.d.ts.map +1 -0
  33. package/dist/handoff.js +52 -0
  34. package/dist/handoff.js.map +1 -0
  35. package/dist/handoffNote.d.ts +11 -0
  36. package/dist/handoffNote.d.ts.map +1 -0
  37. package/dist/handoffNote.js +6 -0
  38. package/dist/handoffNote.js.map +1 -0
  39. package/dist/index.d.ts +19 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +22 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/integration.d.ts +9 -0
  44. package/dist/integration.d.ts.map +1 -0
  45. package/dist/integration.js +13 -0
  46. package/dist/integration.js.map +1 -0
  47. package/dist/integrationAuth.d.ts +8 -0
  48. package/dist/integrationAuth.d.ts.map +1 -0
  49. package/dist/integrationAuth.js +5 -0
  50. package/dist/integrationAuth.js.map +1 -0
  51. package/dist/lifecycle.d.ts +16 -0
  52. package/dist/lifecycle.d.ts.map +1 -0
  53. package/dist/lifecycle.js +57 -0
  54. package/dist/lifecycle.js.map +1 -0
  55. package/dist/lifecycleNote.d.ts +13 -0
  56. package/dist/lifecycleNote.d.ts.map +1 -0
  57. package/dist/lifecycleNote.js +17 -0
  58. package/dist/lifecycleNote.js.map +1 -0
  59. package/dist/messages.d.ts +21 -0
  60. package/dist/messages.d.ts.map +1 -0
  61. package/dist/messages.js +78 -0
  62. package/dist/messages.js.map +1 -0
  63. package/dist/prompt.d.ts +3 -0
  64. package/dist/prompt.d.ts.map +1 -0
  65. package/dist/prompt.js +6 -0
  66. package/dist/prompt.js.map +1 -0
  67. package/dist/providers.d.ts +4 -0
  68. package/dist/providers.d.ts.map +1 -0
  69. package/dist/providers.js +12 -0
  70. package/dist/providers.js.map +1 -0
  71. package/dist/types.d.ts +158 -0
  72. package/dist/types.d.ts.map +1 -0
  73. package/dist/types.js +2 -0
  74. package/dist/types.js.map +1 -0
  75. package/package.json +43 -0
  76. package/src/conformance.ts +118 -0
  77. package/src/defineAgent.ts +63 -0
  78. package/src/definePrompt.ts +33 -0
  79. package/src/defineWorkflow.ts +52 -0
  80. package/src/delivery.ts +28 -0
  81. package/src/fold.ts +129 -0
  82. package/src/gate.ts +26 -0
  83. package/src/handoff.ts +58 -0
  84. package/src/handoffNote.ts +15 -0
  85. package/src/index.ts +101 -0
  86. package/src/integration.ts +14 -0
  87. package/src/integrationAuth.ts +8 -0
  88. package/src/lifecycle.ts +61 -0
  89. package/src/lifecycleNote.ts +27 -0
  90. package/src/messages.ts +99 -0
  91. package/src/prompt.ts +8 -0
  92. package/src/providers.ts +12 -0
  93. package/src/types.ts +151 -0
package/src/handoff.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod'
2
+ import type { Message, RunAgentInput } from '@ag-ui/client'
3
+
4
+ export const HandoffPayloadSchema = z.object({
5
+ threadId: z.string(),
6
+ from: z.string(),
7
+ subject: z.string(),
8
+ summary: z.string(),
9
+ category: z.string(),
10
+ priority: z.string(),
11
+ })
12
+ export type HandoffPayload = z.infer<typeof HandoffPayloadSchema>
13
+
14
+ export const TicketHandoffPayloadSchema = z.object({
15
+ repo: z.string(),
16
+ number: z.number(),
17
+ title: z.string(),
18
+ status: z.string(),
19
+ priority: z.string(),
20
+ body: z.string(),
21
+ lastComment: z
22
+ .object({
23
+ author: z.string(),
24
+ body: z.string(),
25
+ })
26
+ .nullable(),
27
+ recommendation: z.string(),
28
+ url: z.string(),
29
+ })
30
+ export type TicketHandoffPayload = z.infer<typeof TicketHandoffPayloadSchema>
31
+
32
+ const HANDOFF_PREFIX = '[handoff]'
33
+
34
+ /** Encode a payload as a user message that downstream agents can decode. */
35
+ export function encodeHandoff(payload: unknown): Message {
36
+ return {
37
+ id: crypto.randomUUID(),
38
+ role: 'user',
39
+ content: `${HANDOFF_PREFIX} ${JSON.stringify(payload)}`,
40
+ }
41
+ }
42
+
43
+ /** Decode the most recent handoff payload in the run input, validated by `schema`. */
44
+ export function decodeHandoff<T>(input: RunAgentInput, schema: z.ZodType<T>): T | null {
45
+ const messages = input?.messages ?? []
46
+ for (let i = messages.length - 1; i >= 0; i--) {
47
+ const m = messages[i]
48
+ if (m && m.role === 'user' && typeof m.content === 'string' && m.content.startsWith(HANDOFF_PREFIX)) {
49
+ try {
50
+ const parsed = schema.safeParse(JSON.parse(m.content.slice(HANDOFF_PREFIX.length).trim()))
51
+ return parsed.success ? parsed.data : null
52
+ } catch {
53
+ return null
54
+ }
55
+ }
56
+ }
57
+ return null
58
+ }
@@ -0,0 +1,15 @@
1
+ import { EventType } from '@ag-ui/client'
2
+ import type { BaseEvent } from '@ag-ui/client'
3
+
4
+ export interface HandoffNoteValue {
5
+ kind: 'handoff'
6
+ targetAgentId: string
7
+ childWorkItemId: string
8
+ deduped: boolean
9
+ at: number
10
+ }
11
+
12
+ /** Emit a handoff note event. */
13
+ export function handoffNote(value: HandoffNoteValue): BaseEvent {
14
+ return { type: EventType.CUSTOM, name: 'handoff', value } as unknown as BaseEvent
15
+ }
package/src/index.ts ADDED
@@ -0,0 +1,101 @@
1
+ // @hanfani/core — headless engine and types for the Hanfani agent framework.
2
+ //
3
+ // The one invariant this package encodes: an agent proposes, a human approves,
4
+ // the server acts. `defineAgent` refuses any effect that isn't gated behind an
5
+ // approval; the conformance suite proves providers honour that at runtime.
6
+
7
+ // Re-export the two @ag-ui/client types that appear in the public API.
8
+ export type { Message, ToolCall } from '@ag-ui/client'
9
+
10
+ export {
11
+ AgentDefinitionSchema,
12
+ defineAgent,
13
+ type AgentDefinition,
14
+ type AgentDefinitionInput,
15
+ } from './defineAgent.js'
16
+
17
+ export { defineWorkflow, instanceId } from './defineWorkflow.js'
18
+
19
+ export { definePrompt, type PromptSpec } from './definePrompt.js'
20
+
21
+ export { defineProviders } from './providers.js'
22
+
23
+ export { composeInstructions } from './prompt.js'
24
+
25
+ export {
26
+ approvalResolved,
27
+ hasPendingApproval,
28
+ isAssistant,
29
+ isToolMessage,
30
+ lastApprovalArgs,
31
+ pairToolResults,
32
+ resolvedApprovalCount,
33
+ toolCallsOf,
34
+ type AssistantMessage,
35
+ type ToolMessage,
36
+ } from './messages.js'
37
+
38
+ export {
39
+ decodeHandoff,
40
+ encodeHandoff,
41
+ HandoffPayloadSchema,
42
+ TicketHandoffPayloadSchema,
43
+ type HandoffPayload,
44
+ type TicketHandoffPayload,
45
+ } from './handoff.js'
46
+
47
+ export { handoffNote, type HandoffNoteValue } from './handoffNote.js'
48
+
49
+ export {
50
+ GATE_OPENED,
51
+ gateOpened,
52
+ GateOpenedValueSchema,
53
+ readGateOpened,
54
+ type GateOpenedValue,
55
+ } from './gate.js'
56
+
57
+ export { LIFECYCLE_NOTE_TEXT, lifecycleNote, type LifecycleNoteValue } from './lifecycleNote.js'
58
+
59
+ export { hasLiveDescendant, lifecycle } from './lifecycle.js'
60
+
61
+ export { foldEventsToMessages } from './fold.js'
62
+
63
+ export { resolveDelivery } from './delivery.js'
64
+
65
+ export {
66
+ providerConformanceChecks,
67
+ type ConformanceCheck,
68
+ type ConformanceScenario,
69
+ } from './conformance.js'
70
+
71
+ export { aggregateHealth, isOk } from './integration.js'
72
+
73
+ export { isOAuth2 } from './integrationAuth.js'
74
+
75
+ export type {
76
+ AgentRole,
77
+ AuthSpec,
78
+ BatchActionResult,
79
+ CredentialResolver,
80
+ DeliveryResult,
81
+ Destination,
82
+ EffectFn,
83
+ GateResolution,
84
+ HealthCheck,
85
+ Lifecycle,
86
+ Outcome,
87
+ Phase,
88
+ PromptStrategy,
89
+ Provider,
90
+ ProviderConfig,
91
+ ProviderFactory,
92
+ ProviderRegistry,
93
+ ReadResult,
94
+ ResolvedCredential,
95
+ ResumeHandle,
96
+ ResumeOutcome,
97
+ WorkflowAgent,
98
+ WorkflowConnection,
99
+ WorkflowDescriptor,
100
+ WorkflowInput,
101
+ } from './types.js'
@@ -0,0 +1,14 @@
1
+ import type { HealthCheck } from './types.js'
2
+
3
+ /** Narrow a HealthCheck to its ok variant. */
4
+ export function isOk(h: HealthCheck): h is { ok: true; detail?: string } {
5
+ return h.ok
6
+ }
7
+
8
+ /** Return the first failing check, or an ok result if all pass. */
9
+ export function aggregateHealth(checks: HealthCheck[]): HealthCheck {
10
+ for (const check of checks) {
11
+ if (!check.ok) return check
12
+ }
13
+ return { ok: true }
14
+ }
@@ -0,0 +1,8 @@
1
+ import type { AuthSpec } from './types.js'
2
+
3
+ /** Narrow an AuthSpec to its oauth2 variant. */
4
+ export function isOAuth2(
5
+ auth: AuthSpec,
6
+ ): auth is { kind: 'oauth2'; provider: string; scopes: string[] } {
7
+ return auth.kind === 'oauth2'
8
+ }
@@ -0,0 +1,61 @@
1
+ import type { Lifecycle, Outcome, Phase } from './types.js'
2
+
3
+ const LIVE_PHASES = new Set<Phase>(['queued', 'active', 'awaiting_human'])
4
+ const SILENT_OUTCOMES = new Set<Outcome>(['superseded', 'reset', 'dismissed'])
5
+ const FAILED_OUTCOMES = new Set<Outcome>(['stopped', 'rejected', 'error'])
6
+ const COVERING_OUTCOMES = new Set<Outcome>(['done', 'stopped', 'rejected'])
7
+
8
+ /**
9
+ * Derive display lifecycle from a work item's phase/outcome plus whether it has
10
+ * a render card and whether it has a live descendant.
11
+ */
12
+ export function lifecycle(
13
+ phase: Phase,
14
+ outcome: Outcome,
15
+ hasCard: boolean,
16
+ hasLiveDescendant: boolean,
17
+ ): Lifecycle {
18
+ const isLive = LIVE_PHASES.has(phase)
19
+ let isVisible: boolean
20
+ if (phase === 'queued') isVisible = false
21
+ else if (isLive) isVisible = true
22
+ else if (SILENT_OUTCOMES.has(outcome)) isVisible = false
23
+ else isVisible = hasCard || FAILED_OUTCOMES.has(outcome) || hasLiveDescendant
24
+ const covers = isLive || COVERING_OUTCOMES.has(outcome)
25
+ return { phase, outcome, isLive, isVisible, covers }
26
+ }
27
+
28
+ /**
29
+ * Given flat parent/child rows, return the set of ids that have at least one
30
+ * live descendant (a queued/active/awaiting_human node somewhere below them).
31
+ */
32
+ export function hasLiveDescendant<T extends { id: string; parentId: string | null; phase: Phase }>(
33
+ rows: readonly T[],
34
+ ): Set<string> {
35
+ const childrenByParent = new Map<string, T[]>()
36
+ for (const row of rows) {
37
+ if (!row.parentId) continue
38
+ const siblings = childrenByParent.get(row.parentId) ?? []
39
+ siblings.push(row)
40
+ childrenByParent.set(row.parentId, siblings)
41
+ }
42
+
43
+ const memo = new Map<string, boolean>()
44
+ const visit = (id: string): boolean => {
45
+ const cached = memo.get(id)
46
+ if (cached !== undefined) return cached
47
+ memo.set(id, false) // guard against cycles
48
+ let live = false
49
+ for (const child of childrenByParent.get(id) ?? []) {
50
+ if (LIVE_PHASES.has(child.phase) || visit(child.id)) live = true
51
+ }
52
+ memo.set(id, live)
53
+ return live
54
+ }
55
+
56
+ const result = new Set<string>()
57
+ for (const row of rows) {
58
+ if (visit(row.id)) result.add(row.id)
59
+ }
60
+ return result
61
+ }
@@ -0,0 +1,27 @@
1
+ import { EventType } from '@ag-ui/client'
2
+ import type { BaseEvent } from '@ag-ui/client'
3
+ import type { Outcome } from './types.js'
4
+
5
+ export interface LifecycleNoteValue {
6
+ kind: 'lifecycle'
7
+ outcome: Outcome
8
+ actor: string | null
9
+ at: number
10
+ }
11
+
12
+ /** Emit a lifecycle note event. */
13
+ export function lifecycleNote(value: LifecycleNoteValue): BaseEvent {
14
+ return { type: EventType.CUSTOM, name: 'lifecycle', value } as unknown as BaseEvent
15
+ }
16
+
17
+ /** Human-readable label shown for each terminal outcome. */
18
+ export const LIFECYCLE_NOTE_TEXT: Record<Outcome, string> = {
19
+ running: '',
20
+ done: 'Done',
21
+ stopped: 'Stopped — cancelled',
22
+ rejected: 'Rejected',
23
+ error: 'Error',
24
+ superseded: 'Superseded by a re-run',
25
+ reset: 'Cleared from board',
26
+ dismissed: 'Error acknowledged — dismissed',
27
+ }
@@ -0,0 +1,99 @@
1
+ import type { Message, ToolCall } from '@ag-ui/client'
2
+
3
+ export type AssistantMessage = Extract<Message, { role: 'assistant' }>
4
+ export type ToolMessage = Extract<Message, { role: 'tool' }>
5
+
6
+ export function isAssistant(m: Message): m is AssistantMessage {
7
+ return m.role === 'assistant'
8
+ }
9
+
10
+ export function isToolMessage(m: Message): m is ToolMessage {
11
+ return m.role === 'tool'
12
+ }
13
+
14
+ export function toolCallsOf(m: Message): ToolCall[] {
15
+ return isAssistant(m) && Array.isArray(m.toolCalls) ? m.toolCalls : []
16
+ }
17
+
18
+ /** True when an approval tool has been called but has no tool result yet. */
19
+ export function hasPendingApproval(
20
+ messages: readonly Message[],
21
+ approvalNames: readonly string[],
22
+ ): boolean {
23
+ const resolvedIds = new Set<string>()
24
+ for (const m of messages) {
25
+ if (isToolMessage(m) && typeof m.toolCallId === 'string') resolvedIds.add(m.toolCallId)
26
+ }
27
+ for (const m of messages) {
28
+ for (const call of toolCallsOf(m)) {
29
+ if (
30
+ approvalNames.includes(call.function.name) &&
31
+ typeof call.id === 'string' &&
32
+ !resolvedIds.has(call.id)
33
+ ) {
34
+ return true
35
+ }
36
+ }
37
+ }
38
+ return false
39
+ }
40
+
41
+ /** Map each tool result message by the toolCallId it answers. */
42
+ export function pairToolResults(messages: readonly Message[]): Map<string, ToolMessage> {
43
+ const byCallId = new Map<string, ToolMessage>()
44
+ for (const m of messages) {
45
+ if (isToolMessage(m) && typeof m.toolCallId === 'string') byCallId.set(m.toolCallId, m)
46
+ }
47
+ return byCallId
48
+ }
49
+
50
+ /** Parsed arguments of the most recent approval tool call, or null. */
51
+ export function lastApprovalArgs(
52
+ messages: readonly Message[],
53
+ approvalNames: readonly string[],
54
+ ): Record<string, unknown> | null {
55
+ for (let i = messages.length - 1; i >= 0; i--) {
56
+ const m = messages[i]
57
+ if (!m) continue
58
+ for (const call of toolCallsOf(m)) {
59
+ if (approvalNames.includes(call.function.name)) {
60
+ try {
61
+ return JSON.parse(call.function.arguments)
62
+ } catch {
63
+ return null
64
+ }
65
+ }
66
+ }
67
+ }
68
+ return null
69
+ }
70
+
71
+ /** How many approval tool calls have a matching tool result. */
72
+ export function resolvedApprovalCount(
73
+ messages: readonly Message[],
74
+ approvalNames: readonly string[],
75
+ ): number {
76
+ const approvalCallIds = new Set<string>()
77
+ for (const m of messages) {
78
+ for (const call of toolCallsOf(m)) {
79
+ if (approvalNames.includes(call.function.name) && typeof call.id === 'string') {
80
+ approvalCallIds.add(call.id)
81
+ }
82
+ }
83
+ }
84
+ const resolved = new Set<string>()
85
+ for (const m of messages) {
86
+ if (isToolMessage(m) && typeof m.toolCallId === 'string' && approvalCallIds.has(m.toolCallId)) {
87
+ resolved.add(m.toolCallId)
88
+ }
89
+ }
90
+ return resolved.size
91
+ }
92
+
93
+ /** True when at least one approval call has resolved. */
94
+ export function approvalResolved(
95
+ messages: readonly Message[],
96
+ approvalNames: readonly string[],
97
+ ): boolean {
98
+ return resolvedApprovalCount(messages, approvalNames) > 0
99
+ }
package/src/prompt.ts ADDED
@@ -0,0 +1,8 @@
1
+ /** Prepend the workflow-level prompt (if any) to an agent's own instructions. */
2
+ export function composeInstructions(
3
+ workflowPrompt: string | undefined,
4
+ agentInstructions: string,
5
+ ): string {
6
+ const prefix = workflowPrompt?.trim()
7
+ return prefix ? `${prefix}\n\n${agentInstructions}` : agentInstructions
8
+ }
@@ -0,0 +1,12 @@
1
+ import type { ProviderFactory, ProviderRegistry } from './types.js'
2
+
3
+ /** Build a registry that resolves provider factories by name. */
4
+ export function defineProviders(map: Record<string, ProviderFactory>): ProviderRegistry {
5
+ return {
6
+ resolve(name: string): ProviderFactory {
7
+ const factory = map[name]
8
+ if (!factory) throw new Error(`Unknown provider: ${name}`)
9
+ return factory
10
+ },
11
+ }
12
+ }
package/src/types.ts ADDED
@@ -0,0 +1,151 @@
1
+ import type { BaseEvent, Message, RunAgentInput, ToolCall } from '@ag-ui/client'
2
+ import type { z } from 'zod'
3
+ import type { AgentDefinition } from './defineAgent.js'
4
+
5
+ // Re-export the two @ag-ui/client types that appear in this package's public API,
6
+ // so consumers can import them from @hanfani/core directly.
7
+ export type { Message, ToolCall }
8
+
9
+ /** Lifecycle phase of a work item. */
10
+ export type Phase = 'queued' | 'active' | 'awaiting_human' | 'terminal'
11
+
12
+ /** Terminal (and running) outcome of a work item. */
13
+ export type Outcome =
14
+ | 'running'
15
+ | 'done'
16
+ | 'stopped'
17
+ | 'rejected'
18
+ | 'error'
19
+ | 'superseded'
20
+ | 'reset'
21
+ | 'dismissed'
22
+
23
+ /** Whether an agent accepts external input or only runs as a downstream worker. */
24
+ export type AgentRole = 'input' | 'worker'
25
+
26
+ /** Result of a health probe. */
27
+ export type HealthCheck =
28
+ | { ok: true; detail?: string }
29
+ | { ok: false; error: string; hint: string }
30
+
31
+ /** How an integration authenticates. */
32
+ export type AuthSpec =
33
+ | { kind: 'none' }
34
+ | { kind: 'apiKey' }
35
+ | { kind: 'oauth2'; provider: string; scopes: string[] }
36
+ | { kind: string; [key: string]: unknown }
37
+
38
+ /** A credential resolved for a given integration + connection. */
39
+ export type ResolvedCredential =
40
+ | { kind: 'apiKey'; apiKey: string }
41
+ | { kind: 'oauth2'; accessToken: string; refreshToken?: string; expiresAt?: number; raw?: unknown }
42
+ | { kind: string; [key: string]: unknown }
43
+
44
+ export type CredentialResolver = (ctx: {
45
+ integration: string
46
+ connectionId: string
47
+ auth: AuthSpec
48
+ }) => Promise<ResolvedCredential | null>
49
+
50
+ /** What a prompt strategy decides to do when a gate resolves. */
51
+ export type ResumeOutcome =
52
+ | { kind: 'prompt'; text: string }
53
+ | { kind: 'message'; text: string }
54
+ | null
55
+
56
+ export interface ResumeHandle {
57
+ runId: string
58
+ input: RunAgentInput
59
+ }
60
+
61
+ export interface GateResolution {
62
+ gateId: string
63
+ decision: 'approved' | 'rejected'
64
+ form?: Record<string, unknown>
65
+ comment?: string
66
+ executedResult?: Record<string, unknown>
67
+ }
68
+
69
+ export interface PromptStrategy {
70
+ buildFirst(input: RunAgentInput): string
71
+ buildResume?(args: Record<string, unknown>, executedResult?: Record<string, unknown>): ResumeOutcome
72
+ }
73
+
74
+ export interface Provider {
75
+ run(input: RunAgentInput): AsyncIterable<BaseEvent>
76
+ resume?(handle: ResumeHandle, resolution: GateResolution): AsyncIterable<BaseEvent>
77
+ }
78
+
79
+ export interface ProviderConfig {
80
+ approvalNames: readonly string[]
81
+ surfaceTools: readonly string[]
82
+ allowedTools: readonly string[]
83
+ prompts: PromptStrategy
84
+ instructions: string
85
+ agentId: string
86
+ }
87
+
88
+ export type ProviderFactory = (config: ProviderConfig) => Provider
89
+
90
+ export interface ProviderRegistry {
91
+ resolve(name: string): ProviderFactory
92
+ }
93
+
94
+ export interface Lifecycle {
95
+ phase: Phase
96
+ outcome: Outcome
97
+ isLive: boolean
98
+ isVisible: boolean
99
+ covers: boolean
100
+ }
101
+
102
+ /** Where a produced payload should be delivered. */
103
+ export type Destination =
104
+ | { kind: 'agent'; agentId: string }
105
+ | { kind: 'contract'; workflow: string; input: string }
106
+
107
+ export type DeliveryResult =
108
+ | { ok: true; instanceId: string; targetWorkflow?: string }
109
+ | { ok: false; error: string }
110
+
111
+ /** The consequential side effect bound to an approved tool. Runs on the server only. */
112
+ export type EffectFn = (
113
+ form: Record<string, unknown>,
114
+ ctx: { workItemId: string; gateId: string },
115
+ ) => Promise<Record<string, unknown>>
116
+
117
+ export type WorkflowAgent = {
118
+ agent: AgentDefinition
119
+ role: AgentRole
120
+ }
121
+
122
+ export type WorkflowConnection = {
123
+ integration: string
124
+ connection?: string
125
+ provider: string
126
+ }
127
+
128
+ export type WorkflowInput = {
129
+ name: string
130
+ schema: z.ZodTypeAny
131
+ agentId: string
132
+ }
133
+
134
+ export type WorkflowDescriptor = {
135
+ id: string
136
+ label: string
137
+ iconName: string
138
+ agents: WorkflowAgent[]
139
+ entryAgentId: string
140
+ inputs: WorkflowInput[]
141
+ prompt?: string
142
+ connections?: WorkflowConnection[]
143
+ rerun?: 'refresh' | 'history'
144
+ resetOnStart?: boolean
145
+ }
146
+
147
+ export type ReadResult<T> = T | { error: string }
148
+
149
+ export type BatchActionResult =
150
+ | { done: string[]; failed: { messageId: string; error: string }[] }
151
+ | { error: string }