@nylorun/runtime 0.8.0-beta → 0.10.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.
Files changed (130) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +96 -25
  3. package/dist/adapters/media.d.ts +2 -2
  4. package/dist/adapters/media.js +2 -2
  5. package/dist/adapters/observe.d.ts +2 -1
  6. package/dist/adapters/observe.js +2 -3
  7. package/dist/assets.d.ts +2 -2
  8. package/dist/assets.js +5 -3
  9. package/dist/core/executors.d.ts +11 -2
  10. package/dist/core/executors.js +20 -4
  11. package/dist/core/flow-host.d.ts +264 -0
  12. package/dist/core/flow-host.js +783 -0
  13. package/dist/core/limits.d.ts +40 -0
  14. package/dist/core/limits.js +76 -0
  15. package/dist/core/runtime.d.ts +5 -90
  16. package/dist/core/runtime.js +3 -1418
  17. package/dist/core/sandbox-routes.d.ts +35 -0
  18. package/dist/core/sandbox-routes.js +106 -0
  19. package/dist/core/store.d.ts +5 -2
  20. package/dist/core/store.js +20 -72
  21. package/dist/core/turn-manifest.d.ts +50 -0
  22. package/dist/core/turn-manifest.js +66 -0
  23. package/dist/host/config-for.d.ts +16 -0
  24. package/dist/host/config-for.js +45 -0
  25. package/dist/host/config.d.ts +24 -0
  26. package/dist/host/create-host.d.ts +21 -0
  27. package/dist/host/create-host.js +396 -0
  28. package/dist/host/environment.d.ts +13 -0
  29. package/dist/host/environment.js +53 -0
  30. package/dist/host/http.d.ts +35 -0
  31. package/dist/host/http.js +136 -0
  32. package/dist/host/index.d.ts +6 -0
  33. package/dist/host/index.js +5 -0
  34. package/dist/host/logger.d.ts +3 -0
  35. package/dist/host/logger.js +18 -0
  36. package/dist/host/main.d.ts +3 -0
  37. package/dist/host/main.js +142 -0
  38. package/dist/index.d.ts +7 -2
  39. package/dist/index.js +4 -2
  40. package/dist/launcher/commands.d.ts +24 -0
  41. package/dist/launcher/commands.js +260 -0
  42. package/dist/launcher/ensure-config.d.ts +16 -0
  43. package/dist/launcher/ensure-config.js +79 -0
  44. package/dist/launcher/errors.d.ts +12 -0
  45. package/dist/launcher/errors.js +22 -0
  46. package/dist/launcher/host-config.d.ts +17 -0
  47. package/dist/launcher/host-config.js +64 -0
  48. package/dist/launcher/host-state.d.ts +17 -0
  49. package/dist/launcher/host-state.js +61 -0
  50. package/dist/launcher/lifecycle.d.ts +66 -0
  51. package/dist/launcher/lifecycle.js +407 -0
  52. package/dist/launcher/locks.d.ts +26 -0
  53. package/dist/launcher/locks.js +117 -0
  54. package/dist/launcher/logs.d.ts +18 -0
  55. package/dist/launcher/logs.js +117 -0
  56. package/dist/launcher/main.d.ts +2 -0
  57. package/dist/launcher/main.js +66 -0
  58. package/dist/launcher/output.d.ts +11 -0
  59. package/dist/launcher/output.js +68 -0
  60. package/dist/launcher/paths.d.ts +20 -0
  61. package/dist/launcher/paths.js +36 -0
  62. package/dist/launcher/probe.d.ts +23 -0
  63. package/dist/launcher/probe.js +72 -0
  64. package/dist/launcher/protocol.d.ts +62 -0
  65. package/dist/launcher/protocol.js +1 -0
  66. package/dist/launcher/schema-guard.d.ts +6 -0
  67. package/dist/launcher/schema-guard.js +36 -0
  68. package/dist/launcher/spawn.d.ts +22 -0
  69. package/dist/launcher/spawn.js +85 -0
  70. package/dist/launcher/status.d.ts +6 -0
  71. package/dist/launcher/status.js +65 -0
  72. package/dist/mcp/connect.d.ts +1 -0
  73. package/dist/mcp/connect.js +2 -0
  74. package/dist/mcp/pool.d.ts +16 -2
  75. package/dist/mcp/pool.js +54 -19
  76. package/dist/mcp/snapshot.d.ts +4 -0
  77. package/dist/mcp/stdio.d.ts +2 -0
  78. package/dist/mcp/stdio.js +3 -1
  79. package/dist/model/auth-store.d.ts +1 -1
  80. package/dist/model/auth-store.js +2 -2
  81. package/dist/model/models.d.ts +4 -0
  82. package/dist/model/models.js +22 -26
  83. package/dist/model/pi-model.js +1 -1
  84. package/dist/model/settings.d.ts +5 -1
  85. package/dist/model/settings.js +6 -2
  86. package/dist/sandbox/select.d.ts +4 -3
  87. package/dist/sandbox/select.js +15 -9
  88. package/dist/sandbox/share.d.ts +23 -0
  89. package/dist/sandbox/share.js +58 -0
  90. package/dist/tenant/envelope.d.ts +10 -0
  91. package/dist/tenant/envelope.js +54 -0
  92. package/dist/tenant/ephemeral.d.ts +31 -0
  93. package/dist/tenant/ephemeral.js +156 -0
  94. package/dist/tenant/logger.d.ts +9 -0
  95. package/dist/tenant/logger.js +25 -0
  96. package/dist/tenant/migration.d.ts +16 -0
  97. package/dist/tenant/migration.js +116 -0
  98. package/dist/tenant/module.d.ts +17 -0
  99. package/dist/tenant/module.js +315 -0
  100. package/dist/tenant/paths.d.ts +24 -0
  101. package/dist/tenant/paths.js +65 -0
  102. package/dist/tenant/pool.d.ts +10 -0
  103. package/dist/tenant/pool.js +54 -0
  104. package/dist/tenant/principals.d.ts +17 -0
  105. package/dist/tenant/principals.js +44 -0
  106. package/dist/tenant/quarantine-error.d.ts +6 -0
  107. package/dist/tenant/quarantine-error.js +15 -0
  108. package/dist/tenant/quarantine.d.ts +35 -0
  109. package/dist/tenant/quarantine.js +94 -0
  110. package/dist/tenant/reset.d.ts +21 -0
  111. package/dist/tenant/reset.js +125 -0
  112. package/dist/tenant/runtime.d.ts +80 -0
  113. package/dist/tenant/runtime.js +2256 -0
  114. package/dist/tenant/schema.d.ts +23 -0
  115. package/dist/tenant/schema.js +151 -0
  116. package/dist/tenant/status.d.ts +28 -0
  117. package/dist/tenant/status.js +95 -0
  118. package/dist/tenant/store-fs.d.ts +16 -0
  119. package/dist/tenant/store-fs.js +205 -0
  120. package/dist/tenant/store-memory.d.ts +19 -0
  121. package/dist/tenant/store-memory.js +100 -0
  122. package/dist/tenant/types.d.ts +125 -0
  123. package/dist/tenant/types.js +1 -0
  124. package/dist/vault/kek.d.ts +5 -2
  125. package/dist/vault/kek.js +7 -10
  126. package/dist/version.d.ts +1 -1
  127. package/dist/version.js +1 -1
  128. package/package.json +9 -6
  129. package/dist/core/main.js +0 -35
  130. /package/dist/{core/main.d.ts → host/config.js} +0 -0
