experimental-ash 0.25.0 → 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 +12 -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-75wLLW-E.js → compile-agent-C4OrJW6C.js} +1 -1
- package/dist/src/chunks/{dev-authored-source-watcher-DqoJsDup.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/index.d.ts +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.d.ts +0 -2
- package/dist/src/execution/await-authorization-orchestrator.js +12 -20
- package/dist/src/execution/connection-auth-steps.js +1 -4
- 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/turn-workflow.js +0 -1
- package/dist/src/execution/workflow-entry.js +21 -1
- package/dist/src/execution/workflow-steps.d.ts +6 -3
- package/dist/src/execution/workflow-steps.js +79 -37
- 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/framework-tools/connection-search.d.ts +0 -46
- package/dist/src/runtime/framework-tools/connection-search.js +3 -78
- package/dist/src/runtime/framework-tools/connection-tools.d.ts +3 -6
- package/dist/src/runtime/framework-tools/connection-tools.js +6 -13
- 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-JVy7fewA.js +0 -70
- package/dist/src/chunks/paths-DNjq5JOy.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};
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* effects stay inside durable steps.
|
|
7
7
|
*/
|
|
8
8
|
import type { HarnessSession } from "#harness/types.js";
|
|
9
|
-
import type { PendingConnectionAuthorization } from "#runtime/framework-tools/connection-search.js";
|
|
10
9
|
import type { PendingConnectionToolCall } from "#runtime/framework-tools/pending-connection-tool-calls.js";
|
|
11
10
|
/**
|
|
12
11
|
* Return value of {@link awaitAuthorizationAndResolve}. The workflow
|
|
@@ -33,7 +32,6 @@ export interface AwaitAuthorizationResolveResult {
|
|
|
33
32
|
export declare function awaitAuthorizationAndResolve(input: {
|
|
34
33
|
readonly parentWritable: WritableStream<Uint8Array>;
|
|
35
34
|
readonly pendingToolCalls: readonly PendingConnectionToolCall[];
|
|
36
|
-
readonly pendingAuths: readonly PendingConnectionAuthorization[];
|
|
37
35
|
readonly serializedContext: Record<string, unknown>;
|
|
38
36
|
readonly session: HarnessSession;
|
|
39
37
|
}): Promise<AwaitAuthorizationResolveResult>;
|
|
@@ -23,8 +23,8 @@ import { completeAuthorizationForConnectionStep, emitConnectionAuthorizationPend
|
|
|
23
23
|
*/
|
|
24
24
|
export async function awaitAuthorizationAndResolve(input) {
|
|
25
25
|
const emissionState = getHarnessEmissionState(input.session);
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
26
|
+
const connectionNames = uniqueConnectionNames(input.pendingToolCalls);
|
|
27
|
+
if (connectionNames.length === 0) {
|
|
28
28
|
return {
|
|
29
29
|
serializedContext: input.serializedContext,
|
|
30
30
|
session: input.session,
|
|
@@ -39,18 +39,18 @@ 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,
|
|
46
46
|
// throttling, logging) without leaking generic workflow primitives.
|
|
47
47
|
const callbackBaseUrl = trimTrailingSlash(getWorkflowMetadata().url);
|
|
48
|
-
const hooks =
|
|
48
|
+
const hooks = connectionNames.map((name) => {
|
|
49
49
|
const hook = createHook();
|
|
50
50
|
return {
|
|
51
|
-
connectionName:
|
|
51
|
+
connectionName: name,
|
|
52
52
|
hook,
|
|
53
|
-
webhookUrl: `${callbackBaseUrl}${createAshConnectionCallbackRoutePath(
|
|
53
|
+
webhookUrl: `${callbackBaseUrl}${createAshConnectionCallbackRoutePath(name, hook.token)}`,
|
|
54
54
|
};
|
|
55
55
|
});
|
|
56
56
|
// Run every `startAuthorization` inside its own durable step,
|
|
@@ -157,23 +157,15 @@ export async function awaitAuthorizationAndResolve(input) {
|
|
|
157
157
|
tokens,
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
* least one entry of `pendingCalls`.
|
|
163
|
-
*/
|
|
164
|
-
function filterAuthorizationsWithPendingCalls(pending, pendingCalls) {
|
|
165
|
-
const targetConnectionNames = new Set();
|
|
160
|
+
function uniqueConnectionNames(pendingCalls) {
|
|
161
|
+
const seen = new Set();
|
|
166
162
|
for (const call of pendingCalls) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
else {
|
|
171
|
-
for (const name of call.connectionNames) {
|
|
172
|
-
targetConnectionNames.add(name);
|
|
173
|
-
}
|
|
163
|
+
const names = call.kind === "connection-execute" ? [call.connectionName] : call.connectionNames;
|
|
164
|
+
for (const name of names) {
|
|
165
|
+
seen.add(name);
|
|
174
166
|
}
|
|
175
167
|
}
|
|
176
|
-
return
|
|
168
|
+
return [...seen];
|
|
177
169
|
}
|
|
178
170
|
/**
|
|
179
171
|
* Awaits the first payload delivered to `hook` via `resumeHook`, using
|
|
@@ -14,7 +14,7 @@ import { writeCachedToken } from "#runtime/connections/authorization-tokens.js";
|
|
|
14
14
|
import { withConnectionPrincipalOverride } from "#runtime/connections/principal-context.js";
|
|
15
15
|
import { principalKey, resolveConnectionPrincipal } from "#runtime/connections/principal.js";
|
|
16
16
|
import { ConnectionRegistryImpl } from "#runtime/connections/registry.js";
|
|
17
|
-
import { ConnectionRegistryKey, executeConnectionSearch,
|
|
17
|
+
import { ConnectionRegistryKey, executeConnectionSearch, } from "#runtime/framework-tools/connection-search.js";
|
|
18
18
|
import { isConnectionAuthorizationPlaceholder, PendingConnectionToolCallsKey, } from "#runtime/framework-tools/pending-connection-tool-calls.js";
|
|
19
19
|
import { withDefaultAuthorizationInstructions } from "#execution/authorization-challenge-defaults.js";
|
|
20
20
|
import { splicePendingToolResults } from "#execution/await-authorization-splice.js";
|
|
@@ -289,9 +289,6 @@ export async function resolvePendingToolCallsStep(input) {
|
|
|
289
289
|
return names.some((name) => !resolvedSet.has(name) && input.failedConnections[name] === undefined);
|
|
290
290
|
});
|
|
291
291
|
ctx.set(PendingConnectionToolCallsKey, remainingPending);
|
|
292
|
-
const currentPendingAuths = ctx.get(PendingConnectionAuthorizationsKey) ?? [];
|
|
293
|
-
const remainingPendingAuths = currentPendingAuths.filter((p) => !resolvedSet.has(p.connectionName) && input.failedConnections[p.connectionName] === undefined);
|
|
294
|
-
ctx.set(PendingConnectionAuthorizationsKey, remainingPendingAuths);
|
|
295
292
|
return {
|
|
296
293
|
serializedContext: serializeContext(ctx),
|
|
297
294
|
session: splicedSession,
|
|
@@ -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
|
}
|
|
@@ -61,7 +61,6 @@ export async function turnWorkflow(input) {
|
|
|
61
61
|
if (result.action === "await-authorization") {
|
|
62
62
|
const resolved = await awaitAuthorizationAndResolve({
|
|
63
63
|
parentWritable,
|
|
64
|
-
pendingAuths: result.pendingAuths,
|
|
65
64
|
pendingToolCalls: result.pendingToolCalls,
|
|
66
65
|
serializedContext: currentSerializedContext,
|
|
67
66
|
session: currentSession,
|
|
@@ -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) {
|
|
@@ -2,9 +2,9 @@ import type { DeliverPayload, HookPayload, SessionAuthContext, SubagentInputRequ
|
|
|
2
2
|
import { deserializeContext } from "#context/serialize.js";
|
|
3
3
|
import type { HarnessSession } from "#harness/types.js";
|
|
4
4
|
import type { RuntimeCompiledArtifactsSource } from "#runtime/compiled-artifacts-source.js";
|
|
5
|
-
import { type PendingConnectionAuthorization } from "#runtime/framework-tools/connection-search.js";
|
|
6
5
|
import { type PendingConnectionToolCall } from "#runtime/framework-tools/pending-connection-tool-calls.js";
|
|
7
6
|
import { type TurnWorkflowInput } from "#execution/turn-workflow.js";
|
|
7
|
+
import type { RuntimeSubagentResultActionResult } from "#runtime/actions/types.js";
|
|
8
8
|
/**
|
|
9
9
|
* Serializable projection of a step result for workflow persistence.
|
|
10
10
|
*/
|
|
@@ -16,7 +16,6 @@ export type DurableStepResult = {
|
|
|
16
16
|
} | {
|
|
17
17
|
readonly action: "await-authorization";
|
|
18
18
|
readonly pendingToolCalls: readonly PendingConnectionToolCall[];
|
|
19
|
-
readonly pendingAuths: readonly PendingConnectionAuthorization[];
|
|
20
19
|
readonly serializedContext: Record<string, unknown>;
|
|
21
20
|
readonly session: HarnessSession;
|
|
22
21
|
};
|
|
@@ -57,10 +56,14 @@ export declare function reconcileSessionContinuationToken(ctx: Awaited<ReturnTyp
|
|
|
57
56
|
* runs independently on its own public child stream.
|
|
58
57
|
*/
|
|
59
58
|
export declare function dispatchPendingRuntimeActionsStep(input: {
|
|
59
|
+
readonly callbackBaseUrl?: string;
|
|
60
60
|
readonly parentWritable: WritableStream<Uint8Array>;
|
|
61
61
|
readonly serializedContext: Record<string, unknown>;
|
|
62
62
|
readonly session: HarnessSession;
|
|
63
|
-
}): Promise<
|
|
63
|
+
}): Promise<{
|
|
64
|
+
readonly results: readonly RuntimeSubagentResultActionResult[];
|
|
65
|
+
readonly session: HarnessSession;
|
|
66
|
+
}>;
|
|
64
67
|
/**
|
|
65
68
|
* Emits a terminal `session.failed` event and delivers it to the
|
|
66
69
|
* adapter before the workflow run tears down.
|