@nylorun/runtime 0.5.0-beta → 0.7.0-beta
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/CHANGELOG.md +45 -0
- package/README.md +30 -60
- package/dist/config.d.ts +1 -1
- package/dist/configuration.d.ts +3 -0
- package/dist/configuration.js +3 -0
- package/dist/contracts.d.ts +4 -2
- package/dist/core/main.js +40 -0
- package/dist/core/provider.d.ts +12 -0
- package/dist/core/provider.js +60 -0
- package/dist/core/runtime.d.ts +67 -0
- package/dist/core/runtime.js +1126 -0
- package/dist/core/store.d.ts +17 -0
- package/dist/core/store.js +146 -0
- package/dist/index.d.ts +6 -11
- package/dist/index.js +4 -6
- package/dist/mcp/connect.d.ts +31 -0
- package/dist/mcp/connect.js +190 -0
- package/dist/mcp/pool.d.ts +45 -0
- package/dist/mcp/pool.js +143 -0
- package/dist/mcp/snapshot.d.ts +25 -0
- package/dist/mcp/snapshot.js +10 -0
- package/dist/mcp/stdio.d.ts +15 -0
- package/dist/mcp/stdio.js +80 -0
- package/dist/model/defaults.d.ts +1 -1
- package/dist/model/http-model.d.ts +1 -1
- package/dist/node/local-sessions.js +17 -0
- package/dist/server/host.d.ts +42 -4
- package/dist/server/host.js +69 -3
- package/dist/session/api.d.ts +10 -0
- package/dist/session/api.js +15 -0
- package/dist/session/default.d.ts +5 -0
- package/dist/session/default.js +30 -0
- package/dist/session/handle.d.ts +27 -0
- package/dist/session/handle.js +199 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/sessions/host.d.ts +11 -1
- package/dist/sessions/host.js +75 -7
- package/dist/sessions/store.d.ts +1 -0
- package/dist/sessions/store.js +3 -0
- package/dist/vault/crypto.d.ts +12 -0
- package/dist/vault/crypto.js +80 -0
- package/dist/vault/error.d.ts +4 -0
- package/dist/vault/error.js +7 -0
- package/dist/vault/kek.d.ts +6 -0
- package/dist/vault/kek.js +26 -0
- package/dist/vault/service.d.ts +59 -0
- package/dist/vault/service.js +538 -0
- package/dist/vault/url.d.ts +3 -0
- package/dist/vault/url.js +27 -0
- package/package.json +19 -13
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -100
- package/dist/dev-entry.js +0 -2
- package/dist/dev.d.ts +0 -2
- package/dist/dev.js +0 -126
- package/dist/environment.d.ts +0 -2
- package/dist/environment.js +0 -64
- package/dist/launcher.d.ts +0 -1
- package/dist/launcher.js +0 -33
- package/dist/model/configure.d.ts +0 -12
- package/dist/model/configure.js +0 -155
- /package/dist/{dev-entry.d.ts → core/main.d.ts} +0 -0
package/dist/sessions/host.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { isHarnessError } from "@nylorun/
|
|
1
|
+
import { HarnessError, isHarnessError } from "@nylorun/core/define";
|
|
2
|
+
import { bindingFromAgent, createRunState, run, } from "@nylorun/harness/run";
|
|
2
3
|
import { scrub } from "../redact.js";
|
|
3
4
|
/** The built-in single-process coordinator. Storage adapters do not implement execution. */
|
|
4
5
|
export class SessionHost {
|
|
@@ -26,6 +27,19 @@ export class SessionHost {
|
|
|
26
27
|
? { ...session, status: "interrupted" }
|
|
27
28
|
: session);
|
|
28
29
|
}
|
|
30
|
+
async delete(agentId, sessionId) {
|
|
31
|
+
const key = this.key(agentId, sessionId);
|
|
32
|
+
if (this.active.has(key))
|
|
33
|
+
throw new Error("Cannot delete a session while a run is active");
|
|
34
|
+
if ("delete" in this.store && typeof this.store.delete === "function")
|
|
35
|
+
return this.store.delete(agentId, sessionId);
|
|
36
|
+
const existing = await this.store.get(agentId, sessionId);
|
|
37
|
+
if (!existing)
|
|
38
|
+
return false;
|
|
39
|
+
// Stores without delete: archive by removing via put tombstone is not supported;
|
|
40
|
+
// require ManagedSessionStore.delete or memorySessions.delete.
|
|
41
|
+
throw new Error("Session store does not support delete");
|
|
42
|
+
}
|
|
29
43
|
subscribe(agentId, sessionId, listener) {
|
|
30
44
|
const key = this.key(agentId, sessionId);
|
|
31
45
|
let listeners = this.listeners.get(key);
|
|
@@ -144,17 +158,21 @@ export class SessionHost {
|
|
|
144
158
|
...(typeof input === "string" ? { input } : {}),
|
|
145
159
|
});
|
|
146
160
|
await save();
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
161
|
+
const binding = bindingFromAgent(agent);
|
|
162
|
+
const engineInput = resolveEngineInput(document.state, input);
|
|
163
|
+
const state = ensureEngineState(binding, document.state, options.sessionState);
|
|
164
|
+
const result = await run({
|
|
165
|
+
binding,
|
|
166
|
+
state,
|
|
167
|
+
input: engineInput,
|
|
150
168
|
info: options.info,
|
|
151
169
|
signal: controller.signal,
|
|
152
170
|
onModelCall: options.onModelCall,
|
|
153
171
|
onEvent: (event) => {
|
|
154
172
|
add(event.type, event);
|
|
155
173
|
},
|
|
156
|
-
record: async (
|
|
157
|
-
document = { ...document, state };
|
|
174
|
+
record: async (next) => {
|
|
175
|
+
document = { ...document, state: next };
|
|
158
176
|
await save();
|
|
159
177
|
},
|
|
160
178
|
});
|
|
@@ -235,7 +253,9 @@ export class SessionHost {
|
|
|
235
253
|
throw new Error("This session was interrupted; reconcile its external effects before cancellation.");
|
|
236
254
|
if (stored?.state?.status !== "paused")
|
|
237
255
|
return;
|
|
238
|
-
const
|
|
256
|
+
const binding = bindingFromAgent(agent);
|
|
257
|
+
const result = await run({
|
|
258
|
+
binding,
|
|
239
259
|
state: stored.state,
|
|
240
260
|
input: { kind: "continue" },
|
|
241
261
|
signal: AbortSignal.abort(new Error("Paused execution cancelled")),
|
|
@@ -289,3 +309,51 @@ export class SessionHost {
|
|
|
289
309
|
await this.store.close();
|
|
290
310
|
}
|
|
291
311
|
}
|
|
312
|
+
function ensureEngineState(binding, existing, sessionState) {
|
|
313
|
+
if (existing) {
|
|
314
|
+
if (!sessionState || Object.keys(existing.state ?? {}).length > 0)
|
|
315
|
+
return existing;
|
|
316
|
+
return {
|
|
317
|
+
...existing,
|
|
318
|
+
state: { ...(existing.state ?? {}), ...sessionState },
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
return createRunState(binding, {
|
|
322
|
+
...(sessionState ? { state: sessionState } : {}),
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Map DX `wait-resolve` onto harness approve/respond/settle using wait metadata.
|
|
327
|
+
* Engine `ExecutionInput` already includes `wait-resolve`; resume is wired via
|
|
328
|
+
* existing interaction/deferred kinds until full wait-id resume lands.
|
|
329
|
+
*/
|
|
330
|
+
export function resolveEngineInput(state, input) {
|
|
331
|
+
if (typeof input !== "object" || !("kind" in input) || input.kind !== "wait-resolve")
|
|
332
|
+
return input;
|
|
333
|
+
const call = state?.plan?.calls.find((item) => item.wait?.waitId === input.waitId);
|
|
334
|
+
if (!call)
|
|
335
|
+
throw new HarnessError("execution.invalid-input", "No pending wait matches waitId");
|
|
336
|
+
if (call.status === "interaction" && call.interaction) {
|
|
337
|
+
if (call.interaction.kind === "approval")
|
|
338
|
+
return {
|
|
339
|
+
kind: "approve",
|
|
340
|
+
interactionId: call.interaction.id,
|
|
341
|
+
approved: input.value !== false && input.value !== "false",
|
|
342
|
+
};
|
|
343
|
+
return {
|
|
344
|
+
kind: "respond",
|
|
345
|
+
interactionId: call.interaction.id,
|
|
346
|
+
value: input.value ?? null,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
if (call.status === "deferred")
|
|
350
|
+
return {
|
|
351
|
+
kind: "settle",
|
|
352
|
+
invocationId: call.invocationId,
|
|
353
|
+
outcome: {
|
|
354
|
+
kind: "completed",
|
|
355
|
+
output: input.value ?? null,
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
throw new HarnessError("execution.invalid-input", "Wait is no longer pending");
|
|
359
|
+
}
|
package/dist/sessions/store.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export interface SessionStore {
|
|
|
32
32
|
get(agentId: string, sessionId: string): Promise<StoredSession | undefined>;
|
|
33
33
|
put(agentId: string, sessionId: string, session: StoredSession): Promise<void>;
|
|
34
34
|
list(agentId: string): Promise<readonly SessionSummary[]>;
|
|
35
|
+
delete?(agentId: string, sessionId: string): Promise<boolean>;
|
|
35
36
|
}
|
|
36
37
|
export interface ManagedSessionStore extends SessionStore {
|
|
37
38
|
close(): Promise<void>;
|
package/dist/sessions/store.js
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class VaultCryptoError extends Error {
|
|
2
|
+
constructor();
|
|
3
|
+
}
|
|
4
|
+
export declare function kekId(kek: Buffer): string;
|
|
5
|
+
export declare function parseKek(value: Buffer | string): Buffer;
|
|
6
|
+
export declare function encryptSecret(kek: Buffer, aad: Buffer, plaintext: Buffer): {
|
|
7
|
+
nonce: Buffer;
|
|
8
|
+
ciphertext: Buffer;
|
|
9
|
+
wrappedDek: Buffer;
|
|
10
|
+
kekId: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function decryptSecret(kek: Buffer, aad: Buffer, nonce: Buffer, ciphertext: Buffer, wrappedDek: Buffer, expectedKekId: string): Buffer;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes, } from "node:crypto";
|
|
2
|
+
const IV_BYTES = 12;
|
|
3
|
+
const TAG_BYTES = 16;
|
|
4
|
+
export class VaultCryptoError extends Error {
|
|
5
|
+
constructor() {
|
|
6
|
+
super("Vault credential could not be read");
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function kekId(kek) {
|
|
10
|
+
return createHash("sha256").update(kek).digest("hex");
|
|
11
|
+
}
|
|
12
|
+
export function parseKek(value) {
|
|
13
|
+
const bytes = typeof value === "string" ? Buffer.from(value.trim(), "base64") : value;
|
|
14
|
+
if (bytes.length !== 32)
|
|
15
|
+
throw new Error("Vault key-encryption key must be 32 bytes");
|
|
16
|
+
return Buffer.from(bytes);
|
|
17
|
+
}
|
|
18
|
+
export function encryptSecret(kek, aad, plaintext) {
|
|
19
|
+
const dek = randomBytes(32);
|
|
20
|
+
try {
|
|
21
|
+
const nonce = randomBytes(IV_BYTES);
|
|
22
|
+
const cipher = createCipheriv("aes-256-gcm", dek, nonce);
|
|
23
|
+
cipher.setAAD(aad);
|
|
24
|
+
const body = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
25
|
+
const tag = cipher.getAuthTag();
|
|
26
|
+
return {
|
|
27
|
+
nonce,
|
|
28
|
+
ciphertext: Buffer.concat([body, tag]),
|
|
29
|
+
wrappedDek: wrapKey(kek, dek),
|
|
30
|
+
kekId: kekId(kek),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
dek.fill(0);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function decryptSecret(kek, aad, nonce, ciphertext, wrappedDek, expectedKekId) {
|
|
38
|
+
if (expectedKekId !== kekId(kek))
|
|
39
|
+
throw new VaultCryptoError();
|
|
40
|
+
const dek = unwrapKey(kek, wrappedDek);
|
|
41
|
+
try {
|
|
42
|
+
if (ciphertext.length < TAG_BYTES)
|
|
43
|
+
throw new VaultCryptoError();
|
|
44
|
+
const tag = ciphertext.subarray(ciphertext.length - TAG_BYTES);
|
|
45
|
+
const body = ciphertext.subarray(0, ciphertext.length - TAG_BYTES);
|
|
46
|
+
const decipher = createDecipheriv("aes-256-gcm", dek, nonce);
|
|
47
|
+
decipher.setAAD(aad);
|
|
48
|
+
decipher.setAuthTag(tag);
|
|
49
|
+
return Buffer.concat([decipher.update(body), decipher.final()]);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof VaultCryptoError)
|
|
53
|
+
throw error;
|
|
54
|
+
throw new VaultCryptoError();
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
dek.fill(0);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function wrapKey(kek, dek) {
|
|
61
|
+
const nonce = randomBytes(IV_BYTES);
|
|
62
|
+
const cipher = createCipheriv("aes-256-gcm", kek, nonce);
|
|
63
|
+
const body = Buffer.concat([cipher.update(dek), cipher.final()]);
|
|
64
|
+
return Buffer.concat([nonce, body, cipher.getAuthTag()]);
|
|
65
|
+
}
|
|
66
|
+
function unwrapKey(kek, wrapped) {
|
|
67
|
+
try {
|
|
68
|
+
if (wrapped.length < IV_BYTES + TAG_BYTES)
|
|
69
|
+
throw new VaultCryptoError();
|
|
70
|
+
const nonce = wrapped.subarray(0, IV_BYTES);
|
|
71
|
+
const tag = wrapped.subarray(wrapped.length - TAG_BYTES);
|
|
72
|
+
const body = wrapped.subarray(IV_BYTES, wrapped.length - TAG_BYTES);
|
|
73
|
+
const decipher = createDecipheriv("aes-256-gcm", kek, nonce);
|
|
74
|
+
decipher.setAuthTag(tag);
|
|
75
|
+
return Buffer.concat([decipher.update(body), decipher.final()]);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
throw new VaultCryptoError();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { parseKek } from "./crypto.js";
|
|
5
|
+
export function defaultKekPath() {
|
|
6
|
+
return join(process.cwd(), ".nylorun", "vault-kek");
|
|
7
|
+
}
|
|
8
|
+
export function readVaultKek(options) {
|
|
9
|
+
if (options.vaultKek === null)
|
|
10
|
+
return undefined;
|
|
11
|
+
if (options.vaultKek !== undefined)
|
|
12
|
+
return parseKek(options.vaultKek);
|
|
13
|
+
const fromEnv = process.env.NYLORUN_VAULT_KEK;
|
|
14
|
+
if (fromEnv)
|
|
15
|
+
return parseKek(fromEnv);
|
|
16
|
+
const path = options.vaultKekPath ?? defaultKekPath();
|
|
17
|
+
if (!existsSync(path))
|
|
18
|
+
return undefined;
|
|
19
|
+
return parseKek(readFileSync(path, "utf8"));
|
|
20
|
+
}
|
|
21
|
+
export function createKekFile(path) {
|
|
22
|
+
const bytes = randomBytes(32);
|
|
23
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
24
|
+
writeFileSync(path, `${bytes.toString("base64")}\n`, { mode: 0o600 });
|
|
25
|
+
return bytes;
|
|
26
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import type { CreateCredentialRequest, CreateVaultRequest, CredentialInfo, CredentialSelection, RotateCredentialRequest, VaultInfo } from "@nylorun/core/contracts";
|
|
3
|
+
export type AuthorizeResult = {
|
|
4
|
+
status: "unauthenticated";
|
|
5
|
+
url: string;
|
|
6
|
+
headers: Record<string, string>;
|
|
7
|
+
} | {
|
|
8
|
+
status: "authorized";
|
|
9
|
+
url: string;
|
|
10
|
+
headers: {
|
|
11
|
+
authorization: string;
|
|
12
|
+
};
|
|
13
|
+
} | {
|
|
14
|
+
status: "refused";
|
|
15
|
+
url: string;
|
|
16
|
+
credentialIds: string[];
|
|
17
|
+
reason: string;
|
|
18
|
+
};
|
|
19
|
+
export declare class VaultService {
|
|
20
|
+
private readonly db;
|
|
21
|
+
private readonly tx;
|
|
22
|
+
private readonly kek;
|
|
23
|
+
private readonly fetchImpl;
|
|
24
|
+
constructor(db: DatabaseSync, tx: <T>(fn: () => T) => T, kek: () => Buffer, fetchImpl: typeof fetch);
|
|
25
|
+
createVault(body: CreateVaultRequest): VaultInfo;
|
|
26
|
+
listVaults(ownerUserId: string): VaultInfo[];
|
|
27
|
+
getVault(id: string): VaultInfo;
|
|
28
|
+
deleteVault(id: string): {
|
|
29
|
+
id: string;
|
|
30
|
+
};
|
|
31
|
+
createCredential(vaultId: string, body: CreateCredentialRequest): CredentialInfo;
|
|
32
|
+
listCredentials(vaultId: string): CredentialInfo[];
|
|
33
|
+
getCredential(vaultId: string, id: string): CredentialInfo;
|
|
34
|
+
rotateCredential(vaultId: string, id: string, body: RotateCredentialRequest): CredentialInfo;
|
|
35
|
+
deleteCredential(vaultId: string, id: string): {
|
|
36
|
+
id: string;
|
|
37
|
+
};
|
|
38
|
+
assertAttachment(ownerUserId: string, vaultIds: readonly string[], selections: readonly CredentialSelection[]): void;
|
|
39
|
+
recordAttachment(sessionId: string, vaultIds: readonly string[]): void;
|
|
40
|
+
authorize(input: {
|
|
41
|
+
sessionId: string;
|
|
42
|
+
vaultIds: readonly string[];
|
|
43
|
+
credentialSelections: readonly CredentialSelection[];
|
|
44
|
+
url: string;
|
|
45
|
+
serverName?: string;
|
|
46
|
+
}): Promise<AuthorizeResult>;
|
|
47
|
+
reject(route: string): void;
|
|
48
|
+
private refresh;
|
|
49
|
+
private replay;
|
|
50
|
+
private insertCredential;
|
|
51
|
+
private writePayload;
|
|
52
|
+
private readPayload;
|
|
53
|
+
private vaultInfo;
|
|
54
|
+
private vaultRow;
|
|
55
|
+
private credentialInfo;
|
|
56
|
+
private credentialRow;
|
|
57
|
+
private rowsForVault;
|
|
58
|
+
private audit;
|
|
59
|
+
}
|