@@ -0,0 +1,65 @@
1
+ import { LAUNCHER_PROTOCOL } from "@nylorun/core/compatibility";
2
+ import { readHostConfig } from "./host-config.js";
3
+ import { readHostState } from "./host-state.js";
4
+ import { processAlive } from "./locks.js";
5
+ import { hostUrl } from "./paths.js";
6
+ import { portBindable, probeHostHealth } from "./probe.js";
7
+ /**
8
+ * Status with no lock and no mutation (D§9.3).
9
+ */
10
+ export async function status(paths, launcherVersion) {
11
+ const config = await readHostConfig(paths);
12
+ const stateFile = await readHostState(paths);
13
+ const runtimeVersion = config && typeof config.runtimeVersion === "string"
14
+ ? config.runtimeVersion
15
+ : undefined;
16
+ const base = {
17
+ launcherProtocol: LAUNCHER_PROTOCOL,
18
+ home: paths.root,
19
+ state: "absent",
20
+ launcherVersion,
21
+ ...(runtimeVersion ? { runtimeVersion } : {}),
22
+ };
23
+ if (!config) {
24
+ return base;
25
+ }
26
+ const url = hostUrl(config);
27
+ const withConfig = {
28
+ ...base,
29
+ state: "stopped",
30
+ url,
31
+ hostId: config.hostId,
32
+ ...(stateFile?.pid ? { pid: stateFile.pid } : {}),
33
+ ...(stateFile?.version ? { version: stateFile.version } : {}),
34
+ };
35
+ const probe = await probeHostHealth(url);
36
+ const ours = probe.health !== undefined &&
37
+ (probe.health.hostId === undefined ||
38
+ probe.health.hostId === config.hostId);
39
+ if (probe.foreign) {
40
+ return { ...withConfig, state: "foreign-port" };
41
+ }
42
+ if (probe.health && ours) {
43
+ return {
44
+ ...withConfig,
45
+ state: "running",
46
+ ...(probe.health.version ? { version: probe.health.version } : {}),
47
+ ...(probe.health.hostId ? { hostId: probe.health.hostId } : {}),
48
+ ...(probe.health.pid ? { pid: probe.health.pid } : {}),
49
+ };
50
+ }
51
+ if (probe.health && !ours) {
52
+ return {
53
+ ...withConfig,
54
+ state: "foreign-port",
55
+ ...(probe.health.hostId ? { hostId: probe.health.hostId } : {}),
56
+ };
57
+ }
58
+ if (stateFile && processAlive(stateFile.pid)) {
59
+ return { ...withConfig, state: "unresponsive", pid: stateFile.pid };
60
+ }
61
+ if (!(await portBindable(config.host, config.port))) {
62
+ return { ...withConfig, state: "foreign-port" };
63
+ }
64
+ return withConfig;
65
+ }
@@ -11,6 +11,7 @@ export declare function openMcpServer(input: {
11
11
  authorize?: (url: string) => Promise<AuthorizeResult>;
12
12
  pluginRoot?: string;
13
13
  pluginData: string;
14
+ childEnv?: Readonly<Record<string, string>>;
14
15
  }): Promise<LiveConnection>;
