@xiashe/sdk 0.1.34 → 0.1.35-superconnector.2

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
@@ -11,3 +11,31 @@ The SDK wraps:
11
11
  - Unified file upload/listing.
12
12
 
13
13
  It stores local CLI state in `~/.xiashe/config.json` by default.
14
+
15
+ ## Superconnector runtime preview
16
+
17
+ Version `0.1.35-superconnector.1` adds user-owned non-OAuth credential connection APIs alongside existing OAuth connections, explicit action grants, immutable runtime releases, verified installation, foreground Agent/script execution, remote hosts and result-only recovery. SaaS credentials remain in the platform gateway.
18
+
19
+ Runtime exports include `installRuntimePackage`, `runRuntimeScript`, `createRuntimeAgentSession`, `createRuntimeAgentHost`, `serveRuntimeScriptHost`, `serveRuntimeMcp`, `retryRuntimeResults` and `retryRuntimeHostResults`. Script execution requires Docker and a pre-pulled official Node image pinned by SHA-256. Runtime container helper files are included in the package.
20
+
21
+ This is a local test prerelease. Real non-OAuth staging acceptance and native interaction validation remain release gates for this delivery. See the repository's `docs/superconnector-development.md` for setup and acceptance procedures.
22
+
23
+ `createRuntimeRelease({ ..., signature })` optionally imports a public BYOA v1 P-256 package proof. The backend verifies every delivered file and preserves existing creator ownership and runtime review. Private signing material is rejected before transmission. Sign the exact filtered package; source-package proofs cannot survive changed files.
24
+
25
+ Script and hybrid contracts support Node `.mjs` and `.js` entrypoints. The pinned container applies Node module semantics to the exact reviewed package; installation never runs dependency or lifecycle scripts.
26
+
27
+ Use `listRuntimeReleasePage(registryEntryId, { limit: 20, cursor: null })` for complete creator history. Follow `continueCursor` until `isDone`; each page supports 1–50 entries. The older `listRuntimeReleases` array response remains capped at 100.
28
+
29
+ Optional script `dependencies` declare a Node minimum (`>=18`) and required command names. The pinned container checks these before package code starts, without running probe commands or installing packages.
30
+
31
+ For an operator-enabled public action, use `grantConnector({ publicAccess: true, actionIds, purpose, releaseId })`. Omit `connectionId`: public grants cannot carry account credentials and still require explicit consent, expiration and the reviewed release scope.
32
+
33
+ ## Non-OAuth API surface
34
+
35
+ - `getSaasCredentialSetup(provider)` returns sanitized form metadata and the credential submission endpoint.
36
+ - `connectSaasCredentials(provider, { authType, values, requestKey, replaceConnectionId? })` submits API Key or custom credential fields over the dedicated authenticated HTTP route. It does not retry uncertain submissions.
37
+ - `getSaasCredentialStatus(requestKey)` checks only the signed-in user's request; `disconnectSaas(connectionId)` revokes access and schedules upstream deletion.
38
+ - `prepareRuntimeRun(releaseId)` returns reviewed requirements and valid saved choices, without granting access.
39
+ - `launchRuntimeRun({ releaseId, input, connections, publicActionIds, creatorServiceIds, requestKey, targetAgentConnectionId, executionMode? })` validates and grants the explicitly confirmed scope, then queues execution atomically. It stores only declared parameters and connection revisions for this exact release.
40
+
41
+ Keep `values` out of logs, command arguments, Skill files, runtime inputs and persisted Agent state. A replacement invalidates old approvals; the new connection must be selected and approved again. Creator-provided services use the existing constrained service broker, never transfer the creator's raw credential to a consumer or Agent.
@@ -0,0 +1,25 @@
1
+ // Compatibility with BYOA's helper API. No credentials or direct-network fallback.
2
+ import { randomUUID } from 'node:crypto';
3
+ const pending = new Map();
4
+ process.on('message', message => {
5
+ if (message?.type !== 'action-result') return;
6
+ const operation = pending.get(message.id);
7
+ if (!operation) return;
8
+ pending.delete(message.id);
9
+ if (pending.size === 0) process.channel?.unref();
10
+ if (message.error) operation.reject(new Error(message.error));
11
+ else operation.resolve(message.data);
12
+ });
13
+ process.channel?.unref();
14
+ export function executeAction(actionId, input = {}) {
15
+ if (!process.send || pending.size >= 8) return Promise.reject(new Error('CONNECTOR_BROKER_UNAVAILABLE'));
16
+ const id = randomUUID();
17
+ return new Promise((resolve, reject) => {
18
+ pending.set(id, { resolve, reject });
19
+ process.channel?.ref();
20
+ process.send({ type: 'action', id, actionId, input });
21
+ });
22
+ }
23
+ export async function executeActionRaw(actionId, input = {}) {
24
+ return { success: true, data: await executeAction(actionId, input) };
25
+ }
@@ -0,0 +1,50 @@
1
+ // Runs only inside the disposable, network-disabled container.
2
+ import { spawn } from 'node:child_process';
3
+ import { collectOutputs } from './outputs.mjs';
4
+ import readline from 'node:readline';
5
+ import { accessSync, statSync, constants } from 'node:fs';
6
+ const emit = value => process.stdout.write(JSON.stringify(value) + '\n');
7
+ const lines = readline.createInterface({ input: process.stdin });
8
+ let child;
9
+ let initialized = false;
10
+ lines.on('line', line => {
11
+ try {
12
+ const message = JSON.parse(line);
13
+ if (message.type === 'init' && !initialized) {
14
+ initialized = true;
15
+ const { entry, input, runId, version, name, maxOutputBytes, outputs: specifications, dependencies } = message;
16
+ const env = { PATH: '/usr/local/bin:/usr/bin:/bin', HOME: '/work', USERPROFILE: '/work', TMPDIR: '/work', TEMP: '/work', TMP: '/work',
17
+ PROJECT_DIR: '/work', JOB_ID: runId, WORKER_ID: 'xiashe-container', SKILL_VERSION: version, SKILL_NAME: name,
18
+ SKILL_PARAMETERS: JSON.stringify(input), USER_INPUT: typeof input === 'string' ? input : typeof input?.userInput === 'string' ? input.userInput : '',
19
+ CONNECTOR_HELPER_PATH: '/runtime/connector-helper.mjs', SC_BROKER_URL: 'ipc://xiashe' };
20
+ if (dependencies !== undefined) {
21
+ if (!dependencies || typeof dependencies !== 'object' || Array.isArray(dependencies) ||
22
+ Object.keys(dependencies).some(key => !['node', 'commands'].includes(key)) ||
23
+ dependencies.node !== undefined && (typeof dependencies.node !== 'string' || !/^>=\d{1,6}$/.test(dependencies.node)) ||
24
+ dependencies.commands !== undefined && (!Array.isArray(dependencies.commands) || dependencies.commands.length > 20 || dependencies.commands.some(command => typeof command !== 'string' || !/^[A-Za-z0-9_-]{1,64}$/.test(command)))) throw new Error('INVALID_RUNTIME_DEPENDENCIES');
25
+ if (dependencies.node && Number(process.versions.node.split('.')[0]) < Number(dependencies.node.slice(2))) throw new Error('NODE_REQUIREMENT_NOT_MET');
26
+ for (const command of dependencies.commands ?? []) {
27
+ const found = env.PATH.split(':').some(directory => {
28
+ try { const file = directory + '/' + command; accessSync(file, constants.X_OK); return statSync(file).isFile(); }
29
+ catch { return false; }
30
+ });
31
+ if (!found) throw new Error('COMMAND_REQUIREMENT_NOT_MET');
32
+ }
33
+ }
34
+ child = spawn(process.execPath, ['/package/' + entry], { cwd: '/work', env, stdio: ['ignore', 'pipe', 'pipe', 'ipc'] });
35
+ child.stdout.setEncoding('utf8'); child.stderr.setEncoding('utf8');
36
+ child.stdout.on('data', data => emit({ type: 'log', stream: 'stdout', text: data.toString('utf8') }));
37
+ child.stderr.on('data', data => emit({ type: 'log', stream: 'stderr', text: data.toString('utf8') }));
38
+ child.on('message', value => emit(value));
39
+ child.on('error', () => { emit({ type: 'error', code: 'SCRIPT_START_FAILED' }); process.exitCode = 1; lines.close(); process.stdin.destroy(); });
40
+ child.on('close', async code => {
41
+ try {
42
+ if (code !== 0) throw new Error('SCRIPT_EXIT_FAILED');
43
+ const result = await collectOutputs(specifications, maxOutputBytes);
44
+ emit({ type: 'result', result });
45
+ } catch (error) { emit({ type: 'error', code: error.message }); process.exitCode = 1; }
46
+ lines.close(); process.stdin.destroy();
47
+ });
48
+ } else if (message.type === 'action-result' && child?.connected) child.send(message);
49
+ } catch (error) { emit({ type: 'error', code: ['INVALID_RUNTIME_DEPENDENCIES', 'NODE_REQUIREMENT_NOT_MET', 'COMMAND_REQUIREMENT_NOT_MET'].includes(error.message) ? error.message : 'BROKER_PROTOCOL_ERROR' }); process.exitCode = 1; lines.close(); process.stdin.destroy(); }
50
+ });
@@ -0,0 +1,78 @@
1
+ import { constants } from 'node:fs';
2
+ import { lstat, open, readdir, realpath } from 'node:fs/promises';
3
+
4
+ const safePath = value => typeof value === 'string' && value.length <= 240 && !/[\\\u0000-\u001f]/.test(value) && !/^[A-Za-z]:/.test(value) &&
5
+ value.split('/').every(part => part && part !== '.' && part !== '..');
6
+
7
+ /** Linux container only: inspect the opened descriptor, then perform bounded reads. */
8
+ export async function collectOutputs(specifications, maxOutputBytes) {
9
+ let size = 0, count = 0;
10
+ async function read(relative, required = true) {
11
+ if (!safePath(relative)) throw new Error('OUTPUT_UNSAFE_FILE');
12
+ const parts = relative.split('/');
13
+ for (let i = 1; i < parts.length; i++) {
14
+ const stat = await lstat('/work/' + parts.slice(0, i).join('/')).catch(error => {
15
+ if (error.code === 'ENOENT') { if (!required) return null; throw new Error('OUTPUT_MISSING'); }
16
+ throw new Error('OUTPUT_UNSAFE_FILE');
17
+ });
18
+ if (!stat) return null;
19
+ if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('OUTPUT_UNSAFE_FILE');
20
+ }
21
+ const handle = await open('/work/' + relative, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK).catch(error => {
22
+ if (error.code === 'ENOENT') { if (!required) return null; throw new Error('OUTPUT_MISSING'); }
23
+ throw new Error('OUTPUT_UNSAFE_FILE');
24
+ });
25
+ if (!handle) return null;
26
+ try {
27
+ const actual = await realpath('/proc/self/fd/' + handle.fd);
28
+ const stat = await handle.stat();
29
+ if (!actual.startsWith('/work/') || !stat.isFile() || stat.nlink !== 1) throw new Error('OUTPUT_UNSAFE_FILE');
30
+ if (++count > 100 || stat.size > maxOutputBytes - size) throw new Error('OUTPUT_LIMIT');
31
+ const bytes = Buffer.alloc(maxOutputBytes - size + 1);
32
+ let length = 0;
33
+ while (length < bytes.length) {
34
+ const { bytesRead } = await handle.read(bytes, length, bytes.length - length, null);
35
+ if (!bytesRead) break;
36
+ length += bytesRead;
37
+ }
38
+ size += length;
39
+ if (size > maxOutputBytes) throw new Error('OUTPUT_LIMIT');
40
+ return bytes.subarray(0, length);
41
+ } finally { await handle.close(); }
42
+ }
43
+ let outputs;
44
+ if (specifications === undefined) {
45
+ outputs = [];
46
+ const walk = async (directory, prefix = '') => {
47
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
48
+ const relative = prefix + entry.name;
49
+ if (entry.isDirectory()) {
50
+ if (relative.split('/').length > 10) throw new Error('OUTPUT_LIMIT');
51
+ await walk(directory + '/' + entry.name, relative + '/');
52
+ } else {
53
+ const bytes = await read(relative);
54
+ outputs.push({ path: relative, encoding: 'base64', content: bytes.toString('base64') });
55
+ }
56
+ }
57
+ };
58
+ await walk('/work');
59
+ } else {
60
+ if (!specifications || typeof specifications !== 'object' || Array.isArray(specifications) || Object.keys(specifications).length > 100) throw new Error('INVALID_RUNTIME_OUTPUTS');
61
+ outputs = {};
62
+ for (const [name, spec] of Object.entries(specifications)) {
63
+ if (!name.trim() || name.length > 128 || /[\u0000-\u001f]/.test(name) || ['constructor', 'prototype', '__proto__'].includes(name) || !spec || !['text', 'markdown', 'json', 'file'].includes(spec.type)) throw new Error('INVALID_RUNTIME_OUTPUTS');
64
+ const bytes = await read(spec.path, spec.required !== false);
65
+ if (!bytes) continue;
66
+ let content;
67
+ if (spec.type !== 'file') {
68
+ try { content = new TextDecoder('utf-8', { fatal: true }).decode(bytes); } catch { throw new Error('OUTPUT_INVALID_TEXT'); }
69
+ if (spec.type === 'json') { try { JSON.parse(content); } catch { throw new Error('OUTPUT_INVALID_JSON'); } }
70
+ }
71
+ outputs[name] = { type: spec.type, path: spec.path, sizeBytes: bytes.length,
72
+ ...(spec.type === 'file' ? { encoding: 'base64', content: bytes.toString('base64') } : { content }) };
73
+ }
74
+ }
75
+ const result = { outputs };
76
+ if (Buffer.byteLength(JSON.stringify(result)) > maxOutputBytes) throw new Error('OUTPUT_LIMIT');
77
+ return result;
78
+ }
package/index.mjs CHANGED
@@ -5,8 +5,16 @@ import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { ConvexHttpClient } from 'convex/browser';
7
7
  import { anyApi as api } from 'convex/server';
