experimental-ash 0.25.1 → 0.25.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/CHANGELOG.md +6 -0
- package/dist/src/channel/routes.d.ts +6 -1
- package/dist/src/channel/send.js +5 -2
- package/dist/src/channel/session-callback.d.ts +10 -0
- package/dist/src/channel/session-callback.js +65 -0
- package/dist/src/channel/types.d.ts +19 -0
- package/dist/src/chunks/{client-BShLWzR6.js → client-ZqNLLMZB.js} +3 -3
- package/dist/src/chunks/{compile-agent-CyP6FrL8.js → compile-agent-C4OrJW6C.js} +1 -1
- package/dist/src/chunks/{dev-authored-source-watcher-DIWfVUsu.js → dev-authored-source-watcher-PwAxalwl.js} +1 -1
- package/dist/src/chunks/host-F-DkwYJK.js +70 -0
- package/dist/src/chunks/paths-DnlVBqHu.js +85 -0
- package/dist/src/chunks/{token-BOkIxJeV.js → token-YW4VSeBB.js} +1 -1
- package/dist/src/chunks/types-BJSR0JNV.js +1 -0
- package/dist/src/cli/commands/info.js +1 -1
- package/dist/src/cli/dev/repl.js +3 -3
- package/dist/src/cli/run.js +1 -1
- package/dist/src/client/message-reducer.js +6 -0
- package/dist/src/context/keys.d.ts +1 -1
- package/dist/src/context/keys.js +1 -1
- package/dist/src/context/seed-keys.d.ts +5 -1
- package/dist/src/context/seed-keys.js +4 -0
- package/dist/src/evals/cli/eval.js +1 -1
- package/dist/src/execution/await-authorization-orchestrator.js +1 -1
- package/dist/src/execution/node-step.js +13 -0
- package/dist/src/execution/remote-agent-dispatch.d.ts +15 -0
- package/dist/src/execution/remote-agent-dispatch.js +79 -0
- package/dist/src/execution/runtime-context.js +4 -1
- package/dist/src/execution/session-callback-step.d.ts +16 -0
- package/dist/src/execution/session-callback-step.js +72 -0
- package/dist/src/execution/subagent-invocation.d.ts +16 -0
- package/dist/src/execution/subagent-invocation.js +16 -0
- package/dist/src/execution/subagent-tool.js +5 -8
- package/dist/src/execution/workflow-entry.js +21 -1
- package/dist/src/execution/workflow-steps.d.ts +6 -1
- package/dist/src/execution/workflow-steps.js +76 -25
- package/dist/src/harness/execute-tool.d.ts +3 -3
- package/dist/src/harness/runtime-actions.d.ts +1 -0
- package/dist/src/harness/runtime-actions.js +18 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/protocol/message.d.ts +6 -0
- package/dist/src/protocol/message.js +1 -0
- package/dist/src/protocol/routes.d.ts +11 -0
- package/dist/src/protocol/routes.js +13 -0
- package/dist/src/public/channels/ash.js +25 -1
- package/dist/src/runtime/actions/keys.js +2 -0
- package/dist/src/runtime/actions/types.d.ts +47 -1
- package/dist/src/runtime/actions/types.js +23 -0
- package/dist/src/runtime/connections/callback-route.d.ts +1 -1
- package/dist/src/runtime/connections/callback-route.js +1 -1
- package/dist/src/runtime/connections/mcp-client.d.ts +1 -2
- package/dist/src/runtime/connections/mcp-client.js +69 -3
- package/dist/src/runtime/framework-channels/index.js +7 -2
- package/dist/src/runtime/session-callback-route.d.ts +6 -0
- package/dist/src/runtime/session-callback-route.js +87 -0
- package/package.json +1 -1
- package/dist/src/chunks/host-BxT35q6K.js +0 -70
- package/dist/src/chunks/paths-B2hLA0Fn.js +0 -85
- package/dist/src/chunks/types-CjIyrcYo.js +0 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* ({@link import("./keys.js").SessionKey SessionKey}, etc.) live in
|
|
10
10
|
* `context/keys.ts` which re-exports everything from this module.
|
|
11
11
|
*/
|
|
12
|
-
import type { SessionAuthContext, SessionCapabilities, SessionParent } from "#channel/types.js";
|
|
12
|
+
import type { SessionAuthContext, SessionCallback, SessionCapabilities, SessionParent } from "#channel/types.js";
|
|
13
13
|
import type { RunMode } from "#shared/run-mode.js";
|
|
14
14
|
import { ContextKey } from "#context/key.js";
|
|
15
15
|
export declare const AuthKey: ContextKey<SessionAuthContext | null>;
|
|
@@ -24,6 +24,10 @@ export declare const ParentSessionKey: ContextKey<SessionParent>;
|
|
|
24
24
|
* dispatch so HITL readiness flows through a conversation chain.
|
|
25
25
|
*/
|
|
26
26
|
export declare const CapabilitiesKey: ContextKey<SessionCapabilities>;
|
|
27
|
+
/**
|
|
28
|
+
* Optional framework-owned terminal callback metadata for this session.
|
|
29
|
+
*/
|
|
30
|
+
export declare const SessionCallbackKey: ContextKey<SessionCallback>;
|
|
27
31
|
/**
|
|
28
32
|
* Marker durable boolean set by the runtime **before** the
|
|
29
33
|
* `lifecycle.session` hook chain runs. The runtime checks this flag at
|
|
@@ -22,6 +22,10 @@ export const ParentSessionKey = new ContextKey("ash.parentSession");
|
|
|
22
22
|
* dispatch so HITL readiness flows through a conversation chain.
|
|
23
23
|
*/
|
|
24
24
|
export const CapabilitiesKey = new ContextKey("ash.capabilities");
|
|
25
|
+
/**
|
|
26
|
+
* Optional framework-owned terminal callback metadata for this session.
|
|
27
|
+
*/
|
|
28
|
+
export const SessionCallbackKey = new ContextKey("ash.sessionCallback");
|
|
25
29
|
/**
|
|
26
30
|
* Marker durable boolean set by the runtime **before** the
|
|
27
31
|
* `lifecycle.session` hook chain runs. The runtime checks this flag at
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"../../chunks/paths-
|
|
1
|
+
import{n as e}from"../../chunks/paths-DnlVBqHu.js";import{loadDevelopmentEnvironmentFiles as t}from"../../cli/dev/environment.js";import{n,s as r,t as i}from"../../chunks/client-ZqNLLMZB.js";import{n as a}from"../../chunks/host-F-DkwYJK.js";import{discoverAndImportSuites as o}from"../runner/discover.js";import{executeSuite as s}from"../runner/execute-suite.js";import{ConsoleReporter as c}from"../runner/reporters/console.js";var l=r();async function u(n,r){let i=e();t(i);let c=n.suite,l=await o(i,c);if(l.length===0){c&&c.length>0?r.error(`No suites found matching: ${c.join(`, `)}`):r.error(`No eval suites found. Create suite files under evals/ with the *.eval.ts extension.`),process.exitCode=1;return}let u,f;n.url?f={kind:`remote`,url:n.url}:(u=await a(i,{host:`127.0.0.1`,port:0}),f={kind:`local`,url:u.url});let p=d(f);try{let e=[];for(let t of l){let r=m(t,n),a=h(r,{json:n.json===!0,skipReport:n.skipReport===!0}),o=await s({suite:r,target:f,reporters:a,appRoot:i,client:p});e.push(o)}n.json&&r.log(JSON.stringify(e,null,2)),e.some(e=>e.errored>0)&&(process.exitCode=1)}finally{u&&await u.close()}process.exit(process.exitCode??0)}function d(e){if(e.kind===`local`)return new i({host:e.url});let t={},r=process.env.VERCEL_AUTOMATION_BYPASS_SECRET?.trim();return r&&(t[n]=r),new i({auth:f(),headers:Object.keys(t).length>0?t:void 0,host:e.url})}function f(){let e=process.env.ASH_EVAL_AUTH_TOKEN?.trim();return e?{bearer:e}:{bearer:p}}async function p(){try{let e=(await(0,l.getVercelOidcToken)()).trim();if(e.length>0)return e}catch{}return process.env.VERCEL_OIDC_TOKEN?.trim()??``}function m(e,t){let n=t.maxConcurrency?Number.parseInt(t.maxConcurrency,10):void 0,r=t.timeout?Number.parseInt(t.timeout,10):void 0;if(n===void 0&&r===void 0)return e;let i={...e};return n!==void 0&&(i.maxConcurrency=n),r!==void 0&&(i.timeoutMs=r),i}function h(e,t){let n=t.json?[]:[new c];return!t.skipReport&&e.reporters&&n.push(...e.reporters),n}export{u as runEvalCommand};
|
|
@@ -39,7 +39,7 @@ export async function awaitAuthorizationAndResolve(input) {
|
|
|
39
39
|
// the IdP at Workflow's raw
|
|
40
40
|
// `/.well-known/workflow/v1/webhook/:token` endpoint, we build a
|
|
41
41
|
// namespaced Ash route at
|
|
42
|
-
//
|
|
42
|
+
// `/ash/v1/connections/:name/callback/:token`. The
|
|
43
43
|
// matching framework route handler projects the inbound request and
|
|
44
44
|
// resumes this hook via `resumeHook(token, payload)`. Owning the
|
|
45
45
|
// callback in Ash lets the framework decide delivery policy (auth,
|
|
@@ -116,6 +116,19 @@ function resolveHarnessToolDefinition(input) {
|
|
|
116
116
|
},
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
|
+
if (input.tool.kind === "remote") {
|
|
120
|
+
return {
|
|
121
|
+
description: input.tool.description ?? "",
|
|
122
|
+
inputSchema: jsonSchema(input.tool.inputSchema ?? {}),
|
|
123
|
+
name: input.tool.name,
|
|
124
|
+
runtimeAction: {
|
|
125
|
+
kind: "remote-agent-call",
|
|
126
|
+
nodeId: input.tool.nodeId,
|
|
127
|
+
remoteAgentName: input.tool.name,
|
|
128
|
+
subagentName: input.tool.name,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
119
132
|
const registeredTool = findRegisteredRuntimeTool(input.node.toolRegistry, input.tool.name);
|
|
120
133
|
if (registeredTool === null) {
|
|
121
134
|
return null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HarnessSession } from "#harness/types.js";
|
|
2
|
+
import type { RuntimeRemoteAgentCallActionRequest } from "#runtime/actions/types.js";
|
|
3
|
+
import type { RuntimeSubagentRegistry } from "#runtime/subagents/registry.js";
|
|
4
|
+
import type { ResolvedRuntimeRemoteAgentNode } from "#runtime/types.js";
|
|
5
|
+
export declare function startRemoteAgentSession(input: {
|
|
6
|
+
readonly action: RuntimeRemoteAgentCallActionRequest;
|
|
7
|
+
readonly callbackBaseUrl: string | undefined;
|
|
8
|
+
readonly remote: ResolvedRuntimeRemoteAgentNode;
|
|
9
|
+
readonly session: HarnessSession;
|
|
10
|
+
}): Promise<string>;
|
|
11
|
+
export declare function resolveRemoteAgentForAction(input: {
|
|
12
|
+
readonly nodeId: string;
|
|
13
|
+
readonly registry: RuntimeSubagentRegistry["subagentsByNodeId"];
|
|
14
|
+
readonly remoteAgentName: string;
|
|
15
|
+
}): ResolvedRuntimeRemoteAgentNode;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { ASH_SESSION_ID_HEADER } from "#protocol/message.js";
|
|
2
|
+
import { createAshCallbackRoutePath } from "#protocol/routes.js";
|
|
3
|
+
import { formatSubagentInvocation } from "#execution/subagent-invocation.js";
|
|
4
|
+
export async function startRemoteAgentSession(input) {
|
|
5
|
+
const callbackToken = input.session.continuationToken;
|
|
6
|
+
if (!callbackToken) {
|
|
7
|
+
throw new Error("Cannot dispatch remote agent without a parent continuation token.");
|
|
8
|
+
}
|
|
9
|
+
if (!input.callbackBaseUrl) {
|
|
10
|
+
throw new Error("Cannot dispatch remote agent without a callback base URL.");
|
|
11
|
+
}
|
|
12
|
+
const headers = await resolveRemoteAgentRequestHeaders(input.remote);
|
|
13
|
+
const response = await fetch(createRemoteAgentSessionUrl(input.remote), {
|
|
14
|
+
body: JSON.stringify({
|
|
15
|
+
callback: {
|
|
16
|
+
callId: input.action.callId,
|
|
17
|
+
subagentName: input.action.remoteAgentName,
|
|
18
|
+
token: callbackToken,
|
|
19
|
+
url: `${input.callbackBaseUrl}${createAshCallbackRoutePath(callbackToken)}`,
|
|
20
|
+
},
|
|
21
|
+
message: formatRemoteAgentCallInputMessage(input.action),
|
|
22
|
+
mode: "task",
|
|
23
|
+
}),
|
|
24
|
+
headers: {
|
|
25
|
+
"content-type": "application/json",
|
|
26
|
+
...headers,
|
|
27
|
+
},
|
|
28
|
+
method: "POST",
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`Remote agent "${input.action.remoteAgentName}" create-session request failed with HTTP ${response.status}.`);
|
|
32
|
+
}
|
|
33
|
+
const sessionIdFromHeader = response.headers.get(ASH_SESSION_ID_HEADER);
|
|
34
|
+
if (sessionIdFromHeader !== null && sessionIdFromHeader.length > 0) {
|
|
35
|
+
return sessionIdFromHeader;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const body = (await response.json());
|
|
39
|
+
if (typeof body.sessionId === "string" && body.sessionId.length > 0) {
|
|
40
|
+
return body.sessionId;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Fall through to the generic error below.
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`Remote agent "${input.action.remoteAgentName}" create-session response did not include a session id.`);
|
|
47
|
+
}
|
|
48
|
+
export function resolveRemoteAgentForAction(input) {
|
|
49
|
+
const registered = input.registry.get(input.nodeId);
|
|
50
|
+
const definition = registered?.definition;
|
|
51
|
+
if (definition?.kind !== "remote") {
|
|
52
|
+
throw new Error(`Missing remote agent "${input.remoteAgentName}" in runtime registry.`);
|
|
53
|
+
}
|
|
54
|
+
return definition;
|
|
55
|
+
}
|
|
56
|
+
function createRemoteAgentSessionUrl(remote) {
|
|
57
|
+
return new URL(remote.path, `${trimTrailingSlash(remote.url)}/`).toString();
|
|
58
|
+
}
|
|
59
|
+
async function resolveRemoteAgentRequestHeaders(remote) {
|
|
60
|
+
const headers = {};
|
|
61
|
+
if (remote.headers !== undefined) {
|
|
62
|
+
Object.assign(headers, typeof remote.headers === "function" ? await remote.headers() : remote.headers);
|
|
63
|
+
}
|
|
64
|
+
if (remote.auth !== undefined) {
|
|
65
|
+
Object.assign(headers, (await remote.auth()).headers);
|
|
66
|
+
}
|
|
67
|
+
return headers;
|
|
68
|
+
}
|
|
69
|
+
function formatRemoteAgentCallInputMessage(input) {
|
|
70
|
+
const message = typeof input.input.message === "string" ? input.input.message : "";
|
|
71
|
+
return formatSubagentInvocation({
|
|
72
|
+
description: input.description,
|
|
73
|
+
message,
|
|
74
|
+
name: input.remoteAgentName,
|
|
75
|
+
}).message;
|
|
76
|
+
}
|
|
77
|
+
function trimTrailingSlash(value) {
|
|
78
|
+
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
79
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ContextContainer } from "#context/container.js";
|
|
2
|
-
import { AuthKey, BundleKey, CapabilitiesKey, ChannelKey, ContinuationTokenKey, InitiatorAuthKey, ModeKey, ParentSessionKey, } from "#context/keys.js";
|
|
2
|
+
import { AuthKey, BundleKey, CapabilitiesKey, ChannelKey, ContinuationTokenKey, InitiatorAuthKey, ModeKey, ParentSessionKey, SessionCallbackKey, } from "#context/keys.js";
|
|
3
3
|
/**
|
|
4
4
|
* Builds the bootstrap {@link ContextContainer} for one run.
|
|
5
5
|
*/
|
|
@@ -16,6 +16,9 @@ export function buildRunContext(input) {
|
|
|
16
16
|
if (run.capabilities !== undefined) {
|
|
17
17
|
ctx.set(CapabilitiesKey, run.capabilities);
|
|
18
18
|
}
|
|
19
|
+
if (run.callback !== undefined) {
|
|
20
|
+
ctx.set(SessionCallbackKey, run.callback);
|
|
21
|
+
}
|
|
19
22
|
if (run.parent !== undefined) {
|
|
20
23
|
ctx.set(ParentSessionKey, run.parent);
|
|
21
24
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sends the configured session terminal callback.
|
|
3
|
+
*
|
|
4
|
+
* Absence is a no-op. Once callback metadata is present, delivery is part of
|
|
5
|
+
* the remote delegation result path, so failures are logged and rethrown
|
|
6
|
+
* instead of being reported as a successful terminal step. Throwing is
|
|
7
|
+
* intentional: this function runs as a durable Workflow step, so rejection
|
|
8
|
+
* hands retry/failure policy back to the Workflow orchestrator rather than
|
|
9
|
+
* letting Ash falsely mark the callback delivery as complete.
|
|
10
|
+
*/
|
|
11
|
+
export declare function fireSessionCallbackStep(input: {
|
|
12
|
+
readonly error?: unknown;
|
|
13
|
+
readonly output?: string;
|
|
14
|
+
readonly serializedContext: Record<string, unknown>;
|
|
15
|
+
readonly status: "completed" | "failed";
|
|
16
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { parseSessionCallback } from "#channel/session-callback.js";
|
|
2
|
+
import { SessionCallbackKey } from "#context/keys.js";
|
|
3
|
+
import { createLogger } from "#internal/logging.js";
|
|
4
|
+
import { toErrorMessage } from "#shared/errors.js";
|
|
5
|
+
const SESSION_CALLBACK_TIMEOUT_MS = 30_000;
|
|
6
|
+
const log = createLogger("execution.session-callback");
|
|
7
|
+
/**
|
|
8
|
+
* Sends the configured session terminal callback.
|
|
9
|
+
*
|
|
10
|
+
* Absence is a no-op. Once callback metadata is present, delivery is part of
|
|
11
|
+
* the remote delegation result path, so failures are logged and rethrown
|
|
12
|
+
* instead of being reported as a successful terminal step. Throwing is
|
|
13
|
+
* intentional: this function runs as a durable Workflow step, so rejection
|
|
14
|
+
* hands retry/failure policy back to the Workflow orchestrator rather than
|
|
15
|
+
* letting Ash falsely mark the callback delivery as complete.
|
|
16
|
+
*/
|
|
17
|
+
export async function fireSessionCallbackStep(input) {
|
|
18
|
+
"use step";
|
|
19
|
+
const sessionId = input.serializedContext["ash.sessionId"] ?? "";
|
|
20
|
+
const value = input.serializedContext[SessionCallbackKey.name];
|
|
21
|
+
if (value === undefined) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const callback = parseSerializedSessionCallback(value);
|
|
26
|
+
const body = input.status === "completed"
|
|
27
|
+
? {
|
|
28
|
+
callId: callback.callId,
|
|
29
|
+
kind: "session.completed",
|
|
30
|
+
output: input.output ?? "",
|
|
31
|
+
sessionId,
|
|
32
|
+
subagentName: callback.subagentName,
|
|
33
|
+
}
|
|
34
|
+
: {
|
|
35
|
+
callId: callback.callId,
|
|
36
|
+
error: {
|
|
37
|
+
code: "SESSION_FAILED",
|
|
38
|
+
message: toErrorMessage(input.error),
|
|
39
|
+
},
|
|
40
|
+
kind: "session.failed",
|
|
41
|
+
sessionId,
|
|
42
|
+
subagentName: callback.subagentName,
|
|
43
|
+
};
|
|
44
|
+
const response = await fetch(callback.url, {
|
|
45
|
+
body: JSON.stringify(body),
|
|
46
|
+
headers: {
|
|
47
|
+
"content-type": "application/json",
|
|
48
|
+
},
|
|
49
|
+
method: "POST",
|
|
50
|
+
signal: AbortSignal.timeout(SESSION_CALLBACK_TIMEOUT_MS),
|
|
51
|
+
});
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
throw new Error(`Session callback failed with HTTP ${response.status}.`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
log.error("failed to post session callback", {
|
|
58
|
+
error,
|
|
59
|
+
sessionId,
|
|
60
|
+
});
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function parseSerializedSessionCallback(value) {
|
|
65
|
+
const parsed = parseSessionCallback(value);
|
|
66
|
+
if (!parsed.ok) {
|
|
67
|
+
throw new Error("Serialized session callback is invalid.", {
|
|
68
|
+
cause: parsed.cause,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return parsed.callback;
|
|
72
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { StepInput } from "#harness/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Narrowed form of {@link StepInput} whose `message` is always a plain string.
|
|
4
|
+
* Delegated child runs receive a synthesized text-only prompt.
|
|
5
|
+
*/
|
|
6
|
+
export interface FormattedSubagentInvocation extends StepInput {
|
|
7
|
+
readonly message: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Formats the stable delegated input handed to one child agent invocation.
|
|
11
|
+
*/
|
|
12
|
+
export declare function formatSubagentInvocation(input: {
|
|
13
|
+
readonly description: string;
|
|
14
|
+
readonly message: string;
|
|
15
|
+
readonly name: string;
|
|
16
|
+
}): FormattedSubagentInvocation;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats the stable delegated input handed to one child agent invocation.
|
|
3
|
+
*/
|
|
4
|
+
export function formatSubagentInvocation(input) {
|
|
5
|
+
return {
|
|
6
|
+
message: [
|
|
7
|
+
`You are the subagent "${input.name}".`,
|
|
8
|
+
`Description: ${input.description}`,
|
|
9
|
+
"",
|
|
10
|
+
"The caller delegated the following task to you. Complete it and return the final result directly.",
|
|
11
|
+
"",
|
|
12
|
+
"Caller message:",
|
|
13
|
+
input.message,
|
|
14
|
+
].join("\n"),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SUBAGENT_ADAPTER_KIND } from "#execution/subagent-adapter.js";
|
|
2
|
+
import { formatSubagentInvocation } from "#execution/subagent-invocation.js";
|
|
2
3
|
import { mintSubagentContinuationToken } from "#execution/session.js";
|
|
3
4
|
/**
|
|
4
5
|
* Builds the {@link RunInput} for one delegated subagent child run.
|
|
@@ -39,13 +40,9 @@ export function buildSubagentRunInput(input) {
|
|
|
39
40
|
*/
|
|
40
41
|
function formatSubagentCallInputMessage(action) {
|
|
41
42
|
const { message } = action.input;
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
`Description: ${action.description}`,
|
|
45
|
-
"",
|
|
46
|
-
"The caller delegated the following task to you. Complete it and return the final result directly.",
|
|
47
|
-
"",
|
|
48
|
-
"Caller message:",
|
|
43
|
+
return formatSubagentInvocation({
|
|
44
|
+
description: action.description,
|
|
49
45
|
message,
|
|
50
|
-
|
|
46
|
+
name: action.subagentName,
|
|
47
|
+
}).message;
|
|
51
48
|
}
|
|
@@ -8,6 +8,7 @@ import { accumulateRuntimeActionResults, hasPendingRuntimeActionBatch, } from "#
|
|
|
8
8
|
import { toErrorMessage } from "#shared/errors.js";
|
|
9
9
|
import { normalizeSerializableError, rebuildSerializableError, } from "#execution/workflow-errors.js";
|
|
10
10
|
import { createSessionStep, dispatchTurnStep, dispatchPendingRuntimeActionsStep, emitTerminalSessionFailureStep, routeProxiedDeliverStep, runProxyInputRequestStep, } from "#execution/workflow-steps.js";
|
|
11
|
+
import { fireSessionCallbackStep } from "#execution/session-callback-step.js";
|
|
11
12
|
/**
|
|
12
13
|
* Long-lived workflow entrypoint for the durable runtime.
|
|
13
14
|
*
|
|
@@ -64,6 +65,11 @@ export async function workflowEntry(input) {
|
|
|
64
65
|
parentWritable: driverWritable,
|
|
65
66
|
serializedContext: input.serializedContext,
|
|
66
67
|
});
|
|
68
|
+
await fireSessionCallbackStep({
|
|
69
|
+
error: normalizeSerializableError(error),
|
|
70
|
+
serializedContext: input.serializedContext,
|
|
71
|
+
status: "failed",
|
|
72
|
+
});
|
|
67
73
|
await notifyDelegatedParentStep({
|
|
68
74
|
result: createDelegatedSubagentErrorResult(input.serializedContext, error),
|
|
69
75
|
serializedContext: input.serializedContext,
|
|
@@ -88,6 +94,11 @@ async function runDriverLoop(input) {
|
|
|
88
94
|
currentSession = currentResult.session;
|
|
89
95
|
currentSerializedContext = currentResult.serializedContext;
|
|
90
96
|
if (currentResult.action === "done") {
|
|
97
|
+
await fireSessionCallbackStep({
|
|
98
|
+
output: currentResult.output ?? "",
|
|
99
|
+
serializedContext: currentSerializedContext,
|
|
100
|
+
status: "completed",
|
|
101
|
+
});
|
|
91
102
|
await notifyDelegatedParentStep({
|
|
92
103
|
result: createDelegatedSubagentSuccessResult(currentSerializedContext, currentResult.output ?? ""),
|
|
93
104
|
serializedContext: currentSerializedContext,
|
|
@@ -138,15 +149,18 @@ async function runDriverLoop(input) {
|
|
|
138
149
|
try {
|
|
139
150
|
while (true) {
|
|
140
151
|
if (hasPendingRuntimeActionBatch(currentSession)) {
|
|
141
|
-
|
|
152
|
+
const dispatchResult = await dispatchPendingRuntimeActionsStep({
|
|
153
|
+
callbackBaseUrl: getWorkflowMetadata().url.replace(/\/$/, ""),
|
|
142
154
|
parentWritable: input.driverWritable,
|
|
143
155
|
serializedContext: currentSerializedContext,
|
|
144
156
|
session: currentSession,
|
|
145
157
|
});
|
|
158
|
+
currentSession = dispatchResult.session;
|
|
146
159
|
const runtimeResults = await waitForPendingRuntimeActionResults({
|
|
147
160
|
bufferedDeliveries,
|
|
148
161
|
getNextPromise,
|
|
149
162
|
consumeNext,
|
|
163
|
+
initialResults: dispatchResult.results,
|
|
150
164
|
rekeyHook,
|
|
151
165
|
parentWritable: input.driverWritable,
|
|
152
166
|
serializedContext: currentSerializedContext,
|
|
@@ -207,6 +221,11 @@ async function runDriverLoop(input) {
|
|
|
207
221
|
currentSession = currentResult.session;
|
|
208
222
|
currentSerializedContext = currentResult.serializedContext;
|
|
209
223
|
if (currentResult.action === "done") {
|
|
224
|
+
await fireSessionCallbackStep({
|
|
225
|
+
output: currentResult.output ?? "",
|
|
226
|
+
serializedContext: currentSerializedContext,
|
|
227
|
+
status: "completed",
|
|
228
|
+
});
|
|
210
229
|
await notifyDelegatedParentStep({
|
|
211
230
|
result: createDelegatedSubagentSuccessResult(currentSerializedContext, currentResult.output ?? ""),
|
|
212
231
|
serializedContext: currentSerializedContext,
|
|
@@ -309,6 +328,7 @@ async function waitForPendingRuntimeActionResults(input) {
|
|
|
309
328
|
await input.rekeyHook(currentSession.continuationToken);
|
|
310
329
|
}
|
|
311
330
|
},
|
|
331
|
+
initialResults: input.initialResults,
|
|
312
332
|
session: currentSession,
|
|
313
333
|
});
|
|
314
334
|
if (results === null) {
|
|
@@ -4,6 +4,7 @@ import type { HarnessSession } from "#harness/types.js";
|
|
|
4
4
|
import type { RuntimeCompiledArtifactsSource } from "#runtime/compiled-artifacts-source.js";
|
|
5
5
|
import { type PendingConnectionToolCall } from "#runtime/framework-tools/pending-connection-tool-calls.js";
|
|
6
6
|
import { type TurnWorkflowInput } from "#execution/turn-workflow.js";
|
|
7
|
+
import type { RuntimeSubagentResultActionResult } from "#runtime/actions/types.js";
|
|
7
8
|
/**
|
|
8
9
|
* Serializable projection of a step result for workflow persistence.
|
|
9
10
|
*/
|
|
@@ -55,10 +56,14 @@ export declare function reconcileSessionContinuationToken(ctx: Awaited<ReturnTyp
|
|
|
55
56
|
* runs independently on its own public child stream.
|
|
56
57
|
*/
|
|
57
58
|
export declare function dispatchPendingRuntimeActionsStep(input: {
|
|
59
|
+
readonly callbackBaseUrl?: string;
|
|
58
60
|
readonly parentWritable: WritableStream<Uint8Array>;
|
|
59
61
|
readonly serializedContext: Record<string, unknown>;
|
|
60
62
|
readonly session: HarnessSession;
|
|
61
|
-
}): Promise<
|
|
63
|
+
}): Promise<{
|
|
64
|
+
readonly results: readonly RuntimeSubagentResultActionResult[];
|
|
65
|
+
readonly session: HarnessSession;
|
|
66
|
+
}>;
|
|
62
67
|
/**
|
|
63
68
|
* Emits a terminal `session.failed` event and delivers it to the
|
|
64
69
|
* adapter before the workflow run tears down.
|
|
@@ -13,11 +13,13 @@ import { createSessionFailedEvent, createSubagentCalledEvent, encodeMessageStrea
|
|
|
13
13
|
import { PendingConnectionToolCallsKey, } from "#runtime/framework-tools/pending-connection-tool-calls.js";
|
|
14
14
|
import { getCompiledRuntimeAgentBundle } from "#runtime/sessions/compiled-agent-cache.js";
|
|
15
15
|
import { createExecutionNodeStep } from "#execution/node-step.js";
|
|
16
|
+
import { resolveRemoteAgentForAction, startRemoteAgentSession, } from "#execution/remote-agent-dispatch.js";
|
|
16
17
|
import { emitProxiedInputRequest, routeDeliverPayload } from "#execution/subagent-hitl-proxy.js";
|
|
17
18
|
import { createSession, refreshSessionFromTurnAgent } from "#execution/session.js";
|
|
18
19
|
import { buildSubagentRunInput } from "#execution/subagent-tool.js";
|
|
19
20
|
import { turnWorkflow } from "#execution/turn-workflow.js";
|
|
20
21
|
import { createWorkflowRuntime, startWorkflowPreferLatest, workflowEntryReference, } from "#execution/workflow-runtime.js";
|
|
22
|
+
import { toErrorMessage } from "#shared/errors.js";
|
|
21
23
|
/**
|
|
22
24
|
* Runs one atomic harness step inside a durable `"use step"` boundary.
|
|
23
25
|
*/
|
|
@@ -201,7 +203,7 @@ export async function dispatchPendingRuntimeActionsStep(input) {
|
|
|
201
203
|
"use step";
|
|
202
204
|
const batch = getPendingRuntimeActionBatch(input.session);
|
|
203
205
|
if (batch === undefined || batch.actions.length === 0) {
|
|
204
|
-
return input.session;
|
|
206
|
+
return { results: [], session: input.session };
|
|
205
207
|
}
|
|
206
208
|
const ctx = await deserializeContext(input.serializedContext);
|
|
207
209
|
const bundle = ctx.require(BundleKey);
|
|
@@ -212,36 +214,73 @@ export async function dispatchPendingRuntimeActionsStep(input) {
|
|
|
212
214
|
const writer = input.parentWritable.getWriter();
|
|
213
215
|
const adapterCtx = buildAdapterContext(adapter, ctx);
|
|
214
216
|
let nextSession = input.session;
|
|
217
|
+
const results = [];
|
|
215
218
|
try {
|
|
216
219
|
for (const action of batch.actions) {
|
|
217
|
-
|
|
218
|
-
|
|
220
|
+
let childSessionId;
|
|
221
|
+
let name;
|
|
222
|
+
let remote;
|
|
223
|
+
let toolName;
|
|
224
|
+
switch (action.kind) {
|
|
225
|
+
case "subagent-call": {
|
|
226
|
+
const childRuntime = createWorkflowRuntime({
|
|
227
|
+
compiledArtifactsSource: bundle.compiledArtifactsSource,
|
|
228
|
+
nodeId: action.nodeId,
|
|
229
|
+
});
|
|
230
|
+
const { childContinuationToken, runInput } = buildSubagentRunInput({
|
|
231
|
+
action,
|
|
232
|
+
auth,
|
|
233
|
+
batchEvent: batch.event,
|
|
234
|
+
capabilities,
|
|
235
|
+
initiatorAuth,
|
|
236
|
+
session: input.session,
|
|
237
|
+
});
|
|
238
|
+
const handle = await childRuntime.run(runInput);
|
|
239
|
+
nextSession = recordPendingSubagentChildToken({
|
|
240
|
+
callId: action.callId,
|
|
241
|
+
childContinuationToken,
|
|
242
|
+
session: nextSession,
|
|
243
|
+
});
|
|
244
|
+
childSessionId = handle.sessionId;
|
|
245
|
+
name = action.name;
|
|
246
|
+
toolName = action.subagentName;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
case "remote-agent-call": {
|
|
250
|
+
let resolvedRemote;
|
|
251
|
+
try {
|
|
252
|
+
resolvedRemote = resolveRemoteAgentForAction({
|
|
253
|
+
nodeId: action.nodeId,
|
|
254
|
+
remoteAgentName: action.remoteAgentName,
|
|
255
|
+
registry: bundle.subagentRegistry.subagentsByNodeId,
|
|
256
|
+
});
|
|
257
|
+
childSessionId = await startRemoteAgentSession({
|
|
258
|
+
action,
|
|
259
|
+
callbackBaseUrl: input.callbackBaseUrl,
|
|
260
|
+
remote: resolvedRemote,
|
|
261
|
+
session: input.session,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
results.push(createRemoteAgentStartFailureResult({ action, error }));
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
name = action.name;
|
|
269
|
+
remote = { url: resolvedRemote.url };
|
|
270
|
+
toolName = action.remoteAgentName;
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
default:
|
|
274
|
+
throw new Error(`Unsupported runtime action kind "${action.kind}" in workflow runtime.`);
|
|
219
275
|
}
|
|
220
|
-
const childRuntime = createWorkflowRuntime({
|
|
221
|
-
compiledArtifactsSource: bundle.compiledArtifactsSource,
|
|
222
|
-
nodeId: action.nodeId,
|
|
223
|
-
});
|
|
224
|
-
const { childContinuationToken, runInput } = buildSubagentRunInput({
|
|
225
|
-
action,
|
|
226
|
-
auth,
|
|
227
|
-
batchEvent: batch.event,
|
|
228
|
-
capabilities,
|
|
229
|
-
initiatorAuth,
|
|
230
|
-
session: input.session,
|
|
231
|
-
});
|
|
232
|
-
const handle = await childRuntime.run(runInput);
|
|
233
|
-
nextSession = recordPendingSubagentChildToken({
|
|
234
|
-
callId: action.callId,
|
|
235
|
-
childContinuationToken,
|
|
236
|
-
session: nextSession,
|
|
237
|
-
});
|
|
238
276
|
const parentEvent = await callAdapterEventHandler(adapter, createSubagentCalledEvent({
|
|
239
277
|
callId: action.callId,
|
|
240
|
-
childSessionId
|
|
241
|
-
name
|
|
278
|
+
childSessionId,
|
|
279
|
+
name,
|
|
280
|
+
remote,
|
|
242
281
|
sequence: batch.event.sequence,
|
|
243
282
|
sessionId: input.session.sessionId,
|
|
244
|
-
toolName
|
|
283
|
+
toolName,
|
|
245
284
|
turnId: batch.event.turnId,
|
|
246
285
|
workflowId: workflowEntryReference.workflowId,
|
|
247
286
|
}), adapterCtx);
|
|
@@ -251,7 +290,19 @@ export async function dispatchPendingRuntimeActionsStep(input) {
|
|
|
251
290
|
finally {
|
|
252
291
|
writer.releaseLock();
|
|
253
292
|
}
|
|
254
|
-
return nextSession;
|
|
293
|
+
return { results, session: nextSession };
|
|
294
|
+
}
|
|
295
|
+
function createRemoteAgentStartFailureResult(input) {
|
|
296
|
+
return {
|
|
297
|
+
callId: input.action.callId,
|
|
298
|
+
isError: true,
|
|
299
|
+
kind: "subagent-result",
|
|
300
|
+
output: {
|
|
301
|
+
code: "REMOTE_AGENT_START_FAILED",
|
|
302
|
+
message: toErrorMessage(input.error),
|
|
303
|
+
},
|
|
304
|
+
subagentName: input.action.remoteAgentName,
|
|
305
|
+
};
|
|
255
306
|
}
|
|
256
307
|
const log = createLogger("execution.workflow-entry");
|
|
257
308
|
/**
|
|
@@ -6,9 +6,10 @@ import type { NeedsApprovalContext } from "#public/definitions/tool.js";
|
|
|
6
6
|
* These tools are surfaced to the model without a local `execute` function.
|
|
7
7
|
* The harness records the tool call and the runtime executes it later.
|
|
8
8
|
*/
|
|
9
|
-
type HarnessRuntimeActionDefinition = {
|
|
10
|
-
readonly kind: "subagent-call";
|
|
9
|
+
export type HarnessRuntimeActionDefinition = {
|
|
10
|
+
readonly kind: "remote-agent-call" | "subagent-call";
|
|
11
11
|
readonly nodeId: string;
|
|
12
|
+
readonly remoteAgentName?: string;
|
|
12
13
|
readonly subagentName: string;
|
|
13
14
|
};
|
|
14
15
|
/**
|
|
@@ -24,4 +25,3 @@ export interface HarnessToolDefinition {
|
|
|
24
25
|
readonly runtimeAction?: HarnessRuntimeActionDefinition;
|
|
25
26
|
readonly toModelOutput?: (output: unknown) => unknown;
|
|
26
27
|
}
|
|
27
|
-
export {};
|
|
@@ -86,6 +86,7 @@ type RuntimeActionAccumulatorItem<TDeliver> = {
|
|
|
86
86
|
export declare function accumulateRuntimeActionResults<TDeliver>(input: {
|
|
87
87
|
readonly bufferedDeliveries: TDeliver[];
|
|
88
88
|
readonly getNext: () => Promise<RuntimeActionAccumulatorItem<TDeliver> | null>;
|
|
89
|
+
readonly initialResults?: readonly RuntimeActionResult[];
|
|
89
90
|
readonly session: HarnessSession;
|
|
90
91
|
}): Promise<RuntimeActionResult[] | null>;
|
|
91
92
|
/**
|
|
@@ -65,7 +65,13 @@ export function recordPendingSubagentChildToken(input) {
|
|
|
65
65
|
*/
|
|
66
66
|
export async function accumulateRuntimeActionResults(input) {
|
|
67
67
|
const batch = getPendingRuntimeActionBatch(input.session);
|
|
68
|
-
const buffered = [];
|
|
68
|
+
const buffered = [...(input.initialResults ?? [])];
|
|
69
|
+
if (batch !== undefined && buffered.length > 0) {
|
|
70
|
+
const ready = resolveRuntimeActionResultsForBatch({ batch, results: buffered });
|
|
71
|
+
if (ready !== undefined) {
|
|
72
|
+
return ready;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
69
75
|
while (true) {
|
|
70
76
|
const item = await input.getNext();
|
|
71
77
|
if (item === null) {
|
|
@@ -249,6 +255,17 @@ export function createRuntimeActionRequestFromToolCall(input) {
|
|
|
249
255
|
subagentName: definition.runtimeAction.subagentName,
|
|
250
256
|
};
|
|
251
257
|
}
|
|
258
|
+
if (definition?.runtimeAction?.kind === "remote-agent-call") {
|
|
259
|
+
return {
|
|
260
|
+
callId: input.toolCall.toolCallId,
|
|
261
|
+
description: definition.description,
|
|
262
|
+
input: resolveToolCallInputObject(input.toolCall.input),
|
|
263
|
+
kind: "remote-agent-call",
|
|
264
|
+
name: definition.name,
|
|
265
|
+
nodeId: definition.runtimeAction.nodeId,
|
|
266
|
+
remoteAgentName: definition.runtimeAction.remoteAgentName ?? definition.name,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
252
269
|
return {
|
|
253
270
|
callId: input.toolCall.toolCallId,
|
|
254
271
|
input: resolveToolCallInputObject(input.toolCall.input),
|
|
@@ -6,7 +6,7 @@ import { ASH_PACKAGE_NAME } from "#internal/package-name.js";
|
|
|
6
6
|
let cachedPackageInfo;
|
|
7
7
|
// The package build stamps the published version into `dist` so bundled
|
|
8
8
|
// deployments can still report package metadata without resolving package.json.
|
|
9
|
-
const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.25.
|
|
9
|
+
const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.25.2";
|
|
10
10
|
const BUNDLED_FALLBACK_PACKAGE_VERSION_PLACEHOLDER = "__ASH_PACKAGE_VERSION_PLACEHOLDER__";
|
|
11
11
|
const WORKFLOW_MODULE_ALIASES = {
|
|
12
12
|
"workflow/api": "src/compiled/@workflow/core/runtime.js",
|