@relayflows/sdk 2.0.0
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/authored-flow-error.d.ts +9 -0
- package/dist/authored-flow-error.d.ts.map +1 -0
- package/dist/authored-flow-error.js +13 -0
- package/dist/authored-flow-error.js.map +1 -0
- package/dist/authored-flow-executor.d.ts +28 -0
- package/dist/authored-flow-executor.d.ts.map +1 -0
- package/dist/authored-flow-executor.js +203 -0
- package/dist/authored-flow-executor.js.map +1 -0
- package/dist/authored-flow-lifecycle.d.ts +61 -0
- package/dist/authored-flow-lifecycle.d.ts.map +1 -0
- package/dist/authored-flow-lifecycle.js +284 -0
- package/dist/authored-flow-lifecycle.js.map +1 -0
- package/dist/authored-flow-loader.d.ts +7 -0
- package/dist/authored-flow-loader.d.ts.map +1 -0
- package/dist/authored-flow-loader.js +39 -0
- package/dist/authored-flow-loader.js.map +1 -0
- package/dist/authored-flow-operation.d.ts +43 -0
- package/dist/authored-flow-operation.d.ts.map +1 -0
- package/dist/authored-flow-operation.js +176 -0
- package/dist/authored-flow-operation.js.map +1 -0
- package/dist/authored-flow.d.ts +12 -0
- package/dist/authored-flow.d.ts.map +1 -0
- package/dist/authored-flow.js +13 -0
- package/dist/authored-flow.js.map +1 -0
- package/dist/authored-promise-graph.d.ts +67 -0
- package/dist/authored-promise-graph.d.ts.map +1 -0
- package/dist/authored-promise-graph.js +206 -0
- package/dist/authored-promise-graph.js.map +1 -0
- package/dist/backlog-picker.d.ts +55 -0
- package/dist/backlog-picker.d.ts.map +1 -0
- package/dist/backlog-picker.js +117 -0
- package/dist/backlog-picker.js.map +1 -0
- package/dist/canonical.d.ts +18 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +55 -0
- package/dist/canonical.js.map +1 -0
- package/dist/cli/check.d.ts +30 -0
- package/dist/cli/check.d.ts.map +1 -0
- package/dist/cli/check.js +324 -0
- package/dist/cli/check.js.map +1 -0
- package/dist/cli/direct-run.d.ts +3 -0
- package/dist/cli/direct-run.d.ts.map +1 -0
- package/dist/cli/direct-run.js +69 -0
- package/dist/cli/direct-run.js.map +1 -0
- package/dist/cli/hn-monitor.d.ts +107 -0
- package/dist/cli/hn-monitor.d.ts.map +1 -0
- package/dist/cli/hn-monitor.js +220 -0
- package/dist/cli/hn-monitor.js.map +1 -0
- package/dist/cli/interruptible-sleep.d.ts +13 -0
- package/dist/cli/interruptible-sleep.d.ts.map +1 -0
- package/dist/cli/interruptible-sleep.js +31 -0
- package/dist/cli/interruptible-sleep.js.map +1 -0
- package/dist/cli/run.d.ts +54 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +316 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/tick-runner.d.ts +151 -0
- package/dist/cli/tick-runner.d.ts.map +1 -0
- package/dist/cli/tick-runner.js +258 -0
- package/dist/cli/tick-runner.js.map +1 -0
- package/dist/cli-adapter.d.ts +29 -0
- package/dist/cli-adapter.d.ts.map +1 -0
- package/dist/cli-adapter.js +92 -0
- package/dist/cli-adapter.js.map +1 -0
- package/dist/cli-executable.d.ts +3 -0
- package/dist/cli-executable.d.ts.map +1 -0
- package/dist/cli-executable.js +10 -0
- package/dist/cli-executable.js.map +1 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +348 -0
- package/dist/cli.js.map +1 -0
- package/dist/compile.d.ts +42 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +515 -0
- package/dist/compile.js.map +1 -0
- package/dist/demo-hn-monitor.d.ts +2 -0
- package/dist/demo-hn-monitor.d.ts.map +1 -0
- package/dist/demo-hn-monitor.js +118 -0
- package/dist/demo-hn-monitor.js.map +1 -0
- package/dist/dir-watcher-poller.d.ts +73 -0
- package/dist/dir-watcher-poller.d.ts.map +1 -0
- package/dist/dir-watcher-poller.js +80 -0
- package/dist/dir-watcher-poller.js.map +1 -0
- package/dist/direct-input.d.ts +10 -0
- package/dist/direct-input.d.ts.map +1 -0
- package/dist/direct-input.js +61 -0
- package/dist/direct-input.js.map +1 -0
- package/dist/failure-kinds.d.ts +25 -0
- package/dist/failure-kinds.d.ts.map +1 -0
- package/dist/failure-kinds.js +64 -0
- package/dist/failure-kinds.js.map +1 -0
- package/dist/gate-contract.d.ts +24 -0
- package/dist/gate-contract.d.ts.map +1 -0
- package/dist/gate-contract.js +50 -0
- package/dist/gate-contract.js.map +1 -0
- package/dist/hn-poller.d.ts +50 -0
- package/dist/hn-poller.d.ts.map +1 -0
- package/dist/hn-poller.js +79 -0
- package/dist/hn-poller.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/journal-client.d.ts +122 -0
- package/dist/journal-client.d.ts.map +1 -0
- package/dist/journal-client.js +277 -0
- package/dist/journal-client.js.map +1 -0
- package/dist/json-schema-bound.d.ts +7 -0
- package/dist/json-schema-bound.d.ts.map +1 -0
- package/dist/json-schema-bound.js +353 -0
- package/dist/json-schema-bound.js.map +1 -0
- package/dist/json-schema.d.ts +5 -0
- package/dist/json-schema.d.ts.map +1 -0
- package/dist/json-schema.js +72 -0
- package/dist/json-schema.js.map +1 -0
- package/dist/json-value.d.ts +6 -0
- package/dist/json-value.d.ts.map +1 -0
- package/dist/json-value.js +94 -0
- package/dist/json-value.js.map +1 -0
- package/dist/model-name.d.ts +7 -0
- package/dist/model-name.d.ts.map +1 -0
- package/dist/model-name.js +21 -0
- package/dist/model-name.js.map +1 -0
- package/dist/output-schema.d.ts +8 -0
- package/dist/output-schema.d.ts.map +1 -0
- package/dist/output-schema.js +28 -0
- package/dist/output-schema.js.map +1 -0
- package/dist/preflight.d.ts +80 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +361 -0
- package/dist/preflight.js.map +1 -0
- package/dist/protocol.d.ts +359 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +15 -0
- package/dist/protocol.js.map +1 -0
- package/dist/spec.d.ts +294 -0
- package/dist/spec.d.ts.map +1 -0
- package/dist/spec.js +13 -0
- package/dist/spec.js.map +1 -0
- package/dist/step-dependencies.d.ts +2 -0
- package/dist/step-dependencies.d.ts.map +1 -0
- package/dist/step-dependencies.js +88 -0
- package/dist/step-dependencies.js.map +1 -0
- package/dist/step-fields.d.ts +17 -0
- package/dist/step-fields.d.ts.map +1 -0
- package/dist/step-fields.js +32 -0
- package/dist/step-fields.js.map +1 -0
- package/dist/tick-source.d.ts +205 -0
- package/dist/tick-source.d.ts.map +1 -0
- package/dist/tick-source.js +234 -0
- package/dist/tick-source.js.map +1 -0
- package/dist/unknown-keys.d.ts +7 -0
- package/dist/unknown-keys.d.ts.map +1 -0
- package/dist/unknown-keys.js +47 -0
- package/dist/unknown-keys.js.map +1 -0
- package/dist/validate.d.ts +7 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +458 -0
- package/dist/validate.js.map +1 -0
- package/dist/work-package-consumer.d.ts +33 -0
- package/dist/work-package-consumer.d.ts.map +1 -0
- package/dist/work-package-consumer.js +44 -0
- package/dist/work-package-consumer.js.map +1 -0
- package/dist/work-package-validator.d.ts +15 -0
- package/dist/work-package-validator.d.ts.map +1 -0
- package/dist/work-package-validator.js +72 -0
- package/dist/work-package-validator.js.map +1 -0
- package/dist/worker-cli.d.ts +16 -0
- package/dist/worker-cli.d.ts.map +1 -0
- package/dist/worker-cli.js +77 -0
- package/dist/worker-cli.js.map +1 -0
- package/dist/worker.d.ts +57 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +129 -0
- package/dist/worker.js.map +1 -0
- package/dist/wrapper-runtime.d.ts +10 -0
- package/dist/wrapper-runtime.d.ts.map +1 -0
- package/dist/wrapper-runtime.js +83 -0
- package/dist/wrapper-runtime.js.map +1 -0
- package/dist/wrapper-session.d.ts +16 -0
- package/dist/wrapper-session.d.ts.map +1 -0
- package/dist/wrapper-session.js +244 -0
- package/dist/wrapper-session.js.map +1 -0
- package/package.json +50 -0
- package/src/authored-flow-error.ts +31 -0
- package/src/authored-flow-executor.ts +365 -0
- package/src/authored-flow-lifecycle.ts +344 -0
- package/src/authored-flow-loader.ts +44 -0
- package/src/authored-flow-operation.ts +252 -0
- package/src/authored-flow.ts +21 -0
- package/src/authored-promise-graph.ts +196 -0
- package/src/backlog-picker.ts +153 -0
- package/src/canonical.ts +57 -0
- package/src/cli/check.ts +391 -0
- package/src/cli/direct-run.ts +86 -0
- package/src/cli/hn-monitor.ts +286 -0
- package/src/cli/interruptible-sleep.ts +27 -0
- package/src/cli/run.ts +439 -0
- package/src/cli/tick-runner.ts +378 -0
- package/src/cli-adapter.ts +115 -0
- package/src/cli-executable.ts +12 -0
- package/src/cli.ts +364 -0
- package/src/compile.ts +573 -0
- package/src/demo-hn-monitor.ts +132 -0
- package/src/dir-watcher-poller.ts +122 -0
- package/src/direct-input.ts +69 -0
- package/src/failure-kinds.ts +77 -0
- package/src/gate-contract.ts +72 -0
- package/src/hn-poller.ts +104 -0
- package/src/index.ts +195 -0
- package/src/journal-client.ts +379 -0
- package/src/json-schema-bound.ts +357 -0
- package/src/json-schema.ts +65 -0
- package/src/json-value.ts +110 -0
- package/src/model-name.ts +20 -0
- package/src/output-schema.ts +33 -0
- package/src/preflight.ts +482 -0
- package/src/protocol.ts +364 -0
- package/src/spec.ts +343 -0
- package/src/step-dependencies.ts +100 -0
- package/src/step-fields.ts +36 -0
- package/src/tick-source.ts +334 -0
- package/src/unknown-keys.ts +52 -0
- package/src/validate.ts +503 -0
- package/src/work-package-consumer.ts +73 -0
- package/src/work-package-validator.ts +90 -0
- package/src/worker-cli.ts +110 -0
- package/src/worker.ts +150 -0
- package/src/wrapper-runtime.ts +93 -0
- package/src/wrapper-session.ts +287 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// @relayflows/sdk — TypeScript-first authoring SDK for Relayflows.
|
|
2
|
+
// Compiles specs (RFC-0001 §1 ladder) and speaks the journal protocol v0
|
|
3
|
+
// (kernel DESIGN.md §5).
|
|
4
|
+
|
|
5
|
+
export type {
|
|
6
|
+
AgentStepSpec,
|
|
7
|
+
AgentSurfaces,
|
|
8
|
+
BaseStepSpec,
|
|
9
|
+
BudgetSpec,
|
|
10
|
+
DeterministicStepSpec,
|
|
11
|
+
ExitCodeGate,
|
|
12
|
+
FlowSpec,
|
|
13
|
+
JsonSchemaGate,
|
|
14
|
+
JsonOutputSchema,
|
|
15
|
+
KernelAgentStep,
|
|
16
|
+
KernelAgentSurfaces,
|
|
17
|
+
KernelBudgetSpec,
|
|
18
|
+
KernelDeterministicStep,
|
|
19
|
+
KernelLlmStep,
|
|
20
|
+
KernelPermissionsSpec,
|
|
21
|
+
KernelRetryPolicy,
|
|
22
|
+
KernelRunSpec,
|
|
23
|
+
KernelStepCommon,
|
|
24
|
+
KernelStepSpec,
|
|
25
|
+
KernelTriggerSpec,
|
|
26
|
+
KernelVerificationSpec,
|
|
27
|
+
LlmStepSpec,
|
|
28
|
+
NamedAgentSpec,
|
|
29
|
+
OutputContainsGate,
|
|
30
|
+
OutputVerificationSpec,
|
|
31
|
+
PermissionsSpec,
|
|
32
|
+
RecoveryMode,
|
|
33
|
+
StreamSurface,
|
|
34
|
+
StepSpec,
|
|
35
|
+
StepType,
|
|
36
|
+
TriggerSpec,
|
|
37
|
+
VerificationGateType,
|
|
38
|
+
VerificationSpec,
|
|
39
|
+
WorkspaceSurface,
|
|
40
|
+
} from './spec.js';
|
|
41
|
+
export { SPEC_SCHEMA_VERSION } from './spec.js';
|
|
42
|
+
|
|
43
|
+
export { canonicalize, specHash } from './canonical.js';
|
|
44
|
+
export {
|
|
45
|
+
compileAndHash,
|
|
46
|
+
compileSpec,
|
|
47
|
+
compileYaml,
|
|
48
|
+
compileYamlToCanonicalJson,
|
|
49
|
+
kernelToAuthoring,
|
|
50
|
+
toKernelSpec,
|
|
51
|
+
CompileError,
|
|
52
|
+
} from './compile.js';
|
|
53
|
+
export { validateSpec, type ValidationResult } from './validate.js';
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
getAuthoredFlowDefinition,
|
|
57
|
+
type AuthoredFlowDefinition,
|
|
58
|
+
type FlowHandle,
|
|
59
|
+
} from './authored-flow.js';
|
|
60
|
+
export {
|
|
61
|
+
preflight,
|
|
62
|
+
type CliResolution,
|
|
63
|
+
type CliResolutionSource,
|
|
64
|
+
type CliProbeResult,
|
|
65
|
+
type PreflightDiagnostic,
|
|
66
|
+
type PreflightOptions,
|
|
67
|
+
type PreflightProbes,
|
|
68
|
+
type PreflightRefusal,
|
|
69
|
+
type PreflightResult,
|
|
70
|
+
type PreflightWarning,
|
|
71
|
+
} from './preflight.js';
|
|
72
|
+
export {
|
|
73
|
+
type DataGateClassification,
|
|
74
|
+
type JournalGateCheck,
|
|
75
|
+
type StepGateInspection,
|
|
76
|
+
} from './gate-contract.js';
|
|
77
|
+
export {
|
|
78
|
+
CHECK_FAILURE_KINDS,
|
|
79
|
+
CHECK_INPUT_FAILURE_KINDS,
|
|
80
|
+
PREFLIGHT_FAILURE_KINDS,
|
|
81
|
+
PREFLIGHT_WARNING_KINDS,
|
|
82
|
+
isCheckFailureKind,
|
|
83
|
+
type CheckFailureKind,
|
|
84
|
+
type PreflightFailureKind,
|
|
85
|
+
type PreflightWarningKind,
|
|
86
|
+
} from './failure-kinds.js';
|
|
87
|
+
export { runCli, type CheckInputDiagnostic, type CheckReport, type CliIo } from './cli.js';
|
|
88
|
+
|
|
89
|
+
export type {
|
|
90
|
+
CompletionReason,
|
|
91
|
+
EffectRecordParams,
|
|
92
|
+
EffectRecordResult,
|
|
93
|
+
EffectRef,
|
|
94
|
+
EventEmitParams,
|
|
95
|
+
EventEmitResult,
|
|
96
|
+
HelloParams,
|
|
97
|
+
HelloResult,
|
|
98
|
+
JournalReadParams,
|
|
99
|
+
JournalReadResult,
|
|
100
|
+
ProtocolError,
|
|
101
|
+
Pins,
|
|
102
|
+
Request,
|
|
103
|
+
Response,
|
|
104
|
+
RunGetParams,
|
|
105
|
+
RunGetResult,
|
|
106
|
+
RunOutcome,
|
|
107
|
+
RunCompletionReason,
|
|
108
|
+
RunCancelParams,
|
|
109
|
+
RunCancelResult,
|
|
110
|
+
RunResumeParams,
|
|
111
|
+
RunResumeResult,
|
|
112
|
+
RunStatus,
|
|
113
|
+
RunWatchParams,
|
|
114
|
+
RunWatchResult,
|
|
115
|
+
ServerEvent,
|
|
116
|
+
StepCompleteParams,
|
|
117
|
+
StepCompleteResult,
|
|
118
|
+
StepDispatchEvent,
|
|
119
|
+
StepHeartbeatParams,
|
|
120
|
+
StepHeartbeatResult,
|
|
121
|
+
StreamAppendParams,
|
|
122
|
+
StreamAppendResult,
|
|
123
|
+
StreamReadParams,
|
|
124
|
+
StreamReadResult,
|
|
125
|
+
Verb,
|
|
126
|
+
VerbContract,
|
|
127
|
+
WorkerAttachParams,
|
|
128
|
+
WorkerAttachResult,
|
|
129
|
+
} from './protocol.js';
|
|
130
|
+
export { JOURNAL_WRITE_FAILED, PROTOCOL_VERSION } from './protocol.js';
|
|
131
|
+
|
|
132
|
+
export { JournalClient, type JournalClientOptions } from './journal-client.js';
|
|
133
|
+
export { AgentWorker, type AgentWorkerOptions } from './worker.js';
|
|
134
|
+
|
|
135
|
+
export {
|
|
136
|
+
validateWorkPackage,
|
|
137
|
+
packageFromEntry,
|
|
138
|
+
type ValidatedWorkPackage,
|
|
139
|
+
type WorkPackageValidation,
|
|
140
|
+
type WorkPackageValidationReason,
|
|
141
|
+
} from './backlog-picker.js';
|
|
142
|
+
|
|
143
|
+
export {
|
|
144
|
+
consumeWorkPackage,
|
|
145
|
+
type EmittedWorkPackage,
|
|
146
|
+
type WorkPackageConsumption,
|
|
147
|
+
type WorkPackageRefusalReason,
|
|
148
|
+
} from './work-package-consumer.js';
|
|
149
|
+
|
|
150
|
+
export {
|
|
151
|
+
validateNextWorkPackage,
|
|
152
|
+
type NextWorkPackageRefusalReason,
|
|
153
|
+
type NextWorkPackageValidation,
|
|
154
|
+
type WorkPackagePathExists,
|
|
155
|
+
} from './work-package-validator.js';
|
|
156
|
+
|
|
157
|
+
// Hacker News adapter — deliberately outside kernel/ (see sdk/src/hn-poller.ts).
|
|
158
|
+
export {
|
|
159
|
+
pollHackerNewsOnce,
|
|
160
|
+
HnTransientFetchError,
|
|
161
|
+
HN_TOP_STORIES_URL,
|
|
162
|
+
type EventSink,
|
|
163
|
+
type Fetcher,
|
|
164
|
+
type PollOptions,
|
|
165
|
+
} from './hn-poller.js';
|
|
166
|
+
|
|
167
|
+
// Directory watcher — second proactive workload for gate 2 primitives.
|
|
168
|
+
// Non-provider: no HTTP, no API tokens, no gate-6 dependency. Proves the
|
|
169
|
+
// pattern generalizes without regressing RFC-0001 §6 (providers = relayfile
|
|
170
|
+
// adapters, not SDK code).
|
|
171
|
+
export {
|
|
172
|
+
pollDirectoryOnce,
|
|
173
|
+
type DirLister,
|
|
174
|
+
type PollOptions as DirWatcherPollOptions,
|
|
175
|
+
} from './dir-watcher-poller.js';
|
|
176
|
+
|
|
177
|
+
// Tick event source — a relayflow can be scheduled. A schedule is an event
|
|
178
|
+
// source subject to the same liveness sweep as any other subscription, not a
|
|
179
|
+
// scheduler inside the kernel (RFC-0001 gate 2: "triggers are entry
|
|
180
|
+
// conditions, not schedulers").
|
|
181
|
+
export {
|
|
182
|
+
emitDueTicks,
|
|
183
|
+
scheduledForMs,
|
|
184
|
+
TickEmitError,
|
|
185
|
+
slotFor,
|
|
186
|
+
tickDedupeKey,
|
|
187
|
+
DEFAULT_MAX_CATCH_UP,
|
|
188
|
+
TICK_DEDUPE_KEY_TEMPLATE,
|
|
189
|
+
TICK_EVENT_TYPE,
|
|
190
|
+
type EventSink as TickEventSink,
|
|
191
|
+
type TickCursor,
|
|
192
|
+
type TickEmitResult,
|
|
193
|
+
type TickPayload,
|
|
194
|
+
type TickSchedule,
|
|
195
|
+
} from './tick-source.js';
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
// Journal-protocol v0 client (kernel DESIGN.md §5).
|
|
2
|
+
//
|
|
3
|
+
// A real client implementation of the wire protocol — newline-delimited JSON
|
|
4
|
+
// over a unix socket. It correlates requests by `id`, demultiplexes
|
|
5
|
+
// server-pushed events, and is fail-closed: a connection drop or write error
|
|
6
|
+
// rejects every pending request (a journal write that fails fails the step;
|
|
7
|
+
// AGENTS.md rule 4). The kernel binary (`kernel/relayflowd serve`) speaks
|
|
8
|
+
// this transport, including out-of-band leases, watches, events, and durable
|
|
9
|
+
// stream plumbing. The client's framing and failure behavior is covered by a
|
|
10
|
+
// loopback double in tests.
|
|
11
|
+
|
|
12
|
+
import { EventEmitter } from 'node:events';
|
|
13
|
+
import { randomUUID } from 'node:crypto';
|
|
14
|
+
import { createConnection, type Socket } from 'node:net';
|
|
15
|
+
import type { VerbContract, EventSubmitParams } from './protocol.js';
|
|
16
|
+
import {
|
|
17
|
+
PROTOCOL_VERSION,
|
|
18
|
+
type CompletionReason,
|
|
19
|
+
type EffectRef,
|
|
20
|
+
type Pins,
|
|
21
|
+
type Request,
|
|
22
|
+
type Response,
|
|
23
|
+
type ServerEvent,
|
|
24
|
+
} from './protocol.js';
|
|
25
|
+
import type { KernelRunSpec, StepType } from './spec.js';
|
|
26
|
+
|
|
27
|
+
export interface JournalClientOptions {
|
|
28
|
+
/** Override the timeout for bounded protocol requests (ms). Default 30000. */
|
|
29
|
+
requestTimeoutMs?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface Pending {
|
|
33
|
+
resolve: (value: unknown) => void;
|
|
34
|
+
reject: (err: Error) => void;
|
|
35
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A structured rejection returned by relayflowd over the journal protocol. */
|
|
39
|
+
export class JournalProtocolError extends Error {
|
|
40
|
+
readonly code: string;
|
|
41
|
+
|
|
42
|
+
constructor(code: string, message: string) {
|
|
43
|
+
super(`${code}: ${message}`);
|
|
44
|
+
this.name = 'JournalProtocolError';
|
|
45
|
+
this.code = code;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class JournalClient extends EventEmitter {
|
|
50
|
+
private socket: Socket | null = null;
|
|
51
|
+
private buffer = '';
|
|
52
|
+
private readonly pending = new Map<string, Pending>();
|
|
53
|
+
private readonly requestTimeoutMs: number;
|
|
54
|
+
|
|
55
|
+
constructor(
|
|
56
|
+
private readonly socketPath: string,
|
|
57
|
+
options: JournalClientOptions = {},
|
|
58
|
+
) {
|
|
59
|
+
super();
|
|
60
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? 30_000;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Open the unix socket connection. Rejects on connect failure (fail-closed). */
|
|
64
|
+
connect(): Promise<void> {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
if (this.socket) return resolve();
|
|
67
|
+
const socket = createConnection({ path: this.socketPath });
|
|
68
|
+
const onError = (err: Error): void => {
|
|
69
|
+
socket.removeAllListeners();
|
|
70
|
+
this.failAll(err);
|
|
71
|
+
reject(new Error(`journal client: connect failed: ${err.message}`));
|
|
72
|
+
};
|
|
73
|
+
socket.once('error', onError);
|
|
74
|
+
socket.once('connect', () => {
|
|
75
|
+
socket.removeListener('error', onError);
|
|
76
|
+
socket.on('error', (err) => this.failAll(err));
|
|
77
|
+
socket.on('data', (chunk) => this.onData(chunk));
|
|
78
|
+
socket.on('close', () => this.failAll(new Error('journal client: connection closed')));
|
|
79
|
+
this.socket = socket;
|
|
80
|
+
resolve();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Close the connection and reject any pending requests. */
|
|
86
|
+
close(): void {
|
|
87
|
+
this.failAll(new Error('journal client: closed by caller'));
|
|
88
|
+
this.socket?.destroy();
|
|
89
|
+
this.socket = null;
|
|
90
|
+
this.buffer = '';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private onData(chunk: Buffer): void {
|
|
94
|
+
this.buffer += chunk.toString('utf8');
|
|
95
|
+
let nl: number;
|
|
96
|
+
while ((nl = this.buffer.indexOf('\n')) !== -1) {
|
|
97
|
+
const line = this.buffer.slice(0, nl);
|
|
98
|
+
this.buffer = this.buffer.slice(nl + 1);
|
|
99
|
+
if (line.length > 0) this.onLine(line);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private onLine(line: string): void {
|
|
104
|
+
let msg: Response | ServerEvent;
|
|
105
|
+
try {
|
|
106
|
+
msg = JSON.parse(line) as Response | ServerEvent;
|
|
107
|
+
} catch {
|
|
108
|
+
// A malformed frame is a protocol violation; fail closed.
|
|
109
|
+
this.failAll(new Error('journal client: malformed frame from server'));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (typeof (msg as Response).id === 'string' && 'ok' in (msg as Response)) {
|
|
114
|
+
const res = msg as Response;
|
|
115
|
+
const pending = this.pending.get(res.id);
|
|
116
|
+
if (!pending) return; // reply for an already-timed-out request
|
|
117
|
+
this.pending.delete(res.id);
|
|
118
|
+
if (pending.timer !== undefined) clearTimeout(pending.timer);
|
|
119
|
+
if (res.ok) pending.resolve(res.result);
|
|
120
|
+
else pending.reject(new JournalProtocolError(res.error.code, res.error.message));
|
|
121
|
+
} else {
|
|
122
|
+
const ev = msg as ServerEvent;
|
|
123
|
+
this.emit(ev.event, ev.data);
|
|
124
|
+
this.emit('event', ev);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private failAll(err: Error): void {
|
|
129
|
+
for (const [, p] of this.pending) {
|
|
130
|
+
if (p.timer !== undefined) clearTimeout(p.timer);
|
|
131
|
+
p.reject(err);
|
|
132
|
+
}
|
|
133
|
+
this.pending.clear();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private request<V extends keyof VerbContract>(
|
|
137
|
+
verb: V,
|
|
138
|
+
params: VerbContract[V]['params'],
|
|
139
|
+
timeoutMs: number | null = this.requestTimeoutMs,
|
|
140
|
+
): Promise<VerbContract[V]['result']> {
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
if (!this.socket || this.socket.destroyed) {
|
|
143
|
+
reject(new Error(`journal client: not connected (${verb})`));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const id = randomUUID();
|
|
147
|
+
const frame: Request = { id, verb: verb as string, params };
|
|
148
|
+
const timer = timeoutMs === null ? undefined : setTimeout(() => {
|
|
149
|
+
this.pending.delete(id);
|
|
150
|
+
reject(new Error(`journal client: ${verb} timed out after ${timeoutMs}ms`));
|
|
151
|
+
}, timeoutMs);
|
|
152
|
+
this.pending.set(id, { resolve: resolve as (v: unknown) => void, reject, timer });
|
|
153
|
+
this.socket.write(JSON.stringify(frame) + '\n', (err) => {
|
|
154
|
+
if (err) {
|
|
155
|
+
const p = this.pending.get(id);
|
|
156
|
+
if (p) {
|
|
157
|
+
if (p.timer !== undefined) clearTimeout(p.timer);
|
|
158
|
+
this.pending.delete(id);
|
|
159
|
+
p.reject(new Error(`journal client: ${verb} write failed: ${err.message}`));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// --- Typed verb methods (gate 1 minimal set, kernel DESIGN.md §5) --------
|
|
167
|
+
|
|
168
|
+
/** Handshake; version mismatch is a hard error. */
|
|
169
|
+
hello(client: string): Promise<VerbContract['hello']['result']> {
|
|
170
|
+
return this.request('hello', { protocol: PROTOCOL_VERSION, client });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Validate a compiled kernel-dialect spec (zero-agent flows legal), create
|
|
175
|
+
* the run file, and append `run.spawned`. Authoring specs must be compiled
|
|
176
|
+
* with `toKernelSpec` before crossing this journal-protocol boundary.
|
|
177
|
+
*/
|
|
178
|
+
runStart(spec: KernelRunSpec): Promise<VerbContract['run.start']['result']> {
|
|
179
|
+
return this.request('run.start', { spec }, null);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** §3 memoized resume. */
|
|
183
|
+
runResume(runId: string): Promise<VerbContract['run.resume']['result']> {
|
|
184
|
+
return this.request('run.resume', { run_id: runId }, null);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Durably request cancellation and return the terminal run fact. */
|
|
188
|
+
runCancel(runId: string): Promise<VerbContract['run.cancel']['result']> {
|
|
189
|
+
return this.request('run.cancel', { run_id: runId }, null);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Snapshot for legibility. */
|
|
193
|
+
runGet(runId: string): Promise<VerbContract['run.get']['result']> {
|
|
194
|
+
return this.request('run.get', { run_id: runId });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Open a push stream of every appended entry. Resolves once subscribed;
|
|
199
|
+
* entries arrive as `'entry'` events: `client.on('entry', (entry) => …)`.
|
|
200
|
+
*/
|
|
201
|
+
runWatch(runId: string): Promise<VerbContract['run.watch']['result']> {
|
|
202
|
+
return this.request('run.watch', { run_id: runId });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Connection becomes a worker; receives `step.dispatch` events. */
|
|
206
|
+
workerAttach(workerId: string, stepTypes: StepType[], pins?: Pins, capacity?: number): Promise<VerbContract['worker.attach']['result']> {
|
|
207
|
+
return this.request('worker.attach', { worker_id: workerId, step_types: stepTypes, pins, capacity });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Phase one of the writeback protocol (Appendix A rule 5): ask the journal to
|
|
212
|
+
* elect this attempt to perform the effect. **Do not call this directly to
|
|
213
|
+
* perform an effect** — use {@link performEffect}, which cannot leave the
|
|
214
|
+
* election and the provider call separated. A `false` here means this attempt
|
|
215
|
+
* owes the provider call *and* the {@link effectConfirm} that closes it;
|
|
216
|
+
* returning from `effectRecord` without doing both leaves the election open.
|
|
217
|
+
*/
|
|
218
|
+
effectRecord(
|
|
219
|
+
runId: string,
|
|
220
|
+
stepId: string,
|
|
221
|
+
attempt: number,
|
|
222
|
+
idempotencyKey: string,
|
|
223
|
+
surfacePath: string,
|
|
224
|
+
revisionBefore: string,
|
|
225
|
+
revisionAfter: string,
|
|
226
|
+
): Promise<VerbContract['effect.record']['result']> {
|
|
227
|
+
return this.request('effect.record', {
|
|
228
|
+
run_id: runId,
|
|
229
|
+
step_id: stepId,
|
|
230
|
+
attempt,
|
|
231
|
+
idempotency_key: idempotencyKey,
|
|
232
|
+
surface_path: surfacePath,
|
|
233
|
+
revision_before: revisionBefore,
|
|
234
|
+
revision_after: revisionAfter,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Phase two: the provider call this attempt was elected for has happened.
|
|
240
|
+
* Confirming closes the election so no later attempt reclaims it. Only the
|
|
241
|
+
* attempt holding the election may confirm it; the kernel refuses anything
|
|
242
|
+
* else.
|
|
243
|
+
*/
|
|
244
|
+
effectConfirm(
|
|
245
|
+
runId: string,
|
|
246
|
+
stepId: string,
|
|
247
|
+
attempt: number,
|
|
248
|
+
idempotencyKey: string,
|
|
249
|
+
surfacePath: string,
|
|
250
|
+
): Promise<VerbContract['effect.confirm']['result']> {
|
|
251
|
+
return this.request('effect.confirm', {
|
|
252
|
+
run_id: runId,
|
|
253
|
+
step_id: stepId,
|
|
254
|
+
attempt,
|
|
255
|
+
idempotency_key: idempotencyKey,
|
|
256
|
+
surface_path: surfacePath,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Perform one declared external effect exactly once: elect, perform, confirm.
|
|
262
|
+
*
|
|
263
|
+
* Election alone is not a promise that the writeback happened — a worker that
|
|
264
|
+
* dies between `effect.record` and its provider call would otherwise leave a
|
|
265
|
+
* winner nothing ever performed, and every retry would skip the call while
|
|
266
|
+
* the run completed as if the effect had occurred. Holding the three phases
|
|
267
|
+
* inside one call is what makes them inseparable at this boundary: `perform`
|
|
268
|
+
* runs only for the attempt that won the election, and the election is closed
|
|
269
|
+
* only after `perform` returns. A `perform` that throws leaves the election
|
|
270
|
+
* unconfirmed and therefore reclaimable, so the next attempt performs it.
|
|
271
|
+
*
|
|
272
|
+
* Returns whether this attempt made the provider call; `false` means a
|
|
273
|
+
* confirmed election already covered it.
|
|
274
|
+
*/
|
|
275
|
+
async performEffect(
|
|
276
|
+
effect: {
|
|
277
|
+
runId: string;
|
|
278
|
+
stepId: string;
|
|
279
|
+
attempt: number;
|
|
280
|
+
idempotencyKey: string;
|
|
281
|
+
surfacePath: string;
|
|
282
|
+
revisionBefore: string;
|
|
283
|
+
revisionAfter: string;
|
|
284
|
+
},
|
|
285
|
+
perform: () => Promise<void>,
|
|
286
|
+
): Promise<boolean> {
|
|
287
|
+
const { deduped } = await this.effectRecord(
|
|
288
|
+
effect.runId,
|
|
289
|
+
effect.stepId,
|
|
290
|
+
effect.attempt,
|
|
291
|
+
effect.idempotencyKey,
|
|
292
|
+
effect.surfacePath,
|
|
293
|
+
effect.revisionBefore,
|
|
294
|
+
effect.revisionAfter,
|
|
295
|
+
);
|
|
296
|
+
if (deduped) return false;
|
|
297
|
+
await perform();
|
|
298
|
+
await this.effectConfirm(
|
|
299
|
+
effect.runId,
|
|
300
|
+
effect.stepId,
|
|
301
|
+
effect.attempt,
|
|
302
|
+
effect.idempotencyKey,
|
|
303
|
+
effect.surfacePath,
|
|
304
|
+
);
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Renew the lease — the one lease primitive. */
|
|
309
|
+
stepHeartbeat(
|
|
310
|
+
runId: string,
|
|
311
|
+
stepId: string,
|
|
312
|
+
attempt: number,
|
|
313
|
+
leaseId: string,
|
|
314
|
+
): Promise<VerbContract['step.heartbeat']['result']> {
|
|
315
|
+
return this.request('step.heartbeat', {
|
|
316
|
+
run_id: runId,
|
|
317
|
+
step_id: stepId,
|
|
318
|
+
attempt,
|
|
319
|
+
lease_id: leaseId,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Complete a dispatched step — also the out-of-band path. */
|
|
324
|
+
stepComplete(
|
|
325
|
+
runId: string,
|
|
326
|
+
stepId: string,
|
|
327
|
+
attempt: number,
|
|
328
|
+
idempotencyKey: string,
|
|
329
|
+
completionReason: CompletionReason,
|
|
330
|
+
extra: {
|
|
331
|
+
output?: unknown;
|
|
332
|
+
usage?: { tokens_in: number; tokens_out: number; dollars: string };
|
|
333
|
+
started_pins?: Pins;
|
|
334
|
+
end_pins?: Pins;
|
|
335
|
+
effects?: EffectRef[];
|
|
336
|
+
trajectory_tail?: unknown;
|
|
337
|
+
} = {},
|
|
338
|
+
): Promise<VerbContract['step.complete']['result']> {
|
|
339
|
+
return this.request('step.complete', {
|
|
340
|
+
run_id: runId,
|
|
341
|
+
step_id: stepId,
|
|
342
|
+
attempt,
|
|
343
|
+
idempotency_key: idempotencyKey,
|
|
344
|
+
completionReason,
|
|
345
|
+
...extra,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Satisfy `wait.event`; a human response arrives here too. */
|
|
350
|
+
eventEmit(runId: string, eventKey: string, payload: unknown): Promise<VerbContract['event.emit']['result']> {
|
|
351
|
+
return this.request('event.emit', { run_id: runId, event_key: eventKey, payload });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Submit an external event to a flow that declares an event trigger.
|
|
356
|
+
*
|
|
357
|
+
* Without this wrapper the verb existed on the server but was unreachable
|
|
358
|
+
* through the typed client, so authors had to bypass the protocol surface
|
|
359
|
+
* entirely to use the feature.
|
|
360
|
+
*/
|
|
361
|
+
eventSubmit(spec: unknown, event: EventSubmitParams['event']): Promise<VerbContract['event.submit']['result']> {
|
|
362
|
+
return this.request('event.submit', { spec, event });
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Durable channel write; journals `stream.appended`. */
|
|
366
|
+
streamAppend(runId: string, stream: string, message: unknown): Promise<VerbContract['stream.append']['result']> {
|
|
367
|
+
return this.request('stream.append', { run_id: runId, stream, message });
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** At-least-once replayable read. Committing the consumer offset happens via pins. */
|
|
371
|
+
streamRead(runId: string, stream: string, fromOffset: number, limit?: number): Promise<VerbContract['stream.read']['result']> {
|
|
372
|
+
return this.request('stream.read', { run_id: runId, stream, from_offset: fromOffset, limit });
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** Raw journal access — replay, audit, the report step. */
|
|
376
|
+
journalRead(runId: string, fromSeq: number, limit?: number): Promise<VerbContract['journal.read']['result']> {
|
|
377
|
+
return this.request('journal.read', { run_id: runId, from_seq: fromSeq, limit });
|
|
378
|
+
}
|
|
379
|
+
}
|