@cleocode/contracts 2026.5.129 → 2026.5.131
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/__tests__/atomic-tools-contract.test.d.ts +14 -0
- package/dist/__tests__/atomic-tools-contract.test.d.ts.map +1 -0
- package/dist/__tests__/atomic-tools-contract.test.js +55 -0
- package/dist/__tests__/atomic-tools-contract.test.js.map +1 -0
- package/dist/__tests__/gateway-contract.test.d.ts +18 -0
- package/dist/__tests__/gateway-contract.test.d.ts.map +1 -0
- package/dist/__tests__/gateway-contract.test.js +187 -0
- package/dist/__tests__/gateway-contract.test.js.map +1 -0
- package/dist/__tests__/mvi.test.d.ts +17 -0
- package/dist/__tests__/mvi.test.d.ts.map +1 -0
- package/dist/__tests__/mvi.test.js +147 -0
- package/dist/__tests__/mvi.test.js.map +1 -0
- package/dist/__tests__/operation-def.test.js +1 -1
- package/dist/__tests__/operation-def.test.js.map +1 -1
- package/dist/boundary.d.ts +26 -0
- package/dist/boundary.d.ts.map +1 -1
- package/dist/boundary.js +42 -4
- package/dist/boundary.js.map +1 -1
- package/dist/branch-lock.d.ts +35 -1
- package/dist/branch-lock.d.ts.map +1 -1
- package/dist/branch-lock.js +23 -2
- package/dist/branch-lock.js.map +1 -1
- package/dist/daemon/__tests__/health-roundtrip.test.d.ts +13 -0
- package/dist/daemon/__tests__/health-roundtrip.test.d.ts.map +1 -0
- package/dist/daemon/__tests__/health-roundtrip.test.js +69 -0
- package/dist/daemon/__tests__/health-roundtrip.test.js.map +1 -0
- package/dist/daemon/health.d.ts +108 -0
- package/dist/daemon/health.d.ts.map +1 -0
- package/dist/daemon/health.js +95 -0
- package/dist/daemon/health.js.map +1 -0
- package/dist/daemon/index.d.ts +24 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +22 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/subsystem.d.ts +121 -0
- package/dist/daemon/subsystem.d.ts.map +1 -0
- package/dist/daemon/subsystem.js +28 -0
- package/dist/daemon/subsystem.js.map +1 -0
- package/dist/daemon-ipc/__tests__/daemon-ipc-freeze.test.d.ts +15 -0
- package/dist/daemon-ipc/__tests__/daemon-ipc-freeze.test.d.ts.map +1 -0
- package/dist/daemon-ipc/__tests__/daemon-ipc-freeze.test.js +40 -0
- package/dist/daemon-ipc/__tests__/daemon-ipc-freeze.test.js.map +1 -0
- package/dist/daemon-ipc/index.d.ts +44 -0
- package/dist/daemon-ipc/index.d.ts.map +1 -0
- package/dist/daemon-ipc/index.js +43 -0
- package/dist/daemon-ipc/index.js.map +1 -0
- package/dist/db-inventory.json +8 -8
- package/dist/dispatch/identity.d.ts +1 -1
- package/dist/dispatch/identity.d.ts.map +1 -1
- package/dist/dispatch/identity.js +3 -0
- package/dist/dispatch/identity.js.map +1 -1
- package/dist/dispatch/operation-def.d.ts +16 -0
- package/dist/dispatch/operation-def.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.js +133 -0
- package/dist/dispatch/operations-registry.js.map +1 -1
- package/dist/exit-codes.d.ts +1 -1
- package/dist/exit-codes.js +1 -1
- package/dist/gateway/rpc/__tests__/freeze.test.d.ts +20 -0
- package/dist/gateway/rpc/__tests__/freeze.test.d.ts.map +1 -0
- package/dist/gateway/rpc/__tests__/freeze.test.js +122 -0
- package/dist/gateway/rpc/__tests__/freeze.test.js.map +1 -0
- package/dist/gateway/rpc/index.d.ts +23 -0
- package/dist/gateway/rpc/index.d.ts.map +1 -0
- package/dist/gateway/rpc/index.js +22 -0
- package/dist/gateway/rpc/index.js.map +1 -0
- package/dist/gateway/rpc/messages.d.ts +263 -0
- package/dist/gateway/rpc/messages.d.ts.map +1 -0
- package/dist/gateway/rpc/messages.js +152 -0
- package/dist/gateway/rpc/messages.js.map +1 -0
- package/dist/gateway/rpc/version.d.ts +39 -0
- package/dist/gateway/rpc/version.d.ts.map +1 -0
- package/dist/gateway/rpc/version.js +39 -0
- package/dist/gateway/rpc/version.js.map +1 -0
- package/dist/gateway.d.ts +239 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +171 -0
- package/dist/gateway.js.map +1 -0
- package/dist/goal.d.ts +314 -0
- package/dist/goal.d.ts.map +1 -0
- package/dist/goal.js +118 -0
- package/dist/goal.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/mvi.d.ts +209 -0
- package/dist/mvi.d.ts.map +1 -0
- package/dist/mvi.js +99 -0
- package/dist/mvi.js.map +1 -0
- package/dist/operations/attention.d.ts +114 -0
- package/dist/operations/attention.d.ts.map +1 -0
- package/dist/operations/attention.js +22 -0
- package/dist/operations/attention.js.map +1 -0
- package/dist/operations/docs.d.ts +7 -1
- package/dist/operations/docs.d.ts.map +1 -1
- package/dist/operations/docs.js.map +1 -1
- package/dist/operations/focus.d.ts +25 -0
- package/dist/operations/focus.d.ts.map +1 -1
- package/dist/operations/index.d.ts +1 -0
- package/dist/operations/index.d.ts.map +1 -1
- package/dist/operations/index.js +1 -0
- package/dist/operations/index.js.map +1 -1
- package/dist/operations/session.d.ts +11 -0
- package/dist/operations/session.d.ts.map +1 -1
- package/dist/sdk-tool.d.ts +1 -1
- package/dist/sdk-tool.js +1 -1
- package/dist/supervisor-ipc/__tests__/freeze.test.d.ts +15 -0
- package/dist/supervisor-ipc/__tests__/freeze.test.d.ts.map +1 -0
- package/dist/supervisor-ipc/__tests__/freeze.test.js +159 -0
- package/dist/supervisor-ipc/__tests__/freeze.test.js.map +1 -0
- package/dist/supervisor-ipc/index.d.ts +15 -0
- package/dist/supervisor-ipc/index.d.ts.map +1 -0
- package/dist/supervisor-ipc/index.js +14 -0
- package/dist/supervisor-ipc/index.js.map +1 -0
- package/dist/supervisor-ipc/messages.d.ts +457 -0
- package/dist/supervisor-ipc/messages.d.ts.map +1 -0
- package/dist/supervisor-ipc/messages.js +312 -0
- package/dist/supervisor-ipc/messages.js.map +1 -0
- package/dist/supervisor-ipc/version.d.ts +29 -0
- package/dist/supervisor-ipc/version.d.ts.map +1 -0
- package/dist/supervisor-ipc/version.js +29 -0
- package/dist/supervisor-ipc/version.js.map +1 -0
- package/dist/tools/__tests__/composite.test.d.ts +14 -0
- package/dist/tools/__tests__/composite.test.d.ts.map +1 -0
- package/dist/tools/__tests__/composite.test.js +75 -0
- package/dist/tools/__tests__/composite.test.js.map +1 -0
- package/dist/tools/atomic.d.ts +195 -0
- package/dist/tools/atomic.d.ts.map +1 -0
- package/dist/tools/atomic.js +82 -0
- package/dist/tools/atomic.js.map +1 -0
- package/dist/tools/composite.d.ts +128 -0
- package/dist/tools/composite.d.ts.map +1 -0
- package/dist/tools/composite.js +88 -0
- package/dist/tools/composite.js.map +1 -0
- package/package.json +35 -3
- package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
- package/src/__tests__/atomic-tools-contract.test.ts +67 -0
- package/src/__tests__/gateway-contract.test.ts +219 -0
- package/src/__tests__/mvi.test.ts +175 -0
- package/src/__tests__/operation-def.test.ts +2 -1
- package/src/boundary.ts +74 -4
- package/src/branch-lock.ts +49 -2
- package/src/daemon/__tests__/health-roundtrip.test.ts +84 -0
- package/src/daemon/health.ts +121 -0
- package/src/daemon/index.ts +35 -0
- package/src/daemon/subsystem.ts +126 -0
- package/src/daemon-ipc/__tests__/daemon-ipc-freeze.test.ts +53 -0
- package/src/daemon-ipc/index.ts +90 -0
- package/src/db-inventory.json +8 -8
- package/src/dispatch/identity.ts +3 -0
- package/src/dispatch/operation-def.ts +16 -0
- package/src/dispatch/operations-registry.ts +140 -0
- package/src/exit-codes.ts +1 -1
- package/src/gateway/rpc/__tests__/freeze.test.ts +139 -0
- package/src/gateway/rpc/index.ts +43 -0
- package/src/gateway/rpc/messages.ts +187 -0
- package/src/gateway/rpc/version.ts +40 -0
- package/src/gateway.ts +252 -0
- package/src/goal.ts +348 -0
- package/src/index.ts +109 -0
- package/src/mvi.ts +236 -0
- package/src/operations/attention.ts +117 -0
- package/src/operations/docs.ts +7 -1
- package/src/operations/focus.ts +26 -0
- package/src/operations/index.ts +1 -0
- package/src/operations/session.ts +11 -0
- package/src/sdk-tool.ts +1 -1
- package/src/supervisor-ipc/__tests__/freeze.test.ts +180 -0
- package/src/supervisor-ipc/index.ts +62 -0
- package/src/supervisor-ipc/messages.ts +396 -0
- package/src/supervisor-ipc/version.ts +30 -0
- package/src/tools/__tests__/composite.test.ts +88 -0
- package/src/tools/atomic.ts +274 -0
- package/src/tools/composite.ts +173 -0
package/src/gateway.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway contract — the transport-agnostic CQRS dispatch shapes.
|
|
3
|
+
*
|
|
4
|
+
* Every operation flows through:
|
|
5
|
+
* GatewayRequest → Middleware → DomainHandler → GatewayResponse
|
|
6
|
+
*
|
|
7
|
+
* These shapes were promoted verbatim from `packages/cleo/src/dispatch/types.ts`
|
|
8
|
+
* into `@cleocode/contracts/gateway` (R3-T2 · T11446 · SG-RUNTIME-UNIFICATION)
|
|
9
|
+
* so that EVERY transport adapter — CLI, MCP, RPC, HTTP — shares one canonical,
|
|
10
|
+
* zod-validated contract instead of the CLI owning a private copy. The CLI was
|
|
11
|
+
* the only historical transport (`Source = 'cli'`); {@link GatewaySource} now
|
|
12
|
+
* widens that to the four planned transports.
|
|
13
|
+
*
|
|
14
|
+
* Design: the zod schemas are the single source of truth (AC8 — runtime
|
|
15
|
+
* validation at untrusted transport boundaries such as MCP/RPC/HTTP). The TS
|
|
16
|
+
* types are derived via {@link https://zod.dev | z.infer}, so the static and
|
|
17
|
+
* runtime contracts can never drift. The behavioral contracts that zod cannot
|
|
18
|
+
* model (function types: {@link DomainHandler}, {@link Middleware},
|
|
19
|
+
* {@link DispatchNext}) remain TS-only and reference the inferred data types.
|
|
20
|
+
*
|
|
21
|
+
* @epic T4820
|
|
22
|
+
* @task T11446
|
|
23
|
+
* @saga T11243
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { LAFSPage, MVILevel } from '@cleocode/lafs';
|
|
27
|
+
import { isMVILevel } from '@cleocode/lafs';
|
|
28
|
+
import { z } from 'zod';
|
|
29
|
+
import type { CanonicalDomain, Gateway, Tier } from './dispatch/identity.js';
|
|
30
|
+
import type { ProblemDetails } from './engine-result.js';
|
|
31
|
+
|
|
32
|
+
export { CANONICAL_DOMAINS } from './dispatch/identity.js';
|
|
33
|
+
export type { CanonicalDomain, Gateway, Tier };
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Frozen version of the gateway contract. Bump on any breaking change to the
|
|
37
|
+
* request/response/error shapes; the freeze + spec are owned by R3-T8 (T11452).
|
|
38
|
+
*/
|
|
39
|
+
export const GATEWAY_CONTRACT_VERSION = '1.0.0' as const;
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// GatewaySource — transport of origin
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
/** The four canonical transports a gateway request can originate from. */
|
|
46
|
+
export const GATEWAY_SOURCES = ['cli', 'mcp', 'rpc', 'http'] as const;
|
|
47
|
+
|
|
48
|
+
/** Where a gateway request originated. Widened from the historical CLI-only `'cli'`. */
|
|
49
|
+
export const gatewaySourceSchema = z.enum(GATEWAY_SOURCES);
|
|
50
|
+
|
|
51
|
+
/** Where a gateway request originated. */
|
|
52
|
+
export type GatewaySource = z.infer<typeof gatewaySourceSchema>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @deprecated Use {@link GatewaySource}. Retained as a structural alias so the
|
|
56
|
+
* historical `import type { Source }` sites keep compiling unchanged.
|
|
57
|
+
*/
|
|
58
|
+
export type Source = GatewaySource;
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Foreign-type bridges (kept opaque to avoid re-deriving lafs/contracts schemas)
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
/** MVI verbosity level — validated through lafs's own {@link isMVILevel} guard (DRY SoT). */
|
|
65
|
+
const mviLevelSchema = z.custom<MVILevel>((v) => isMVILevel(v));
|
|
66
|
+
|
|
67
|
+
/** LAFS pagination envelope — produced internally; carried opaquely across transports. */
|
|
68
|
+
const lafsPageSchema = z.custom<LAFSPage>();
|
|
69
|
+
|
|
70
|
+
/** RFC 9457 Problem Details — produced internally from CleoError.toProblemDetails(). */
|
|
71
|
+
const problemDetailsSchema = z.custom<ProblemDetails>();
|
|
72
|
+
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// DispatchRequest
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Canonical request shape produced by every transport adapter. The dispatcher
|
|
79
|
+
* validates this against the OperationRegistry before passing it through the
|
|
80
|
+
* middleware pipeline and into a {@link DomainHandler}.
|
|
81
|
+
*/
|
|
82
|
+
export const dispatchRequestSchema = z.object({
|
|
83
|
+
/** CQRS gateway (command/query). */
|
|
84
|
+
gateway: z.custom<Gateway>(),
|
|
85
|
+
/** Target domain (canonical name). */
|
|
86
|
+
domain: z.string(),
|
|
87
|
+
/** Domain-specific operation name. */
|
|
88
|
+
operation: z.string(),
|
|
89
|
+
/** Operation parameters (already sanitized by middleware). */
|
|
90
|
+
params: z.record(z.string(), z.unknown()).optional(),
|
|
91
|
+
/** Which transport this request came from. */
|
|
92
|
+
source: gatewaySourceSchema,
|
|
93
|
+
/** Unique request identifier for tracing. */
|
|
94
|
+
requestId: z.string(),
|
|
95
|
+
/** Bound session ID, if any. */
|
|
96
|
+
sessionId: z.string().optional(),
|
|
97
|
+
/** Root session that originated the workflow/saga, if any. */
|
|
98
|
+
originSessionId: z.string().optional(),
|
|
99
|
+
/** Specific execution instance for this command/step, if any. */
|
|
100
|
+
executionSessionId: z.string().optional(),
|
|
101
|
+
/** LAFS field selection: filter response data to these fields only. */
|
|
102
|
+
_fields: z.array(z.string()).optional(),
|
|
103
|
+
/** LAFS envelope verbosity. Defaults to 'standard'. 'custom' is server-set via _fields. */
|
|
104
|
+
_mvi: mviLevelSchema.optional(),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/** {@inheritDoc dispatchRequestSchema} */
|
|
108
|
+
export type DispatchRequest = z.infer<typeof dispatchRequestSchema>;
|
|
109
|
+
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// DispatchResponse + supporting shapes
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
/** Rate-limit metadata attached to every response. */
|
|
115
|
+
export const rateLimitMetaSchema = z.object({
|
|
116
|
+
limit: z.number(),
|
|
117
|
+
remaining: z.number(),
|
|
118
|
+
resetMs: z.number(),
|
|
119
|
+
category: z.string(),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
/** {@inheritDoc rateLimitMetaSchema} */
|
|
123
|
+
export type RateLimitMeta = z.infer<typeof rateLimitMetaSchema>;
|
|
124
|
+
|
|
125
|
+
/** Structured error shape (LAFS-compatible). */
|
|
126
|
+
export const dispatchErrorSchema = z.object({
|
|
127
|
+
/** Machine-readable error code (E_NOT_FOUND, E_VALIDATION_FAILED, …). */
|
|
128
|
+
code: z.string(),
|
|
129
|
+
/** LAFS exit code (1-99). */
|
|
130
|
+
exitCode: z.number().optional(),
|
|
131
|
+
/** Human-readable message. */
|
|
132
|
+
message: z.string(),
|
|
133
|
+
/** Additional structured details. */
|
|
134
|
+
details: z.record(z.string(), z.unknown()).optional(),
|
|
135
|
+
/** Copy-paste fix command. */
|
|
136
|
+
fix: z.string().optional(),
|
|
137
|
+
/** Alternative actions the caller can try. */
|
|
138
|
+
alternatives: z.array(z.object({ action: z.string(), command: z.string() })).optional(),
|
|
139
|
+
/** RFC 9457 Problem Details (populated from CleoError.toProblemDetails()). */
|
|
140
|
+
problemDetails: problemDetailsSchema.optional(),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
/** {@inheritDoc dispatchErrorSchema} */
|
|
144
|
+
export type DispatchError = z.infer<typeof dispatchErrorSchema>;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Always-present metadata block on every {@link DispatchResponse}.
|
|
148
|
+
*
|
|
149
|
+
* The catch-all (`[key: string]: unknown`) makes this assignable to
|
|
150
|
+
* `Record<string, unknown>` in covariant positions, eliminating the T9767
|
|
151
|
+
* cast-chain anti-pattern at every responseMeta call site.
|
|
152
|
+
*
|
|
153
|
+
* @task T9767
|
|
154
|
+
*/
|
|
155
|
+
export const dispatchResponseMetaSchema = z
|
|
156
|
+
.object({
|
|
157
|
+
gateway: z.custom<Gateway>(),
|
|
158
|
+
domain: z.string(),
|
|
159
|
+
operation: z.string(),
|
|
160
|
+
timestamp: z.string(),
|
|
161
|
+
duration_ms: z.number(),
|
|
162
|
+
source: gatewaySourceSchema,
|
|
163
|
+
requestId: z.string(),
|
|
164
|
+
rateLimit: rateLimitMetaSchema.optional(),
|
|
165
|
+
/** Session ID that processed this request (T4959). */
|
|
166
|
+
sessionId: z.string().optional(),
|
|
167
|
+
/** Root session that originated the workflow/saga. */
|
|
168
|
+
originSessionId: z.string().optional(),
|
|
169
|
+
/** Specific execution instance for this command/step. */
|
|
170
|
+
executionSessionId: z.string().optional(),
|
|
171
|
+
/** Preserves protocol-level version for backward compat. */
|
|
172
|
+
version: z.string().optional(),
|
|
173
|
+
})
|
|
174
|
+
.catchall(z.unknown());
|
|
175
|
+
|
|
176
|
+
/** {@inheritDoc dispatchResponseMetaSchema} */
|
|
177
|
+
export type DispatchResponseMeta = z.infer<typeof dispatchResponseMetaSchema>;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Canonical response shape returned by the dispatcher. The CLI adapter
|
|
181
|
+
* translates this into cliOutput() / cliError() + process.exit(); other
|
|
182
|
+
* transports serialize it onto their wire.
|
|
183
|
+
*/
|
|
184
|
+
export const dispatchResponseSchema = z.object({
|
|
185
|
+
/** Always-present metadata for every dispatch response. */
|
|
186
|
+
meta: dispatchResponseMetaSchema,
|
|
187
|
+
success: z.boolean(),
|
|
188
|
+
data: z.unknown().optional(),
|
|
189
|
+
page: lafsPageSchema.optional(),
|
|
190
|
+
partial: z.boolean().optional(),
|
|
191
|
+
error: dispatchErrorSchema.optional(),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
/** {@inheritDoc dispatchResponseSchema} */
|
|
195
|
+
export type DispatchResponse = z.infer<typeof dispatchResponseSchema>;
|
|
196
|
+
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// GatewayStreamEvent — for streaming transports (SSE/HTTP, RPC server-push)
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* A single frame emitted by a streaming gateway operation. Unary transports
|
|
203
|
+
* (CLI) never see these; streaming transports (Studio SSE, RPC server-push)
|
|
204
|
+
* emit a sequence terminated by a `done` or `error` event.
|
|
205
|
+
*/
|
|
206
|
+
export const gatewayStreamEventSchema = z.object({
|
|
207
|
+
/** Frame kind: incremental data, terminal success, or terminal error. */
|
|
208
|
+
kind: z.enum(['data', 'done', 'error']),
|
|
209
|
+
/** Monotonic sequence number within the stream (0-based). */
|
|
210
|
+
seq: z.number(),
|
|
211
|
+
/** Payload for `data`/`done` frames. */
|
|
212
|
+
data: z.unknown().optional(),
|
|
213
|
+
/** Error detail for `error` frames. */
|
|
214
|
+
error: dispatchErrorSchema.optional(),
|
|
215
|
+
/** Correlates the stream back to its originating request. */
|
|
216
|
+
requestId: z.string(),
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
/** {@inheritDoc gatewayStreamEventSchema} */
|
|
220
|
+
export type GatewayStreamEvent = z.infer<typeof gatewayStreamEventSchema>;
|
|
221
|
+
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
// Behavioral contracts (function types — TS-only; zod cannot model functions)
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Contract for domain handlers.
|
|
228
|
+
*
|
|
229
|
+
* Each canonical domain (tasks, session, memory, check, pipeline, orchestrate,
|
|
230
|
+
* tools, admin, nexus, sticky, intelligence) implements this interface.
|
|
231
|
+
*/
|
|
232
|
+
export interface DomainHandler {
|
|
233
|
+
/** Execute a read-only query operation. */
|
|
234
|
+
query(operation: string, params?: Record<string, unknown>): Promise<DispatchResponse>;
|
|
235
|
+
/** Execute a state-modifying mutation operation. */
|
|
236
|
+
mutate(operation: string, params?: Record<string, unknown>): Promise<DispatchResponse>;
|
|
237
|
+
/** Declared operations for introspection and validation. */
|
|
238
|
+
getSupportedOperations(): { query: string[]; mutate: string[] };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Async continuation that produces a {@link DispatchResponse}. */
|
|
242
|
+
export type DispatchNext = () => Promise<DispatchResponse>;
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Middleware function signature. Receives the request and a `next`
|
|
246
|
+
* continuation; can short-circuit (e.g. rate-limit exceeded) or modify the
|
|
247
|
+
* request/response.
|
|
248
|
+
*/
|
|
249
|
+
export type Middleware = (
|
|
250
|
+
request: DispatchRequest,
|
|
251
|
+
next: DispatchNext,
|
|
252
|
+
) => Promise<DispatchResponse>;
|
package/src/goal.ts
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLEO-native goal-system contracts (Layer 4 of SG-COGNITIVE-SUBSTRATE).
|
|
3
|
+
*
|
|
4
|
+
* A *goal* is a DB-persisted, per-agent, turn-budgeted intent that CLEO drives
|
|
5
|
+
* to completion by judging progress after every agent turn. CLEO's goal loop is
|
|
6
|
+
* designed to beat two prior-art systems:
|
|
7
|
+
*
|
|
8
|
+
* - **Claude-Code** — a `Stop` hook + a Haiku *transcript judge* (the model
|
|
9
|
+
* reads the conversation and guesses whether the task is done). It has no
|
|
10
|
+
* turn budget and no persistence: a fresh process forgets the goal.
|
|
11
|
+
* - **Hermes** — a post-turn loop with a turn budget and parse-failure
|
|
12
|
+
* auto-pause, but it still judges *fuzzy* intent against transcript text.
|
|
13
|
+
*
|
|
14
|
+
* CLEO's differentiator is the **evidence-gate-aware judge**: when a goal is to
|
|
15
|
+
* *complete a task* (`complete T123`), the judge does NOT read the transcript.
|
|
16
|
+
* It resolves the target task's actual `status` and re-uses the shipped ADR-051
|
|
17
|
+
* evidence infrastructure ({@link GateEvidenceRequirement} +
|
|
18
|
+
* `validateEvidenceForGate`) — the goal is satisfied ONLY when the task is
|
|
19
|
+
* `done` with its required gates backed by real, validated evidence atoms
|
|
20
|
+
* (commit reachable, file sha256 match, test-run hash match). Transcript text
|
|
21
|
+
* can never satisfy a task-completion goal. Fuzzy goals fall back to an
|
|
22
|
+
* injectable LLM judge (see {@link GoalJudge}) so the model is consulted only
|
|
23
|
+
* where no machine-checkable artifact exists.
|
|
24
|
+
*
|
|
25
|
+
* This module is the LEAF contract: it declares the persisted record shape
|
|
26
|
+
* ({@link GoalRecord}), the lifecycle status union ({@link GoalStatus}), the
|
|
27
|
+
* discriminated goal-kind union ({@link GoalKind}), and the judge verdict shape
|
|
28
|
+
* ({@link GoalJudgeVerdict}) that every layer (store, judge, loop, continuation,
|
|
29
|
+
* CLI) agrees on. It has ZERO runtime dependencies beyond sibling contracts.
|
|
30
|
+
*
|
|
31
|
+
* @packageDocumentation
|
|
32
|
+
* @module @cleocode/contracts/goal
|
|
33
|
+
*
|
|
34
|
+
* @epic T11290 EP-CLEO-GOAL-SYSTEM
|
|
35
|
+
* @task T11376
|
|
36
|
+
* @saga T11283 SG-COGNITIVE-SUBSTRATE
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Canonical task-id shape recognized by a {@link TaskCompletionGoal}.
|
|
41
|
+
*
|
|
42
|
+
* Mirrors the ADR-079-r2 `SATISFIES_TASK_ID_REGEX` / evidence-atom task-id
|
|
43
|
+
* grammar (`T` + 1–7 digits). Exported so the store, judge, and CLI layers
|
|
44
|
+
* validate target ids against ONE pattern rather than re-deriving it.
|
|
45
|
+
*
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
48
|
+
export const GOAL_TARGET_TASK_ID_REGEX = /^T[0-9]{1,7}$/;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Lifecycle status of a {@link GoalRecord}.
|
|
52
|
+
*
|
|
53
|
+
* Transitions are owned by the turn-budgeted loop (`advanceGoal`):
|
|
54
|
+
* - `active` — the goal is being pursued; the loop consumes turns.
|
|
55
|
+
* - `paused` — auto-paused after a judge/parse failure (Hermes pattern);
|
|
56
|
+
* carries a {@link GoalRecord.pausedReason}. No turns are consumed while
|
|
57
|
+
* paused; a human/agent must resume.
|
|
58
|
+
* - `satisfied` — the judge returned `ok: true` (evidence-backed for task
|
|
59
|
+
* goals, LLM-confirmed for fuzzy goals). Terminal.
|
|
60
|
+
* - `abandoned` — the turn budget was exhausted before satisfaction. Terminal.
|
|
61
|
+
* - `impossible` — the judge proved the goal can never be satisfied
|
|
62
|
+
* (e.g. the target task was cancelled/deleted). Terminal; no further turns.
|
|
63
|
+
*
|
|
64
|
+
* @public
|
|
65
|
+
*/
|
|
66
|
+
export type GoalStatus = 'active' | 'paused' | 'satisfied' | 'abandoned' | 'impossible';
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The set of all {@link GoalStatus} values, in lifecycle order.
|
|
70
|
+
*
|
|
71
|
+
* Use for runtime validation (`GOAL_STATUSES.includes(x)`) and to drive
|
|
72
|
+
* exhaustive switch checks without hand-maintaining a parallel array.
|
|
73
|
+
*
|
|
74
|
+
* @public
|
|
75
|
+
*/
|
|
76
|
+
export const GOAL_STATUSES: readonly GoalStatus[] = Object.freeze([
|
|
77
|
+
'active',
|
|
78
|
+
'paused',
|
|
79
|
+
'satisfied',
|
|
80
|
+
'abandoned',
|
|
81
|
+
'impossible',
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The terminal {@link GoalStatus} values a continuation builder must NOT nudge:
|
|
86
|
+
* a satisfied/abandoned/impossible goal yields no next-turn message.
|
|
87
|
+
*
|
|
88
|
+
* `paused` is intentionally absent — a paused goal CAN be resumed, so the
|
|
89
|
+
* continuation builder still emits a nudge for it (see `buildContinuation`).
|
|
90
|
+
*
|
|
91
|
+
* @public
|
|
92
|
+
*/
|
|
93
|
+
export const GOAL_TERMINAL_STATUSES: readonly GoalStatus[] = Object.freeze([
|
|
94
|
+
'satisfied',
|
|
95
|
+
'abandoned',
|
|
96
|
+
'impossible',
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Discriminant tag for the {@link GoalKind} union.
|
|
101
|
+
*
|
|
102
|
+
* @public
|
|
103
|
+
*/
|
|
104
|
+
export type GoalKindTag = 'task-completion' | 'fuzzy';
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A goal whose satisfaction is decided by the **evidence-gate-aware** judge.
|
|
108
|
+
*
|
|
109
|
+
* The judge resolves `targetTaskId`'s actual status and re-uses the ADR-051
|
|
110
|
+
* evidence path — NEVER transcript text. This is CLEO's core differentiator
|
|
111
|
+
* over Claude-Code and Hermes.
|
|
112
|
+
*
|
|
113
|
+
* @public
|
|
114
|
+
*/
|
|
115
|
+
export interface TaskCompletionGoal {
|
|
116
|
+
/** Discriminant: judged via task status + evidence atoms (ADR-051). */
|
|
117
|
+
readonly kind: 'task-completion';
|
|
118
|
+
/**
|
|
119
|
+
* The task to complete. MUST match {@link GOAL_TARGET_TASK_ID_REGEX}
|
|
120
|
+
* (`T` + 1–7 digits). The judge loads this task and inspects its real
|
|
121
|
+
* `status` and gate evidence.
|
|
122
|
+
*/
|
|
123
|
+
readonly targetTaskId: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* A goal with no machine-checkable completion artifact — satisfaction is
|
|
128
|
+
* decided by the injectable LLM judge ({@link GoalJudge}) as a fallback.
|
|
129
|
+
*
|
|
130
|
+
* The fuzzy path NEVER touches the evidence infrastructure; it exists only for
|
|
131
|
+
* intents that cannot be reduced to a task + gates (e.g. "explore the auth
|
|
132
|
+
* module and summarize the risks").
|
|
133
|
+
*
|
|
134
|
+
* @public
|
|
135
|
+
*/
|
|
136
|
+
export interface FuzzyGoal {
|
|
137
|
+
/** Discriminant: judged by the fallback LLM judge (no evidence path). */
|
|
138
|
+
readonly kind: 'fuzzy';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Discriminated union distinguishing an evidence-judged task-completion goal
|
|
143
|
+
* from an LLM-judged fuzzy goal. The `kind` tag routes the judge: see
|
|
144
|
+
* {@link TaskCompletionGoal} (evidence) vs {@link FuzzyGoal} (LLM fallback).
|
|
145
|
+
*
|
|
146
|
+
* @public
|
|
147
|
+
*/
|
|
148
|
+
export type GoalKind = TaskCompletionGoal | FuzzyGoal;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* A DB-persisted, per-agent goal record (survives process restart).
|
|
152
|
+
*
|
|
153
|
+
* Persisted in `tasks.db` under the `tasks_goal` table (Pattern A — single file
|
|
154
|
+
* per scope + domain-prefixed table). Keyed per agent by the resolved
|
|
155
|
+
* `sessionId` + `agentId` from E0 (`resolveSessionIdFromEnv` /
|
|
156
|
+
* `resolveAgentIdFromEnv`) so two concurrent agents never collide on one global
|
|
157
|
+
* row — the session-bleed class this saga exists to kill.
|
|
158
|
+
*
|
|
159
|
+
* @public
|
|
160
|
+
*/
|
|
161
|
+
export interface GoalRecord {
|
|
162
|
+
/**
|
|
163
|
+
* Stable, content-independent goal id (the idempotency key / primary key in
|
|
164
|
+
* the store). Generated by the store on create.
|
|
165
|
+
*/
|
|
166
|
+
readonly id: string;
|
|
167
|
+
/**
|
|
168
|
+
* Resolved session id that owns this goal (from `resolveSessionIdFromEnv`).
|
|
169
|
+
* `null` only for the global-scope / no-session case.
|
|
170
|
+
*/
|
|
171
|
+
readonly sessionId: string | null;
|
|
172
|
+
/**
|
|
173
|
+
* Resolved agent handle that owns this goal (from `resolveAgentIdFromEnv`).
|
|
174
|
+
* `null` when spawn did not inject an agent identity.
|
|
175
|
+
*/
|
|
176
|
+
readonly agentId: string | null;
|
|
177
|
+
/**
|
|
178
|
+
* Parent goal id when this is a sub-goal, else `null`. Sub-goals let an agent
|
|
179
|
+
* decompose a large intent without losing the turn budget of the parent.
|
|
180
|
+
*/
|
|
181
|
+
readonly parentGoalId: string | null;
|
|
182
|
+
/**
|
|
183
|
+
* The kind discriminator + its payload (target task id for task goals).
|
|
184
|
+
* Drives whether the judge takes the evidence path or the LLM-fallback path.
|
|
185
|
+
*/
|
|
186
|
+
readonly goalKind: GoalKind;
|
|
187
|
+
/** Human-readable statement of what the agent is trying to achieve. */
|
|
188
|
+
readonly intent: string;
|
|
189
|
+
/**
|
|
190
|
+
* Outstanding, append-only acceptance criteria. The continuation builder
|
|
191
|
+
* embeds these so the agent knows what remains. Append via
|
|
192
|
+
* `appendCriteria` — never mutated in place.
|
|
193
|
+
*/
|
|
194
|
+
readonly criteria: readonly string[];
|
|
195
|
+
/** Current lifecycle status. */
|
|
196
|
+
readonly status: GoalStatus;
|
|
197
|
+
/**
|
|
198
|
+
* Maximum number of turns the loop may consume before the goal is abandoned.
|
|
199
|
+
* A hard cap that bounds runaway loops (the protection Claude-Code lacks).
|
|
200
|
+
*/
|
|
201
|
+
readonly turnBudget: number;
|
|
202
|
+
/** Turns consumed so far. Never exceeds {@link turnBudget}. */
|
|
203
|
+
readonly turnsUsed: number;
|
|
204
|
+
/**
|
|
205
|
+
* Why the goal was auto-paused, when `status === 'paused'`. `null` otherwise.
|
|
206
|
+
* Set on judge/parse failure so the resume path can surface the cause.
|
|
207
|
+
*/
|
|
208
|
+
readonly pausedReason: string | null;
|
|
209
|
+
/**
|
|
210
|
+
* The most recent judge verdict, persisted so `cleo goal status` can show the
|
|
211
|
+
* last reason without re-running the judge. `null` before the first judge.
|
|
212
|
+
*/
|
|
213
|
+
readonly lastVerdict: GoalJudgeVerdict | null;
|
|
214
|
+
/** ISO-8601 creation timestamp. */
|
|
215
|
+
readonly createdAt: string;
|
|
216
|
+
/** ISO-8601 last-update timestamp. */
|
|
217
|
+
readonly updatedAt: string;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* The verdict returned by every judge path — evidence-gate-aware OR LLM
|
|
222
|
+
* fallback — for a single goal evaluation.
|
|
223
|
+
*
|
|
224
|
+
* The shape is uniform across both paths (the loop never branches on which
|
|
225
|
+
* judge produced it):
|
|
226
|
+
* - `ok` — the goal is satisfied right now.
|
|
227
|
+
* - `reason` — human-readable explanation (embedded into the continuation
|
|
228
|
+
* nudge so the agent knows WHY it must continue, or why it succeeded).
|
|
229
|
+
* - `impossible` — the goal can NEVER be satisfied (e.g. target task cancelled);
|
|
230
|
+
* the loop transitions the goal to `impossible` and stops consuming turns.
|
|
231
|
+
* `impossible` implies `ok === false`.
|
|
232
|
+
* - `evidence` — optional list of evidence-atom strings (ADR-051) the
|
|
233
|
+
* evidence judge consulted, for audit/telemetry. Absent on the LLM path.
|
|
234
|
+
*
|
|
235
|
+
* @public
|
|
236
|
+
*/
|
|
237
|
+
export interface GoalJudgeVerdict {
|
|
238
|
+
/** True when the goal is satisfied. Mutually exclusive with `impossible`. */
|
|
239
|
+
readonly ok: boolean;
|
|
240
|
+
/** Human-readable explanation of the verdict (for the continuation nudge). */
|
|
241
|
+
readonly reason: string;
|
|
242
|
+
/** True when the goal can never be satisfied (terminal `impossible`). */
|
|
243
|
+
readonly impossible: boolean;
|
|
244
|
+
/**
|
|
245
|
+
* Evidence-atom strings the evidence judge inspected (ADR-051). Present only
|
|
246
|
+
* on the task-completion path; omitted by the LLM fallback.
|
|
247
|
+
*/
|
|
248
|
+
readonly evidence?: readonly string[];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Injectable LLM-judge interface for {@link FuzzyGoal} evaluation.
|
|
253
|
+
*
|
|
254
|
+
* The goal judge depends on THIS interface, never on a concrete model client,
|
|
255
|
+
* so vitest can pass a deterministic mock and stay fully offline/hermetic — CI
|
|
256
|
+
* must never flake on a live model call. Production wires a real LLM-backed
|
|
257
|
+
* implementation; tests wire a stub that returns a canned verdict.
|
|
258
|
+
*
|
|
259
|
+
* @public
|
|
260
|
+
*/
|
|
261
|
+
export interface GoalJudge {
|
|
262
|
+
/**
|
|
263
|
+
* Judge a fuzzy goal from its record alone (no transcript dependency in the
|
|
264
|
+
* contract — an implementation MAY consult external context, but the
|
|
265
|
+
* interface keeps the judge a pure function of the goal for testability).
|
|
266
|
+
*
|
|
267
|
+
* @param goal - The goal record under evaluation.
|
|
268
|
+
* @returns The verdict (same shape as the evidence path).
|
|
269
|
+
*/
|
|
270
|
+
judge(goal: GoalRecord): Promise<GoalJudgeVerdict>;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* A single next-turn continuation message produced by the cache-safe builder.
|
|
275
|
+
*
|
|
276
|
+
* Critically, the role is ALWAYS `'user'`: the goal loop nudges the agent by
|
|
277
|
+
* appending a user-style message, NEVER by mutating the system prompt. A stable
|
|
278
|
+
* system-prompt prefix is what makes prompt-caching effective; rewriting it on
|
|
279
|
+
* every turn would bust the cache. See `buildContinuation` for the full
|
|
280
|
+
* prompt-cache-safety contract.
|
|
281
|
+
*
|
|
282
|
+
* @public
|
|
283
|
+
*/
|
|
284
|
+
export interface GoalContinuation {
|
|
285
|
+
/** Always `'user'` — the nudge is a user message, never a system mutation. */
|
|
286
|
+
readonly role: 'user';
|
|
287
|
+
/** The continuation text: goal intent + outstanding criteria + judge reason. */
|
|
288
|
+
readonly content: string;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Result of advancing a goal one turn through the turn-budgeted loop.
|
|
293
|
+
*
|
|
294
|
+
* @public
|
|
295
|
+
*/
|
|
296
|
+
export interface GoalAdvanceResult {
|
|
297
|
+
/** The verdict the judge returned this turn. */
|
|
298
|
+
readonly verdict: GoalJudgeVerdict;
|
|
299
|
+
/** The status the goal transitions to after this turn. */
|
|
300
|
+
readonly nextStatus: GoalStatus;
|
|
301
|
+
/** Turns left after this one. Never negative. */
|
|
302
|
+
readonly turnsRemaining: number;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Type guard: is this a {@link TaskCompletionGoal} (evidence-judged)?
|
|
307
|
+
*
|
|
308
|
+
* @param kind - A {@link GoalKind} discriminated union value.
|
|
309
|
+
* @returns `true` when the goal is judged via task status + evidence atoms.
|
|
310
|
+
* @public
|
|
311
|
+
*/
|
|
312
|
+
export function isTaskCompletionGoal(kind: GoalKind): kind is TaskCompletionGoal {
|
|
313
|
+
return kind.kind === 'task-completion';
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Type guard: is this a {@link FuzzyGoal} (LLM-judged fallback)?
|
|
318
|
+
*
|
|
319
|
+
* @param kind - A {@link GoalKind} discriminated union value.
|
|
320
|
+
* @returns `true` when the goal is judged by the injectable LLM judge.
|
|
321
|
+
* @public
|
|
322
|
+
*/
|
|
323
|
+
export function isFuzzyGoal(kind: GoalKind): kind is FuzzyGoal {
|
|
324
|
+
return kind.kind === 'fuzzy';
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Test whether a string is a valid goal target task id (`T` + 1–7 digits).
|
|
329
|
+
*
|
|
330
|
+
* @param value - Candidate task id.
|
|
331
|
+
* @returns `true` when `value` matches {@link GOAL_TARGET_TASK_ID_REGEX}.
|
|
332
|
+
* @public
|
|
333
|
+
*/
|
|
334
|
+
export function isValidGoalTargetTaskId(value: string): boolean {
|
|
335
|
+
return GOAL_TARGET_TASK_ID_REGEX.test(value);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Test whether a {@link GoalStatus} is terminal (no further turns / nudges that
|
|
340
|
+
* advance work). A terminal status yields NO continuation.
|
|
341
|
+
*
|
|
342
|
+
* @param status - A goal status.
|
|
343
|
+
* @returns `true` for `satisfied` / `abandoned` / `impossible`.
|
|
344
|
+
* @public
|
|
345
|
+
*/
|
|
346
|
+
export function isTerminalGoalStatus(status: GoalStatus): boolean {
|
|
347
|
+
return GOAL_TERMINAL_STATUSES.includes(status);
|
|
348
|
+
}
|