@hasna/skills 0.5.4 → 0.5.6

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.
@@ -124,7 +124,7 @@ export interface LocalSkillsFleet {
124
124
  }
125
125
  export type SkillsFleet = HostedSkillsFleet | LocalSkillsFleet;
126
126
  /** Machine-readable reasons a hosted resolution was refused. */
127
- export type SkillsFleetErrorCode = "MISSING_API_CREDENTIAL" | "INVALID_API_URL" | "INSTANCE_CREDENTIAL_MISMATCH";
127
+ export type SkillsFleetErrorCode = "MISSING_API_CREDENTIAL" | "INVALID_API_URL" | "INSTANCE_CREDENTIAL_MISMATCH" | "GATEWAY_AUTH_UNAVAILABLE";
128
128
  /**
129
129
  * A configured install could not produce a usable hosted client.
130
130
  *
@@ -155,6 +155,11 @@ export declare function isSkillsFleetCredentialError(error: unknown): error is S
155
155
  * URL printed by every error message — must not end up with `/api/v1/api/v1`.
156
156
  */
157
157
  export declare function normalizeSkillsApiOrigin(apiUrl: string): string;
158
+ /** Compose a known Skills route without changing its credential-bound instance.
159
+ * The fleet gateway strips /skills and forwards /v1 to its independent origin.
160
+ * Other instances retain the established /api/v1 and /api/auth contracts.
161
+ */
162
+ export declare function skillsApiRequestUrl(apiUrl: string, route: string): string;
158
163
  /** One configured authority: its value and the source that decided it. */
