@hasna/skills 0.5.7 → 0.5.8
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 +47 -1
- package/bin/index.js +6 -6
- package/bin/mcp.js +6 -6
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/index.js +6 -6
- package/dist/lib/private-publication-recovery.d.ts +2 -1
- package/dist/lib/remote-private-publications.d.ts +2 -1
- package/dist/sdk/index.d.ts +1 -0
- package/dist/sdk/index.js +207 -4
- package/dist/sdk/operations.d.ts +68 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -522,7 +522,10 @@ the same declaration, and an uncertain upload is never sent twice. If a process
|
|
|
522
522
|
crashes while holding `operation.lock`, confirm it has stopped before removing
|
|
523
523
|
that lock explicitly. Status and cancellation remain available when new
|
|
524
524
|
publishing is disabled. Exit 2 means publication is still pending; `committed`
|
|
525
|
-
means source was published.
|
|
525
|
+
means source was published. Execution requires a separate server quote and approval.
|
|
526
|
+
Publication recovery results report `executionEnabled: null` because their durable
|
|
527
|
+
receipts contain no server capability observation. Use `getCapability()` for the
|
|
528
|
+
server's current boolean capability; it does not authorize an individual run.
|
|
526
529
|
|
|
527
530
|
The SDK exports `RemotePrivatePublicationsClient` through both the root and
|
|
528
531
|
`./sdk`; `RemoteSkillsAuthClient.openPrivatePublications` creates one from fresh
|
|
@@ -1193,3 +1196,46 @@ returned or saved by the tools.
|
|
|
1193
1196
|
|
|
1194
1197
|
These clients still require deployed configuration and controlled real recipient
|
|
1195
1198
|
email acceptance before recovery can be offered as a live product capability.
|
|
1199
|
+
|
|
1200
|
+
### Injected operation transport
|
|
1201
|
+
|
|
1202
|
+
`@hasna/skills/sdk` exports `createSkillOperationClient` for an embedder-supplied
|
|
1203
|
+
`SkillOperationTransport`. It provides a bounded JSON envelope, immutable
|
|
1204
|
+
snapshots and explicit status lookup. It does not connect to a provider, discover
|
|
1205
|
+
an endpoint, read credentials, or provide guest IPC or authorization.
|
|
1206
|
+
|
|
1207
|
+
```ts
|
|
1208
|
+
import { createSkillOperationClient, type SkillOperationTransport } from "@hasna/skills/sdk";
|
|
1209
|
+
|
|
1210
|
+
function operationsForCapturedRun(transport: SkillOperationTransport) {
|
|
1211
|
+
return createSkillOperationClient(transport, { timeoutMs: 30_000 });
|
|
1212
|
+
}
|
|
1213
|
+
// The embedder supplies invoke(request, { signal }) and get(requestId, { signal }).
|
|
1214
|
+
// invoke accepts { contractVersion: 1, requestId: UUID, operation: "text.generate",
|
|
1215
|
+
// input: { prompt: "..." } }.
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
Create one client per captured authority scope. The transport must enforce that
|
|
1219
|
+
scope, bind the request ID to the exact payload durably, and enforce approval,
|
|
1220
|
+
budget and execution policy. The client remembers up to 256 request identities
|
|
1221
|
+
and 1 MiB of canonical requests. It refuses capacity before transport and never
|
|
1222
|
+
evicts an old identity. An explicit repeat with the same payload makes one new
|
|
1223
|
+
transport call; a changed payload under a remembered ID is refused locally.
|
|
1224
|
+
This local check does not replace server deduplication.
|
|
1225
|
+
|
|
1226
|
+
Requests are limited to 64 KiB and responses to 1 MiB of serialized UTF-8 JSON,
|
|
1227
|
+
with depth and node limits exported in `SKILL_OPERATION_LIMITS`. Plain JSON data
|
|
1228
|
+
is copied and deeply frozen; cycles, accessors, `toJSON` functions, unsupported
|
|
1229
|
+
values and extra envelope fields are refused. Ordinary data inside `input` and
|
|
1230
|
+
`output` is preserved. Request IDs use canonical lowercase UUID strings;
|
|
1231
|
+
operation names use lowercase letters, digits and dot or hyphen separators.
|
|
1232
|
+
|
|
1233
|
+
Responses preserve `contractVersion` and `requestId`. A status of `succeeded`
|
|
1234
|
+
includes `output`; `refused` includes a fixed `SkillOperationRefusal` code.
|
|
1235
|
+
`pending`, `unknown` and authoritative `not-executed` have no additional fields.
|
|
1236
|
+
Transport failure, in-flight abort and timeout produce a safe
|
|
1237
|
+
`SkillOperationClientError` with an unknown outcome. They never prove that an
|
|
1238
|
+
operation did not execute. The client does not retry or issue a status read
|
|
1239
|
+
automatically: explicitly call `get` with the same request ID to reconcile.
|
|
1240
|
+
Aborting locally does not establish server cancellation. An already-aborted
|
|
1241
|
+
signal refuses before calling the transport.
|
package/bin/index.js
CHANGED
|
@@ -36860,7 +36860,7 @@ var package_default;
|
|
|
36860
36860
|
var init_package = __esm(() => {
|
|
36861
36861
|
package_default = {
|
|
36862
36862
|
name: "@hasna/skills",
|
|
36863
|
-
version: "0.5.
|
|
36863
|
+
version: "0.5.8",
|
|
36864
36864
|
description: "Skills library for AI coding agents",
|
|
36865
36865
|
type: "module",
|
|
36866
36866
|
bin: {
|
|
@@ -72952,7 +72952,7 @@ var init_mcp_contracts = __esm(() => {
|
|
|
72952
72952
|
].map((operation) => ({
|
|
72953
72953
|
name: operation.name,
|
|
72954
72954
|
title: operation.title,
|
|
72955
|
-
description: "Manage private source publication with fresh workspace verification and durable host-local recovery. Upload consent and current version comparison are explicit;
|
|
72955
|
+
description: "Manage private source publication with fresh workspace verification and durable host-local recovery. Upload consent and current version comparison are explicit; execution requires a separate server quote and approval.",
|
|
72956
72956
|
params: [...Object.keys(publicationVerification), ...Object.keys(operation.extras)],
|
|
72957
72957
|
category: "storage",
|
|
72958
72958
|
sideEffects: "filesystem",
|
|
@@ -72965,7 +72965,7 @@ var init_mcp_contracts = __esm(() => {
|
|
|
72965
72965
|
state: { type: "string" },
|
|
72966
72966
|
versionId: { oneOf: [publicationUuidSchema, { type: "null" }] },
|
|
72967
72967
|
committed: { type: "boolean" },
|
|
72968
|
-
executionEnabled: {
|
|
72968
|
+
executionEnabled: { oneOf: [{ type: "boolean" }, { type: "null" }] },
|
|
72969
72969
|
nextAction: { type: "string" }
|
|
72970
72970
|
}, ["recoveryDirectory", "skillId", "intentId", "state", "versionId", "committed", "executionEnabled", "nextAction"])
|
|
72971
72971
|
}));
|
|
@@ -74747,7 +74747,7 @@ class RemotePrivatePublicationsClient {
|
|
|
74747
74747
|
return bad();
|
|
74748
74748
|
const response = await boundedJson(skillsApiRequestUrl(this.apiOrigin, "/api/v1/capabilities"), {}, this.#token, false, options.timeoutMs, options.signal);
|
|
74749
74749
|
const p = record7(response) && response.privatePublishing;
|
|
74750
|
-
if (!record7(response) || response.contractVersion !== 1 || response.apiVersion !== 1 || !exact(p, ["contractVersion", "enabled", "authentication", "maxArchiveBytes", "uploadMaxTtlSeconds", "executionEnabled"]) || p.contractVersion !== 1 || typeof p.enabled !== "boolean" || p.authentication !== "interactive-session" || p.maxArchiveBytes !== PRIVATE_PUBLICATION_MAX_BYTES || p.uploadMaxTtlSeconds !== 300 || p.executionEnabled !==
|
|
74750
|
+
if (!record7(response) || response.contractVersion !== 1 || response.apiVersion !== 1 || !exact(p, ["contractVersion", "enabled", "authentication", "maxArchiveBytes", "uploadMaxTtlSeconds", "executionEnabled"]) || p.contractVersion !== 1 || typeof p.enabled !== "boolean" || p.authentication !== "interactive-session" || p.maxArchiveBytes !== PRIVATE_PUBLICATION_MAX_BYTES || p.uploadMaxTtlSeconds !== 300 || typeof p.executionEnabled !== "boolean")
|
|
74751
74751
|
throw new PrivatePublicationError("PUBLICATION_CONTRACT_UNAVAILABLE", "This server does not support the hosted private publication contract.");
|
|
74752
74752
|
return Object.freeze({ ...p });
|
|
74753
74753
|
}
|
|
@@ -75655,7 +75655,7 @@ async function preparePrivatePublication(client, sourceDirectory, recoveryDirect
|
|
|
75655
75655
|
}
|
|
75656
75656
|
function privatePublicationResult(directory, receipt) {
|
|
75657
75657
|
const state = receipt.intent?.state ?? receipt.phase, committed = state === "committed";
|
|
75658
|
-
const nextAction = committed ? "
|
|
75658
|
+
const nextAction = committed ? "Published; execution requires a separate server quote and approval." : ["rejected", "cancelled", "expired"].includes(state) ? "This intent is terminal. Inspect the result before explicitly preparing another version." : state === "needs_attention" ? "Keep this intent and contact the service operator; do not create a replacement or upload again." : receipt.phase === "upload_uncertain" ? "Run publication resume with this recovery directory to finalize the same intent without another upload." : receipt.intent ? "Run publication status or resume with this recovery directory; cancel explicitly if you want to stop." : "Run publication resume with this recovery directory to reconcile the identical request key and declaration.";
|
|
75659
75659
|
return {
|
|
75660
75660
|
recoveryDirectory: directory,
|
|
75661
75661
|
skillId: receipt.skillId,
|
|
@@ -75663,7 +75663,7 @@ function privatePublicationResult(directory, receipt) {
|
|
|
75663
75663
|
state,
|
|
75664
75664
|
versionId: receipt.intent?.versionId ?? null,
|
|
75665
75665
|
committed,
|
|
75666
|
-
executionEnabled:
|
|
75666
|
+
executionEnabled: null,
|
|
75667
75667
|
nextAction
|
|
75668
75668
|
};
|
|
75669
75669
|
}
|
package/bin/mcp.js
CHANGED
|
@@ -5586,7 +5586,7 @@ var package_default;
|
|
|
5586
5586
|
var init_package = __esm(() => {
|
|
5587
5587
|
package_default = {
|
|
5588
5588
|
name: "@hasna/skills",
|
|
5589
|
-
version: "0.5.
|
|
5589
|
+
version: "0.5.8",
|
|
5590
5590
|
description: "Skills library for AI coding agents",
|
|
5591
5591
|
type: "module",
|
|
5592
5592
|
bin: {
|
|
@@ -26861,7 +26861,7 @@ var init_mcp_contracts = __esm(() => {
|
|
|
26861
26861
|
].map((operation) => ({
|
|
26862
26862
|
name: operation.name,
|
|
26863
26863
|
title: operation.title,
|
|
26864
|
-
description: "Manage private source publication with fresh workspace verification and durable host-local recovery. Upload consent and current version comparison are explicit;
|
|
26864
|
+
description: "Manage private source publication with fresh workspace verification and durable host-local recovery. Upload consent and current version comparison are explicit; execution requires a separate server quote and approval.",
|
|
26865
26865
|
params: [...Object.keys(publicationVerification), ...Object.keys(operation.extras)],
|
|
26866
26866
|
category: "storage",
|
|
26867
26867
|
sideEffects: "filesystem",
|
|
@@ -26874,7 +26874,7 @@ var init_mcp_contracts = __esm(() => {
|
|
|
26874
26874
|
state: { type: "string" },
|
|
26875
26875
|
versionId: { oneOf: [publicationUuidSchema, { type: "null" }] },
|
|
26876
26876
|
committed: { type: "boolean" },
|
|
26877
|
-
executionEnabled: {
|
|
26877
|
+
executionEnabled: { oneOf: [{ type: "boolean" }, { type: "null" }] },
|
|
26878
26878
|
nextAction: { type: "string" }
|
|
26879
26879
|
}, ["recoveryDirectory", "skillId", "intentId", "state", "versionId", "committed", "executionEnabled", "nextAction"])
|
|
26880
26880
|
}));
|
|
@@ -31000,7 +31000,7 @@ class RemotePrivatePublicationsClient {
|
|
|
31000
31000
|
return bad();
|
|
31001
31001
|
const response = await boundedJson(skillsApiRequestUrl(this.apiOrigin, "/api/v1/capabilities"), {}, this.#token, false, options.timeoutMs, options.signal);
|
|
31002
31002
|
const p = record7(response) && response.privatePublishing;
|
|
31003
|
-
if (!record7(response) || response.contractVersion !== 1 || response.apiVersion !== 1 || !exact(p, ["contractVersion", "enabled", "authentication", "maxArchiveBytes", "uploadMaxTtlSeconds", "executionEnabled"]) || p.contractVersion !== 1 || typeof p.enabled !== "boolean" || p.authentication !== "interactive-session" || p.maxArchiveBytes !== PRIVATE_PUBLICATION_MAX_BYTES || p.uploadMaxTtlSeconds !== 300 || p.executionEnabled !==
|
|
31003
|
+
if (!record7(response) || response.contractVersion !== 1 || response.apiVersion !== 1 || !exact(p, ["contractVersion", "enabled", "authentication", "maxArchiveBytes", "uploadMaxTtlSeconds", "executionEnabled"]) || p.contractVersion !== 1 || typeof p.enabled !== "boolean" || p.authentication !== "interactive-session" || p.maxArchiveBytes !== PRIVATE_PUBLICATION_MAX_BYTES || p.uploadMaxTtlSeconds !== 300 || typeof p.executionEnabled !== "boolean")
|
|
31004
31004
|
throw new PrivatePublicationError("PUBLICATION_CONTRACT_UNAVAILABLE", "This server does not support the hosted private publication contract.");
|
|
31005
31005
|
return Object.freeze({ ...p });
|
|
31006
31006
|
}
|
|
@@ -32271,7 +32271,7 @@ async function preparePrivatePublication(client, sourceDirectory, recoveryDirect
|
|
|
32271
32271
|
}
|
|
32272
32272
|
function privatePublicationResult(directory, receipt) {
|
|
32273
32273
|
const state = receipt.intent?.state ?? receipt.phase, committed = state === "committed";
|
|
32274
|
-
const nextAction = committed ? "
|
|
32274
|
+
const nextAction = committed ? "Published; execution requires a separate server quote and approval." : ["rejected", "cancelled", "expired"].includes(state) ? "This intent is terminal. Inspect the result before explicitly preparing another version." : state === "needs_attention" ? "Keep this intent and contact the service operator; do not create a replacement or upload again." : receipt.phase === "upload_uncertain" ? "Run publication resume with this recovery directory to finalize the same intent without another upload." : receipt.intent ? "Run publication status or resume with this recovery directory; cancel explicitly if you want to stop." : "Run publication resume with this recovery directory to reconcile the identical request key and declaration.";
|
|
32275
32275
|
return {
|
|
32276
32276
|
recoveryDirectory: directory,
|
|
32277
32277
|
skillId: receipt.skillId,
|
|
@@ -32279,7 +32279,7 @@ function privatePublicationResult(directory, receipt) {
|
|
|
32279
32279
|
state,
|
|
32280
32280
|
versionId: receipt.intent?.versionId ?? null,
|
|
32281
32281
|
committed,
|
|
32282
|
-
executionEnabled:
|
|
32282
|
+
executionEnabled: null,
|
|
32283
32283
|
nextAction
|
|
32284
32284
|
};
|
|
32285
32285
|
}
|
package/bin/migrate.js
CHANGED
package/bin/server.js
CHANGED
|
@@ -23100,7 +23100,7 @@ var init_dist_es9 = __esm(() => {
|
|
|
23100
23100
|
// package.json
|
|
23101
23101
|
var package_default = {
|
|
23102
23102
|
name: "@hasna/skills",
|
|
23103
|
-
version: "0.5.
|
|
23103
|
+
version: "0.5.8",
|
|
23104
23104
|
description: "Skills library for AI coding agents",
|
|
23105
23105
|
type: "module",
|
|
23106
23106
|
bin: {
|
package/bin/worker.js
CHANGED
|
@@ -23103,7 +23103,7 @@ import { randomUUID as randomUUID4 } from "crypto";
|
|
|
23103
23103
|
// package.json
|
|
23104
23104
|
var package_default = {
|
|
23105
23105
|
name: "@hasna/skills",
|
|
23106
|
-
version: "0.5.
|
|
23106
|
+
version: "0.5.8",
|
|
23107
23107
|
description: "Skills library for AI coding agents",
|
|
23108
23108
|
type: "module",
|
|
23109
23109
|
bin: {
|
package/dist/index.js
CHANGED
|
@@ -13336,7 +13336,7 @@ import { dirname as dirname7, relative as relative4 } from "path";
|
|
|
13336
13336
|
// package.json
|
|
13337
13337
|
var package_default = {
|
|
13338
13338
|
name: "@hasna/skills",
|
|
13339
|
-
version: "0.5.
|
|
13339
|
+
version: "0.5.8",
|
|
13340
13340
|
description: "Skills library for AI coding agents",
|
|
13341
13341
|
type: "module",
|
|
13342
13342
|
bin: {
|
|
@@ -14412,7 +14412,7 @@ var privatePublicationContracts = [
|
|
|
14412
14412
|
].map((operation) => ({
|
|
14413
14413
|
name: operation.name,
|
|
14414
14414
|
title: operation.title,
|
|
14415
|
-
description: "Manage private source publication with fresh workspace verification and durable host-local recovery. Upload consent and current version comparison are explicit;
|
|
14415
|
+
description: "Manage private source publication with fresh workspace verification and durable host-local recovery. Upload consent and current version comparison are explicit; execution requires a separate server quote and approval.",
|
|
14416
14416
|
params: [...Object.keys(publicationVerification), ...Object.keys(operation.extras)],
|
|
14417
14417
|
category: "storage",
|
|
14418
14418
|
sideEffects: "filesystem",
|
|
@@ -14425,7 +14425,7 @@ var privatePublicationContracts = [
|
|
|
14425
14425
|
state: { type: "string" },
|
|
14426
14426
|
versionId: { oneOf: [publicationUuidSchema, { type: "null" }] },
|
|
14427
14427
|
committed: { type: "boolean" },
|
|
14428
|
-
executionEnabled: {
|
|
14428
|
+
executionEnabled: { oneOf: [{ type: "boolean" }, { type: "null" }] },
|
|
14429
14429
|
nextAction: { type: "string" }
|
|
14430
14430
|
}, ["recoveryDirectory", "skillId", "intentId", "state", "versionId", "committed", "executionEnabled", "nextAction"])
|
|
14431
14431
|
}));
|
|
@@ -16119,7 +16119,7 @@ class RemotePrivatePublicationsClient {
|
|
|
16119
16119
|
return bad();
|
|
16120
16120
|
const response = await boundedJson(skillsApiRequestUrl(this.apiOrigin, "/api/v1/capabilities"), {}, this.#token, false, options.timeoutMs, options.signal);
|
|
16121
16121
|
const p = record5(response) && response.privatePublishing;
|
|
16122
|
-
if (!record5(response) || response.contractVersion !== 1 || response.apiVersion !== 1 || !exact(p, ["contractVersion", "enabled", "authentication", "maxArchiveBytes", "uploadMaxTtlSeconds", "executionEnabled"]) || p.contractVersion !== 1 || typeof p.enabled !== "boolean" || p.authentication !== "interactive-session" || p.maxArchiveBytes !== PRIVATE_PUBLICATION_MAX_BYTES || p.uploadMaxTtlSeconds !== 300 || p.executionEnabled !==
|
|
16122
|
+
if (!record5(response) || response.contractVersion !== 1 || response.apiVersion !== 1 || !exact(p, ["contractVersion", "enabled", "authentication", "maxArchiveBytes", "uploadMaxTtlSeconds", "executionEnabled"]) || p.contractVersion !== 1 || typeof p.enabled !== "boolean" || p.authentication !== "interactive-session" || p.maxArchiveBytes !== PRIVATE_PUBLICATION_MAX_BYTES || p.uploadMaxTtlSeconds !== 300 || typeof p.executionEnabled !== "boolean")
|
|
16123
16123
|
throw new PrivatePublicationError("PUBLICATION_CONTRACT_UNAVAILABLE", "This server does not support the hosted private publication contract.");
|
|
16124
16124
|
return Object.freeze({ ...p });
|
|
16125
16125
|
}
|
|
@@ -16642,7 +16642,7 @@ async function preparePrivatePublication(client, sourceDirectory, recoveryDirect
|
|
|
16642
16642
|
}
|
|
16643
16643
|
function privatePublicationResult(directory, receipt) {
|
|
16644
16644
|
const state = receipt.intent?.state ?? receipt.phase, committed = state === "committed";
|
|
16645
|
-
const nextAction = committed ? "
|
|
16645
|
+
const nextAction = committed ? "Published; execution requires a separate server quote and approval." : ["rejected", "cancelled", "expired"].includes(state) ? "This intent is terminal. Inspect the result before explicitly preparing another version." : state === "needs_attention" ? "Keep this intent and contact the service operator; do not create a replacement or upload again." : receipt.phase === "upload_uncertain" ? "Run publication resume with this recovery directory to finalize the same intent without another upload." : receipt.intent ? "Run publication status or resume with this recovery directory; cancel explicitly if you want to stop." : "Run publication resume with this recovery directory to reconcile the identical request key and declaration.";
|
|
16646
16646
|
return {
|
|
16647
16647
|
recoveryDirectory: directory,
|
|
16648
16648
|
skillId: receipt.skillId,
|
|
@@ -16650,7 +16650,7 @@ function privatePublicationResult(directory, receipt) {
|
|
|
16650
16650
|
state,
|
|
16651
16651
|
versionId: receipt.intent?.versionId ?? null,
|
|
16652
16652
|
committed,
|
|
16653
|
-
executionEnabled:
|
|
16653
|
+
executionEnabled: null,
|
|
16654
16654
|
nextAction
|
|
16655
16655
|
};
|
|
16656
16656
|
}
|
|
@@ -18,7 +18,8 @@ export interface PrivatePublicationResult {
|
|
|
18
18
|
state: string;
|
|
19
19
|
versionId: string | null;
|
|
20
20
|
committed: boolean;
|
|
21
|
-
|
|
21
|
+
/** Null when this recovery receipt contains no directly observed server capability. Publication alone never proves execution availability. */
|
|
22
|
+
executionEnabled: boolean | null;
|
|
22
23
|
nextAction: string;
|
|
23
24
|
}
|
|
24
25
|
/** Reads only two bounded local files. The receipt never contains a token or signed URL. */
|
|
@@ -27,7 +27,8 @@ export interface PrivatePublishingCapability {
|
|
|
27
27
|
authentication: "interactive-session";
|
|
28
28
|
maxArchiveBytes: 16777216;
|
|
29
29
|
uploadMaxTtlSeconds: 300;
|
|
30
|
-
|
|
30
|
+
/** Server capability only; each execution still requires a separate quote and approval. */
|
|
31
|
+
executionEnabled: boolean;
|
|
31
32
|
}
|
|
32
33
|
/** Contains a short-lived bearer capability. Never print or persist this object. */
|
|
33
34
|
export interface PrivatePublicationUpload {
|
package/dist/sdk/index.d.ts
CHANGED
|
@@ -48,3 +48,4 @@ export { WorkspaceInvitationInputError, RemoteWorkspaceInvitationError, RemoteWo
|
|
|
48
48
|
export { InvitationEmailInputError, RemoteInvitationEmailError, RemoteInvitationEmailUnconfirmedError, type RequestInvitationEmailChallenge, type AcceptInvitationEmailChallenge, type RemoteInvitationEmailChallenge, type RemoteInvitationEmailAcceptance, type RemoteInvitationEmailErrorCode } from "../lib/remote-invitation-recovery.js";
|
|
49
49
|
export { RemotePrivatePublicationsClient, PrivatePublicationError, PRIVATE_PUBLICATION_MAX_BYTES, type PrivatePublicationDeclaration, type PrivatePublicationView, type PrivatePublicationState, type PrivatePublishingCapability } from "../lib/remote-private-publications.js";
|
|
50
50
|
export { preparePrivatePublication, readPrivatePublicationRecovery, continuePrivatePublication, inspectPrivatePublication, type PrivatePublicationRecovery, type PrivatePublicationResult } from "../lib/private-publication-recovery.js";
|
|
51
|
+
export { createSkillOperationClient, SkillOperationClientError, SKILL_OPERATION_LIMITS, type SkillOperationClient, type SkillOperationTransport, type SkillOperationRequest, type SkillOperationResult, type SkillOperationJson, type SkillOperationRefusal, type SkillOperationClientErrorCode } from "./operations.js";
|
package/dist/sdk/index.js
CHANGED
|
@@ -25496,7 +25496,7 @@ class MissingSkillsFleetError extends Error {
|
|
|
25496
25496
|
// package.json
|
|
25497
25497
|
var package_default = {
|
|
25498
25498
|
name: "@hasna/skills",
|
|
25499
|
-
version: "0.5.
|
|
25499
|
+
version: "0.5.8",
|
|
25500
25500
|
description: "Skills library for AI coding agents",
|
|
25501
25501
|
type: "module",
|
|
25502
25502
|
bin: {
|
|
@@ -54464,7 +54464,7 @@ class RemotePrivatePublicationsClient {
|
|
|
54464
54464
|
return bad();
|
|
54465
54465
|
const response = await boundedJson(skillsApiRequestUrl(this.apiOrigin, "/api/v1/capabilities"), {}, this.#token, false, options.timeoutMs, options.signal);
|
|
54466
54466
|
const p2 = record5(response) && response.privatePublishing;
|
|
54467
|
-
if (!record5(response) || response.contractVersion !== 1 || response.apiVersion !== 1 || !exact(p2, ["contractVersion", "enabled", "authentication", "maxArchiveBytes", "uploadMaxTtlSeconds", "executionEnabled"]) || p2.contractVersion !== 1 || typeof p2.enabled !== "boolean" || p2.authentication !== "interactive-session" || p2.maxArchiveBytes !== PRIVATE_PUBLICATION_MAX_BYTES || p2.uploadMaxTtlSeconds !== 300 || p2.executionEnabled !==
|
|
54467
|
+
if (!record5(response) || response.contractVersion !== 1 || response.apiVersion !== 1 || !exact(p2, ["contractVersion", "enabled", "authentication", "maxArchiveBytes", "uploadMaxTtlSeconds", "executionEnabled"]) || p2.contractVersion !== 1 || typeof p2.enabled !== "boolean" || p2.authentication !== "interactive-session" || p2.maxArchiveBytes !== PRIVATE_PUBLICATION_MAX_BYTES || p2.uploadMaxTtlSeconds !== 300 || typeof p2.executionEnabled !== "boolean")
|
|
54468
54468
|
throw new PrivatePublicationError("PUBLICATION_CONTRACT_UNAVAILABLE", "This server does not support the hosted private publication contract.");
|
|
54469
54469
|
return Object.freeze({ ...p2 });
|
|
54470
54470
|
}
|
|
@@ -54987,7 +54987,7 @@ async function preparePrivatePublication(client, sourceDirectory, recoveryDirect
|
|
|
54987
54987
|
}
|
|
54988
54988
|
function privatePublicationResult(directory, receipt) {
|
|
54989
54989
|
const state = receipt.intent?.state ?? receipt.phase, committed = state === "committed";
|
|
54990
|
-
const nextAction = committed ? "
|
|
54990
|
+
const nextAction = committed ? "Published; execution requires a separate server quote and approval." : ["rejected", "cancelled", "expired"].includes(state) ? "This intent is terminal. Inspect the result before explicitly preparing another version." : state === "needs_attention" ? "Keep this intent and contact the service operator; do not create a replacement or upload again." : receipt.phase === "upload_uncertain" ? "Run publication resume with this recovery directory to finalize the same intent without another upload." : receipt.intent ? "Run publication status or resume with this recovery directory; cancel explicitly if you want to stop." : "Run publication resume with this recovery directory to reconcile the identical request key and declaration.";
|
|
54991
54991
|
return {
|
|
54992
54992
|
recoveryDirectory: directory,
|
|
54993
54993
|
skillId: receipt.skillId,
|
|
@@ -54995,7 +54995,7 @@ function privatePublicationResult(directory, receipt) {
|
|
|
54995
54995
|
state,
|
|
54996
54996
|
versionId: receipt.intent?.versionId ?? null,
|
|
54997
54997
|
committed,
|
|
54998
|
-
executionEnabled:
|
|
54998
|
+
executionEnabled: null,
|
|
54999
54999
|
nextAction
|
|
55000
55000
|
};
|
|
55001
55001
|
}
|
|
@@ -55061,6 +55061,206 @@ async function inspectLocked(client, directory, cancel) {
|
|
|
55061
55061
|
throw new PrivatePublicationError("PUBLICATION_INTENT_UNKNOWN", "Reconcile the saved begin request with publication resume before cancelling its intent.");
|
|
55062
55062
|
return privatePublicationResult(directory, receipt);
|
|
55063
55063
|
}
|
|
55064
|
+
// src/sdk/operations.ts
|
|
55065
|
+
var SKILL_OPERATION_LIMITS = Object.freeze({ requestBytes: 65536, resultBytes: 1048576, depth: 32, nodes: 16384, rememberedRequests: 256, rememberedBytes: 1048576 });
|
|
55066
|
+
|
|
55067
|
+
class SkillOperationClientError extends Error {
|
|
55068
|
+
code;
|
|
55069
|
+
outcome;
|
|
55070
|
+
constructor(code, outcome) {
|
|
55071
|
+
super(`Skill operation client: ${code}`);
|
|
55072
|
+
this.code = code;
|
|
55073
|
+
this.outcome = outcome;
|
|
55074
|
+
this.name = "SkillOperationClientError";
|
|
55075
|
+
}
|
|
55076
|
+
}
|
|
55077
|
+
var encoder = new TextEncoder;
|
|
55078
|
+
var refusalCodes = new Set(["NOT_ALLOWED", "APPROVAL_REQUIRED", "BUDGET_EXHAUSTED", "EXPIRED", "CANCELLED", "UNAVAILABLE", "INVALID_INPUT"]);
|
|
55079
|
+
var requestIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
55080
|
+
function validId(value) {
|
|
55081
|
+
return typeof value === "string" && requestIdPattern.test(value);
|
|
55082
|
+
}
|
|
55083
|
+
function record6(value) {
|
|
55084
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
55085
|
+
}
|
|
55086
|
+
function exact2(value, names) {
|
|
55087
|
+
const keys = Object.keys(value).sort(), expected = [...names].sort();
|
|
55088
|
+
return keys.length === expected.length && keys.every((key, index) => key === expected[index]);
|
|
55089
|
+
}
|
|
55090
|
+
function snapshot(value, maximum) {
|
|
55091
|
+
let nodes4 = 0, bytes = 0;
|
|
55092
|
+
const parts = [], ancestors = new Set;
|
|
55093
|
+
const append = (part) => {
|
|
55094
|
+
if (part.length > maximum - bytes)
|
|
55095
|
+
throw Error();
|
|
55096
|
+
bytes += encoder.encode(part).byteLength;
|
|
55097
|
+
if (bytes > maximum)
|
|
55098
|
+
throw Error();
|
|
55099
|
+
parts.push(part);
|
|
55100
|
+
};
|
|
55101
|
+
function visit(input, depth) {
|
|
55102
|
+
if (++nodes4 > SKILL_OPERATION_LIMITS.nodes || depth > SKILL_OPERATION_LIMITS.depth)
|
|
55103
|
+
throw Error();
|
|
55104
|
+
if (input === null || typeof input === "boolean") {
|
|
55105
|
+
append(String(input));
|
|
55106
|
+
return input;
|
|
55107
|
+
}
|
|
55108
|
+
if (typeof input === "number") {
|
|
55109
|
+
if (!Number.isFinite(input))
|
|
55110
|
+
throw Error();
|
|
55111
|
+
append(JSON.stringify(input));
|
|
55112
|
+
return Object.is(input, -0) ? 0 : input;
|
|
55113
|
+
}
|
|
55114
|
+
if (typeof input === "string") {
|
|
55115
|
+
if (input.length > maximum - bytes || /[\ud800-\udfff]/u.test(input))
|
|
55116
|
+
throw Error();
|
|
55117
|
+
append(JSON.stringify(input));
|
|
55118
|
+
return input;
|
|
55119
|
+
}
|
|
55120
|
+
if (!input || typeof input !== "object" || ancestors.has(input))
|
|
55121
|
+
throw Error();
|
|
55122
|
+
const array = Array.isArray(input), proto = Object.getPrototypeOf(input);
|
|
55123
|
+
if (array ? proto !== Array.prototype : proto !== Object.prototype && proto !== null)
|
|
55124
|
+
throw Error();
|
|
55125
|
+
const keys = Reflect.ownKeys(input);
|
|
55126
|
+
if (keys.length > SKILL_OPERATION_LIMITS.nodes + 1 || keys.some((key) => typeof key !== "string"))
|
|
55127
|
+
throw Error();
|
|
55128
|
+
ancestors.add(input);
|
|
55129
|
+
try {
|
|
55130
|
+
if (array) {
|
|
55131
|
+
if (input.length > SKILL_OPERATION_LIMITS.nodes || keys.length !== input.length + 1)
|
|
55132
|
+
throw Error();
|
|
55133
|
+
const result2 = [];
|
|
55134
|
+
append("[");
|
|
55135
|
+
for (let i4 = 0;i4 < input.length; i4++) {
|
|
55136
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, String(i4));
|
|
55137
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable)
|
|
55138
|
+
throw Error();
|
|
55139
|
+
if (i4)
|
|
55140
|
+
append(",");
|
|
55141
|
+
result2.push(visit(descriptor.value, depth + 1));
|
|
55142
|
+
}
|
|
55143
|
+
append("]");
|
|
55144
|
+
return Object.freeze(result2);
|
|
55145
|
+
}
|
|
55146
|
+
const result = Object.create(null);
|
|
55147
|
+
append("{");
|
|
55148
|
+
for (const [i4, key] of keys.sort().entries()) {
|
|
55149
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
55150
|
+
if (!("value" in descriptor) || !descriptor.enumerable || /[\ud800-\udfff]/u.test(key) || key.length > maximum - bytes)
|
|
55151
|
+
throw Error();
|
|
55152
|
+
if (i4)
|
|
55153
|
+
append(",");
|
|
55154
|
+
append(JSON.stringify(key));
|
|
55155
|
+
append(":");
|
|
55156
|
+
result[key] = visit(descriptor.value, depth + 1);
|
|
55157
|
+
}
|
|
55158
|
+
append("}");
|
|
55159
|
+
return Object.freeze(result);
|
|
55160
|
+
} finally {
|
|
55161
|
+
ancestors.delete(input);
|
|
55162
|
+
}
|
|
55163
|
+
}
|
|
55164
|
+
const owned = visit(value, 0);
|
|
55165
|
+
return { value: owned, canonical: parts.join(""), bytes };
|
|
55166
|
+
}
|
|
55167
|
+
function requestSnapshot(input) {
|
|
55168
|
+
try {
|
|
55169
|
+
const owned = snapshot(input, SKILL_OPERATION_LIMITS.requestBytes), value = owned.value;
|
|
55170
|
+
if (!record6(value) || !exact2(value, ["contractVersion", "requestId", "operation", "input"]) || value.contractVersion !== 1 || !validId(value.requestId) || typeof value.operation !== "string" || value.operation.length > 128 || !/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/.test(value.operation) || !record6(value.input))
|
|
55171
|
+
throw Error();
|
|
55172
|
+
return { ...owned, value };
|
|
55173
|
+
} catch {
|
|
55174
|
+
throw new SkillOperationClientError("INVALID_REQUEST", "not-invoked");
|
|
55175
|
+
}
|
|
55176
|
+
}
|
|
55177
|
+
function resultSnapshot(input, requestId) {
|
|
55178
|
+
try {
|
|
55179
|
+
const value = snapshot(input, SKILL_OPERATION_LIMITS.resultBytes).value;
|
|
55180
|
+
if (!record6(value) || value.contractVersion !== 1 || value.requestId !== requestId)
|
|
55181
|
+
throw Error();
|
|
55182
|
+
const fields = ["contractVersion", "requestId", "status"];
|
|
55183
|
+
if (value.status === "succeeded")
|
|
55184
|
+
fields.push("output");
|
|
55185
|
+
else if (value.status === "refused") {
|
|
55186
|
+
fields.push("code");
|
|
55187
|
+
if (!refusalCodes.has(value.code))
|
|
55188
|
+
throw Error();
|
|
55189
|
+
} else if (!["not-executed", "pending", "unknown"].includes(value.status))
|
|
55190
|
+
throw Error();
|
|
55191
|
+
if (!exact2(value, fields))
|
|
55192
|
+
throw Error();
|
|
55193
|
+
return value;
|
|
55194
|
+
} catch {
|
|
55195
|
+
throw new SkillOperationClientError("INVALID_RESPONSE", "unknown");
|
|
55196
|
+
}
|
|
55197
|
+
}
|
|
55198
|
+
function createSkillOperationClient(transport, options = {}) {
|
|
55199
|
+
const timeoutMs = options.timeoutMs ?? 30000;
|
|
55200
|
+
if (!transport || typeof transport.invoke !== "function" || typeof transport.get !== "function" || !Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 60000)
|
|
55201
|
+
throw new SkillOperationClientError("INVALID_CONFIGURATION", "not-invoked");
|
|
55202
|
+
const invokeTransport = transport.invoke.bind(transport), getTransport = transport.get.bind(transport);
|
|
55203
|
+
const identities = new Map;
|
|
55204
|
+
let rememberedBytes = 0;
|
|
55205
|
+
async function call(requestId, signal, invoke) {
|
|
55206
|
+
if (signal?.aborted)
|
|
55207
|
+
throw new SkillOperationClientError("ABORTED", "not-invoked");
|
|
55208
|
+
const controller = new AbortController;
|
|
55209
|
+
let timer;
|
|
55210
|
+
const abort = () => controller.abort();
|
|
55211
|
+
let onAbort = () => {};
|
|
55212
|
+
const interrupted = new Promise((_, reject) => {
|
|
55213
|
+
onAbort = () => reject(new SkillOperationClientError("UNKNOWN_OUTCOME", "unknown"));
|
|
55214
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
55215
|
+
});
|
|
55216
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
55217
|
+
try {
|
|
55218
|
+
if (signal?.aborted)
|
|
55219
|
+
throw new SkillOperationClientError("ABORTED", "not-invoked");
|
|
55220
|
+
timer = setTimeout(abort, timeoutMs);
|
|
55221
|
+
let pending;
|
|
55222
|
+
try {
|
|
55223
|
+
pending = invoke(controller.signal);
|
|
55224
|
+
} catch {
|
|
55225
|
+
pending = Promise.reject(new SkillOperationClientError("UNKNOWN_OUTCOME", "unknown"));
|
|
55226
|
+
}
|
|
55227
|
+
const owned = Promise.resolve(pending).then((value) => resultSnapshot(value, requestId), () => {
|
|
55228
|
+
throw new SkillOperationClientError("UNKNOWN_OUTCOME", "unknown");
|
|
55229
|
+
});
|
|
55230
|
+
const result = await Promise.race([owned, interrupted]);
|
|
55231
|
+
if (controller.signal.aborted)
|
|
55232
|
+
throw new SkillOperationClientError("UNKNOWN_OUTCOME", "unknown");
|
|
55233
|
+
return result;
|
|
55234
|
+
} finally {
|
|
55235
|
+
if (timer !== undefined)
|
|
55236
|
+
clearTimeout(timer);
|
|
55237
|
+
signal?.removeEventListener("abort", abort);
|
|
55238
|
+
controller.signal.removeEventListener("abort", onAbort);
|
|
55239
|
+
controller.abort();
|
|
55240
|
+
}
|
|
55241
|
+
}
|
|
55242
|
+
return Object.freeze({
|
|
55243
|
+
async invoke(input, requestOptions = {}) {
|
|
55244
|
+
if (requestOptions.signal?.aborted)
|
|
55245
|
+
throw new SkillOperationClientError("ABORTED", "not-invoked");
|
|
55246
|
+
const owned = requestSnapshot(input), previous = identities.get(owned.value.requestId);
|
|
55247
|
+
if (previous !== undefined && previous !== owned.canonical)
|
|
55248
|
+
throw new SkillOperationClientError("REQUEST_CONFLICT", "not-invoked");
|
|
55249
|
+
if (previous === undefined) {
|
|
55250
|
+
if (identities.size >= SKILL_OPERATION_LIMITS.rememberedRequests || rememberedBytes + owned.bytes > SKILL_OPERATION_LIMITS.rememberedBytes)
|
|
55251
|
+
throw new SkillOperationClientError("REQUEST_CAPACITY", "not-invoked");
|
|
55252
|
+
identities.set(owned.value.requestId, owned.canonical);
|
|
55253
|
+
rememberedBytes += owned.bytes;
|
|
55254
|
+
}
|
|
55255
|
+
return call(owned.value.requestId, requestOptions.signal, (signal) => invokeTransport(owned.value, { signal }));
|
|
55256
|
+
},
|
|
55257
|
+
async get(requestId, requestOptions = {}) {
|
|
55258
|
+
if (!validId(requestId))
|
|
55259
|
+
throw new SkillOperationClientError("INVALID_REQUEST", "not-invoked");
|
|
55260
|
+
return call(requestId, requestOptions.signal, (signal) => getTransport(requestId, { signal }));
|
|
55261
|
+
}
|
|
55262
|
+
});
|
|
55263
|
+
}
|
|
55064
55264
|
export {
|
|
55065
55265
|
verifyContentHashFromEntries,
|
|
55066
55266
|
validateRunLifecycleEvent,
|
|
@@ -55119,6 +55319,7 @@ export {
|
|
|
55119
55319
|
createStore,
|
|
55120
55320
|
createSpendService,
|
|
55121
55321
|
createSkillsFetchHandler,
|
|
55322
|
+
createSkillOperationClient,
|
|
55122
55323
|
createServer,
|
|
55123
55324
|
createRunStateMachine,
|
|
55124
55325
|
createRunService,
|
|
@@ -55151,7 +55352,9 @@ export {
|
|
|
55151
55352
|
SqliteRunExecutionStore,
|
|
55152
55353
|
SqliteGovernanceStore,
|
|
55153
55354
|
SkillsFleetCredentialError,
|
|
55355
|
+
SkillOperationClientError,
|
|
55154
55356
|
SkillBundleInspectionError,
|
|
55357
|
+
SKILL_OPERATION_LIMITS,
|
|
55155
55358
|
SKILL_BUNDLE_INSPECTION_LIMITS,
|
|
55156
55359
|
SKILLS_LOCAL_OPT_IN_ENV_KEYS,
|
|
55157
55360
|
SKILLS_APP,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** Provider-neutral guest operation client. Authority and IPC belong to the embedder.
|
|
2
|
+
* No credentials, endpoint discovery, provider SDK, or automatic retry is provided.
|
|
3
|
+
*/
|
|
4
|
+
export type SkillOperationJson = null | boolean | number | string | readonly SkillOperationJson[] | {
|
|
5
|
+
readonly [key: string]: SkillOperationJson;
|
|
6
|
+
};
|
|
7
|
+
export declare const SKILL_OPERATION_LIMITS: Readonly<{
|
|
8
|
+
requestBytes: 65536;
|
|
9
|
+
resultBytes: 1048576;
|
|
10
|
+
depth: 32;
|
|
11
|
+
nodes: 16384;
|
|
12
|
+
rememberedRequests: 256;
|
|
13
|
+
rememberedBytes: 1048576;
|
|
14
|
+
}>;
|
|
15
|
+
export type SkillOperationRefusal = "NOT_ALLOWED" | "APPROVAL_REQUIRED" | "BUDGET_EXHAUSTED" | "EXPIRED" | "CANCELLED" | "UNAVAILABLE" | "INVALID_INPUT";
|
|
16
|
+
export interface SkillOperationRequest {
|
|
17
|
+
readonly contractVersion: 1;
|
|
18
|
+
readonly requestId: string;
|
|
19
|
+
readonly operation: string;
|
|
20
|
+
readonly input: {
|
|
21
|
+
readonly [key: string]: SkillOperationJson;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
interface OperationIdentity {
|
|
25
|
+
readonly contractVersion: 1;
|
|
26
|
+
readonly requestId: string;
|
|
27
|
+
}
|
|
28
|
+
export type SkillOperationResult = OperationIdentity & ({
|
|
29
|
+
readonly status: "not-executed" | "pending" | "unknown";
|
|
30
|
+
} | {
|
|
31
|
+
readonly status: "succeeded";
|
|
32
|
+
readonly output: SkillOperationJson;
|
|
33
|
+
} | {
|
|
34
|
+
readonly status: "refused";
|
|
35
|
+
readonly code: SkillOperationRefusal;
|
|
36
|
+
});
|
|
37
|
+
/** Implementations must authenticate the captured run/attempt separately. A
|
|
38
|
+
* not-executed response is authoritative proof, never a guess from transport loss. */
|
|
39
|
+
export interface SkillOperationTransport {
|
|
40
|
+
invoke(request: SkillOperationRequest, options: {
|
|
41
|
+
signal: AbortSignal;
|
|
42
|
+
}): Promise<unknown>;
|
|
43
|
+
get(requestId: string, options: {
|
|
44
|
+
signal: AbortSignal;
|
|
45
|
+
}): Promise<unknown>;
|
|
46
|
+
}
|
|
47
|
+
export type SkillOperationClientErrorCode = "INVALID_REQUEST" | "REQUEST_CONFLICT" | "REQUEST_CAPACITY" | "ABORTED" | "UNKNOWN_OUTCOME" | "INVALID_RESPONSE" | "INVALID_CONFIGURATION";
|
|
48
|
+
export declare class SkillOperationClientError extends Error {
|
|
49
|
+
readonly code: SkillOperationClientErrorCode;
|
|
50
|
+
readonly outcome: "not-invoked" | "unknown";
|
|
51
|
+
constructor(code: SkillOperationClientErrorCode, outcome: "not-invoked" | "unknown");
|
|
52
|
+
}
|
|
53
|
+
export interface SkillOperationClient {
|
|
54
|
+
invoke(request: SkillOperationRequest, options?: {
|
|
55
|
+
signal?: AbortSignal;
|
|
56
|
+
}): Promise<SkillOperationResult>;
|
|
57
|
+
get(requestId: string, options?: {
|
|
58
|
+
signal?: AbortSignal;
|
|
59
|
+
}): Promise<SkillOperationResult>;
|
|
60
|
+
}
|
|
61
|
+
/** One client belongs to one captured authority scope. Remembered identities
|
|
62
|
+
* are bounded and never evicted. This is local misuse protection, not durable
|
|
63
|
+
* deduplication: the server must bind request IDs and payloads atomically.
|
|
64
|
+
* Each explicit call invokes transport once; uncertainty requires explicit get. */
|
|
65
|
+
export declare function createSkillOperationClient(transport: SkillOperationTransport, options?: {
|
|
66
|
+
timeoutMs?: number;
|
|
67
|
+
}): SkillOperationClient;
|
|
68
|
+
export {};
|