@nylorun/runtime 0.8.0-beta → 0.9.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 CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0-beta
4
+
5
+ ### Minor Changes
6
+
7
+ - 1cd7dc7: Add subagents: put an agent in another agent's `tools` (`Agent({ tools: [lookupOrder, researcher] })` or `.use({ tools: [researcher] })`) and the model can delegate to it. The tool is named after the agent's id, takes `{ task: string }`, and its description is the agent's `description`, which is now required for an agent used as a tool. The engine runs the child inside the parent's turn as a durable branch: fresh context, its own tools and hooks served by the root agent's executor, its own MCP servers, the session's sandbox, and only its final output (or `outputSchema` result) returned. Empty output, failures (with partial output marked as evidence), and requests for input or approval inside a child reach the parent as failed tool results. Parallel delegation calls run concurrently, completed child work is never re-run on replay, and cancelling the session cancels every child.
8
+
9
+ v1 is one level deep and non-interactive. Nested delegation, child tools that declare `approval`, and differing sandboxes across the tree fail the build with a named diagnostic. The manifest adds an optional `agent` body on a tool (schema version unchanged), actions and effects carry `agent: { id, path, delegationId }`, tool context gains `ctx.agent`, the durable host resolves a new `delegation` effect kind, and the Runtime emits `delegation.started` / `delegation.completed` events and filters history with `?agent=` (`session.history({ agent })`). Studio shows delegations, labels child actions with their agent, and filters events by agent.
10
+
11
+ ### Patch Changes
12
+
13
+ - Pin core to the tested release.
14
+ - Pin harness to the tested release.
15
+ - Updated dependencies [1cd7dc7]
16
+ - Updated dependencies
17
+ - @nylorun/core@0.4.0-beta
18
+ - @nylorun/harness@0.18.0-beta
19
+
3
20
  ## 0.8.0-beta
4
21
 
5
22
  ### Minor Changes
@@ -7,7 +7,7 @@ import { tmpdir } from "node:os";
7
7
  import { AgentManifestSchema, CreateCredentialRequestSchema, CreateVaultRequestSchema, PutAgentRequestSchema, PutHostModelRequestSchema, SelectHostModelRequestSchema, PutSessionRequestSchema, RegisterExecutorsRequestSchema, RotateCredentialRequestSchema, SessionCommandSchema, ActionClaimRequestSchema, ActionHeartbeatRequestSchema, } from "@nylorun/core/contracts";
8
8
  import { createDurableCheckpoint, runDurable, } from "@nylorun/harness/run";
9
9
  import { hashManifest } from "@nylorun/core/compatibility";
10
- import { schemaFromJSON, } from "@nylorun/core/define";
10
+ import { delegateManifest, schemaFromJSON, } from "@nylorun/core/define";
11
11
  import { Store, canonical } from "./store.js";
12
12
  import { ExecutorRegistry, assertExecutorCredential, hashToken, } from "./executors.js";
13
13
  import { RUNTIME_VERSION } from "../version.js";
@@ -18,7 +18,7 @@ import { scrub } from "../redact.js";
18
18
  import { createKekFile, defaultKekPath, readVaultKek } from "../vault/kek.js";
19
19
  import { VaultError } from "../vault/error.js";
20
20
  import { VaultService } from "../vault/service.js";
21
- import { McpPool } from "../mcp/pool.js";
21
+ import { McpPool, serversOf } from "../mcp/pool.js";
22
22
  import { SandboxManager, sandboxCapabilityOf } from "../sandbox/manager.js";
23
23
  import { defaultSandboxBackends } from "../sandbox/select.js";