15
16
  export declare function listMcpTools(client: Client, input: {
16
17
  capabilityId: string;
@@ -10,11 +10,13 @@ export async function openMcpServer(input) {
10
10
  const launch = prepareStdioLaunch(input.server, {
11
11
  pluginRoot: input.pluginRoot,
12
12
  pluginData: input.pluginData,
13
+ childEnv: input.childEnv,
13
14
  });
14
15
  const transport = new StdioClientTransport({
15
16
  command: launch.command,
16
17
  args: [...launch.args],
17
18
  cwd: launch.cwd,
19
+ // Full explicit env so HOME/TMPDIR from the Tenant override getDefaultEnvironment().
18
20
  env: { ...launch.env },
19
21
  stderr: "pipe",
20
22
  });
@@ -1,4 +1,4 @@
1
- import type { AgentManifest } from "@nylorun/core/define";
1
+ import type { AgentManifest, McpServerManifest } from "@nylorun/core/define";
2
2
  import type { AuthorizeResult } from "../vault/service.js";
3
3
  import { callMcpTool } from "./connect.js";
4
4
  import { type McpDiagnostic, type McpSnapshot, type McpToolRecord } from "./snapshot.js";
@@ -6,7 +6,9 @@ export declare class McpPool {
6
6
  private readonly options;
7
7
  private readonly live;
8
8
  constructor(options: {
9
- readonly dataDir: string;
9
+ /** Absolute Tenant `plugin-data` directory. */
10
+ readonly pluginData: string;
11
+ readonly childEnv: Readonly<Record<string, string>>;
10
12
  readonly authorize: (sessionId: string, request: {
11
13
  url: string;
12
14
  serverName: string;
@@ -31,6 +33,7 @@ export declare class McpPool {
31
33
  }): Promise<McpDiagnostic[]>;
32
34
  call(input: {
33
35
  sessionId: string;
36
+ agentId?: string;
34
37
  capabilityId: string;
35
38
  serverName: string;
36
39
  serverToolName: string;
@@ -43,3 +46,14 @@ export declare class McpPool {
43
46
  private liveKey;
44
47
  private connectDeclared;
45
48
  }
49
+ interface DeclaredServer {
50
+ /** Absent for the session's root agent. */
51
+ readonly agentId?: string;
52
+ /** The manifest of the agent that declares the server. */
53
+ readonly manifest: AgentManifest;
54
+ readonly capabilityId: string;
55
+ readonly server: McpServerManifest;
56
+ }
57
+ /** Servers of the root agent, then of each agent it uses as a tool. */
58
+ export declare function serversOf(manifest: AgentManifest): DeclaredServer[];
59
+ export {};
package/dist/mcp/pool.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { join } from "node:path";
2
+ import { delegatesOf } from "@nylorun/core/define";
2
3
  import { callMcpTool, diagnosticFromError, listMcpTools, openMcpServer, } from "./connect.js";
3
4
  import { declaredToolNames, } from "./snapshot.js";
4
5
  export class McpPool {
@@ -8,7 +9,8 @@ export class McpPool {
8
9
  this.options = options;
9
10
  }
10
11
  async discover(input) {
11
- const taken = declaredToolNames(input.manifest);
12
+ // Each agent names its own tools, so collisions are checked per agent.
13
+ const taken = new Map();
12
14
  const tools = [];
13
15
  const diagnostics = [];
14
16
  for (const declared of serversOf(input.manifest)) {
@@ -19,13 +21,16 @@ export class McpPool {
19
21
  continue;
20
22
  }
21
23
  try {
24
+ if (!taken.has(declared.agentId))
25
+ taken.set(declared.agentId, declaredToolNames(declared.manifest));
22
26
  const listed = await listMcpTools(opened.connection.client, {
23
27
  capabilityId: declared.capabilityId,
24
28
  serverName: declared.server.name,
25
- taken,
29
+ taken: taken.get(declared.agentId),
26
30
  });
27
- tools.push(...listed.tools);
31
+ tools.push(...listed.tools.map((tool) => owned(declared, tool)));
28
32
  diagnostics.push({
33
+ ...ownerOf(declared),
29
34
  capabilityId: declared.capabilityId,
30
35
  serverName: declared.server.name,
31
36
  outcome: "connected",
@@ -37,7 +42,7 @@ export class McpPool {
37
42
  }
38
43
  catch (error) {
39
44
  await opened.connection.close().catch(() => { });
40
- diagnostics.push(diagnosticFromError(declared.capabilityId, declared.server.name, error));
45
+ diagnostics.push(owned(declared, diagnosticFromError(declared.capabilityId, declared.server.name, error)));
41
46
  }
42
47
  }
43
48
  return {
@@ -53,16 +58,17 @@ export class McpPool {
53
58
  const diagnostics = [];
54
59
  const seen = new Set();
55
60
  for (const tool of input.tools) {
56
- const key = `${tool.capabilityId}\0${tool.serverName}`;
61
+ const key = this.liveKey(input.sessionId, tool.agentId, tool.capabilityId, tool.serverName);
57
62
  if (seen.has(key))
58
63
  continue;
59
64
  seen.add(key);
60
- if (this.live.has(this.liveKey(input.sessionId, tool.capabilityId, tool.serverName)))
65
+ if (this.live.has(key))
61
66
  continue;
62
67
  input.signal?.throwIfAborted();
63
- const declared = serversOf(input.manifest).find((item) => item.capabilityId === tool.capabilityId && item.server.name === tool.serverName);
68
+ const declared = findServer(input.manifest, tool.agentId, tool.capabilityId, tool.serverName);
64
69
  if (!declared) {
65
70
  diagnostics.push({
71
+ ...(tool.agentId === undefined ? {} : { agentId: tool.agentId }),
66
72
  capabilityId: tool.capabilityId,
67
73
  serverName: tool.serverName,
68
74
  outcome: "failed",
@@ -80,10 +86,10 @@ export class McpPool {
80
86
  return diagnostics;
81
87
  }
82
88
  async call(input) {
83
- const key = this.liveKey(input.sessionId, input.capabilityId, input.serverName);
89
+ const key = this.liveKey(input.sessionId, input.agentId, input.capabilityId, input.serverName);
84
90
  let live = this.live.get(key);
85
91
  if (!live) {
86
- const declared = serversOf(input.manifest).find((item) => item.capabilityId === input.capabilityId && item.server.name === input.serverName);
92
+ const declared = findServer(input.manifest, input.agentId, input.capabilityId, input.serverName);
87
93
  if (!declared)
88
94
  throw new Error(`MCP server '${input.serverName}' is not declared`);
89
95
  const opened = await this.connectDeclared(input, declared);
@@ -102,21 +108,22 @@ export class McpPool {
102
108
  await Promise.all(connections.map((item) => item.connection.close().catch(() => { })));
103
109
  }
104
110
  remember(sessionId, declared, connection) {
105
- this.live.set(this.liveKey(sessionId, declared.capabilityId, declared.server.name), {
111
+ this.live.set(this.liveKey(sessionId, declared.agentId, declared.capabilityId, declared.server.name), {
106
112
  capabilityId: declared.capabilityId,
107
113
  serverName: declared.server.name,
108
114
  connection,
109
115
  });
110
116
  }
111
- liveKey(sessionId, capabilityId, serverName) {
112
- return JSON.stringify([sessionId, capabilityId, serverName]);
117
+ liveKey(sessionId, agentId, capabilityId, serverName) {
118
+ return JSON.stringify([sessionId, agentId ?? null, capabilityId, serverName]);
113
119
  }
114
120
  async connectDeclared(input, declared) {
115
121
  try {
116
122
  const connection = await openMcpServer({
117
123
  server: declared.server,
118
- pluginRoot: input.pluginRoots[declared.capabilityId],
119
- pluginData: join(this.options.dataDir, "plugin-data", declared.capabilityId),
124
+ pluginRoot: input.pluginRoots[pluginKey(declared)],
125
+ pluginData: join(this.options.pluginData, ...pluginKey(declared).split("/")),
126
+ childEnv: this.options.childEnv,
120
127
  authorize: declared.server.type === "stdio"
121
128
  ? undefined
122
129
  : (url) => this.options.authorize(input.sessionId, {
@@ -129,15 +136,43 @@ export class McpPool {
129
136
  catch (error) {
130
137
  return {
131
138
  ok: false,
132
- diagnostic: diagnosticFromError(declared.capabilityId, declared.server.name, error),
139
+ diagnostic: owned(declared, diagnosticFromError(declared.capabilityId, declared.server.name, error)),
133
140
  };
134
141
  }
135
142
  }
136
143
  }
137
- function serversOf(manifest) {
144
+ /** Servers of the root agent, then of each agent it uses as a tool. */
145
+ export function serversOf(manifest) {
138
146
  const servers = [];
139
- for (const capability of manifest.capabilities)
140
- for (const server of Object.values(capability.mcpServers ?? {}))
141
- servers.push({ capabilityId: capability.id, server });
147
+ const add = (agent, agentId) => {
148
+ for (const capability of agent.capabilities)
149
+ for (const server of Object.values(capability.mcpServers ?? {}))
150
+ servers.push({
151
+ ...(agentId === undefined ? {} : { agentId }),
152
+ manifest: agent,
153
+ capabilityId: capability.id,
154
+ server,
155
+ });
156
+ };
157
+ add(manifest);
158
+ for (const child of delegatesOf(manifest))
159
+ add(child.manifest, child.manifest.id);
142
160
  return servers;
143
161
  }
162
+ function findServer(manifest, agentId, capabilityId, serverName) {
163
+ return serversOf(manifest).find((item) => item.agentId === agentId &&
164
+ item.capabilityId === capabilityId &&
165
+ item.server.name === serverName);
166
+ }
167
+ /** Plugin roots of agents used as tools are registered as `<agent>/<capability>`. */
168
+ function pluginKey(declared) {
169
+ return declared.agentId === undefined
170
+ ? declared.capabilityId
171
+ : `${declared.agentId}/${declared.capabilityId}`;
172
+ }
173
+ function ownerOf(declared) {
174
+ return declared.agentId === undefined ? {} : { agentId: declared.agentId };
175
+ }
176
+ function owned(declared, value) {
177
+ return { ...ownerOf(declared), ...value };
178
+ }
@@ -1,6 +1,8 @@
1
1
  import type { JsonObject } from "@nylorun/core/define";
2
2
  import type { AgentManifest } from "@nylorun/core/define";
3
3
  export interface McpToolRecord {
4
+ /** The agent used as a tool that declares the server; absent for the session's root agent. */
5
+ readonly agentId?: string;
4
6
  readonly capabilityId: string;
5
7
  readonly serverName: string;
6
8
  readonly serverToolName: string;
@@ -15,6 +17,8 @@ export interface McpSnapshot {
15
17
  readonly mcpTools: readonly McpToolRecord[];
16
18
  }
17
19
  export interface McpDiagnostic {
20
+ /** The agent used as a tool that declares the server; absent for the session's root agent. */
21
+ readonly agentId?: string;
18
22
  readonly capabilityId: string;
19
23
  readonly serverName: string;
20
24
  readonly outcome: "connected" | "refused" | "failed";
@@ -12,4 +12,6 @@ export declare function prepareStdioLaunch(server: Extract<McpServerManifest, {
12
12
  }>, options: {
13
13
  readonly pluginRoot?: string;
14
14
  readonly pluginData: string;
15
+ /** Allowlisted base env (Tenant HOME/TMPDIR/PATH). Manifest env overlays it (A9). */
16
+ readonly childEnv?: Readonly<Record<string, string>>;
15
17
  }): StdioLaunch;
package/dist/mcp/stdio.js CHANGED
@@ -38,7 +38,9 @@ export function prepareStdioLaunch(server, options) {
38
38
  const cwd = server.cwd
39
39
  ? resolveContainedCwd(server.cwd, pluginRoot, pluginData)
40
40
  : (pluginRoot ?? pluginData);
41
- const env = {};
41
+ // childEnv under manifest env; HOME/TMPDIR from childEnv override Host defaults
42
+ // that StdioClientTransport merges via getDefaultEnvironment() (R2).
43
+ const env = { ...(options.childEnv ?? {}) };
42
44
  for (const [key, value] of Object.entries(server.env ?? {}))
43
45
  env[key] = expand(value, pluginRoot, pluginData);
44
46
  if (pluginRoot)
@@ -3,7 +3,7 @@ export declare class ProjectCredentialStore implements CredentialStore {
3
3
  #private;
4
4
  private readonly file;
5
5
  private readonly legacyFile?;
6
- constructor(file?: string, legacyFile?: string | undefined);
6
+ constructor(file: string, legacyFile?: string | undefined);
7
7
  read(providerId: string): Promise<Credential | undefined>;
8
8
  list(): Promise<readonly CredentialInfo[]>;
9
9
  modify(providerId: string, fn: (current: Credential | undefined) => Promise<Credential | undefined>): Promise<Credential | undefined>;
@@ -1,11 +1,11 @@
1
1
  var _a;
2
2
  import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
- import { dirname, join } from "node:path";
3
+ import { dirname } from "node:path";
4
4
  export class ProjectCredentialStore {
5
5
  file;
6
6
  legacyFile;
7
7
  #chain = Promise.resolve();
8
- constructor(file = join(process.cwd(), ".nylorun", "auth.json"), legacyFile) {
8
+ constructor(file, legacyFile) {
9
9
  this.file = file;
10
10
  this.legacyFile = legacyFile;
11
11
  }
@@ -6,6 +6,10 @@ export type Selection = Readonly<{
6
6
  baseUrl: string;
7
7
  }>;
8
8
  }>;
9
+ /**
10
+ * Build the pi-ai model registry from explicit credentials only.
11
+ * Ambient process environment / provider env auth is never consulted (A7, A10).
12
+ */
9
13
  export declare function modelsFor(selection: Selection, credentials: CredentialStore, options?: {
10
14
  environment?: boolean;
11
15
  }): import("@earendil-works/pi-ai").MutableModels;
@@ -1,31 +1,15 @@
1
- import { createProvider, defaultProviderAuthContext, envApiKeyAuth, } from "@earendil-works/pi-ai";
1
+ import { createProvider, } from "@earendil-works/pi-ai";
2
2
  import { stream, streamSimple, } from "@earendil-works/pi-ai/api/openai-completions";
3
3
  import { builtinModels } from "@earendil-works/pi-ai/providers/all";
4
+ /**
5
+ * Build the pi-ai model registry from explicit credentials only.
6
+ * Ambient process environment / provider env auth is never consulted (A7, A10).
7
+ */
4
8
  export function modelsFor(selection, credentials, options = {}) {
5
- const environmentFirst = {
6
- async read(providerId, options) {
7
- const explicit = process.env.MODEL_PROVIDER_API_KEY;
8
- if (explicit &&
9
- (!selection.provider || selection.provider === providerId))
10
- return { type: "api_key", key: explicit };
11
- const provider = models
12
- .getProviders()
13
- .find((item) => item.id === providerId);
14
- const ambient = await provider?.auth.apiKey?.resolve({
15
- ctx: defaultProviderAuthContext(),
16
- signal: options?.signal ?? new AbortController().signal,
17
- });
18
- if (ambient)
19
- return undefined;
20
- return credentials.read(providerId, options);
21
- },
22
- list: (options) => credentials.list(options),
23
- modify: (id, fn, options) => credentials.modify(id, fn, options),
24
- delete: (id, options) => credentials.delete(id, options),
25
- };
26
- const models = builtinModels({
27
- credentials: options.environment === false ? credentials : environmentFirst,
28
- });
9
+ if (options.environment) {
10
+ throw new Error("Ambient model environment is not supported; pass credentials explicitly");
11
+ }
12
+ const models = builtinModels({ credentials });
29
13
  if (!selection.custom)
30
14
  return models;
31
15
  const model = {
@@ -45,7 +29,19 @@ export function modelsFor(selection, credentials, options = {}) {
45
29
  name: "Custom OpenAI-compatible",
46
30
  baseUrl: selection.custom.baseUrl,
47
31
  auth: {
48
- apiKey: envApiKeyAuth("Custom API key", ["MODEL_PROVIDER_API_KEY"]),
32
+ apiKey: {
33
+ label: "Custom API key",
34
+ schemas: [],
35
+ async resolve() {
36
+ const credential = await credentials.read("custom");
37
+ if (!credential || credential.type !== "api_key")
38
+ return undefined;
39
+ const key = credential.key;
40
+ if (!key)
41
+ return undefined;
42
+ return { type: "api_key", auth: { apiKey: key } };
43
+ },
44
+ },
49
45
  },
50
46
  models: [model],
51
47
  api: { stream, streamSimple },
@@ -13,7 +13,7 @@ const emptyUsage = () => ({
13
13
  export function piModel(options = {}) {
14
14
  return async (call, context) => {
15
15
  context.signal.throwIfAborted();
16
- const root = options.root ?? process.cwd();
16
+ const root = options.root ?? "";
17
17
  const stored = options.readHostModel?.();
18
18
  if (!stored)
19
19
  throw new Error("Model provider is not configured. Start nylorun dev in a terminal, or set it in Studio.");
@@ -1 +1,5 @@
1
- export declare function projectSecrets(root?: string): readonly string[];
1
+ /**
2
+ * Collect likely secret strings from an explicit env map and optional auth.json
3
+ * under `root`. Callers must pass values; no ambient environment or cwd (A7).
4
+ */
5
+ export declare function projectSecrets(root: string, env?: Readonly<Record<string, string | undefined>>): readonly string[];
@@ -1,7 +1,11 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- export function projectSecrets(root = process.cwd()) {
4
- const values = Object.entries(process.env)
3
+ /**
4
+ * Collect likely secret strings from an explicit env map and optional auth.json
5
+ * under `root`. Callers must pass values; no ambient environment or cwd (A7).
6
+ */
7
+ export function projectSecrets(root, env = {}) {
8
+ const values = Object.entries(env)
5
9
  .filter(([key]) => /key|token|secret|password|credential/i.test(key))
6
10
  .flatMap(([, value]) => (value ? [value] : []));
7
11
  for (const directory of [".nylorun", ".env"])
@@ -7,7 +7,7 @@ export interface SandboxSelection {
7
7
  readonly reason: string;
8
8
  readonly probes: readonly SandboxProbe[];
9
9
  }
10
- /** Serializable view of a selection for the CLI and `GET /v1/host/sandbox`. */
10
+ /** Serializable view of a selection for the CLI and `GET /v1/tenant/sandbox`. */
11
11
  export interface SandboxSelectionReport {
12
12
  readonly preference: SandboxPreference;
13
13
  readonly backend: SandboxBackendName | null;
@@ -27,9 +27,10 @@ export declare function parseSandboxPreference(value: string | undefined): Sandb
27
27
  export declare function selectSandboxBackend(backends: readonly SandboxBackend[], preference: SandboxPreference): Promise<SandboxSelection>;
28
28
  export declare function reportSelection(selection: SandboxSelection): SandboxSelectionReport;
29
29
  /**
30
- * Probe this machine the way a Runtime would at startup. Used by `nylorun doctor sandbox`.
31
- * Nothing is created or booted.
30
+ * Probe backends the way a Tenant Runtime would at open.
31
+ * Callers must pass an explicit probe root; no ambient env or OS temp (A7).
32
32
  */
33
33
  export declare function probeSandboxBackends(options?: {
34
34
  readonly preference?: string;
35
+ readonly root?: string;
35
36
  }): Promise<SandboxSelectionReport>;
@@ -1,5 +1,3 @@
1
- import { tmpdir } from "node:os";
2
- import { join } from "node:path";
3
1
  import { microsandboxBackend } from "../adapters/sandbox/microsandbox.js";
4
2
  import { virtualBackend } from "../adapters/sandbox/virtual.js";
5
3
  const PREFERENCES = ["auto", "microsandbox", "virtual"];
@@ -26,10 +24,15 @@ export async function selectSandboxBackend(backends, preference) {
26
24
  const backend = backends.find((item) => item.name === preference);
27
25
  const probe = probeOf(preference);
28
26
  if (backend && probe?.available)
29
- return { preference, backend, reason: `${preference} selected by NYLORUN_SANDBOX`, probes };
27
+ return {
28
+ preference,
29
+ backend,
30
+ reason: `${preference} selected by TenantConfig.sandbox.backend`,
31
+ probes,
32
+ };
30
33
  return {
31
34
  preference,
32
- reason: `NYLORUN_SANDBOX=${preference} but ${preference} is unavailable: ${probe?.reason ?? "unknown backend"}. Fix it, or unset NYLORUN_SANDBOX to use the best available backend`,
35
+ reason: `sandbox.backend=${preference} but ${preference} is unavailable: ${probe?.reason ?? "unknown backend"}. Fix it, or set backend to auto to use the best available backend`,
33
36
  probes,
34
37
  };
35
38
  }
@@ -58,18 +61,21 @@ export function reportSelection(selection) {
58
61
  };
59
62
  }
60
63
  /**
61
- * Probe this machine the way a Runtime would at startup. Used by `nylorun doctor sandbox`.
62
- * Nothing is created or booted.
64
+ * Probe backends the way a Tenant Runtime would at open.
65
+ * Callers must pass an explicit probe root; no ambient env or OS temp (A7).
63
66
  */
64
67
  export async function probeSandboxBackends(options = {}) {
65
- const preference = parseSandboxPreference(options.preference ?? process.env.NYLORUN_SANDBOX);
66
- const backends = defaultSandboxBackends({ root: join(tmpdir(), "nylorun-sandbox-probe") });
68
+ if (!options.root) {
69
+ throw new Error("probeSandboxBackends requires an explicit root (Tenant or Host tmp path)");
70
+ }
71
+ const preference = parseSandboxPreference(options.preference ?? "auto");
72
+ const backends = defaultSandboxBackends({ root: options.root });
67
73
  if (!preference)
68
74
  return {
69
75
  preference: "auto",
70
76
  backend: null,
71
77
  isolation: null,
72
- reason: "NYLORUN_SANDBOX must be auto, microsandbox or virtual",
78
+ reason: "sandbox.backend must be auto, microsandbox or virtual",
73
79
  probes: await Promise.all(backends.map((backend) => backend.probe())),
74
80
  };
75
81
  return reportSelection(await selectSandboxBackend(backends, preference));
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Shared workflow sandbox: one sandbox per owning session, attached via
3
+ * PutSession.sandbox = { session }. Specs must match; keys use the owner id.
4
+ */
5
+ import { type AgentManifest, type CapabilityManifest, type SandboxManifest, type WorkflowManifest } from "@nylorun/core/define";
6
+ export type SessionSandboxRef = {
7
+ readonly id: string;
8
+ readonly ownerUserId: string;
9
+ readonly manifest: AgentManifest | WorkflowManifest | Record<string, unknown>;
10
+ /** Session whose id keys the sandbox. Absent means this session owns its own. */
11
+ readonly sandboxOwnerId?: string | null;
12
+ };
13
+ /** Sandbox spec declared on an agent capability or a workflow manifest. */
14
+ export declare function sandboxSpecOf(manifest: AgentManifest | WorkflowManifest | Record<string, unknown> | undefined): SandboxManifest | undefined;
15
+ export declare function sandboxSpecsEqual(a: SandboxManifest | undefined, b: SandboxManifest | undefined): boolean;
16
+ /** Synthetic capability so SandboxManager.run can use a workflow (or shared) spec. */
17
+ export declare function capabilityForSandbox(sandbox: SandboxManifest): CapabilityManifest;
18
+ /**
19
+ * Resolve the session id that keys the sandbox. Walks one hop via sandboxOwnerId;
20
+ * cycles fall back to the starting session.
21
+ */
22
+ export declare function owningSandboxSessionId(session: SessionSandboxRef, lookup: (id: string) => SessionSandboxRef | undefined): string;
23
+ export declare function sessionHasSandbox(session: SessionSandboxRef, lookup: (id: string) => SessionSandboxRef | undefined): boolean;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Shared workflow sandbox: one sandbox per owning session, attached via
3
+ * PutSession.sandbox = { session }. Specs must match; keys use the owner id.
4
+ */
5
+ import { canonical, } from "@nylorun/core/define";
6
+ /** Sandbox spec declared on an agent capability or a workflow manifest. */
7
+ export function sandboxSpecOf(manifest) {
8
+ if (!manifest || typeof manifest !== "object")
9
+ return undefined;
10
+ if (manifest.kind === "workflow")
11
+ return manifest.sandbox;
12
+ const capabilities = manifest.capabilities;
13
+ if (!Array.isArray(capabilities))
14
+ return undefined;
15
+ for (const capability of capabilities)
16
+ if (capability.sandbox)
17
+ return capability.sandbox;
18
+ return undefined;
19
+ }
20
+ export function sandboxSpecsEqual(a, b) {
21
+ if (a === undefined && b === undefined)
22
+ return true;
23
+ if (a === undefined || b === undefined)
24
+ return false;
25
+ return canonical(a) === canonical(b);
26
+ }
27
+ /** Synthetic capability so SandboxManager.run can use a workflow (or shared) spec. */
28
+ export function capabilityForSandbox(sandbox) {
29
+ return {
30
+ id: "sandbox",
31
+ type: "agent",
32
+ sandbox,
33
+ tools: [],
34
+ };
35
+ }
36
+ /**
37
+ * Resolve the session id that keys the sandbox. Walks one hop via sandboxOwnerId;
38
+ * cycles fall back to the starting session.
39
+ */
40
+ export function owningSandboxSessionId(session, lookup) {
41
+ const seen = new Set();
42
+ let current = session;
43
+ while (current) {
44
+ if (seen.has(current.id))
45
+ return session.id;
46
+ seen.add(current.id);
47
+ const ownerId = current.sandboxOwnerId;
48
+ if (!ownerId || ownerId === current.id)
49
+ return current.id;
50
+ current = lookup(ownerId);
51
+ }
52
+ return session.id;
53
+ }
54
+ export function sessionHasSandbox(session, lookup) {
55
+ const ownerId = owningSandboxSessionId(session, lookup);
56
+ const owner = ownerId === session.id ? session : lookup(ownerId) ?? session;
57
+ return sandboxSpecOf(owner.manifest) !== undefined || sandboxSpecOf(session.manifest) !== undefined;
58
+ }
@@ -0,0 +1,10 @@
1
+ import { type TenantEnvelope } from "@nylorun/core/contracts";
2
+ export declare function parseEnvelope(raw: unknown, expectedId?: string): TenantEnvelope;
3
+ export declare function readEnvelopeFile(path: string, expectedId?: string): TenantEnvelope;
4
+ /** Write envelope via temp sibling then rename (atomic on the same filesystem). */
5
+ export declare function writeEnvelopeFile(envelopePath: string, envelope: TenantEnvelope): void;
6
+ export declare function envelopeNow(input: {
7
+ id: string;
8
+ name: string;
9
+ schemaVersion: number;
10
+ }, now?: Date): TenantEnvelope;