8
+ export { installRuntimePackage, runtimePrincipal, writeRuntimeRecord } from './runtime.mjs';
9
+ export { reportRuntimeResult, retryRuntimeResults } from './runtimeOutbox.mjs';
10
+ export { runRuntimeScript } from './runtimeScript.mjs';
11
+ export { serveRuntimeScriptHost, retryRuntimeHostResults } from './runtimeHost.mjs';
12
+ export { createRuntimeAgentHost } from './runtimeAgentHost.mjs';
13
+ export { createRuntimeAgentSession } from './runtimeAgent.mjs';
14
+ export { serveRuntimeMcp } from './runtimeMcp.mjs';
8
15
 
9
- export const VERSION = '0.1.34';
16
+ export const VERSION = '0.1.35-superconnector.2';
17
+ export { probeRuntimeGateway, runtimeSetupTemplate } from './runtimeSetup.mjs';
10
18
 
11
19
  export const DEFAULTS = {
12
20
  cn: {
@@ -226,6 +234,11 @@ function envProfile(profile) {
226
234
 
227
235
  function normalizeConvexError(error) {
228
236
  const message = error instanceof Error ? error.message : String(error || '');
237
+ // Convex validation errors embed the full submitted object, including session tokens.
238
+ // Never attach the original exception/stack as details for these errors.
239
+ if (/ArgumentValidationError/.test(message)) {
240
+ return new XiasheError('The server rejected the request arguments.', { code: 'CONVEX_INVALID_ARGUMENTS' });
241
+ }
229
242
  if (/fetch failed|failed to fetch|network request failed|econnrefused|enotfound|eai_again/i.test(message)) {
230
243
  return new XiasheError('Unable to reach Convex deployment.', {
231
244
  code: 'CONVEX_NETWORK_UNAVAILABLE',
@@ -443,6 +456,291 @@ export class XiasheClient {
443
456
  return this.query(api.connections.listForUser, this.authArgs());
444
457
  }
445
458
 
459
+ async listSaasProviders() {
460
+ return this.query(api.saasConnections.providers, this.authArgs());
461
+ }
462
+
463
+ async listSaasConnections() {
464
+ return this.query(api.saasConnections.list, this.authArgs());
465
+ }
466
+ async getSaasCredentialSetup(provider) {
467
+ return this.action(api.saasConnections.credentialSetupForProvider, this.authArgs({ provider }));
468
+ }
469
+
470
+ async getSaasCredentialStatus(requestKey) {
471
+ return this.query(api.saasConnections.credentialConnectionStatus, this.authArgs({ requestKey }));
472
+ }
473
+
474
+ async connectSaasCredentials(provider, { authType, values, requestKey, replaceConnectionId } = {}) {
475
+ if (!requestKey || !['api_key', 'custom_credential'].includes(authType) || !values || typeof values !== 'object' || Array.isArray(values))
476
+ throw new XiasheError('Provide credential values, auth type and a stable request key.', { code: 'INVALID_CONNECTION_REQUEST' });
477
+ const setup = await this.getSaasCredentialSetup(provider);
478
+ if (!setup.submissionEndpoint) throw new XiasheError('Credential endpoint is not configured.', { code: 'CONNECTION_SETUP_UNAVAILABLE' });
479
+ const url = new URL(setup.submissionEndpoint);
480
+ const rpc = new URL(this.profile.convexUrl);
481
+ const local = ['localhost', '127.0.0.1', '[::1]'];
482
+ if (url.username || url.password || url.search || url.hash || url.pathname !== '/superconnector/connections/credentials' ||
483
+ url.protocol !== 'https:' && !(url.protocol === 'http:' && local.includes(url.hostname) && local.includes(rpc.hostname)))
484
+ throw new XiasheError('Invalid credential endpoint.', { code: 'CONNECTION_SETUP_UNAVAILABLE' });
485
+ const body = JSON.stringify(this.authArgs({ provider, authType, values, requestKey, ...(replaceConnectionId ? { replaceConnectionId } : {}) }));
486
+ if (Buffer.byteLength(body) > 65536) throw new XiasheError('Credential input is too large.', { code: 'INVALID_CONNECTION_REQUEST' });
487
+ let response;
488
+ try {
489
+ response = await fetch(url, { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(60000),
490
+ headers: { 'content-type': 'application/json' }, body });
491
+ } catch {
492
+ throw new XiasheError('Connection result is unconfirmed. Check status with the same request key; do not create a new request.', { code: 'CONNECTION_RESULT_UNCONFIRMED' });
493
+ }
494
+ let result;
495
+ try { result = await response.json(); } catch {
496
+ throw new XiasheError('Connection result is unconfirmed. Check request status.', { code: 'CONNECTION_RESULT_UNCONFIRMED' });
497
+ }
498
+ if (!response.ok || !['CONNECTED', 'CONNECTION_REQUEST_EXISTS'].includes(result.code) || typeof result.connectionId !== 'string')
499
+ throw new XiasheError('Connection was not confirmed. Check request status before retrying.', { code: 'CONNECTION_NOT_CONFIRMED' });
500
+ return { connectionId: result.connectionId, requestKey };
501
+ }
502
+ async listSaasAuthorizationRequests() {
503
+ return this.query(api.saasConnections.requests, this.authArgs());
504
+ }
505
+
506
+ async connectSaas(provider, requestKey = createIdempotencyKey(), { authorizationOptionIds = [], reauthorizeConnectionId } = {}) {
507
+ return this.action(api.saasConnections.start, this.authArgs({ provider, requestKey, authorizationOptionIds,
508
+ ...(reauthorizeConnectionId ? { reauthorizeConnectionId } : {}) }));
509
+ }
510
+
511
+ async reauthorizeSaas(connectionId, { authorizationOptionIds = [], requestKey = createIdempotencyKey() } = {}) {
512
+ const connection = (await this.listSaasConnections()).find(row => row.id === connectionId);
513
+ if (!connection) throw new XiasheError('Connection not found.', { code: 'CONNECTION_NOT_FOUND' });
514
+ return this.connectSaas(connection.provider, requestKey, { authorizationOptionIds, reauthorizeConnectionId: connectionId });
515
+ }
516
+
517
+ async pollSaasAuthorization(id) {
518
+ return this.action(api.saasConnections.poll, this.authArgs({ id }));
519
+ }
520
+
521
+ async cancelSaasAuthorization(id) {
522
+ return this.mutation(api.saasConnections.cancelAuthorization, this.authArgs({ id }));
523
+ }
524
+
525
+ async disconnectSaas(connectionId) {
526
+ return this.mutation(api.saasConnections.disconnect, this.authArgs({ connectionId }));
527
+ }
528
+
529
+ async listConnectorActions() {
530
+ return this.query(api.superconnector.actions, this.authArgs());
531
+ }
532
+
533
+ async getConnectorActionDefinition(actionId) {
534
+ return this.action(api.superconnector.actionDefinition, this.authArgs({ actionId }));
535
+ }
536
+
537
+ async listConnectorGrants() {
538
+ return this.query(api.superconnector.grants, this.authArgs());
539
+ }
540
+
541
+ async grantConnector({ connectionId, publicAccess, creatorServiceId, actionIds, purpose, duration = 'once', releaseId }) {
542
+ return this.mutation(api.superconnector.grant, this.authArgs({ ...(creatorServiceId ? { creatorServiceId } : {}), ...(connectionId ? { connectionId } : {}), ...(publicAccess === undefined ? {} : { publicAccess }), actionIds, purpose, duration, ...(releaseId ? { releaseId } : {}) }));
543
+ }
544
+
545
+ async revokeConnectorGrant(grantId) {
546
+ return this.mutation(api.superconnector.revokeGrant, this.authArgs({ grantId }));
547
+ }
548
+
549
+ // Callers must persist requestKey before dispatch and reuse it on transport retries.
550
+ async executeConnector({ grantId, actionId, input = {}, requestKey, parentRunId, leaseToken }) {
551
+ if (typeof requestKey !== 'string' || !requestKey.trim()) throw new XiasheError('A stable requestKey is required.', { code: 'INVALID_INPUT' });
552
+ return this.mutation(api.superconnector.execute, this.authArgs({ grantId, actionId, inputJson: JSON.stringify(input), requestKey,
553
+ ...(parentRunId ? { parentRunId, leaseToken } : {}) }));
554
+ }
555
+
556
+ async listSkillRuns() {
557
+ return this.query(api.superconnector.listRuns, this.authArgs());
558
+ }
559
+
560
+ async getSkillRun(runId) {
561
+ return this.query(api.superconnector.getRun, this.authArgs({ runId }));
562
+ }
563
+
564
+ async cancelSkillRun(runId) {
565
+ return this.mutation(api.superconnector.cancel, this.authArgs({ runId }));
566
+ }
567
+
568
+ async listCreatorServices(releaseId) {
569
+ return this.query(api.skillRuntimeReleases.services, this.authArgs({ releaseId }));
570
+ }
571
+
572
+ async createCreatorService({ policy, ...options }) {
573
+ return this.mutation(api.skillRuntimeReleases.createService, this.authArgs({ ...options, policyJson: JSON.stringify(policy) }));
574
+ }
575
+
576
+ async revokeCreatorService(serviceId) {
577
+ return this.mutation(api.skillRuntimeReleases.revokeService, this.authArgs({ serviceId }));
578
+ }
579
+
580
+ async getCreatorServiceDefinition(serviceId) {
581
+ return this.query(api.superconnector.serviceDefinition, this.authArgs({ serviceId }));
582
+ }
583
+
584
+ async listRuntimeReleases(registryEntryId) {
585
+ return this.query(api.skillRuntimeReleases.list, this.authArgs({ registryEntryId }));
586
+ }
587
+
588
+ async listRuntimeReleasePage(registryEntryId, { cursor = null, limit = 20 } = {}) {
589
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 50 || cursor !== null && typeof cursor !== 'string')
590
+ throw new XiasheError('Use a page size from 1 to 50 and a returned cursor.', { code: 'INPUT_LIMIT' });
591
+ return this.query(api.skillRuntimeReleases.listPage, this.authArgs({ registryEntryId, paginationOpts: { numItems: limit, cursor } }));
592
+ }
593
+
594
+ async createRuntimeRelease({ registryEntryId, artifactId, version, contract, signature }) {
595
+ if (signature !== undefined) {
596
+ try {
597
+ const allowed = ['algorithm', 'keyId', 'publicJwk', 'signature', 'signedAt', 'manifest', 'packageHash', 'fileCount'];
598
+ const publicKey = JSON.parse(signature.publicJwk);
599
+ if (Object.keys(signature).some(key => !allowed.includes(key)) || Object.keys(publicKey).some(key => !['kty', 'crv', 'x', 'y', 'ext', 'key_ops', 'alg', 'use'].includes(key)) || Buffer.byteLength(JSON.stringify(signature)) > 200000) throw new Error();
600
+ } catch { throw new XiasheError('Provide a public signature envelope without private keys.', { code: 'INVALID_RUNTIME_SIGNATURE' }); }
601
+ }
602
+ return this.mutation(api.skillRuntimeReleases.create, this.authArgs({ registryEntryId, artifactId, version, contractJson: JSON.stringify(contract), ...(signature === undefined ? {} : { signatureJson: JSON.stringify(signature) }) }));
603
+ }
604
+
605
+ async createConfiguredRuntimeRelease({ registryEntryId, artifactId, version, accountSource, contract, services }) {
606
+ return this.mutation(api.skillRuntimeReleases.createConfigured, this.authArgs({ registryEntryId, artifactId, version, ...(accountSource ? { accountSource } : {}),
607
+ contractJson: JSON.stringify(contract), services: services.map(({ policy, ...service }) => ({ ...service, policyJson: JSON.stringify(policy) })) }));
608
+ }
609
+
610
+ async getRuntimeRelease(releaseId) {
611
+ return this.query(api.skillRuntimeReleases.get, this.authArgs({ releaseId }));
612
+ }
613
+
614
+ async downloadRuntimePackage(releaseId) {
615
+ const release = await this.getRuntimeRelease(releaseId);
616
+ return this.downloadVerifiedRuntimePackage(release, this.authArgs({ releaseId: release.id }));
617
+ }
618
+
619
+ async downloadVerifiedRuntimePackage(release, credentials) {
620
+ if (!release?.packageEndpoint) throw new XiasheError('Runtime package endpoint is not configured.', { code: 'RUNTIME_PACKAGE_UNAVAILABLE' });
621
+ const url = new URL(release.packageEndpoint);
622
+ const rpc = new URL(this.profile.convexUrl);
623
+ const local = ['localhost', '127.0.0.1', '[::1]'];
624
+ if (url.username || url.password || url.search || url.hash || url.pathname !== '/superconnector/package' ||
625
+ url.protocol !== 'https:' && !(url.protocol === 'http:' && local.includes(url.hostname) && local.includes(rpc.hostname)))
626
+ throw new XiasheError('Invalid runtime package endpoint.', { code: 'RUNTIME_PACKAGE_UNAVAILABLE' });
627
+ const response = await fetch(url, { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(60000),
628
+ headers: { 'content-type': 'application/json' }, body: JSON.stringify(credentials) });
629
+ if (!response.ok || !response.body || response.headers.get('x-artifact-sha256') !== release.artifactSha256)
630
+ throw new XiasheError('Runtime package download failed.', { code: 'RUNTIME_PACKAGE_UNAVAILABLE' });
631
+ let size = 0;
632
+ const chunks = [];
633
+ for await (const chunk of response.body) {
634
+ size += chunk.byteLength;
635
+ if (size > 100 * 1024 * 1024) throw new XiasheError('Package is too large.', { code: 'PACKAGE_LIMIT' });
636
+ chunks.push(chunk);
637
+ }
638
+ const bytes = Buffer.concat(chunks);
639
+ if (createHash('sha256').update(bytes).digest('hex') !== release.artifactSha256)
640
+ throw new XiasheError('Package checksum mismatch.', { code: 'PACKAGE_CHECKSUM_MISMATCH' });
641
+ return { release, bytes };
642
+ }
643
+
644
+ async prepareRuntimeRun(releaseId) {
645
+ return this.query(api.runtimeLaunch.preparation, this.authArgs({ releaseId }));
646
+ }
647
+
648
+ async launchRuntimeRun({ releaseId, input = {}, connections = [], publicActionIds = [], creatorServiceIds = [], requestKey, targetAgentConnectionId, executionMode }) {
649
+ if (!requestKey || !targetAgentConnectionId) throw new XiasheError('A stable request key and host are required.', { code: 'INVALID_INPUT' });
650
+ return this.mutation(api.runtimeLaunch.start, this.authArgs({ releaseId, inputJson: JSON.stringify(input), connections, publicActionIds, creatorServiceIds, requestKey, targetAgentConnectionId,
651
+ ...(executionMode ? { executionMode } : {}) }));
652
+ }
653
+
654
+ async startRuntimeRun({ releaseId, input = {}, grantIds = [], requestKey, targetAgentConnectionId, executionMode }) {
655
+ if (!requestKey) throw new XiasheError('A stable requestKey is required.', { code: 'INVALID_INPUT' });
656
+ return this.mutation(api.skillRuntimeRuns.start, this.authArgs({ releaseId, inputJson: JSON.stringify(input), grantIds, requestKey,
657
+ ...(targetAgentConnectionId ? { targetAgentConnectionId } : {}), ...(executionMode ? { executionMode } : {}) }));
658
+ }
659
+
660
+ async listRuntimeHosts() {
661
+ return this.query(api.runtimeHosts.list, this.authArgs());
662
+ }
663
+
664
+ runtimeHostAuth(extra = {}) {
665
+ const runtimeToken = this.profile.agent?.runtimeToken;
666
+ if (!runtimeToken) throw new XiasheError('Connect this Agent before starting a remote executor.', { code: 'HOST_AUTH_REQUIRED' });
667
+ return { ...extra, runtimeToken };
668
+ }
669
+
670
+ async announceRuntimeHost({ hostLeaseToken, modes }) {
671
+ return this.mutation(api.runtimeHosts.announce, this.runtimeHostAuth({ hostLeaseToken, modes }));
672
+ }
673
+
674
+ async runtimeHostIdentity() {
675
+ return this.mutation(api.runtimeHosts.identity, this.runtimeHostAuth());
676
+ }
677
+
678
+ async stopRuntimeHost(hostLeaseToken) {
679
+ return this.mutation(api.runtimeHosts.stop, this.runtimeHostAuth({ hostLeaseToken }));
680
+ }
681
+
682
+ async pendingRuntimeHostRuns(hostLeaseToken) {
683
+ return this.mutation(api.runtimeHosts.pending, this.runtimeHostAuth({ hostLeaseToken }));
684
+ }
685
+
686
+ async claimRuntimeHostRun({ hostLeaseToken, runId, leaseToken }) {
687
+ return this.mutation(api.runtimeHosts.claim, this.runtimeHostAuth({ hostLeaseToken, runId, leaseToken }));
688
+ }
689
+
690
+ async heartbeatRuntimeHostRun({ hostLeaseToken, runId, leaseToken, progressPercent, message }) {
691
+ return this.mutation(api.runtimeHosts.heartbeatRun, this.runtimeHostAuth({ hostLeaseToken, runId, leaseToken,
692
+ ...(progressPercent === undefined ? {} : { progressPercent }), ...(message === undefined ? {} : { message }) }));
693
+ }
694
+
695
+ async finishRuntimeHostRun({ runId, leaseToken, eventId, status, resultJson }) {
696
+ return this.mutation(api.runtimeHosts.finishRun, this.runtimeHostAuth({ runId, leaseToken, eventId, status, resultJson }));
697
+ }
698
+
699
+ async runtimeHostRunStatus(runId) {
700
+ return this.mutation(api.runtimeHosts.status, this.runtimeHostAuth({ runId }));
701
+ }
702
+
703
+ async runtimeHostRunContext({ hostLeaseToken, runId, leaseToken }) {
704
+ return this.mutation(api.runtimeHosts.context, this.runtimeHostAuth({ hostLeaseToken, runId, leaseToken }));
705
+ }
706
+
707
+ async downloadRuntimeHostPackage({ hostLeaseToken, runId, leaseToken }) {
708
+ const context = await this.runtimeHostRunContext({ hostLeaseToken, runId, leaseToken });
709
+ const downloaded = await this.downloadVerifiedRuntimePackage(context.release,
710
+ this.runtimeHostAuth({ remote: true, hostLeaseToken, runId, leaseToken }));
711
+ return { ...downloaded, context };
712
+ }
713
+
714
+ async executeRuntimeHostAction({ hostLeaseToken, runId, leaseToken, actionId, input = {}, requestKey }) {
715
+ return this.mutation(api.runtimeHosts.execute, this.runtimeHostAuth({ hostLeaseToken, runId, leaseToken,
716
+ actionId, inputJson: JSON.stringify(input), requestKey }));
717
+ }
718
+
719
+ async runtimeHostActionDefinition({ hostLeaseToken, runId, leaseToken, actionId }) {
720
+ return this.action(api.runtimeHosts.actionDefinition, this.runtimeHostAuth({ hostLeaseToken, runId, leaseToken, actionId }));
721
+ }
722
+
723
+ async runtimeHostActionStatus({ runId, actionRunId }) {
724
+ return this.mutation(api.runtimeHosts.actionStatus, this.runtimeHostAuth({ runId, actionRunId }));
725
+ }
726
+
727
+ async cancelRuntimeHostRun({ hostLeaseToken, runId, leaseToken }) {
728
+ return this.mutation(api.runtimeHosts.cancelRun, this.runtimeHostAuth({ hostLeaseToken, runId, leaseToken }));
729
+ }
730
+
731
+ async claimRuntimeRun({ runId, workerId, leaseToken }) {
732
+ return this.mutation(api.skillRuntimeRuns.claim, this.authArgs({ runId, workerId, leaseToken }));
733
+ }
734
+
735
+ async heartbeatRuntimeRun({ runId, leaseToken, progressPercent, message }) {
736
+ return this.mutation(api.skillRuntimeRuns.heartbeat, this.authArgs({ runId, leaseToken,
737
+ ...(progressPercent === undefined ? {} : { progressPercent }), ...(message === undefined ? {} : { message }) }));
738
+ }
739
+
740
+ async finishRuntimeRun({ runId, leaseToken, eventId, status, resultJson }) {
741
+ return this.mutation(api.skillRuntimeRuns.finish, this.authArgs({ runId, leaseToken, eventId, status, resultJson }));
742
+ }
743
+
446
744
  async resolveConnection(selector) {
447
745
  const rows = await this.listConnections();
448
746
  const needle = String(selector || '').trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiashe/sdk",
3
- "version": "0.1.34",
3
+ "version": "0.1.35-superconnector.2",
4
4
  "type": "module",
5
5
  "main": "index.mjs",
6
6
  "exports": {
@@ -11,5 +11,10 @@
11
11
  },
12
12
  "engines": {
13
13
  "node": ">=20"
14
- }
14
+ },
15
+ "files": [
16
+ "*.mjs",
17
+ "container/*.mjs",
18
+ "README.md"
19
+ ]
15
20
  }