24
24
  class HttpError extends Error {
@@ -35,6 +35,7 @@ function sessionToolsOf(snapshot) {
35
35
  if (!snapshot?.mcpTools.length)
36
36
  return undefined;
37
37
  return snapshot.mcpTools.map((tool) => ({
38
+ ...(tool.agentId === undefined ? {} : { agentId: tool.agentId }),
38
39
  capabilityId: tool.capabilityId,
39
40
  name: tool.name,
40
41
  ...(tool.description === undefined
@@ -46,8 +47,14 @@ function sessionToolsOf(snapshot) {
46
47
  : { outputSchema: tool.outputSchema }),
47
48
  }));
48
49
  }
49
- function mcpToolOf(session, capabilityId, toolName) {
50
- return session.mcpSnapshot?.mcpTools.find((tool) => tool.capabilityId === capabilityId && tool.name === toolName);
50
+ function mcpToolOf(session, request) {
51
+ return session.mcpSnapshot?.mcpTools.find((tool) => tool.agentId === request.agent?.id &&
52
+ tool.capabilityId === request.capabilityId &&
53
+ tool.name === request.toolName);
54
+ }
55
+ /** The manifest of the agent an effect belongs to: the root, or an agent it uses as a tool. */
56
+ function manifestFor(manifest, agent) {
57
+ return agent ? delegateManifest(manifest, agent.id) : manifest;
51
58
  }
52
59
  function pinnedTool(manifest, capabilityId, toolName) {
53
60
  const capability = manifest.capabilities.find((item) => item.id === capabilityId);
@@ -351,6 +358,7 @@ export class CoreRuntime {
351
358
  this.store.put("sessions", s.id, s);
352
359
  events.push(this.store.event(s.id, s.activeTurnId, "action.uncertain", {
353
360
  actionId: action.actionId,
361
+ ...(action.agent ? { agent: action.agent } : {}),
354
362
  }));
355
363
  }
356
364
  }
@@ -509,11 +517,24 @@ export class CoreRuntime {
509
517
  : "pending",
510
518
  };
511
519
  }
512
- const mcpTool = request.kind === "tool"
513
- ? mcpToolOf(s, request.capabilityId, request.toolName)
514
- : undefined;
520
+ if (request.kind === "delegation") {
521
+ // Lifecycle points of an agent used as a tool: journaled once, so replays never re-emit.
522
+ const outcome = { value: null };
523
+ this.store.put("effects", request.effectId, {
524
+ request,
525
+ status: "completed",
526
+ outcome,
527
+ });
528
+ const settled = request.effectId.endsWith(":settled");
529
+ event = this.store.event(s.id, s.activeTurnId, settled ? "delegation.completed" : "delegation.started", { agent: request.agent, ...request.input });
530
+ return { status: "completed", outcome };
531
+ }
532
+ const agentManifest = manifestFor(s.manifest, request.agent);
533
+ if (!agentManifest)
534
+ throw new Error(`Agent '${request.agent?.id ?? ""}' is not used as a tool`);
535
+ const mcpTool = request.kind === "tool" ? mcpToolOf(s, request) : undefined;
515
536
  const sandboxTool = request.kind === "tool" && !mcpTool
516
- ? sandboxCapabilityOf(s.manifest, request.capabilityId, request.toolName)
537
+ ? sandboxCapabilityOf(agentManifest, request.capabilityId, request.toolName)
517
538
  : undefined;
518
539
  this.store.put("effects", request.effectId, {
519
540
  request,
@@ -526,7 +547,7 @@ export class CoreRuntime {
526
547
  return undefined;
527
548
  }
528
549
  const tool = request.kind === "tool"
529
- ? pinnedTool(s.manifest, request.capabilityId, request.toolName)
550
+ ? pinnedTool(agentManifest, request.capabilityId, request.toolName)
530
551
  : undefined;
531
552
  const base = {
532
553
  actionId: request.effectId,
@@ -541,6 +562,7 @@ export class CoreRuntime {
541
562
  generation: 0,
542
563
  claimId: null,
543
564
  leaseExpiresAt: null,
565
+ ...(request.agent ? { agent: request.agent } : {}),
544
566
  };
545
567
  const action = request.kind === "hook"
546
568
  ? {
@@ -907,7 +929,11 @@ export class CoreRuntime {
907
929
  action.claimId = randomUUID();
908
930
  action.leaseExpiresAt = new Date(Date.now() + (this.options.leaseMs ?? 30000)).toISOString();
909
931
  this.store.put("actions", actionId, action);
910
- event = this.store.event(action.sessionId, action.turnId, "action.claimed", { actionId, generation: action.generation });
932
+ event = this.store.event(action.sessionId, action.turnId, "action.claimed", {
933
+ actionId,
934
+ generation: action.generation,
935
+ ...(action.agent ? { agent: action.agent } : {}),
936
+ });
911
937
  return {
912
938
  action,
913
939
  claimId: action.claimId,
@@ -1127,7 +1153,7 @@ export class CoreRuntime {
1127
1153
  ? request.headers["last-event-id"]
1128
1154
  : undefined);
1129
1155
  if (method === "GET" && path[3] === "items")
1130
- return json(this.store.history(id, cursor));
1156
+ return json(this.store.history(id, cursor, url.searchParams.get("agent") ?? undefined));
1131
1157
  if (method === "GET" && path[3] === "events") {
1132
1158
  const history = this.store.history(id, cursor);
1133
1159
  const set = this.observers.get(id) ?? new Set();
@@ -1199,9 +1225,7 @@ export class CoreRuntime {
1199
1225
  }
1200
1226
  async prepareMcp(id, signal) {
1201
1227
  const s = this.session(id);
1202
- const declared = s.manifest.capabilities.some((capability) => capability.mcpServers !== undefined &&
1203
- Object.keys(capability.mcpServers).length > 0);
1204
- if (!declared)
1228
+ if (serversOf(s.manifest).length === 0)
1205
1229
  return;
1206
1230
  if (!s.mcpSnapshot) {
1207
1231
  const found = await this.mcp.discover({
@@ -1247,11 +1271,12 @@ export class CoreRuntime {
1247
1271
  }
1248
1272
  async callMcpTool(request) {
1249
1273
  const s = this.session(request.sessionId);
1250
- const tool = mcpToolOf(s, request.capabilityId, request.toolName);
1274
+ const tool = mcpToolOf(s, request);
1251
1275
  if (!tool)
1252
1276
  throw new Error(`MCP tool '${request.toolName ?? ""}' is not in the session snapshot`);
1253
1277
  return this.mcp.call({
1254
1278
  sessionId: s.id,
1279
+ ...(tool.agentId === undefined ? {} : { agentId: tool.agentId }),
1255
1280
  capabilityId: tool.capabilityId,
1256
1281
  serverName: tool.serverName,
1257
1282
  serverToolName: tool.serverToolName,
@@ -1262,7 +1287,8 @@ export class CoreRuntime {
1262
1287
  }
1263
1288
  async callSandboxTool(request, signal) {
1264
1289
  const s = this.session(request.sessionId);
1265
- const capability = sandboxCapabilityOf(s.manifest, request.capabilityId, request.toolName);
1290
+ // Agents used as tools share the session's sandbox; the tree declares one sandbox spec.
1291
+ const capability = sandboxCapabilityOf(manifestFor(s.manifest, request.agent), request.capabilityId, request.toolName);
1266
1292
  if (!capability)
1267
1293
  throw new Error(`'${request.toolName ?? ""}' is not a sandbox tool`);
1268
1294
  return this.sandbox.run({ id: s.id, activeTurnId: s.activeTurnId, manifest: s.manifest }, capability, request.toolName, request.input, signal);
@@ -1414,5 +1440,8 @@ export async function startRuntime(options) {
1414
1440
  }
1415
1441
  /** What an action runs, for events: a tool name, or a hook point and its capabilities. */
1416
1442
  function actionTarget(action) {
1417
- return action.kind === "hook" ? { hook: action.hook } : { toolName: action.toolName };
1443
+ return {
1444
+ ...(action.kind === "hook" ? { hook: action.hook } : { toolName: action.toolName }),
1445
+ ...(action.agent ? { agent: action.agent } : {}),
1446
+ };
1418
1447
  }
@@ -22,7 +22,8 @@ export declare class Store {
22
22
  deleteExecutor(agentId: string): void;
23
23
  event(sessionId: string, turnId: string | null, type: string, payload: unknown): LiveEvent;
24
24
  credentialCount(): number;
25
- history(sessionId: string, cursor?: string): {
25
+ /** `agent` keeps only events of one agent used as a tool, by delegationId or path. */
26
+ history(sessionId: string, cursor?: string, agent?: string): {
26
27
  items: LiveEvent[];
27
28
  cursor: string | null;
28
29
  };
@@ -164,7 +164,8 @@ export class Store {
164
164
  .get();
165
165
  return Number(row.n);
166
166
  }
167
- history(sessionId, cursor) {
167
+ /** `agent` keeps only events of one agent used as a tool, by delegationId or path. */
168
+ history(sessionId, cursor, agent) {
168
169
  let sequence = 0;
169
170
  if (cursor) {
170
171
  const decoded = Buffer.from(cursor, "base64url").toString();
@@ -177,7 +178,8 @@ export class Store {
177
178
  const items = this.db
178
179
  .prepare("SELECT body FROM events WHERE session_id=? AND sequence>? ORDER BY sequence")
179
180
  .all(sessionId, sequence)
180
- .map((r) => JSON.parse(String(r.body)));
181
+ .map((r) => JSON.parse(String(r.body)))
182
+ .filter((event) => agent === undefined || belongsTo(event, agent));
181
183
  const last = this.db
182
184
  .prepare("SELECT body FROM events WHERE session_id=? ORDER BY sequence DESC LIMIT 1")
183
185
  .get(sessionId);
@@ -187,6 +189,11 @@ export class Store {
187
189
  };
188
190
  }
189
191
  }
192
+ function belongsTo(event, agent) {
193
+ const ref = event.payload
194
+ ?.agent;
195
+ return ref?.delegationId === agent || ref?.path === agent;
196
+ }
190
197
  function migrateVaultScope(db) {
191
198
  const columns = db.prepare("PRAGMA table_info(vaults)").all();
192
199
  if (!columns.some((column) => column.name === "scope"))
@@ -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";
@@ -31,6 +31,7 @@ export declare class McpPool {
31
31
  }): Promise<McpDiagnostic[]>;
32
32
  call(input: {
33
33
  sessionId: string;
34
+ agentId?: string;
34
35
  capabilityId: string;
35
36
  serverName: string;
36
37
  serverToolName: string;
@@ -43,3 +44,14 @@ export declare class McpPool {
43
44
  private liveKey;
44
45
  private connectDeclared;
45
46
  }
47
+ interface DeclaredServer {
48
+ /** Absent for the session's root agent. */
49
+ readonly agentId?: string;
50
+ /** The manifest of the agent that declares the server. */
51
+ readonly manifest: AgentManifest;
52
+ readonly capabilityId: string;
53
+ readonly server: McpServerManifest;
54
+ }
55
+ /** Servers of the root agent, then of each agent it uses as a tool. */
56
+ export declare function serversOf(manifest: AgentManifest): DeclaredServer[];
57
+ 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,21 @@ 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.dataDir, "plugin-data", ...pluginKey(declared).split("/")),
120
126
  authorize: declared.server.type === "stdio"
121
127
  ? undefined
122
128
  : (url) => this.options.authorize(input.sessionId, {
@@ -129,15 +135,43 @@ export class McpPool {
129
135
  catch (error) {
130
136
  return {
131
137
  ok: false,
132
- diagnostic: diagnosticFromError(declared.capabilityId, declared.server.name, error),
138
+ diagnostic: owned(declared, diagnosticFromError(declared.capabilityId, declared.server.name, error)),
133
139
  };
134
140
  }
135
141
  }
136
142
  }
137
- function serversOf(manifest) {
143
+ /** Servers of the root agent, then of each agent it uses as a tool. */
144
+ export function serversOf(manifest) {
138
145
  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 });
146
+ const add = (agent, agentId) => {
147
+ for (const capability of agent.capabilities)
148
+ for (const server of Object.values(capability.mcpServers ?? {}))
149
+ servers.push({
150
+ ...(agentId === undefined ? {} : { agentId }),
151
+ manifest: agent,
152
+ capabilityId: capability.id,
153
+ server,
154
+ });
155
+ };
156
+ add(manifest);
157
+ for (const child of delegatesOf(manifest))
158
+ add(child.manifest, child.manifest.id);
142
159
  return servers;
143
160
  }
161
+ function findServer(manifest, agentId, capabilityId, serverName) {
162
+ return serversOf(manifest).find((item) => item.agentId === agentId &&
163
+ item.capabilityId === capabilityId &&
164
+ item.server.name === serverName);
165
+ }
166
+ /** Plugin roots of agents used as tools are registered as `<agent>/<capability>`. */
167
+ function pluginKey(declared) {
168
+ return declared.agentId === undefined
169
+ ? declared.capabilityId
170
+ : `${declared.agentId}/${declared.capabilityId}`;
171
+ }
172
+ function ownerOf(declared) {
173
+ return declared.agentId === undefined ? {} : { agentId: declared.agentId };
174
+ }
175
+ function owned(declared, value) {
176
+ return { ...ownerOf(declared), ...value };
177
+ }
@@ -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";
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const RUNTIME_VERSION = "0.8.0-beta";
1
+ export declare const RUNTIME_VERSION = "0.9.0-beta";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Kept in step with package.json by runtime/scripts/check-package.mjs, which runs in npm run check.
2
2
  // release:prepare also rewrites this when it bumps @nylorun/runtime.
3
- export const RUNTIME_VERSION = "0.8.0-beta";
3
+ export const RUNTIME_VERSION = "0.9.0-beta";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/runtime",
3
- "version": "0.8.0-beta",
3
+ "version": "0.9.0-beta",
4
4
  "description": "SQLite agent execution host and model providers.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -55,8 +55,8 @@
55
55
  "@modelcontextprotocol/sdk": "^1.30.0",
56
56
  "@earendil-works/pi-ai": "0.85.1",
57
57
  "@hono/node-server": "^2.1.1",
58
- "@nylorun/harness": "0.17.0-beta",
59
- "@nylorun/core": "0.3.0-beta",
58
+ "@nylorun/harness": "0.18.0-beta",
59
+ "@nylorun/core": "0.4.0-beta",
60
60
  "just-bash": "3.4.2"
61
61
  },
62
62
  "optionalDependencies": {