@rynx-ai/workspace 0.1.9

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.
@@ -0,0 +1,322 @@
1
+ import type { WorkspaceDevice, WorkspaceDeviceIdentity, WorkspaceDeviceInventory, WorkspaceDeviceSkillArtifactBundle, WorkspaceDeviceSkillRuntimeImportReport, WorkspaceDeviceSkillRuntimeImportTask, WorkspaceDeviceSkillRuntimeScanReport, WorkspaceDeviceSkillRuntimeScanTask, WorkspaceRepositoryScanResult } from "./types.js";
2
+ import type { WorkspaceAgentExecutionSpecV1, WorkspaceAgentInteractionResolution } from "./agent-execution.js";
3
+ import type { WorkspaceAgentRuntimeId } from "./agent-options.js";
4
+ export declare const WORKSPACE_DEVICE_PROTOCOL_VERSION: 5;
5
+ export declare const WORKSPACE_DEVICE_WS_PATH = "/api/device-connection";
6
+ export declare const WORKSPACE_INSTALLATION_PROOF_ALGORITHM: "Ed25519";
7
+ export declare const WORKSPACE_INSTALLATION_PROOF_PLUGIN_ID: "workspace";
8
+ export declare const WORKSPACE_INSTALLATION_PROOF_DOMAIN: "rynx-plugin-installation-proof-v1";
9
+ export declare const WORKSPACE_DEVICE_PROOF_CONTEXT: "rynx-workspace-device-proof-v1";
10
+ export declare const WORKSPACE_DEVICE_ACCESS_PROOF_CONTEXT: "rynx-workspace-device-access-proof-v1";
11
+ export declare function workspaceInstallationProofChallenge(serverId: string, workspaceId: string, challenge: string): string;
12
+ export declare function workspaceInstallationProofMessage(installationId: string, serverId: string, workspaceId: string, challenge: string): string;
13
+ export declare function workspaceDeviceAccessProofChallenge(serverId: string, authorizationId: string, nonce: string): string;
14
+ export declare function workspaceDeviceAccessProofMessage(installationId: string, serverId: string, authorizationId: string, nonce: string): string;
15
+ export type WorkspaceDeviceTaskKind = "repository.inspect" | "skill.runtime.scan" | "skill.runtime.import";
16
+ export interface WorkspaceRepositoryInspectPayload {
17
+ requestId: string;
18
+ path: string;
19
+ }
20
+ export type WorkspaceDeviceTaskPayload = {
21
+ kind: "repository.inspect";
22
+ value: WorkspaceRepositoryInspectPayload;
23
+ } | {
24
+ kind: "skill.runtime.scan";
25
+ value: WorkspaceDeviceSkillRuntimeScanTask;
26
+ } | {
27
+ kind: "skill.runtime.import";
28
+ value: WorkspaceDeviceSkillRuntimeImportTask;
29
+ };
30
+ export interface WorkspaceLeasedDeviceTask {
31
+ id: string;
32
+ kind: WorkspaceDeviceTaskKind;
33
+ payload: WorkspaceDeviceTaskPayload;
34
+ payloadHash: string;
35
+ attempt: number;
36
+ leaseToken: string;
37
+ leaseExpiresAt: string;
38
+ }
39
+ export type WorkspaceDeviceTaskCompletion = {
40
+ kind: "repository.inspect";
41
+ requestId: string;
42
+ repository?: WorkspaceRepositoryScanResult;
43
+ error?: string;
44
+ } | {
45
+ kind: "skill.runtime.scan";
46
+ report: WorkspaceDeviceSkillRuntimeScanReport;
47
+ } | {
48
+ kind: "skill.runtime.import";
49
+ report: WorkspaceDeviceSkillRuntimeImportReport;
50
+ };
51
+ export interface WorkspaceDeviceTaskResult {
52
+ taskId: string;
53
+ leaseToken: string;
54
+ resultHash: string;
55
+ completion: WorkspaceDeviceTaskCompletion;
56
+ }
57
+ export type WorkspaceTaskFinishDisposition = "committed" | "duplicate" | "stale" | "rejected" | "discarded";
58
+ export interface WorkspaceDeviceHelloMessage {
59
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
60
+ type: "device.hello";
61
+ instanceId: string;
62
+ installationId: string;
63
+ proof: {
64
+ algorithm: typeof WORKSPACE_INSTALLATION_PROOF_ALGORITHM;
65
+ publicKey: string;
66
+ challenge: string;
67
+ signature: string;
68
+ };
69
+ identity: WorkspaceDeviceIdentity;
70
+ inventoryDigest?: string;
71
+ capacity: number;
72
+ }
73
+ export interface WorkspaceDeviceChallengeMessage {
74
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
75
+ type: "device.challenge";
76
+ challenge: string;
77
+ expiresAt: string;
78
+ }
79
+ export interface WorkspaceDeviceInventoryMessage {
80
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
81
+ type: "inventory.snapshot";
82
+ requestId: string;
83
+ inventory: WorkspaceDeviceInventory;
84
+ }
85
+ export interface WorkspaceDeviceTaskAcceptedMessage {
86
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
87
+ type: "task.accept";
88
+ taskId: string;
89
+ leaseToken: string;
90
+ }
91
+ export interface WorkspaceDeviceTaskRenewMessage {
92
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
93
+ type: "task.renew";
94
+ taskId: string;
95
+ leaseToken: string;
96
+ }
97
+ export interface WorkspaceDeviceTaskCompleteMessage {
98
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
99
+ type: "task.complete";
100
+ result: WorkspaceDeviceTaskResult;
101
+ }
102
+ export interface WorkspaceAgentCapacityMessage {
103
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
104
+ type: "agent.capacity";
105
+ maxConcurrentRuns: number;
106
+ activeRuns: number;
107
+ observedAt: string;
108
+ }
109
+ export interface WorkspaceAgentTurnOfferAcceptedMessage {
110
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
111
+ type: "agent.turn.offer.accept";
112
+ offerId: string;
113
+ permitId: string;
114
+ daemonInstanceId: string;
115
+ }
116
+ export type WorkspaceAgentTurnOfferBusyReason = "capacity" | "runtime_unavailable" | "shutting_down";
117
+ export interface WorkspaceAgentTurnOfferBusyMessage {
118
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
119
+ type: "agent.turn.offer.busy";
120
+ offerId: string;
121
+ daemonInstanceId: string;
122
+ reason: WorkspaceAgentTurnOfferBusyReason;
123
+ }
124
+ export interface WorkspaceAgentTurnAssignAckMessage {
125
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
126
+ type: "agent.turn.assign.ack";
127
+ commandId: string;
128
+ attemptId: string;
129
+ assignmentEpoch: number;
130
+ leaseToken: string;
131
+ daemonInstanceId: string;
132
+ /** `rejected` is legal only when the Daemon can prove that no environment,
133
+ * native Session, or Turn injection side effect started. Any uncertain
134
+ * injection must be reported as `turn.execution_unknown` instead. */
135
+ disposition: "accepted" | "replayed" | "rejected";
136
+ error?: string;
137
+ }
138
+ export interface WorkspaceAgentAttemptLeaseRenewMessage {
139
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
140
+ type: "agent.attempt.lease.renew";
141
+ attemptId: string;
142
+ assignmentEpoch: number;
143
+ leaseToken: string;
144
+ daemonInstanceId: string;
145
+ }
146
+ export type WorkspaceAgentAttemptEventType = "environment.preparing" | "environment.ready" | "environment.failed" | "session.ready" | "turn.started" | "turn.progress" | "interaction.requested" | "interaction.resolved" | "interaction.cancelled" | "turn.completed" | "turn.failed" | "turn.execution_unknown";
147
+ export interface WorkspaceAgentAttemptEventMessage {
148
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
149
+ type: "agent.attempt.event";
150
+ eventId: string;
151
+ eventSequence: number;
152
+ attemptId: string;
153
+ assignmentEpoch: number;
154
+ leaseToken: string;
155
+ daemonInstanceId: string;
156
+ eventType: WorkspaceAgentAttemptEventType;
157
+ data: Record<string, unknown>;
158
+ occurredAt: string;
159
+ }
160
+ /** One source of truth for both the WebSocket boundary and durable Run store. */
161
+ export declare const WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS: {
162
+ readonly codeLength: 256;
163
+ readonly textLength: 16384;
164
+ readonly artifactCount: 256;
165
+ readonly artifactKeyLength: 256;
166
+ readonly interactionFieldCount: 64;
167
+ readonly interactionActionCount: 16;
168
+ readonly interactionOptionCount: 128;
169
+ readonly interactionContextItemBytes: number;
170
+ readonly interactionRequestBytes: number;
171
+ };
172
+ /**
173
+ * Validate and project daemon event data onto the product-visible allowlist.
174
+ * Native runtime payloads, paths, fences and identifiers must never pass this
175
+ * boundary merely because they are valid JSON.
176
+ */
177
+ export declare function normalizeWorkspaceAgentAttemptEventData(eventType: WorkspaceAgentAttemptEventType, value: unknown): Record<string, unknown> | null;
178
+ export interface WorkspaceAgentTurnCancelAckMessage {
179
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
180
+ type: "agent.turn.cancel.ack";
181
+ commandId: string;
182
+ attemptId: string;
183
+ assignmentEpoch: number;
184
+ leaseToken: string;
185
+ daemonInstanceId: string;
186
+ disposition: "cancelled" | "already_terminal" | "stale";
187
+ }
188
+ export interface WorkspaceAgentInteractionResolveAckMessage {
189
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
190
+ type: "agent.interaction.resolve.ack";
191
+ commandId: string;
192
+ requestId: string;
193
+ interactionId: string;
194
+ attemptId: string;
195
+ assignmentEpoch: number;
196
+ leaseToken: string;
197
+ daemonInstanceId: string;
198
+ disposition: "applied" | "already_resolved" | "not_found" | "invalid";
199
+ error?: string;
200
+ }
201
+ export type WorkspaceDeviceClientMessage = WorkspaceDeviceHelloMessage | WorkspaceDeviceInventoryMessage | WorkspaceDeviceTaskAcceptedMessage | WorkspaceDeviceTaskRenewMessage | WorkspaceDeviceTaskCompleteMessage | WorkspaceAgentCapacityMessage | WorkspaceAgentTurnOfferAcceptedMessage | WorkspaceAgentTurnOfferBusyMessage | WorkspaceAgentTurnAssignAckMessage | WorkspaceAgentAttemptLeaseRenewMessage | WorkspaceAgentAttemptEventMessage | WorkspaceAgentTurnCancelAckMessage | WorkspaceAgentInteractionResolveAckMessage;
202
+ export interface WorkspaceDeviceReadyMessage {
203
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
204
+ type: "device.ready";
205
+ connectionId: string;
206
+ device: WorkspaceDevice;
207
+ leaseMs: number;
208
+ pingMs: number;
209
+ credentialId: string;
210
+ credentialStatus: "active";
211
+ }
212
+ export interface WorkspaceDeviceInventoryAckMessage {
213
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
214
+ type: "inventory.ack";
215
+ requestId: string;
216
+ digest: string;
217
+ }
218
+ export interface WorkspaceDeviceTaskDispatchMessage {
219
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
220
+ type: "task.dispatch";
221
+ task: WorkspaceLeasedDeviceTask;
222
+ }
223
+ export interface WorkspaceDeviceTaskRenewedMessage {
224
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
225
+ type: "task.renewed";
226
+ taskId: string;
227
+ leaseToken: string;
228
+ leaseExpiresAt: string;
229
+ }
230
+ export interface WorkspaceDeviceTaskCompleteAckMessage {
231
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
232
+ type: "task.complete.ack";
233
+ taskId: string;
234
+ leaseToken: string;
235
+ disposition: WorkspaceTaskFinishDisposition;
236
+ }
237
+ export interface WorkspaceDeviceProtocolErrorMessage {
238
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
239
+ type: "protocol.error";
240
+ code: string;
241
+ message: string;
242
+ fatal: boolean;
243
+ requestId?: string;
244
+ }
245
+ export interface WorkspaceAgentTurnOfferMessage {
246
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
247
+ type: "agent.turn.offer";
248
+ offerId: string;
249
+ runId: string;
250
+ turnId: string;
251
+ runtime: WorkspaceAgentRuntimeId;
252
+ model: string;
253
+ reasoningEffort?: string;
254
+ /** Relative validity from receipt; daemon scheduling must not trust wall-clock skew. */
255
+ ttlMs: number;
256
+ expiresAt: string;
257
+ }
258
+ export interface WorkspaceAgentTurnAssignMessage {
259
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
260
+ type: "agent.turn.assign";
261
+ commandId: string;
262
+ offerId: string;
263
+ permitId: string;
264
+ attemptId: string;
265
+ assignmentEpoch: number;
266
+ leaseToken: string;
267
+ /** Remaining relative lease duration for this delivery. Replays refresh it
268
+ * from the durable Attempt lease; it is not the original lease duration. */
269
+ leaseMs: number;
270
+ leaseExpiresAt: string;
271
+ spec: WorkspaceAgentExecutionSpecV1;
272
+ }
273
+ export interface WorkspaceAgentTurnCancelMessage {
274
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
275
+ type: "agent.turn.cancel";
276
+ commandId: string;
277
+ attemptId: string;
278
+ assignmentEpoch: number;
279
+ leaseToken: string;
280
+ reason: string;
281
+ }
282
+ export interface WorkspaceAgentInteractionResolveMessage {
283
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
284
+ type: "agent.interaction.resolve";
285
+ commandId: string;
286
+ requestId: string;
287
+ interactionId: string;
288
+ attemptId: string;
289
+ assignmentEpoch: number;
290
+ leaseToken: string;
291
+ resolution: WorkspaceAgentInteractionResolution;
292
+ }
293
+ export interface WorkspaceAgentAttemptLeaseRenewedMessage {
294
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
295
+ type: "agent.attempt.lease.renewed";
296
+ attemptId: string;
297
+ assignmentEpoch: number;
298
+ leaseToken: string;
299
+ disposition: "renewed" | "stale";
300
+ leaseMs: number;
301
+ leaseExpiresAt?: string;
302
+ }
303
+ export interface WorkspaceAgentAttemptEventAckMessage {
304
+ v: typeof WORKSPACE_DEVICE_PROTOCOL_VERSION;
305
+ type: "agent.attempt.event.ack";
306
+ eventId: string;
307
+ attemptId: string;
308
+ eventSequence: number;
309
+ disposition: "committed" | "duplicate" | "stale" | "rejected";
310
+ }
311
+ export type WorkspaceDeviceServerMessage = WorkspaceDeviceChallengeMessage | WorkspaceDeviceReadyMessage | WorkspaceDeviceInventoryAckMessage | WorkspaceDeviceTaskDispatchMessage | WorkspaceDeviceTaskRenewedMessage | WorkspaceDeviceTaskCompleteAckMessage | WorkspaceDeviceProtocolErrorMessage | WorkspaceAgentTurnOfferMessage | WorkspaceAgentTurnAssignMessage | WorkspaceAgentTurnCancelMessage | WorkspaceAgentInteractionResolveMessage | WorkspaceAgentAttemptLeaseRenewedMessage | WorkspaceAgentAttemptEventAckMessage;
312
+ export interface WorkspaceDeviceTaskReceipt {
313
+ taskId: string;
314
+ payloadHash: string;
315
+ leaseToken: string;
316
+ state: "running" | "completed";
317
+ result?: WorkspaceDeviceTaskResult;
318
+ updatedAt: string;
319
+ }
320
+ export interface WorkspaceSkillArtifact {
321
+ bundle: WorkspaceDeviceSkillArtifactBundle;
322
+ }
@@ -0,0 +1,326 @@
1
+ export const WORKSPACE_DEVICE_PROTOCOL_VERSION = 5;
2
+ export const WORKSPACE_DEVICE_WS_PATH = "/api/device-connection";
3
+ export const WORKSPACE_INSTALLATION_PROOF_ALGORITHM = "Ed25519";
4
+ export const WORKSPACE_INSTALLATION_PROOF_PLUGIN_ID = "workspace";
5
+ export const WORKSPACE_INSTALLATION_PROOF_DOMAIN = "rynx-plugin-installation-proof-v1";
6
+ export const WORKSPACE_DEVICE_PROOF_CONTEXT = "rynx-workspace-device-proof-v1";
7
+ export const WORKSPACE_DEVICE_ACCESS_PROOF_CONTEXT = "rynx-workspace-device-access-proof-v1";
8
+ export function workspaceInstallationProofChallenge(serverId, workspaceId, challenge) {
9
+ return `${WORKSPACE_DEVICE_PROOF_CONTEXT}\0${serverId}\0${workspaceId}\0${challenge}`;
10
+ }
11
+ export function workspaceInstallationProofMessage(installationId, serverId, workspaceId, challenge) {
12
+ return `${WORKSPACE_INSTALLATION_PROOF_DOMAIN}\0${installationId}\0plugin:${WORKSPACE_INSTALLATION_PROOF_PLUGIN_ID}\0${workspaceInstallationProofChallenge(serverId, workspaceId, challenge)}`;
13
+ }
14
+ export function workspaceDeviceAccessProofChallenge(serverId, authorizationId, nonce) {
15
+ return `${WORKSPACE_DEVICE_ACCESS_PROOF_CONTEXT}\0${serverId}\0${authorizationId}\0${nonce}`;
16
+ }
17
+ export function workspaceDeviceAccessProofMessage(installationId, serverId, authorizationId, nonce) {
18
+ return `${WORKSPACE_INSTALLATION_PROOF_DOMAIN}\0${installationId}\0plugin:${WORKSPACE_INSTALLATION_PROOF_PLUGIN_ID}\0${workspaceDeviceAccessProofChallenge(serverId, authorizationId, nonce)}`;
19
+ }
20
+ /** One source of truth for both the WebSocket boundary and durable Run store. */
21
+ export const WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS = {
22
+ codeLength: 256,
23
+ textLength: 16_384,
24
+ artifactCount: 256,
25
+ artifactKeyLength: 256,
26
+ interactionFieldCount: 64,
27
+ interactionActionCount: 16,
28
+ interactionOptionCount: 128,
29
+ interactionContextItemBytes: 64 * 1024,
30
+ interactionRequestBytes: 256 * 1024,
31
+ };
32
+ /**
33
+ * Validate and project daemon event data onto the product-visible allowlist.
34
+ * Native runtime payloads, paths, fences and identifiers must never pass this
35
+ * boundary merely because they are valid JSON.
36
+ */
37
+ export function normalizeWorkspaceAgentAttemptEventData(eventType, value) {
38
+ if (!value || typeof value !== "object" || Array.isArray(value))
39
+ return null;
40
+ const input = value;
41
+ const keys = Object.keys(input);
42
+ const exact = (allowed) => keys.every((key) => allowed.includes(key));
43
+ const optionalText = (key, maximum) => input[key] === undefined || (typeof input[key] === "string" && input[key].length <= maximum);
44
+ const text = WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.textLength;
45
+ const code = WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.codeLength;
46
+ switch (eventType) {
47
+ case "environment.preparing":
48
+ case "environment.ready":
49
+ case "session.ready":
50
+ case "turn.started":
51
+ return keys.length === 0 ? {} : null;
52
+ case "environment.failed":
53
+ if (!exact(["retryable", "sessionReusable", "errorCode", "errorMessage"]) ||
54
+ (input.retryable !== undefined && typeof input.retryable !== "boolean") ||
55
+ typeof input.sessionReusable !== "boolean" ||
56
+ (input.retryable === true && input.sessionReusable !== true) ||
57
+ !optionalText("errorCode", code) ||
58
+ !optionalText("errorMessage", text))
59
+ return null;
60
+ return {
61
+ ...(input.retryable !== undefined ? { retryable: input.retryable } : {}),
62
+ sessionReusable: input.sessionReusable,
63
+ ...(input.errorCode !== undefined ? { errorCode: input.errorCode } : {}),
64
+ ...(input.errorMessage !== undefined ? { errorMessage: input.errorMessage } : {}),
65
+ };
66
+ case "turn.progress":
67
+ if (!exact(["phase", "percent"]) ||
68
+ input.phase !== "working" ||
69
+ (input.percent !== undefined && (typeof input.percent !== "number" ||
70
+ !Number.isFinite(input.percent) ||
71
+ input.percent < 0 ||
72
+ input.percent > 100)))
73
+ return null;
74
+ return {
75
+ phase: "working",
76
+ ...(input.percent !== undefined ? { percent: input.percent } : {}),
77
+ };
78
+ case "interaction.requested": {
79
+ if (!exact(["interaction"]))
80
+ return null;
81
+ const interaction = normalizeWorkspaceAgentInteractionRequest(input.interaction);
82
+ return interaction ? { interaction } : null;
83
+ }
84
+ case "interaction.resolved":
85
+ if (!exact(["interactionId", "actionId"]) ||
86
+ !nonEmptyBoundedString(input.interactionId, code) ||
87
+ !nonEmptyBoundedString(input.actionId, code))
88
+ return null;
89
+ return { interactionId: input.interactionId, actionId: input.actionId };
90
+ case "interaction.cancelled":
91
+ if (!exact(["interactionId", "reason"]) ||
92
+ !nonEmptyBoundedString(input.interactionId, code) ||
93
+ !optionalBoundedString(input.reason, text))
94
+ return null;
95
+ return {
96
+ interactionId: input.interactionId,
97
+ ...(input.reason !== undefined ? { reason: input.reason } : {}),
98
+ };
99
+ case "turn.completed": {
100
+ const artifactKeys = input.artifactKeys;
101
+ if (!exact(["finalResponse", "artifactKeys"]) ||
102
+ !optionalText("finalResponse", text) ||
103
+ (artifactKeys !== undefined && (!Array.isArray(artifactKeys) ||
104
+ artifactKeys.length > WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.artifactCount ||
105
+ artifactKeys.some((key) => typeof key !== "string" ||
106
+ !key ||
107
+ key.length > WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.artifactKeyLength))))
108
+ return null;
109
+ return {
110
+ ...(input.finalResponse !== undefined ? { finalResponse: input.finalResponse } : {}),
111
+ ...(artifactKeys !== undefined ? { artifactKeys: [...artifactKeys] } : {}),
112
+ };
113
+ }
114
+ case "turn.failed":
115
+ case "turn.execution_unknown":
116
+ if (!exact(["errorCode", "errorMessage"]) ||
117
+ !optionalText("errorCode", code) ||
118
+ !optionalText("errorMessage", text))
119
+ return null;
120
+ return {
121
+ ...(input.errorCode !== undefined ? { errorCode: input.errorCode } : {}),
122
+ ...(input.errorMessage !== undefined ? { errorMessage: input.errorMessage } : {}),
123
+ };
124
+ }
125
+ }
126
+ function normalizeWorkspaceAgentInteractionRequest(value) {
127
+ if (!isRecord(value))
128
+ return null;
129
+ if (!exactRecordKeys(value, [
130
+ "interactionId",
131
+ "kind",
132
+ "title",
133
+ "description",
134
+ "fields",
135
+ "actions",
136
+ "context",
137
+ "createdAt",
138
+ ]))
139
+ return null;
140
+ const code = WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.codeLength;
141
+ const text = WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.textLength;
142
+ if (!nonEmptyBoundedString(value.interactionId, code) ||
143
+ (value.kind !== "question" && value.kind !== "permission" && value.kind !== "form") ||
144
+ !nonEmptyBoundedString(value.title, text) ||
145
+ !optionalBoundedString(value.description, text) ||
146
+ !Array.isArray(value.fields) ||
147
+ value.fields.length > WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.interactionFieldCount ||
148
+ !Array.isArray(value.actions) ||
149
+ value.actions.length < 1 ||
150
+ value.actions.length > WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.interactionActionCount ||
151
+ typeof value.createdAt !== "string" ||
152
+ !Number.isFinite(Date.parse(value.createdAt)))
153
+ return null;
154
+ const fields = [];
155
+ const fieldIds = new Set();
156
+ for (const candidate of value.fields) {
157
+ const field = normalizeWorkspaceAgentInteractionField(candidate);
158
+ if (!field || fieldIds.has(field.id))
159
+ return null;
160
+ fieldIds.add(field.id);
161
+ fields.push(field);
162
+ }
163
+ const actions = [];
164
+ const actionIds = new Set();
165
+ for (const candidate of value.actions) {
166
+ const action = normalizeWorkspaceAgentInteractionAction(candidate);
167
+ if (!action || actionIds.has(action.id))
168
+ return null;
169
+ actionIds.add(action.id);
170
+ actions.push(action);
171
+ }
172
+ let context;
173
+ if (value.context !== undefined) {
174
+ if (!isRecord(value.context) ||
175
+ !exactRecordKeys(value.context, ["summary", "toolName", "command", "cwd", "diff"]) ||
176
+ !optionalBoundedString(value.context.summary, text) ||
177
+ !optionalBoundedString(value.context.toolName, code) ||
178
+ !optionalUtf8BoundedString(value.context.command, WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.interactionContextItemBytes) ||
179
+ !optionalUtf8BoundedString(value.context.cwd, WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.interactionContextItemBytes) ||
180
+ !optionalUtf8BoundedString(value.context.diff, WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.interactionContextItemBytes))
181
+ return null;
182
+ context = {
183
+ ...(value.context.summary !== undefined ? { summary: value.context.summary } : {}),
184
+ ...(value.context.toolName !== undefined ? { toolName: value.context.toolName } : {}),
185
+ ...(value.context.command !== undefined ? { command: value.context.command } : {}),
186
+ ...(value.context.cwd !== undefined ? { cwd: value.context.cwd } : {}),
187
+ ...(value.context.diff !== undefined ? { diff: value.context.diff } : {}),
188
+ };
189
+ }
190
+ const request = {
191
+ interactionId: value.interactionId,
192
+ kind: value.kind,
193
+ title: value.title,
194
+ ...(value.description !== undefined ? { description: value.description } : {}),
195
+ fields,
196
+ actions,
197
+ ...(context ? { context } : {}),
198
+ createdAt: new Date(value.createdAt).toISOString(),
199
+ };
200
+ if (utf8ByteLength(JSON.stringify(request)) >
201
+ WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.interactionRequestBytes)
202
+ return null;
203
+ return request;
204
+ }
205
+ function normalizeWorkspaceAgentInteractionField(value) {
206
+ if (!isRecord(value))
207
+ return null;
208
+ const code = WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.codeLength;
209
+ const text = WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.textLength;
210
+ if (!nonEmptyBoundedString(value.id, code) ||
211
+ !nonEmptyBoundedString(value.label, text) ||
212
+ !optionalBoundedString(value.description, text) ||
213
+ (value.required !== undefined && typeof value.required !== "boolean"))
214
+ return null;
215
+ if (value.type === "text") {
216
+ if (!exactRecordKeys(value, [
217
+ "id",
218
+ "type",
219
+ "label",
220
+ "description",
221
+ "required",
222
+ "secret",
223
+ "multiline",
224
+ "placeholder",
225
+ ]) ||
226
+ (value.secret !== undefined && typeof value.secret !== "boolean") ||
227
+ (value.multiline !== undefined && typeof value.multiline !== "boolean") ||
228
+ !optionalBoundedString(value.placeholder, text))
229
+ return null;
230
+ return {
231
+ id: value.id,
232
+ type: "text",
233
+ label: value.label,
234
+ ...(value.description !== undefined ? { description: value.description } : {}),
235
+ ...(value.required !== undefined ? { required: value.required } : {}),
236
+ ...(value.secret !== undefined ? { secret: value.secret } : {}),
237
+ ...(value.multiline !== undefined ? { multiline: value.multiline } : {}),
238
+ ...(value.placeholder !== undefined ? { placeholder: value.placeholder } : {}),
239
+ };
240
+ }
241
+ if (value.type !== "select" || !exactRecordKeys(value, [
242
+ "id",
243
+ "type",
244
+ "label",
245
+ "description",
246
+ "required",
247
+ "multiple",
248
+ "allowOther",
249
+ "options",
250
+ ]))
251
+ return null;
252
+ if ((value.multiple !== undefined && typeof value.multiple !== "boolean") ||
253
+ (value.allowOther !== undefined && typeof value.allowOther !== "boolean") ||
254
+ !Array.isArray(value.options) ||
255
+ value.options.length < 1 ||
256
+ value.options.length > WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.interactionOptionCount)
257
+ return null;
258
+ const optionValues = new Set();
259
+ const options = value.options.map((candidate) => {
260
+ if (!isRecord(candidate) ||
261
+ !exactRecordKeys(candidate, ["value", "label", "description"]) ||
262
+ !nonEmptyBoundedString(candidate.value, code) ||
263
+ !nonEmptyBoundedString(candidate.label, text) ||
264
+ !optionalBoundedString(candidate.description, text) ||
265
+ optionValues.has(candidate.value))
266
+ return null;
267
+ optionValues.add(candidate.value);
268
+ return {
269
+ value: candidate.value,
270
+ label: candidate.label,
271
+ ...(candidate.description !== undefined
272
+ ? { description: candidate.description }
273
+ : {}),
274
+ };
275
+ });
276
+ if (options.some((option) => option === null))
277
+ return null;
278
+ return {
279
+ id: value.id,
280
+ type: "select",
281
+ label: value.label,
282
+ ...(value.description !== undefined ? { description: value.description } : {}),
283
+ ...(value.required !== undefined ? { required: value.required } : {}),
284
+ ...(value.multiple !== undefined ? { multiple: value.multiple } : {}),
285
+ ...(value.allowOther !== undefined ? { allowOther: value.allowOther } : {}),
286
+ options: options,
287
+ };
288
+ }
289
+ function normalizeWorkspaceAgentInteractionAction(value) {
290
+ if (!isRecord(value) ||
291
+ !exactRecordKeys(value, ["id", "label", "description", "style", "requiresAnswers"]) ||
292
+ !nonEmptyBoundedString(value.id, WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.codeLength) ||
293
+ !nonEmptyBoundedString(value.label, WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.textLength) ||
294
+ !optionalBoundedString(value.description, WORKSPACE_AGENT_ATTEMPT_EVENT_LIMITS.textLength) ||
295
+ typeof value.requiresAnswers !== "boolean" ||
296
+ (value.style !== undefined &&
297
+ value.style !== "primary" && value.style !== "secondary" && value.style !== "danger"))
298
+ return null;
299
+ return {
300
+ id: value.id,
301
+ label: value.label,
302
+ ...(value.description !== undefined ? { description: value.description } : {}),
303
+ ...(value.style !== undefined
304
+ ? { style: value.style }
305
+ : {}),
306
+ requiresAnswers: value.requiresAnswers,
307
+ };
308
+ }
309
+ function isRecord(value) {
310
+ return typeof value === "object" && value !== null && !Array.isArray(value);
311
+ }
312
+ function exactRecordKeys(value, allowed) {
313
+ return Object.keys(value).every((key) => allowed.includes(key));
314
+ }
315
+ function nonEmptyBoundedString(value, maximum) {
316
+ return typeof value === "string" && Boolean(value.trim()) && value.length <= maximum;
317
+ }
318
+ function optionalBoundedString(value, maximum) {
319
+ return value === undefined || (typeof value === "string" && value.length <= maximum);
320
+ }
321
+ function optionalUtf8BoundedString(value, maximum) {
322
+ return value === undefined || (typeof value === "string" && utf8ByteLength(value) <= maximum);
323
+ }
324
+ function utf8ByteLength(value) {
325
+ return new TextEncoder().encode(value).byteLength;
326
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./agent-options.js";
2
+ export * from "./agent-execution.js";
3
+ export * from "./agent-scheduler.js";
4
+ export * from "./skill-path.js";
5
+ export * from "./device-protocol.js";
6
+ export * from "./skill-name.js";
7
+ export * from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export * from "./agent-options.js";
2
+ export * from "./agent-execution.js";
3
+ export * from "./agent-scheduler.js";
4
+ export * from "./skill-path.js";
5
+ export * from "./device-protocol.js";
6
+ export * from "./skill-name.js";
7
+ export * from "./types.js";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Return the portable skill identity used by workspace, daemon, and runtimes.
3
+ * Runtime skill loaders expect a lowercase kebab-case directory/frontmatter
4
+ * name, so display names must never be used as the installation identity.
5
+ */
6
+ export declare function canonicalWorkspaceSkillName(value: string, fallback?: string): string;