159
164
  export interface ConfiguredSkillsApiUrl {
160
165
  value: string;
@@ -74,8 +74,8 @@ export declare function indexCanonicalCorpus(corpusRoot: string): Map<string, st
74
74
  export declare function scanUnmarkedHomes(options?: AdoptionOptions): AdoptionScan;
75
75
  /**
76
76
  * Write one rollback record listing every marker written (mode "adopt") or
77
- * every directory removed (mode "prune"), so both operations are reversible
78
- * from a single machine-readable file.
77
+ * every directory removed (mode "prune"), so both operations are auditable
78
+ * from a single machine-readable file. Deleted resource bytes are not stored.
79
79
  */
80
80
  export declare function writeRollbackRecord(mode: "adopt" | "prune", entries: RollbackMarker[], appDir?: string): string;
81
81
  /**
@@ -85,9 +85,4 @@ export declare function writeRollbackRecord(mode: "adopt" | "prune", entries: Ro
85
85
  * records every written marker in a rollback file. Never deletes anything.
86
86
  */
87
87
  export declare function adoptUnmarkedHomes(options?: AdoptionOptions): AdoptionResult;
88
- /**
89
- * List (or, with `apply`, remove) marked home skill dirs that have no canonical
90
- * corpus entry. Never touches an unmarked directory. Removals are recorded in
91
- * the rollback store before they happen.
92
- */
93
88
  export declare function pruneStrayHomes(options?: AdoptionOptions): PruneResult;
@@ -13,6 +13,7 @@ export interface JsonSchemaObject {
13
13
  pattern?: string;
14
14
  maxItems?: number;
15
15
  maxLength?: number;
16
+ minLength?: number;
16
17
  items?: JsonSchemaObject;
17
18
  properties?: Record<string, JsonSchemaObject>;
18
19
  required?: string[];
@@ -0,0 +1,10 @@
1
+ import { RemotePrivatePublicationsClient } from "./remote-private-publications.js";
2
+ import type { RemoteWorkspaceContext } from "./remote-workspace-selection.js";
3
+ /** Capture host profile before fresh verification. Neither the session nor a
4
+ * signed upload capability is written to the host's credentials. */
5
+ export declare function privatePublicationSession(email: string, code: string, requested?: RemoteWorkspaceContext): Promise<RemotePrivatePublicationsClient>;
6
+ export declare function privatePublicationCustomerError(error: unknown): {
7
+ error: string;
8
+ code: string;
9
+ uncertain: boolean;
10
+ };
@@ -0,0 +1,43 @@
1
+ import { RemotePrivatePublicationsClient, type PrivatePublicationDeclaration, type PrivatePublicationView } from "./remote-private-publications.js";
2
+ type Phase = "prepared" | "begin_uncertain" | "awaiting_upload" | "upload_uncertain" | "uploaded" | "finalize_uncertain" | "observed";
3
+ export interface PrivatePublicationRecovery {
4
+ contractVersion: 1;
5
+ apiOrigin: string;
6
+ organizationId: string;
7
+ userId: string;
8
+ membershipId: string;
9
+ skillId: string;
10
+ declaration: PrivatePublicationDeclaration;
11
+ phase: Phase;
12
+ intent: PrivatePublicationView | null;
13
+ }
14
+ export interface PrivatePublicationResult {
15
+ recoveryDirectory: string;
16
+ skillId: string;
17
+ intentId: string | null;
18
+ state: string;
19
+ versionId: string | null;
20
+ committed: boolean;
21
+ executionEnabled: false;
22
+ nextAction: string;
23
+ }
24
+ /** Reads only two bounded local files. The receipt never contains a token or signed URL. */
25
+ export declare function readPrivatePublicationRecovery(directory: string): {
26
+ receipt: PrivatePublicationRecovery;
27
+ bytes: Buffer;
28
+ };
29
+ /** Snapshot and verify before the first publication request. Source files are not rewritten. */
30
+ export declare function preparePrivatePublication(client: RemotePrivatePublicationsClient, sourceDirectory: string, recoveryDirectory: string, input: {
31
+ skillId: string;
32
+ expectedCurrentVersionId: string | null;
33
+ idempotencyKey?: string;
34
+ }): Promise<PrivatePublicationRecovery>;
35
+ export declare function privatePublicationResult(directory: string, receipt: PrivatePublicationRecovery): PrivatePublicationResult;
36
+ /** Each write-ahead phase is durable before its network mutation. A resumed
37
+ * uncertain PUT is finalized for server verification, never uploaded again. */
38
+ export declare function continuePrivatePublication(client: RemotePrivatePublicationsClient, directory: string, options: {
39
+ confirm: true;
40
+ waitMs?: number;
41
+ }): Promise<PrivatePublicationResult>;
42
+ export declare function inspectPrivatePublication(client: RemotePrivatePublicationsClient, directory: string, cancel?: boolean): Promise<PrivatePublicationResult>;
43
+ export {};
@@ -2,6 +2,8 @@ import type { RemoteInputFileDescriptor } from "./remote-files.js";
2
2
  /** Optional account APIs supplied by a configured Skills server. No local prices or provider policy. */
3
3
  export interface RemoteRunQuote {
4
4
  skill: string;
5
+ /** Opaque server binding for this exact quote; retain verbatim for approval. */
6
+ quoteReceipt?: string;
5
7
  pricing: {
6
8
  costCents: number;
7
9
  formattedCost: string;
@@ -10,6 +12,8 @@ export interface RemoteRunQuote {
10
12
  [key: string]: unknown;
11
13
  }
12
14
  export interface RemoteRunApproval {
15
+ /** A previously quoted server binding. Never refresh it after user approval. */
16
+ quoteReceipt?: string;
13
17
  /** Preferred public spelling: maximum integer credits approved by the caller. */
14
18
  maxCredits?: number;
15
19
  /** Maximum integer credits approved by the caller (legacy wire spelling). */
@@ -29,6 +33,7 @@ export declare class RemoteCreditApprovalError extends Error {
29
33
  constructor(requiredCredits: number, maximumCredits: number);
30
34
  }
31
35
  export declare function creditCount(value: unknown): number;
36
+ export declare function runQuoteReceipt(value: unknown): string | undefined;
32
37
  export declare function parseRemoteRunQuote(value: unknown): RemoteRunQuote;
33
38
  export declare function parseRemoteCreditPacks(value: unknown): RemoteCreditPack[];
34
39
  export declare function parseRemoteBillingStatus(value: unknown): {
@@ -1,4 +1,5 @@
1
1
  import { type RequestInvitationEmailChallenge, type AcceptInvitationEmailChallenge } from "./remote-invitation-recovery.js";
2
+ import { RemotePrivatePublicationsClient } from "./remote-private-publications.js";
2
3
  import { type ListRemoteWorkspaceInvitations, type IssueRemoteWorkspaceInvitation, type ResendRemoteWorkspaceInvitation, type RevokeRemoteWorkspaceInvitation, type AcceptRemoteWorkspaceInvitation } from "./remote-invitations.js";
3
4
  import { type LeaveRemoteWorkspace } from "./remote-workspace-leave.js";
4
5
  import { type RemoteWorkspaceContext, type RemoteWorkspaceSession, type RemoteAccountWorkspaceDiscovery } from "./remote-workspace-selection.js";
@@ -23,6 +24,8 @@ export declare class HostedApiError extends Error {
23
24
  export declare class RemoteSkillsAuthClient {
24
25
  readonly apiOrigin: string;
25
26
  constructor(apiUrl: string);
27
+ /** One fresh workspace-bound session for an entire publication. No credentials are saved. */
28
+ openPrivatePublications(email: string, code: string, context: RemoteWorkspaceContext): Promise<RemotePrivatePublicationsClient>;
26
29
  requestInvitationEmailChallenge(input: RequestInvitationEmailChallenge): Promise<import("./remote-invitation-recovery.js").RemoteInvitationEmailChallenge>;
27
30
  acceptInvitationEmailChallenge(input: AcceptInvitationEmailChallenge): Promise<import("./remote-invitation-recovery.js").RemoteInvitationEmailAcceptance>;
28
31
  requestCode(email: string): Promise<any>;
@@ -5,7 +5,7 @@ import { type RemoteWorkspaceMembersOptions, type RemoteWorkspaceMembersPage } f
5
5
  import { type RemoteWorkspaceMemberErrorCode, type SetRemoteWorkspaceMemberRole, type RemoveRemoteWorkspaceMember, type RemoteWorkspaceMemberRoleResult, type RemoteWorkspaceMemberRemovalResult } from "./remote-workspace.js";
6
6
  import { type RemoteSkillRunContract } from "./remote-run-contract.js";
7
7
  import { type RemoteCreditPack, type RemoteRunApproval, type RemoteRunQuote } from "./remote-account.js";
8
- import { type RemoteInputFile } from "./remote-files.js";
8
+ import { type RemoteInputFile, type RemoteInputFileDescriptor } from "./remote-files.js";
9
9
  import { type UpdateRemoteProfile, type UpdateRemoteWorkspace } from "./remote-profile.js";
10
10
  import { type RemoteQuoteUnavailableCode } from "./remote-quote-errors.js";
11
11
  export type { RemoteQuoteUnavailableCode } from "./remote-quote-errors.js";
@@ -121,7 +121,7 @@ export declare class RemoteSkillsClient {
121
121
  }>;
122
122
  /** Low-level admission transport; interactive surfaces use submitQuotedRun. */
123
123
  submitRun(slug: string, input?: Record<string, unknown>, args?: string[], approval?: RemoteRunApproval): Promise<RemoteSkillRunContract>;
124
- quoteRun(slug: string, input?: Record<string, unknown>, args?: string[]): Promise<RemoteRunQuote>;
124
+ quoteRun(slug: string, input?: Record<string, unknown>, args?: string[], files?: RemoteInputFileDescriptor[]): Promise<RemoteRunQuote>;
125
125
  getCapabilities(): Promise<{
126
126
  contractVersion: 1;
127
127
  apiVersion: 1;
@@ -0,0 +1,74 @@
1
+ import { type RemoteWorkspaceSession } from "./remote-workspace-selection.js";
2
+ export declare const PRIVATE_PUBLICATION_MAX_BYTES: number;
3
+ export type PrivatePublicationState = "awaiting_upload" | "queued" | "verifying" | "needs_attention" | "committed" | "rejected" | "cancelled" | "expired";
4
+ export interface PrivatePublicationDeclaration {
5
+ idempotencyKey: string;
6
+ version: string;
7
+ expectedCurrentVersionId: string | null;
8
+ manifestText: string;
9
+ archiveSha256: string;
10
+ archiveByteSize: number;
11
+ }
12
+ export interface PrivatePublicationView {
13
+ id: string;
14
+ skillId: string;
15
+ version: string;
16
+ expectedCurrentVersionId: string | null;
17
+ archiveSha256: string;
18
+ archiveByteSize: number;
19
+ state: PrivatePublicationState;
20
+ expiresAt: string;
21
+ createdAt: string;
22
+ versionId: string | null;
23
+ }
24
+ export interface PrivatePublishingCapability {
25
+ contractVersion: 1;
26
+ enabled: boolean;
27
+ authentication: "interactive-session";
28
+ maxArchiveBytes: 16777216;
29
+ uploadMaxTtlSeconds: 300;
30
+ executionEnabled: false;
31
+ }
32
+ /** Contains a short-lived bearer capability. Never print or persist this object. */
33
+ export interface PrivatePublicationUpload {
34
+ method: "PUT";
35
+ uploadUrl: string;
36
+ headers: Readonly<Record<string, string>>;
37
+ expiresAt: string;
38
+ }
39
+ export declare class PrivatePublicationError extends Error {
40
+ readonly code: string;
41
+ readonly uncertain: boolean;
42
+ readonly status?: number | undefined;
43
+ constructor(code: string, message: string, uncertain?: boolean, status?: number | undefined);
44
+ }
45
+ export declare const publicationUuid: (v: unknown) => v is string;
46
+ export declare const publicationSha256: (bytes: Uint8Array | string) => string;
47
+ export declare function checkedPublicationDeclaration(value: unknown): PrivatePublicationDeclaration;
48
+ export declare function checkedPublicationView(value: unknown, skillId: string, intentId?: string, declaration?: PrivatePublicationDeclaration): PrivatePublicationView;
49
+ /** Explicit hosted contract, unrelated to registry publishSkill and its content revisions.
50
+ * Session tokens stay in private fields and are never written to credentials. */
51
+ export declare class RemotePrivatePublicationsClient {
52
+ #private;
53
+ readonly apiOrigin: string;
54
+ readonly organizationId: string;
55
+ readonly userId: string;
56
+ readonly membershipId: string;
57
+ constructor(apiUrl: string, session: RemoteWorkspaceSession);
58
+ getCapability(options?: {
59
+ timeoutMs?: number;
60
+ signal?: AbortSignal;
61
+ }): Promise<PrivatePublishingCapability>;
62
+ begin(skillId: string, input: PrivatePublicationDeclaration): Promise<PrivatePublicationView>;
63
+ get(skillId: string, intentId: string, options?: {
64
+ timeoutMs?: number;
65
+ signal?: AbortSignal;
66
+ }): Promise<PrivatePublicationView>;
67
+ finalize(skillId: string, intentId: string): Promise<PrivatePublicationView>;
68
+ cancel(skillId: string, intentId: string): Promise<PrivatePublicationView>;
69
+ upload(skillId: string, intent: PrivatePublicationView, bytes: Uint8Array): Promise<void>;
70
+ wait(skillId: string, intentId: string, options?: {
71
+ timeoutMs?: number;
72
+ signal?: AbortSignal;
73
+ }): Promise<PrivatePublicationView>;
74
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerPrivatePublicationTools(server: McpServer): void;
@@ -4,7 +4,7 @@
4
4
  * Implements the sdk `Dispatcher` interface (submit/cancel) with the launch
5
5
  * machinery the interface seam leaves to this module:
6
6
  *
7
- * 1. CAS-claim the next attempt's `attempt_id` + `lease_generation`
7
+ * 1. generation-check the attempt's `attempt_id` + `lease_generation`
8
8
  * (stale generation rejected),
9
9
  * 2. persist the launch intent (clientToken, startedBy, request digest)
10
10
  * BEFORE calling ECS,
@@ -12,12 +12,16 @@
12
12
  * (run_id, attempt_id) and an immutable request digest,
13
13
  * 4. on a lost RunTask response, reconcile the SAME token — list tasks by
14
14
  * startedBy, describe them — before any new attempt; a new attempt is
15
- * forbidden until the previous launch is proven absent or terminal.
15
+ * forbidden while the existing attempt remains unresolved.
16
16
  *
17
17
  * The AWS SDK is never called from tests: the `EcsRunTaskClient` interface is
18
18
  * the seam, `createAwsEcsClient` is the only place the real SDK is imported,
19
19
  * and every test injects a mock client.
20
20
  *
21
+ * Cross-process fencing requires a store with atomic transactional claims and
22
+ * transitions. The generic state machine's separate reads and writes alone do
23
+ * not establish that guarantee.
24
+ *
21
25
  * Nothing here names a concrete cluster, task definition, subnet, or account:
22
26
  * all infrastructure identifiers come from configuration (R4).
23
27
  */
@@ -26,6 +30,7 @@ import type { RunExecutionStore } from "../storage.js";
26
30
  import type { AttemptRecord, FrozenAdmission } from "../types.js";
27
31
  import { type RunStateMachine } from "../state-machine.js";
28
32
  import { type ReceiptService } from "../receipts.js";
33
+ import { DescribeTasksCommand, ListTasksCommand, RunTaskCommand, StopTaskCommand } from "@aws-sdk/client-ecs";
29
34
  /** ClientToken bound: ECS RunTask accepts up to 32 ASCII characters. */
30
35
  export declare const CLIENT_TOKEN_BYTES = 16;
31
36
  export interface EcsTaskState {
@@ -126,26 +131,32 @@ export declare class EcsDispatcher implements Dispatcher {
126
131
  constructor(config: EcsDispatcherConfig, client: EcsRunTaskClient, options: EcsDispatcherOptions);
127
132
  /** sdk Dispatcher surface: submit an ADMITTED run (execution domain) to the launch machinery. */
128
133
  submit(run: FrozenAdmission): Promise<DispatchResult>;
129
- /** sdk Dispatcher surface: fence the current generation and stop the task. */
134
+ /** Confirm the task stopped before recording cancellation and its receipt. */
130
135
  cancel(runId: string): Promise<DispatchResult>;
131
136
  /**
132
137
  * Launch the next attempt of a run.
133
138
  *
134
139
  * A previous attempt whose launch outcome is unknown (launching / ambiguous /
135
- * launched) is reconciled FIRST. Only when the previous launch is proven
136
- * absent or terminal is a new attempt minted and claimed.
140
+ * launched) is reconciled FIRST. Missing observations remain ambiguous;
141
+ * terminal observations return to the owner without minting another attempt.
137
142
  */
138
143
  launchAttempt(runId: string): Promise<LaunchOutcome>;
139
- /** Reconcile an attempt whose launch outcome is unknown. */
144
+ /** ECS is eventually consistent: empty lists and missing descriptions cannot
145
+ * prove absence. Only an exact recognized observation can change launch state. */
140
146
  reconcile(admission: FrozenAdmission, attempt: AttemptRecord): Promise<LaunchOutcome>;
141
- /** Resolve the task arn for the current attempt, reconciling if needed. */
142
- private resolveTaskArn;
143
147
  private runTaskInput;
148
+ private cancellationReceipt;
144
149
  private writeCancellationReceipt;
145
150
  }
146
- /**
147
- * The one place the real AWS SDK is wired in. Returns a client bound to the
148
- * configured region; credentials resolve through the standard SDK chain.
149
- * Tests never construct this — they inject a mock `EcsRunTaskClient`.
150
- */
151
- export declare function createAwsEcsClient(region: string): EcsRunTaskClient;
151
+ /** Injectable command transport exercises the actual AWS command adapter without network. */
152
+ export interface EcsCommandTransport {
153
+ send(command: RunTaskCommand | ListTasksCommand | DescribeTasksCommand | StopTaskCommand): Promise<unknown>;
154
+ }
155
+ export interface AwsEcsClientOptions {
156
+ cluster?: string;
157
+ transport?: EcsCommandTransport;
158
+ }
159
+ /** Pass an explicit cluster when restoring an existing named-cluster attempt.
160
+ * The legacy one-argument factory binds to the first operation's cluster; read
161
+ * operations before RunTask retain AWS's default-cluster behavior. */
162
+ export declare function createAwsEcsClient(region: string, options?: AwsEcsClientOptions): EcsRunTaskClient;
@@ -46,3 +46,5 @@ export { WorkspaceLeaveInputError, RemoteWorkspaceLeaveError, RemoteWorkspaceLea
46
46
  export type { RemoteWorkspaceInvitation, RemoteWorkspaceInvitationsPage, RemoteWorkspaceInvitationResult, RemoteWorkspaceInvitationAcceptance, ListRemoteWorkspaceInvitations, IssueRemoteWorkspaceInvitation, ResendRemoteWorkspaceInvitation, RevokeRemoteWorkspaceInvitation, AcceptRemoteWorkspaceInvitation, RemoteWorkspaceInvitationErrorCode } from "../lib/remote-invitations.js";
47
47
  export { WorkspaceInvitationInputError, RemoteWorkspaceInvitationError, RemoteWorkspaceInvitationUnconfirmedError, RemoteWorkspaceInvitationReadError } from "../lib/remote-invitations.js";
48
48
  export { InvitationEmailInputError, RemoteInvitationEmailError, RemoteInvitationEmailUnconfirmedError, type RequestInvitationEmailChallenge, type AcceptInvitationEmailChallenge, type RemoteInvitationEmailChallenge, type RemoteInvitationEmailAcceptance, type RemoteInvitationEmailErrorCode } from "../lib/remote-invitation-recovery.js";
49
+ export { RemotePrivatePublicationsClient, PrivatePublicationError, PRIVATE_PUBLICATION_MAX_BYTES, type PrivatePublicationDeclaration, type PrivatePublicationView, type PrivatePublicationState, type PrivatePublishingCapability } from "../lib/remote-private-publications.js";
50
+ export { preparePrivatePublication, readPrivatePublicationRecovery, continuePrivatePublication, inspectPrivatePublication, type PrivatePublicationRecovery, type PrivatePublicationResult } from "../lib/private-publication-recovery.js";