@cleocode/lafs-protocol 1.4.0 → 1.4.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.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  LAFS defines a standard envelope format for structured responses from LLM-powered agents and tools. It complements transport protocols like [MCP](https://modelcontextprotocol.io/) and [A2A](https://github.com/google/A2A) by standardizing what comes back — not how it gets there.
6
6
 
7
- **Current version:** 1.4.0 | [📚 Documentation](https://codluv.gitbook.io/lafs-protocol/) | [Spec](lafs.md) | [Migration Guides](migrations/)
7
+ **Current version:** 1.4.1 | [📚 Documentation](https://codluv.gitbook.io/lafs-protocol/) | [Spec](lafs.md) | [Migration Guides](migrations/)
8
8
 
9
9
  [![GitBook](https://img.shields.io/badge/docs-gitbook-blue)](https://codluv.gitbook.io/lafs-protocol/)
10
10
  [![npm](https://img.shields.io/npm/v/@cleocode/lafs-protocol)](https://www.npmjs.com/package/@cleocode/lafs-protocol)
@@ -0,0 +1,35 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "version": "1.0.0",
4
+ "tiers": {
5
+ "core": [
6
+ "envelope_schema_valid",
7
+ "envelope_invariants",
8
+ "error_code_registered"
9
+ ],
10
+ "standard": [
11
+ "envelope_schema_valid",
12
+ "envelope_invariants",
13
+ "error_code_registered",
14
+ "transport_mapping_consistent",
15
+ "meta_mvi_present",
16
+ "meta_strict_present",
17
+ "strict_mode_behavior",
18
+ "pagination_mode_consistent",
19
+ "strict_mode_enforced"
20
+ ],
21
+ "complete": [
22
+ "envelope_schema_valid",
23
+ "envelope_invariants",
24
+ "error_code_registered",
25
+ "transport_mapping_consistent",
26
+ "context_mutation_failure",
27
+ "context_preservation_valid",
28
+ "meta_mvi_present",
29
+ "meta_strict_present",
30
+ "strict_mode_behavior",
31
+ "pagination_mode_consistent",
32
+ "strict_mode_enforced"
33
+ ]
34
+ }
35
+ }
@@ -29,3 +29,7 @@ export declare const A2A_ERROR_MAPPINGS: ReadonlyMap<A2AErrorType, ErrorCodeMapp
29
29
  */
30
30
  export declare function getErrorCodeMapping(errorType: A2AErrorType): ErrorCodeMapping;
31
31
  export { A2A_GRPC_ERROR_REASONS } from './grpc.js';
32
+ export declare const SUPPORTED_A2A_VERSIONS: readonly ["1.0"];
33
+ export declare const DEFAULT_A2A_VERSION: "1.0";
34
+ export declare function parseA2AVersionHeader(headerValue: string | undefined): string[];
35
+ export declare function negotiateA2AVersion(requestedVersions: string[]): string | null;
@@ -52,3 +52,28 @@ export function getErrorCodeMapping(errorType) {
52
52
  // Also export A2A_GRPC_ERROR_REASONS for convenience (re-exported from grpc.ts via *)
53
53
  // but explicitly reference it here so the cross-binding module is self-documenting
54
54
  export { A2A_GRPC_ERROR_REASONS } from './grpc.js';
55
+ // ============================================================================
56
+ // Version Negotiation
57
+ // ============================================================================
58
+ export const SUPPORTED_A2A_VERSIONS = ['1.0'];
59
+ export const DEFAULT_A2A_VERSION = '1.0';
60
+ export function parseA2AVersionHeader(headerValue) {
61
+ if (!headerValue)
62
+ return [];
63
+ return headerValue
64
+ .split(',')
65
+ .map((v) => v.trim())
66
+ .filter(Boolean);
67
+ }
68
+ export function negotiateA2AVersion(requestedVersions) {
69
+ if (requestedVersions.length === 0) {
70
+ return DEFAULT_A2A_VERSION;
71
+ }
72
+ const supported = new Set(SUPPORTED_A2A_VERSIONS);
73
+ for (const version of requestedVersions) {
74
+ if (supported.has(version)) {
75
+ return version;
76
+ }
77
+ }
78
+ return null;
79
+ }
@@ -6,7 +6,8 @@
6
6
  *
7
7
  * Reference: specs/external/specification.md
8
8
  */
9
- import type { Task, TaskState, TaskStatus, Artifact, Part, Message, AgentCard, MessageSendConfiguration, SendMessageResponse, SendMessageSuccessResponse, JSONRPCErrorResponse, AGENT_CARD_PATH, HTTP_EXTENSION_HEADER } from '@a2a-js/sdk';
9
+ import { AGENT_CARD_PATH, HTTP_EXTENSION_HEADER } from '@a2a-js/sdk';
10
+ import type { Task, TaskState, TaskStatus, Artifact, Part, Message, AgentCard, MessageSendConfiguration, SendMessageResponse, SendMessageSuccessResponse, JSONRPCErrorResponse } from '@a2a-js/sdk';
10
11
  import type { LAFSEnvelope } from '../types.js';
11
12
  /**
12
13
  * Configuration for LAFS A2A integration
@@ -7,6 +7,10 @@
7
7
  * Reference: specs/external/specification.md
8
8
  */
9
9
  // ============================================================================
10
+ // Imports - Use official A2A SDK types
11
+ // ============================================================================
12
+ import { AGENT_CARD_PATH, HTTP_EXTENSION_HEADER, } from '@a2a-js/sdk';
13
+ // ============================================================================
10
14
  // Result Wrapper
11
15
  // ============================================================================
12
16
  /**
@@ -267,3 +271,16 @@ export function isExtensionRequired(agentCard, extensionUri) {
267
271
  export function getExtensionParams(agentCard, extensionUri) {
268
272
  return agentCard.capabilities?.extensions?.find(ext => ext.uri === extensionUri)?.params;
269
273
  }
274
+ // ============================================================================
275
+ // Constants
276
+ // ============================================================================
277
+ /**
278
+ * A2A Agent Card well-known path
279
+ * Reference: specs/external/agent-discovery.md
280
+ */
281
+ export { AGENT_CARD_PATH };
282
+ /**
283
+ * HTTP header for A2A Extensions
284
+ * Reference: specs/external/extensions.md
285
+ */
286
+ export { HTTP_EXTENSION_HEADER };
@@ -17,7 +17,9 @@ export interface LafsExtensionParams {
17
17
  supportsContextLedger: boolean;
18
18
  supportsTokenBudgets: boolean;
19
19
  envelopeSchema: string;
20
+ kind?: ExtensionKind;
20
21
  }
22
+ export type ExtensionKind = 'data-only' | 'profile' | 'method' | 'state-machine';
21
23
  /** Result of extension negotiation between client and agent */
22
24
  export interface ExtensionNegotiationResult {
23
25
  /** URIs requested by the client */
@@ -28,6 +30,8 @@ export interface ExtensionNegotiationResult {
28
30
  unsupported: string[];
29
31
  /** Agent-required URIs not present in client request */
30
32
  missingRequired: string[];
33
+ /** Activated extensions grouped by declared kind (when provided) */
34
+ activatedByKind: Partial<Record<ExtensionKind, string[]>>;
31
35
  }
32
36
  /**
33
37
  * Parse A2A-Extensions header value into URI array.
@@ -57,6 +61,19 @@ export interface BuildLafsExtensionOptions {
57
61
  * Suitable for inclusion in Agent Card capabilities.extensions[].
58
62
  */
59
63
  export declare function buildLafsExtension(options?: BuildLafsExtensionOptions): AgentExtension;
64
+ export interface BuildExtensionOptions {
65
+ uri: string;
66
+ description: string;
67
+ required?: boolean;
68
+ kind: ExtensionKind;
69
+ params?: Record<string, unknown>;
70
+ }
71
+ export declare function buildExtension(options: BuildExtensionOptions): AgentExtension;
72
+ export declare function isValidExtensionKind(kind: string): kind is ExtensionKind;
73
+ export declare function validateExtensionDeclaration(extension: AgentExtension): {
74
+ valid: boolean;
75
+ error?: string;
76
+ };
60
77
  /**
61
78
  * Error thrown when required A2A extensions are not supported by the client.
62
79
  * Code -32008 (not in SDK, which stops at -32007).
@@ -18,6 +18,7 @@ export const A2A_EXTENSIONS_HEADER = 'A2A-Extensions';
18
18
  * Middleware checks both for SDK compatibility.
19
19
  */
20
20
  const SDK_EXTENSIONS_HEADER = 'x-a2a-extensions';
21
+ const VALID_EXTENSION_KINDS = ['data-only', 'profile', 'method', 'state-machine'];
21
22
  // ============================================================================
22
23
  // Functions
23
24
  // ============================================================================
@@ -57,7 +58,21 @@ export function negotiateExtensions(requestedUris, agentExtensions) {
57
58
  missingRequired.push(ext.uri);
58
59
  }
59
60
  }
60
- return { requested: requestedUris, activated, unsupported, missingRequired };
61
+ const activatedByKind = {};
62
+ for (const uri of activated) {
63
+ const ext = declared.get(uri);
64
+ const kind = ext?.params && typeof ext.params === 'object'
65
+ ? ext.params['kind']
66
+ : undefined;
67
+ if (typeof kind === 'string' && VALID_EXTENSION_KINDS.includes(kind)) {
68
+ const typedKind = kind;
69
+ if (!activatedByKind[typedKind]) {
70
+ activatedByKind[typedKind] = [];
71
+ }
72
+ activatedByKind[typedKind].push(uri);
73
+ }
74
+ }
75
+ return { requested: requestedUris, activated, unsupported, missingRequired, activatedByKind };
61
76
  }
62
77
  /**
63
78
  * Format activated extension URIs into header value.
@@ -79,9 +94,36 @@ export function buildLafsExtension(options) {
79
94
  supportsContextLedger: options?.supportsContextLedger ?? false,
80
95
  supportsTokenBudgets: options?.supportsTokenBudgets ?? false,
81
96
  envelopeSchema: options?.envelopeSchema ?? 'https://lafs.dev/schemas/v1/envelope.schema.json',
97
+ kind: 'profile',
82
98
  },
83
99
  };
84
100
  }
101
+ export function buildExtension(options) {
102
+ return {
103
+ uri: options.uri,
104
+ description: options.description,
105
+ required: options.required ?? false,
106
+ params: {
107
+ kind: options.kind,
108
+ ...(options.params ?? {}),
109
+ },
110
+ };
111
+ }
112
+ export function isValidExtensionKind(kind) {
113
+ return VALID_EXTENSION_KINDS.includes(kind);
114
+ }
115
+ export function validateExtensionDeclaration(extension) {
116
+ const kind = extension.params && typeof extension.params === 'object'
117
+ ? extension.params['kind']
118
+ : undefined;
119
+ if (kind === undefined) {
120
+ return { valid: true };
121
+ }
122
+ if (typeof kind !== 'string' || !isValidExtensionKind(kind)) {
123
+ return { valid: false, error: `invalid extension kind: ${String(kind)}` };
124
+ }
125
+ return { valid: true };
126
+ }
85
127
  // ============================================================================
86
128
  // Error Class
87
129
  // ============================================================================
@@ -31,7 +31,9 @@
31
31
  export { LafsA2AResult, createLafsArtifact, createTextArtifact, createFileArtifact, isExtensionRequired, getExtensionParams, AGENT_CARD_PATH, HTTP_EXTENSION_HEADER, } from './bridge.js';
32
32
  export { LAFS_EXTENSION_URI, A2A_EXTENSIONS_HEADER, parseExtensionsHeader, negotiateExtensions, formatExtensionsHeader, buildLafsExtension, ExtensionSupportRequiredError, extensionNegotiationMiddleware, } from './extensions.js';
33
33
  export type { LafsExtensionParams, ExtensionNegotiationResult, BuildLafsExtensionOptions, ExtensionNegotiationMiddlewareOptions, } from './extensions.js';
34
- export { TERMINAL_STATES, INTERRUPTED_STATES, VALID_TRANSITIONS, isValidTransition, isTerminalState, isInterruptedState, InvalidStateTransitionError, TaskImmutabilityError, TaskNotFoundError, TaskManager, attachLafsEnvelope, } from './task-lifecycle.js';
34
+ export { TERMINAL_STATES, INTERRUPTED_STATES, VALID_TRANSITIONS, isValidTransition, isTerminalState, isInterruptedState, InvalidStateTransitionError, TaskImmutabilityError, TaskNotFoundError, TaskRefinementError, TaskManager, attachLafsEnvelope, } from './task-lifecycle.js';
35
+ export { TaskEventBus, PushNotificationConfigStore, PushNotificationDispatcher, TaskArtifactAssembler, streamTaskEvents, } from './streaming.js';
36
+ export type { TaskStreamEvent, StreamIteratorOptions, PushNotificationDeliveryResult, PushTransport, } from './streaming.js';
35
37
  export type { CreateTaskOptions, ListTasksOptions, ListTasksResult, } from './task-lifecycle.js';
36
38
  export * from './bindings/index.js';
37
39
  export type { LafsA2AConfig, LafsSendMessageParams, } from './bridge.js';
@@ -37,7 +37,9 @@ LafsA2AResult,
37
37
  // Artifact helpers
38
38
  createLafsArtifact, createTextArtifact, createFileArtifact,
39
39
  // Extension helpers
40
- isExtensionRequired, getExtensionParams, } from './bridge.js';
40
+ isExtensionRequired, getExtensionParams,
41
+ // Constants
42
+ AGENT_CARD_PATH, HTTP_EXTENSION_HEADER, } from './bridge.js';
41
43
  // ============================================================================
42
44
  // Extensions (T098)
43
45
  // ============================================================================
@@ -59,12 +61,16 @@ TERMINAL_STATES, INTERRUPTED_STATES, VALID_TRANSITIONS,
59
61
  // State functions
60
62
  isValidTransition, isTerminalState, isInterruptedState,
61
63
  // Error classes
62
- InvalidStateTransitionError, TaskImmutabilityError, TaskNotFoundError,
64
+ InvalidStateTransitionError, TaskImmutabilityError, TaskNotFoundError, TaskRefinementError,
63
65
  // Task manager
64
66
  TaskManager,
65
67
  // LAFS integration
66
68
  attachLafsEnvelope, } from './task-lifecycle.js';
67
69
  // ============================================================================
70
+ // Streaming and Async (T101)
71
+ // ============================================================================
72
+ export { TaskEventBus, PushNotificationConfigStore, PushNotificationDispatcher, TaskArtifactAssembler, streamTaskEvents, } from './streaming.js';
73
+ // ============================================================================
68
74
  // Protocol Bindings (T100)
69
75
  // ============================================================================
70
76
  export * from './bindings/index.js';
@@ -0,0 +1,74 @@
1
+ /**
2
+ * A2A streaming and async runtime primitives (T101).
3
+ *
4
+ * Provides an in-memory event bus for task status/artifact updates and
5
+ * push-notification config storage helpers.
6
+ */
7
+ import type { Artifact, PushNotificationConfig, TaskArtifactUpdateEvent, TaskStatusUpdateEvent } from "@a2a-js/sdk";
8
+ export type TaskStreamEvent = TaskStatusUpdateEvent | TaskArtifactUpdateEvent;
9
+ type StreamListener = (event: TaskStreamEvent) => void;
10
+ /**
11
+ * In-memory event bus for task lifecycle streaming events.
12
+ */
13
+ export declare class TaskEventBus {
14
+ private history;
15
+ private listeners;
16
+ publishStatusUpdate(event: TaskStatusUpdateEvent): void;
17
+ publishArtifactUpdate(event: TaskArtifactUpdateEvent): void;
18
+ publish(event: TaskStreamEvent): void;
19
+ subscribe(taskId: string, listener: StreamListener): () => void;
20
+ getHistory(taskId: string): TaskStreamEvent[];
21
+ }
22
+ export interface StreamIteratorOptions {
23
+ timeoutMs?: number;
24
+ }
25
+ /**
26
+ * Build an async iterator for real-time task stream events.
27
+ */
28
+ export declare function streamTaskEvents(bus: TaskEventBus, taskId: string, options?: StreamIteratorOptions): AsyncGenerator<TaskStreamEvent>;
29
+ /**
30
+ * In-memory manager for async push-notification configs.
31
+ */
32
+ export declare class PushNotificationConfigStore {
33
+ private configs;
34
+ set(taskId: string, configId: string, config: PushNotificationConfig): void;
35
+ get(taskId: string, configId: string): PushNotificationConfig | undefined;
36
+ list(taskId: string): PushNotificationConfig[];
37
+ delete(taskId: string, configId: string): boolean;
38
+ }
39
+ export interface PushNotificationDeliveryResult {
40
+ configId: string;
41
+ ok: boolean;
42
+ status?: number;
43
+ error?: string;
44
+ }
45
+ export type PushTransport = (input: string, init?: {
46
+ method?: string;
47
+ headers?: Record<string, string>;
48
+ body?: string;
49
+ }) => Promise<{
50
+ ok: boolean;
51
+ status: number;
52
+ }>;
53
+ /**
54
+ * Deliver task updates to registered push-notification webhooks.
55
+ */
56
+ export declare class PushNotificationDispatcher {
57
+ private readonly store;
58
+ private readonly transport;
59
+ constructor(store: PushNotificationConfigStore, transport?: PushTransport);
60
+ dispatch(taskId: string, event: TaskStreamEvent): Promise<PushNotificationDeliveryResult[]>;
61
+ private buildHeaders;
62
+ }
63
+ /**
64
+ * Applies append/lastChunk artifact deltas into task-local snapshots.
65
+ */
66
+ export declare class TaskArtifactAssembler {
67
+ private artifacts;
68
+ applyUpdate(event: TaskArtifactUpdateEvent): Artifact;
69
+ get(taskId: string, artifactId: string): Artifact | undefined;
70
+ list(taskId: string): Artifact[];
71
+ private mergeArtifact;
72
+ private withLastChunk;
73
+ }
74
+ export {};
@@ -0,0 +1,265 @@
1
+ /**
2
+ * A2A streaming and async runtime primitives (T101).
3
+ *
4
+ * Provides an in-memory event bus for task status/artifact updates and
5
+ * push-notification config storage helpers.
6
+ */
7
+ function resolveTaskId(event) {
8
+ const candidate = event;
9
+ const taskId = candidate.taskId ?? candidate.task?.id;
10
+ if (!taskId) {
11
+ throw new Error("Task stream event is missing task identifier");
12
+ }
13
+ return taskId;
14
+ }
15
+ /**
16
+ * In-memory event bus for task lifecycle streaming events.
17
+ */
18
+ export class TaskEventBus {
19
+ history = new Map();
20
+ listeners = new Map();
21
+ publishStatusUpdate(event) {
22
+ this.publish(event);
23
+ }
24
+ publishArtifactUpdate(event) {
25
+ this.publish(event);
26
+ }
27
+ publish(event) {
28
+ const taskId = resolveTaskId(event);
29
+ const events = this.history.get(taskId) ?? [];
30
+ events.push(event);
31
+ this.history.set(taskId, events);
32
+ const listeners = this.listeners.get(taskId);
33
+ if (listeners) {
34
+ for (const listener of listeners) {
35
+ listener(event);
36
+ }
37
+ }
38
+ }
39
+ subscribe(taskId, listener) {
40
+ let set = this.listeners.get(taskId);
41
+ if (!set) {
42
+ set = new Set();
43
+ this.listeners.set(taskId, set);
44
+ }
45
+ set.add(listener);
46
+ return () => {
47
+ const active = this.listeners.get(taskId);
48
+ if (!active)
49
+ return;
50
+ active.delete(listener);
51
+ if (active.size === 0) {
52
+ this.listeners.delete(taskId);
53
+ }
54
+ };
55
+ }
56
+ getHistory(taskId) {
57
+ return [...(this.history.get(taskId) ?? [])];
58
+ }
59
+ }
60
+ /**
61
+ * Build an async iterator for real-time task stream events.
62
+ */
63
+ export async function* streamTaskEvents(bus, taskId, options = {}) {
64
+ const queue = [];
65
+ let wakeUp = null;
66
+ const unsubscribe = bus.subscribe(taskId, (event) => {
67
+ queue.push(event);
68
+ if (wakeUp) {
69
+ wakeUp();
70
+ wakeUp = null;
71
+ }
72
+ });
73
+ const timeoutMs = options.timeoutMs ?? 30_000;
74
+ try {
75
+ // Emit existing history first for catch-up behavior.
76
+ for (const event of bus.getHistory(taskId)) {
77
+ yield event;
78
+ }
79
+ while (true) {
80
+ if (queue.length > 0) {
81
+ const event = queue.shift();
82
+ if (event) {
83
+ yield event;
84
+ continue;
85
+ }
86
+ }
87
+ await new Promise((resolve) => {
88
+ const timer = setTimeout(() => {
89
+ if (wakeUp === resolve) {
90
+ wakeUp = null;
91
+ }
92
+ resolve();
93
+ }, timeoutMs);
94
+ wakeUp = () => {
95
+ clearTimeout(timer);
96
+ resolve();
97
+ };
98
+ });
99
+ }
100
+ }
101
+ finally {
102
+ unsubscribe();
103
+ }
104
+ }
105
+ /**
106
+ * In-memory manager for async push-notification configs.
107
+ */
108
+ export class PushNotificationConfigStore {
109
+ configs = new Map();
110
+ set(taskId, configId, config) {
111
+ let taskConfigs = this.configs.get(taskId);
112
+ if (!taskConfigs) {
113
+ taskConfigs = new Map();
114
+ this.configs.set(taskId, taskConfigs);
115
+ }
116
+ taskConfigs.set(configId, config);
117
+ }
118
+ get(taskId, configId) {
119
+ return this.configs.get(taskId)?.get(configId);
120
+ }
121
+ list(taskId) {
122
+ return [...(this.configs.get(taskId)?.values() ?? [])];
123
+ }
124
+ delete(taskId, configId) {
125
+ const taskConfigs = this.configs.get(taskId);
126
+ if (!taskConfigs) {
127
+ return false;
128
+ }
129
+ const removed = taskConfigs.delete(configId);
130
+ if (taskConfigs.size === 0) {
131
+ this.configs.delete(taskId);
132
+ }
133
+ return removed;
134
+ }
135
+ }
136
+ /**
137
+ * Deliver task updates to registered push-notification webhooks.
138
+ */
139
+ export class PushNotificationDispatcher {
140
+ store;
141
+ transport;
142
+ constructor(store, transport = async (input, init) => {
143
+ if (typeof fetch !== "function") {
144
+ throw new Error("Global fetch is not available for push dispatch");
145
+ }
146
+ return fetch(input, init);
147
+ }) {
148
+ this.store = store;
149
+ this.transport = transport;
150
+ }
151
+ async dispatch(taskId, event) {
152
+ const configs = this.store.list(taskId);
153
+ if (configs.length === 0) {
154
+ return [];
155
+ }
156
+ const payload = {
157
+ taskId,
158
+ event,
159
+ timestamp: new Date().toISOString(),
160
+ };
161
+ const deliveries = configs.map(async (config, index) => {
162
+ const configId = config.id ?? `${taskId}:${index}`;
163
+ if (!config.url) {
164
+ return {
165
+ configId,
166
+ ok: false,
167
+ error: "Push notification config is missing url",
168
+ };
169
+ }
170
+ const headers = this.buildHeaders(config);
171
+ try {
172
+ const response = await this.transport(config.url, {
173
+ method: "POST",
174
+ headers,
175
+ body: JSON.stringify(payload),
176
+ });
177
+ return {
178
+ configId,
179
+ ok: response.ok,
180
+ status: response.status,
181
+ ...(response.ok ? {} : { error: `HTTP ${response.status}` }),
182
+ };
183
+ }
184
+ catch (error) {
185
+ return {
186
+ configId,
187
+ ok: false,
188
+ error: error instanceof Error ? error.message : String(error),
189
+ };
190
+ }
191
+ });
192
+ return Promise.all(deliveries);
193
+ }
194
+ buildHeaders(config) {
195
+ const headers = {
196
+ "content-type": "application/json",
197
+ };
198
+ if (config.token) {
199
+ headers["x-a2a-task-token"] = config.token;
200
+ }
201
+ const scheme = config.authentication?.schemes?.[0];
202
+ const credentials = config.authentication?.credentials;
203
+ if (scheme && credentials) {
204
+ headers.authorization = `${scheme} ${credentials}`;
205
+ }
206
+ else if (config.token) {
207
+ headers.authorization = `Bearer ${config.token}`;
208
+ }
209
+ return headers;
210
+ }
211
+ }
212
+ /**
213
+ * Applies append/lastChunk artifact deltas into task-local snapshots.
214
+ */
215
+ export class TaskArtifactAssembler {
216
+ artifacts = new Map();
217
+ applyUpdate(event) {
218
+ if (!event.artifact?.artifactId) {
219
+ throw new Error("Task artifact update is missing artifactId");
220
+ }
221
+ const taskId = resolveTaskId(event);
222
+ const artifactId = event.artifact.artifactId;
223
+ let taskArtifacts = this.artifacts.get(taskId);
224
+ if (!taskArtifacts) {
225
+ taskArtifacts = new Map();
226
+ this.artifacts.set(taskId, taskArtifacts);
227
+ }
228
+ const prior = taskArtifacts.get(artifactId);
229
+ const merged = this.mergeArtifact(prior, event);
230
+ taskArtifacts.set(artifactId, merged);
231
+ return merged;
232
+ }
233
+ get(taskId, artifactId) {
234
+ return this.artifacts.get(taskId)?.get(artifactId);
235
+ }
236
+ list(taskId) {
237
+ return [...(this.artifacts.get(taskId)?.values() ?? [])];
238
+ }
239
+ mergeArtifact(prior, event) {
240
+ const next = event.artifact;
241
+ const append = Boolean(event.append);
242
+ const lastChunk = Boolean(event.lastChunk);
243
+ if (!append || !prior) {
244
+ return {
245
+ ...next,
246
+ metadata: this.withLastChunk(next.metadata, lastChunk),
247
+ };
248
+ }
249
+ return {
250
+ ...prior,
251
+ ...next,
252
+ parts: [...(prior.parts ?? []), ...(next.parts ?? [])],
253
+ metadata: this.withLastChunk({
254
+ ...(prior.metadata ?? {}),
255
+ ...(next.metadata ?? {}),
256
+ }, lastChunk),
257
+ };
258
+ }
259
+ withLastChunk(metadata, lastChunk) {
260
+ return {
261
+ ...(metadata ?? {}),
262
+ "a2a:last_chunk": lastChunk,
263
+ };
264
+ }
265
+ }
@@ -38,10 +38,17 @@ export declare class TaskNotFoundError extends Error {
38
38
  readonly taskId: string;
39
39
  constructor(taskId: string);
40
40
  }
41
+ /** Thrown when a refinement/follow-up task references invalid parent tasks */
42
+ export declare class TaskRefinementError extends Error {
43
+ readonly referenceTaskIds: string[];
44
+ constructor(message: string, referenceTaskIds: string[]);
45
+ }
41
46
  /** Options for creating a new task */
42
47
  export interface CreateTaskOptions {
43
48
  contextId?: string;
44
49
  metadata?: Record<string, unknown>;
50
+ referenceTaskIds?: string[];
51
+ parallelFollowUp?: boolean;
45
52
  }
46
53
  /** Options for listing tasks */
47
54
  export interface ListTasksOptions {
@@ -64,6 +71,8 @@ export declare class TaskManager {
64
71
  private contextIndex;
65
72
  /** Create a new task in the submitted state */
66
73
  createTask(options?: CreateTaskOptions): Task;
74
+ /** Create a refinement/follow-up task referencing existing task(s). */
75
+ createRefinedTask(referenceTaskIds: string[], options?: Omit<CreateTaskOptions, 'referenceTaskIds'>): Task;
67
76
  /** Get a task by ID. Throws TaskNotFoundError if not found. */
68
77
  getTask(taskId: string): Task;
69
78
  /** List tasks with optional filtering and pagination */
@@ -90,6 +99,8 @@ export declare class TaskManager {
90
99
  getTasksByContext(contextId: string): Task[];
91
100
  /** Check if a task is in a terminal state */
92
101
  isTerminal(taskId: string): boolean;
102
+ private resolveContextForReferenceTasks;
103
+ private validateReferenceTasks;
93
104
  }
94
105
  /**
95
106
  * Attach a LAFS envelope as an artifact to an A2A task.
@@ -86,6 +86,15 @@ export class TaskNotFoundError extends Error {
86
86
  this.taskId = taskId;
87
87
  }
88
88
  }
89
+ /** Thrown when a refinement/follow-up task references invalid parent tasks */
90
+ export class TaskRefinementError extends Error {
91
+ referenceTaskIds;
92
+ constructor(message, referenceTaskIds) {
93
+ super(message);
94
+ this.name = 'TaskRefinementError';
95
+ this.referenceTaskIds = referenceTaskIds;
96
+ }
97
+ }
89
98
  // ============================================================================
90
99
  // ID Generation
91
100
  // ============================================================================
@@ -106,7 +115,15 @@ export class TaskManager {
106
115
  /** Create a new task in the submitted state */
107
116
  createTask(options) {
108
117
  const id = generateId();
109
- const contextId = options?.contextId ?? generateId();
118
+ const resolvedContextId = options?.contextId ?? this.resolveContextForReferenceTasks(options?.referenceTaskIds) ?? generateId();
119
+ const contextId = resolvedContextId;
120
+ const referenceTaskIds = options?.referenceTaskIds ?? [];
121
+ this.validateReferenceTasks(referenceTaskIds, contextId);
122
+ const metadata = {
123
+ ...(options?.metadata ?? {}),
124
+ ...(referenceTaskIds.length > 0 ? { referenceTaskIds } : {}),
125
+ ...(options?.parallelFollowUp ? { parallelFollowUp: true } : {}),
126
+ };
110
127
  const task = {
111
128
  id,
112
129
  contextId,
@@ -115,7 +132,7 @@ export class TaskManager {
115
132
  state: 'submitted',
116
133
  timestamp: new Date().toISOString(),
117
134
  },
118
- ...(options?.metadata && { metadata: options.metadata }),
135
+ ...(Object.keys(metadata).length > 0 && { metadata }),
119
136
  };
120
137
  this.tasks.set(id, task);
121
138
  // Index by contextId
@@ -127,6 +144,14 @@ export class TaskManager {
127
144
  contextTasks.add(id);
128
145
  return structuredClone(task);
129
146
  }
147
+ /** Create a refinement/follow-up task referencing existing task(s). */
148
+ createRefinedTask(referenceTaskIds, options) {
149
+ return this.createTask({
150
+ ...options,
151
+ referenceTaskIds,
152
+ parallelFollowUp: options?.parallelFollowUp,
153
+ });
154
+ }
130
155
  /** Get a task by ID. Throws TaskNotFoundError if not found. */
131
156
  getTask(taskId) {
132
157
  const task = this.tasks.get(taskId);
@@ -249,6 +274,31 @@ export class TaskManager {
249
274
  }
250
275
  return isTerminalState(task.status.state);
251
276
  }
277
+ resolveContextForReferenceTasks(referenceTaskIds) {
278
+ if (!referenceTaskIds || referenceTaskIds.length === 0) {
279
+ return undefined;
280
+ }
281
+ const firstId = referenceTaskIds[0];
282
+ if (!firstId) {
283
+ return undefined;
284
+ }
285
+ const first = this.tasks.get(firstId);
286
+ return first?.contextId;
287
+ }
288
+ validateReferenceTasks(referenceTaskIds, contextId) {
289
+ if (referenceTaskIds.length === 0) {
290
+ return;
291
+ }
292
+ for (const refId of referenceTaskIds) {
293
+ const refTask = this.tasks.get(refId);
294
+ if (!refTask) {
295
+ throw new TaskRefinementError(`Referenced task not found: ${refId}`, referenceTaskIds);
296
+ }
297
+ if (refTask.contextId !== contextId) {
298
+ throw new TaskRefinementError(`Referenced task ${refId} has different contextId (${refTask.contextId}) than refinement (${contextId})`, referenceTaskIds);
299
+ }
300
+ }
301
+ }
252
302
  }
253
303
  // ============================================================================
254
304
  // LAFS Integration
@@ -1,3 +1,7 @@
1
1
  import type { ConformanceReport, FlagInput } from "./types.js";
2
- export declare function runEnvelopeConformance(envelope: unknown): ConformanceReport;
2
+ import { type ConformanceTier } from "./conformanceProfiles.js";
3
+ export interface EnvelopeConformanceOptions {
4
+ tier?: ConformanceTier;
5
+ }
6
+ export declare function runEnvelopeConformance(envelope: unknown, options?: EnvelopeConformanceOptions): ConformanceReport;
3
7
  export declare function runFlagConformance(flags: FlagInput): ConformanceReport;
@@ -1,10 +1,11 @@
1
- import { isRegisteredErrorCode } from "./errorRegistry.js";
1
+ import { getTransportMapping, isRegisteredErrorCode } from "./errorRegistry.js";
2
2
  import { resolveOutputFormat, LAFSFlagError } from "./flagSemantics.js";
3
+ import { getChecksForTier } from "./conformanceProfiles.js";
3
4
  import { validateEnvelope } from "./validateEnvelope.js";
4
5
  function pushCheck(checks, name, pass, detail) {
5
6
  checks.push({ name, pass, ...(detail ? { detail } : {}) });
6
7
  }
7
- export function runEnvelopeConformance(envelope) {
8
+ export function runEnvelopeConformance(envelope, options = {}) {
8
9
  const checks = [];
9
10
  const validation = validateEnvelope(envelope);
10
11
  pushCheck(checks, "envelope_schema_valid", validation.valid, validation.valid ? undefined : validation.errors.join("; "));
@@ -30,6 +31,50 @@ export function runEnvelopeConformance(envelope) {
30
31
  else {
31
32
  pushCheck(checks, "error_code_registered", true, "error field absent or null — skipped (optional when success=true)");
32
33
  }
34
+ // transport_mapping_consistent: when an error is present, ensure the code has
35
+ // a transport-specific mapping in the registry for the declared transport.
36
+ if (typed.error) {
37
+ if (typed._meta.transport === "sdk") {
38
+ pushCheck(checks, "transport_mapping_consistent", true, "sdk transport does not require external status-code mapping");
39
+ }
40
+ else {
41
+ const mapping = getTransportMapping(typed.error.code, typed._meta.transport);
42
+ const mappingOk = mapping !== null;
43
+ pushCheck(checks, "transport_mapping_consistent", mappingOk, mappingOk
44
+ ? undefined
45
+ : `no ${typed._meta.transport} mapping found for code ${typed.error.code}`);
46
+ }
47
+ }
48
+ else {
49
+ pushCheck(checks, "transport_mapping_consistent", true, "no error present — mapping check skipped");
50
+ }
51
+ // context_mutation_failure: if the producer marks context as required for a
52
+ // mutation operation, missing context must fail with a context error code.
53
+ {
54
+ const ext = (typed._extensions ?? {});
55
+ const contextObj = (ext["context"] ?? {});
56
+ const lafsObj = (ext["lafs"] ?? {});
57
+ const contextRequired = ext["lafsContextRequired"] === true ||
58
+ contextObj["required"] === true ||
59
+ lafsObj["contextRequired"] === true;
60
+ if (!contextRequired) {
61
+ pushCheck(checks, "context_mutation_failure", true, "context not marked required — skipped");
62
+ }
63
+ else {
64
+ const hasContextIdentity = typed._meta.contextVersion > 0 || Boolean(typed._meta.sessionId);
65
+ if (typed.success) {
66
+ const pass = hasContextIdentity;
67
+ pushCheck(checks, "context_mutation_failure", pass, pass ? undefined : "context required but missing identity (expect E_CONTEXT_MISSING)");
68
+ }
69
+ else {
70
+ const code = typed.error?.code;
71
+ const pass = code === "E_CONTEXT_MISSING" || code === "E_CONTEXT_STALE";
72
+ pushCheck(checks, "context_mutation_failure", pass, pass
73
+ ? undefined
74
+ : `context required failures should return E_CONTEXT_MISSING or E_CONTEXT_STALE, got ${String(code)}`);
75
+ }
76
+ }
77
+ }
33
78
  const validMviLevels = ["minimal", "standard", "full", "custom"];
34
79
  pushCheck(checks, "meta_mvi_present", validMviLevels.includes(typed._meta.mvi), validMviLevels.includes(typed._meta.mvi) ? undefined : `invalid mvi level: ${String(typed._meta.mvi)}`);
35
80
  pushCheck(checks, "meta_strict_present", typeof typed._meta.strict === "boolean");
@@ -72,6 +117,9 @@ export function runEnvelopeConformance(envelope) {
72
117
  }
73
118
  pushCheck(checks, "pagination_mode_consistent", consistent, consistent ? undefined : detail);
74
119
  }
120
+ else {
121
+ pushCheck(checks, "pagination_mode_consistent", true, "page absent — skipped");
122
+ }
75
123
  // strict_mode_enforced: verify the schema enforces additional-property rules.
76
124
  // When strict=true, extra top-level properties must be rejected by validation.
77
125
  // When strict=false, extra top-level properties must be allowed.
@@ -85,7 +133,59 @@ export function runEnvelopeConformance(envelope) {
85
133
  pushCheck(checks, "strict_mode_enforced", extraResult.valid, !extraResult.valid ? "strict=false but additional properties were rejected" : undefined);
86
134
  }
87
135
  }
88
- return { ok: checks.every((check) => check.pass), checks };
136
+ // context_preservation_valid: validate monotonic context version behavior and
137
+ // context-constraint integrity when a context ledger extension is present.
138
+ {
139
+ const ext = (typed._extensions ?? {});
140
+ const ledger = (ext["contextLedger"] ?? ext["context"]);
141
+ if (!ledger || typeof ledger !== "object") {
142
+ pushCheck(checks, "context_preservation_valid", true, "context ledger absent — skipped");
143
+ }
144
+ else {
145
+ const version = ledger["version"];
146
+ const previousVersion = ledger["previousVersion"];
147
+ const removedConstraints = ledger["removedConstraints"];
148
+ const hasNumericVersion = typeof version === "number";
149
+ const matchesEnvelopeVersion = hasNumericVersion && version === typed._meta.contextVersion;
150
+ const monotonicFromPrevious = typeof previousVersion !== "number" || (hasNumericVersion && version >= previousVersion);
151
+ const constraintsPreserved = !Array.isArray(removedConstraints) || removedConstraints.length === 0 || !typed.success;
152
+ let pass = matchesEnvelopeVersion && monotonicFromPrevious && constraintsPreserved;
153
+ let detail;
154
+ if (!hasNumericVersion) {
155
+ pass = false;
156
+ detail = "context ledger version must be numeric";
157
+ }
158
+ else if (!matchesEnvelopeVersion) {
159
+ detail = `context version mismatch: ledger=${String(version)} envelope=${typed._meta.contextVersion}`;
160
+ }
161
+ else if (!monotonicFromPrevious) {
162
+ detail = `non-monotonic context version: previous=${String(previousVersion)} current=${String(version)}`;
163
+ }
164
+ else if (!constraintsPreserved) {
165
+ detail = "context constraint removal detected on successful response";
166
+ }
167
+ // Error-path validation for stale/missing context signaling.
168
+ if (!typed.success && typed.error && ledger["required"] === true) {
169
+ const stale = ledger["stale"] === true;
170
+ if (stale && typed.error.code !== "E_CONTEXT_STALE") {
171
+ pass = false;
172
+ detail = `stale context should return E_CONTEXT_STALE, got ${typed.error.code}`;
173
+ }
174
+ if (!stale && typed.error.code !== "E_CONTEXT_MISSING" && typed.error.code !== "E_CONTEXT_STALE") {
175
+ pass = false;
176
+ detail = `required context failure should return E_CONTEXT_MISSING or E_CONTEXT_STALE, got ${typed.error.code}`;
177
+ }
178
+ }
179
+ pushCheck(checks, "context_preservation_valid", pass, detail);
180
+ }
181
+ }
182
+ const tier = options.tier;
183
+ if (!tier) {
184
+ return { ok: checks.every((check) => check.pass), checks };
185
+ }
186
+ const allowed = new Set(getChecksForTier(tier));
187
+ const tierChecks = checks.filter((check) => allowed.has(check.name));
188
+ return { ok: tierChecks.every((check) => check.pass), checks: tierChecks };
89
189
  }
90
190
  export function runFlagConformance(flags) {
91
191
  const checks = [];
@@ -0,0 +1,11 @@
1
+ export type ConformanceTier = "core" | "standard" | "complete";
2
+ export interface ConformanceProfiles {
3
+ version: string;
4
+ tiers: Record<ConformanceTier, string[]>;
5
+ }
6
+ export declare function getConformanceProfiles(): ConformanceProfiles;
7
+ export declare function getChecksForTier(tier: ConformanceTier): string[];
8
+ export declare function validateConformanceProfiles(availableChecks: string[]): {
9
+ valid: boolean;
10
+ errors: string[];
11
+ };
@@ -0,0 +1,34 @@
1
+ import profilesJson from "../schemas/v1/conformance-profiles.json" with { type: "json" };
2
+ export function getConformanceProfiles() {
3
+ return profilesJson;
4
+ }
5
+ export function getChecksForTier(tier) {
6
+ const profiles = getConformanceProfiles();
7
+ return profiles.tiers[tier] ?? [];
8
+ }
9
+ export function validateConformanceProfiles(availableChecks) {
10
+ const profiles = getConformanceProfiles();
11
+ const errors = [];
12
+ const core = new Set(profiles.tiers.core);
13
+ const standard = new Set(profiles.tiers.standard);
14
+ const complete = new Set(profiles.tiers.complete);
15
+ for (const check of core) {
16
+ if (!standard.has(check)) {
17
+ errors.push(`standard tier must include core check: ${check}`);
18
+ }
19
+ }
20
+ for (const check of standard) {
21
+ if (!complete.has(check)) {
22
+ errors.push(`complete tier must include standard check: ${check}`);
23
+ }
24
+ }
25
+ const known = new Set(availableChecks);
26
+ for (const [tier, checks] of Object.entries(profiles.tiers)) {
27
+ for (const check of checks) {
28
+ if (!known.has(check)) {
29
+ errors.push(`unknown check in ${tier} tier: ${check}`);
30
+ }
31
+ }
32
+ }
33
+ return { valid: errors.length === 0, errors };
34
+ }
@@ -0,0 +1,13 @@
1
+ import type { LAFSEnvelope, Warning } from "./types.js";
2
+ export interface DeprecationEntry {
3
+ id: string;
4
+ code: string;
5
+ message: string;
6
+ deprecated: string;
7
+ replacement?: string;
8
+ removeBy: string;
9
+ detector: (envelope: LAFSEnvelope) => boolean;
10
+ }
11
+ export declare function getDeprecationRegistry(): DeprecationEntry[];
12
+ export declare function detectDeprecatedEnvelopeFields(envelope: LAFSEnvelope): Warning[];
13
+ export declare function emitDeprecationWarnings(envelope: LAFSEnvelope): LAFSEnvelope;
@@ -0,0 +1,39 @@
1
+ const DEPRECATION_REGISTRY = [
2
+ {
3
+ id: "meta-mvi-boolean",
4
+ code: "W_DEPRECATED_META_MVI_BOOLEAN",
5
+ message: "_meta.mvi boolean values are deprecated",
6
+ deprecated: "1.0.0",
7
+ replacement: "Use _meta.mvi as one of: minimal|standard|full|custom",
8
+ removeBy: "2.0.0",
9
+ detector: (envelope) => typeof envelope._meta.mvi === "boolean",
10
+ },
11
+ ];
12
+ export function getDeprecationRegistry() {
13
+ return DEPRECATION_REGISTRY;
14
+ }
15
+ export function detectDeprecatedEnvelopeFields(envelope) {
16
+ return getDeprecationRegistry()
17
+ .filter((entry) => entry.detector(envelope))
18
+ .map((entry) => ({
19
+ code: entry.code,
20
+ message: entry.message,
21
+ deprecated: entry.deprecated,
22
+ replacement: entry.replacement,
23
+ removeBy: entry.removeBy,
24
+ }));
25
+ }
26
+ export function emitDeprecationWarnings(envelope) {
27
+ const detected = detectDeprecatedEnvelopeFields(envelope);
28
+ if (detected.length === 0) {
29
+ return envelope;
30
+ }
31
+ const existingWarnings = envelope._meta.warnings ?? [];
32
+ return {
33
+ ...envelope,
34
+ _meta: {
35
+ ...envelope._meta,
36
+ warnings: [...existingWarnings, ...detected],
37
+ },
38
+ };
39
+ }
@@ -11,5 +11,11 @@ export interface ErrorRegistry {
11
11
  version: string;
12
12
  codes: RegistryCode[];
13
13
  }
14
+ export type TransportMapping = {
15
+ transport: "http" | "grpc" | "cli";
16
+ value: number | string;
17
+ };
14
18
  export declare function getErrorRegistry(): ErrorRegistry;
15
19
  export declare function isRegisteredErrorCode(code: string): boolean;
20
+ export declare function getRegistryCode(code: string): RegistryCode | undefined;
21
+ export declare function getTransportMapping(code: string, transport: "http" | "grpc" | "cli"): TransportMapping | null;
@@ -6,3 +6,19 @@ export function isRegisteredErrorCode(code) {
6
6
  const registry = getErrorRegistry();
7
7
  return registry.codes.some((item) => item.code === code);
8
8
  }
9
+ export function getRegistryCode(code) {
10
+ return getErrorRegistry().codes.find((item) => item.code === code);
11
+ }
12
+ export function getTransportMapping(code, transport) {
13
+ const registryCode = getRegistryCode(code);
14
+ if (!registryCode) {
15
+ return null;
16
+ }
17
+ if (transport === "http") {
18
+ return { transport, value: registryCode.httpStatus };
19
+ }
20
+ if (transport === "grpc") {
21
+ return { transport, value: registryCode.grpcStatus };
22
+ }
23
+ return { transport, value: registryCode.cliExit };
24
+ }
@@ -1,9 +1,11 @@
1
1
  export * from "./types.js";
2
2
  export * from "./errorRegistry.js";
3
+ export * from "./deprecationRegistry.js";
3
4
  export * from "./validateEnvelope.js";
4
5
  export * from "./envelope.js";
5
6
  export * from "./flagSemantics.js";
6
7
  export * from "./conformance.js";
8
+ export * from "./conformanceProfiles.js";
7
9
  export * from "./compliance.js";
8
10
  export * from "./tokenEstimator.js";
9
11
  export * from "./budgetEnforcement.js";
@@ -12,6 +14,6 @@ export * from "./discovery.js";
12
14
  export * from "./health/index.js";
13
15
  export * from "./shutdown/index.js";
14
16
  export * from "./circuit-breaker/index.js";
15
- export { LafsA2AResult, createLafsArtifact, createTextArtifact, createFileArtifact, isExtensionRequired, getExtensionParams, AGENT_CARD_PATH, HTTP_EXTENSION_HEADER, LAFS_EXTENSION_URI, A2A_EXTENSIONS_HEADER, parseExtensionsHeader, negotiateExtensions, formatExtensionsHeader, buildLafsExtension, ExtensionSupportRequiredError, extensionNegotiationMiddleware, TERMINAL_STATES, INTERRUPTED_STATES, VALID_TRANSITIONS, isValidTransition, isTerminalState, isInterruptedState, InvalidStateTransitionError, TaskImmutabilityError, TaskNotFoundError, TaskManager, attachLafsEnvelope, } from "./a2a/index.js";
16
- export type { LafsA2AConfig, LafsSendMessageParams, LafsExtensionParams, ExtensionNegotiationResult, BuildLafsExtensionOptions, ExtensionNegotiationMiddlewareOptions, CreateTaskOptions, ListTasksOptions, ListTasksResult, } from "./a2a/index.js";
17
+ export { LafsA2AResult, createLafsArtifact, createTextArtifact, createFileArtifact, isExtensionRequired, getExtensionParams, AGENT_CARD_PATH, HTTP_EXTENSION_HEADER, LAFS_EXTENSION_URI, A2A_EXTENSIONS_HEADER, parseExtensionsHeader, negotiateExtensions, formatExtensionsHeader, buildLafsExtension, ExtensionSupportRequiredError, extensionNegotiationMiddleware, TERMINAL_STATES, INTERRUPTED_STATES, VALID_TRANSITIONS, isValidTransition, isTerminalState, isInterruptedState, InvalidStateTransitionError, TaskImmutabilityError, TaskNotFoundError, TaskRefinementError, TaskManager, attachLafsEnvelope, TaskEventBus, PushNotificationConfigStore, PushNotificationDispatcher, TaskArtifactAssembler, streamTaskEvents, } from "./a2a/index.js";
18
+ export type { LafsA2AConfig, LafsSendMessageParams, LafsExtensionParams, ExtensionNegotiationResult, BuildLafsExtensionOptions, ExtensionNegotiationMiddlewareOptions, CreateTaskOptions, ListTasksOptions, ListTasksResult, TaskStreamEvent, StreamIteratorOptions, PushNotificationDeliveryResult, PushTransport, } from "./a2a/index.js";
17
19
  export type { Task, TaskState, TaskStatus, Artifact, Part, Message, PushNotificationConfig, MessageSendConfiguration, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, SendMessageResponse, SendMessageSuccessResponse, JSONRPCErrorResponse, TextPart, DataPart, FilePart, } from "./a2a/index.js";
package/dist/src/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  export * from "./types.js";
2
2
  export * from "./errorRegistry.js";
3
+ export * from "./deprecationRegistry.js";
3
4
  export * from "./validateEnvelope.js";
4
5
  export * from "./envelope.js";
5
6
  export * from "./flagSemantics.js";
6
7
  export * from "./conformance.js";
8
+ export * from "./conformanceProfiles.js";
7
9
  export * from "./compliance.js";
8
10
  export * from "./tokenEstimator.js";
9
11
  export * from "./budgetEnforcement.js";
@@ -19,8 +21,10 @@ export * from "./circuit-breaker/index.js";
19
21
  // For full A2A types, import from '@cleocode/lafs-protocol/a2a'.
20
22
  export {
21
23
  // Bridge
22
- LafsA2AResult, createLafsArtifact, createTextArtifact, createFileArtifact, isExtensionRequired, getExtensionParams,
24
+ LafsA2AResult, createLafsArtifact, createTextArtifact, createFileArtifact, isExtensionRequired, getExtensionParams, AGENT_CARD_PATH, HTTP_EXTENSION_HEADER,
23
25
  // Extensions (T098)
24
26
  LAFS_EXTENSION_URI, A2A_EXTENSIONS_HEADER, parseExtensionsHeader, negotiateExtensions, formatExtensionsHeader, buildLafsExtension, ExtensionSupportRequiredError, extensionNegotiationMiddleware,
25
27
  // Task Lifecycle (T099)
26
- TERMINAL_STATES, INTERRUPTED_STATES, VALID_TRANSITIONS, isValidTransition, isTerminalState, isInterruptedState, InvalidStateTransitionError, TaskImmutabilityError, TaskNotFoundError, TaskManager, attachLafsEnvelope, } from "./a2a/index.js";
28
+ TERMINAL_STATES, INTERRUPTED_STATES, VALID_TRANSITIONS, isValidTransition, isTerminalState, isInterruptedState, InvalidStateTransitionError, TaskImmutabilityError, TaskNotFoundError, TaskRefinementError, TaskManager, attachLafsEnvelope,
29
+ // Streaming and Async (T101)
30
+ TaskEventBus, PushNotificationConfigStore, PushNotificationDispatcher, TaskArtifactAssembler, streamTaskEvents, } from "./a2a/index.js";
package/lafs.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # LAFS: LLM-Agent-First Specification
2
2
 
3
3
  > 📚 **Documentation:** https://codluv.gitbook.io/lafs-protocol/
4
- > **Version:** 1.4.0 | **Status:** Production Ready
4
+ > **Version:** 1.4.1 | **Status:** Production Ready
5
5
 
6
6
  ## 1. Scope
7
7
 
@@ -412,6 +412,16 @@ Returns checksum and version for validation.
412
412
  - **Validation**: Use `mode=summary` to verify sync state
413
413
  - **Default recommendation**: `delta` mode for agent-optimal behavior
414
414
 
415
+ ### 8.2 Lazy Context Retrieval
416
+
417
+ To reduce token and I/O overhead, implementations SHOULD support lazy retrieval semantics:
418
+
419
+ - Clients SHOULD start with `mode=summary` and request `mode=delta` only when `version` or `checksum` changes.
420
+ - Delta responses SHOULD be bounded by `limit` and MAY return paged deltas.
421
+ - Servers SHOULD treat context retrieval as task-scoped and MUST NOT leak entries across `contextId` domains.
422
+ - When a consumer requests additional context beyond MVI defaults, servers MAY return progressive context slices rather than full ledgers.
423
+ - If requested context scope cannot be satisfied within declared budget, servers SHOULD fail with `E_MVI_BUDGET_EXCEEDED`.
424
+
415
425
  ---
416
426
 
417
427
  ## 9. MVI and Progressive Disclosure
@@ -447,6 +457,9 @@ Clients MAY request expanded/nested data via the `_expand` request parameter.
447
457
  - List operations SHOULD return deterministic `page` metadata.
448
458
  - Pagination mode (offset or cursor) MUST be documented.
449
459
  - Mixed pagination modes in one request MUST fail validation.
460
+ - `page.limit` SHOULD represent the effective item window after `_fields`/`_expand` processing.
461
+ - When `_meta.mvi` is `minimal` and projected payload size exceeds budget, servers SHOULD reduce `page.limit` rather than silently truncate item content.
462
+ - If limit reduction still cannot satisfy declared budget, servers MUST fail with `E_MVI_BUDGET_EXCEEDED`.
450
463
 
451
464
  ### 9.5 Token Budget Signaling
452
465
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/lafs-protocol",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "LLM-Agent-First Specification schemas and conformance tooling",
@@ -26,7 +26,8 @@
26
26
  "./schemas/v1/envelope.schema.json": "./schemas/v1/envelope.schema.json",
27
27
  "./schemas/v1/error-registry.json": "./schemas/v1/error-registry.json",
28
28
  "./schemas/v1/context-ledger.schema.json": "./schemas/v1/context-ledger.schema.json",
29
- "./schemas/v1/discovery.schema.json": "./schemas/v1/discovery.schema.json"
29
+ "./schemas/v1/discovery.schema.json": "./schemas/v1/discovery.schema.json",
30
+ "./schemas/v1/conformance-profiles.json": "./schemas/v1/conformance-profiles.json"
30
31
  },
31
32
  "files": [
32
33
  "dist",
@@ -0,0 +1,35 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "version": "1.0.0",
4
+ "tiers": {
5
+ "core": [
6
+ "envelope_schema_valid",
7
+ "envelope_invariants",
8
+ "error_code_registered"
9
+ ],
10
+ "standard": [
11
+ "envelope_schema_valid",
12
+ "envelope_invariants",
13
+ "error_code_registered",
14
+ "transport_mapping_consistent",
15
+ "meta_mvi_present",
16
+ "meta_strict_present",
17
+ "strict_mode_behavior",
18
+ "pagination_mode_consistent",
19
+ "strict_mode_enforced"
20
+ ],
21
+ "complete": [
22
+ "envelope_schema_valid",
23
+ "envelope_invariants",
24
+ "error_code_registered",
25
+ "transport_mapping_consistent",
26
+ "context_mutation_failure",
27
+ "context_preservation_valid",
28
+ "meta_mvi_present",
29
+ "meta_strict_present",
30
+ "strict_mode_behavior",
31
+ "pagination_mode_consistent",
32
+ "strict_mode_enforced"
33
+ ]
34
+ }
35
+ }