@finalai/final-agent-provider 0.2.1 → 0.3.1
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/dist/modules/channel-factories.d.mts +3 -1
- package/dist/modules/channel-factories.mjs +32 -18
- package/dist/modules/channel-factories.mjs.map +2 -2
- package/dist/modules/channels/address-context.mjs +16 -53
- package/dist/modules/channels/address-context.mjs.map +3 -3
- package/dist/modules/channels/agent-events.d.mts +3 -29
- package/dist/modules/channels/agent-events.mjs +67 -146
- package/dist/modules/channels/agent-events.mjs.map +3 -3
- package/dist/modules/channels/agent-schedules.d.mts +1 -0
- package/dist/modules/channels/agent-schedules.mjs +18 -19
- package/dist/modules/channels/agent-schedules.mjs.map +2 -2
- package/dist/modules/channels/delivery.d.mts +1 -1
- package/dist/modules/channels/delivery.mjs +5 -23
- package/dist/modules/channels/delivery.mjs.map +2 -2
- package/dist/modules/channels/fake-platform.d.mts +6 -1
- package/dist/modules/channels/fake-platform.mjs +31 -12
- package/dist/modules/channels/fake-platform.mjs.map +2 -2
- package/dist/modules/channels/index.d.mts +1 -0
- package/dist/modules/channels/index.mjs +1 -0
- package/dist/modules/channels/index.mjs.map +2 -2
- package/dist/modules/channels/prompt.d.mts +19 -2
- package/dist/modules/channels/prompt.mjs +44 -17
- package/dist/modules/channels/prompt.mjs.map +2 -2
- package/dist/modules/channels/routing.mjs +1 -15
- package/dist/modules/channels/routing.mjs.map +2 -2
- package/dist/modules/channels/service-input.d.mts +22 -0
- package/dist/modules/channels/service-input.mjs +32 -0
- package/dist/modules/channels/service-input.mjs.map +7 -0
- package/dist/modules/sandboxes/computer.d.mts +2 -0
- package/dist/modules/sandboxes/computer.mjs +52 -7
- package/dist/modules/sandboxes/computer.mjs.map +3 -3
- package/dist/modules/sandboxes/device.d.mts +2 -0
- package/dist/modules/sandboxes/device.mjs +26 -3
- package/dist/modules/sandboxes/device.mjs.map +2 -2
- package/dist/modules/sandboxes/testing/fake-backends.d.mts +3 -0
- package/dist/modules/sandboxes/testing/fake-backends.mjs +35 -3
- package/dist/modules/sandboxes/testing/fake-backends.mjs.map +2 -2
- package/package.json +5 -5
|
@@ -2,8 +2,7 @@ import {
|
|
|
2
2
|
ConversationKind,
|
|
3
3
|
Delivery,
|
|
4
4
|
ParticipantKind,
|
|
5
|
-
RoutingMode
|
|
6
|
-
SystemParticipantKind
|
|
5
|
+
RoutingMode
|
|
7
6
|
} from "@finalai/agent-provider/channel";
|
|
8
7
|
const MAX_AGENT_HOP_DEPTH = 2;
|
|
9
8
|
const activeAgents = (participants) => participants.filter(
|
|
@@ -13,28 +12,15 @@ const effectiveDefaultAgent = (participants) => {
|
|
|
13
12
|
const agents = activeAgents(participants);
|
|
14
13
|
return agents.find((agent) => agent.routingMode === RoutingMode.Default) ?? agents.find((agent) => agent.routingMode === RoutingMode.All) ?? null;
|
|
15
14
|
};
|
|
16
|
-
const observesEverything = (participant) => participant.routingMode === RoutingMode.All || participant.routingMode === RoutingMode.Default;
|
|
17
15
|
const routeMessage = (args) => {
|
|
18
16
|
const { conversation, message, participants } = args;
|
|
19
|
-
const author = participants.find((participant) => participant.id === message.authorParticipantId) ?? null;
|
|
20
17
|
const agents = activeAgents(participants).filter(
|
|
21
18
|
(agent) => agent.id !== message.authorParticipantId && (message.agentAuthorDefinitionId == null || agent.definitionId !== message.agentAuthorDefinitionId)
|
|
22
19
|
);
|
|
23
20
|
if (agents.length === 0) {
|
|
24
21
|
return [];
|
|
25
22
|
}
|
|
26
|
-
const authorKind = author?.kind ?? ParticipantKind.User;
|
|
27
23
|
const mentioned = new Set(message.mentionedParticipantIds);
|
|
28
|
-
if (authorKind === ParticipantKind.System && author?.systemKind !== SystemParticipantKind.ApiKey && message.agentAuthorDefinitionId == null) {
|
|
29
|
-
const sourceAgent = author?.definitionId == null ? null : agents.find((agent) => agent.definitionId === author.definitionId) ?? null;
|
|
30
|
-
const target = sourceAgent ?? (conversation.kind === ConversationKind.Direct ? agents[0] ?? null : effectiveDefaultAgent(agents));
|
|
31
|
-
return agents.flatMap((agent) => {
|
|
32
|
-
if (agent.id === target?.id) {
|
|
33
|
-
return [{ participantId: agent.id, delivery: Delivery.Prompt }];
|
|
34
|
-
}
|
|
35
|
-
return observesEverything(agent) ? [{ participantId: agent.id, delivery: Delivery.Observe }] : [];
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
24
|
const mayPrompt = message.hopDepth < MAX_AGENT_HOP_DEPTH;
|
|
39
25
|
const promptDelivery = mayPrompt ? Delivery.Prompt : Delivery.Observe;
|
|
40
26
|
if (conversation.kind === ConversationKind.Direct) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/channels/routing.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n ConversationKind,\n Delivery,\n ParticipantKind,\n RoutingMode,\n
|
|
5
|
-
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,
|
|
4
|
+
"sourcesContent": ["import {\n ConversationKind,\n Delivery,\n ParticipantKind,\n RoutingMode,\n type ParticipantIface,\n type RouteArgsIface,\n type RouteDecisionIface,\n} from '@finalai/agent-provider/channel';\n\n// Agent-authored messages may prompt other agents only while the chain of\n// agent hops that produced them is shorter than this: a human\n// message (0) prompts A, A's reply (1) may prompt B, B's reply (2) prompts no one.\nexport const MAX_AGENT_HOP_DEPTH = 2;\n\nconst activeAgents = (participants: ParticipantIface[]): ParticipantIface[] =>\n participants.filter(\n (participant) => participant.kind === ParticipantKind.Agent && !participant.left,\n );\n\n// The agent a no-mention message goes to: the Default agent, else (no Default)\n// the earliest-joined All agent, which then acts as the Default. Participants\n// arrive in join order.\nexport const effectiveDefaultAgent = (\n participants: ParticipantIface[],\n): ParticipantIface | null => {\n const agents = activeAgents(participants);\n return (\n agents.find((agent) => agent.routingMode === RoutingMode.Default) ??\n agents.find((agent) => agent.routingMode === RoutingMode.All) ??\n null\n );\n};\n\n// Pure: who gets a message and how. Users never receive decisions.\nexport const routeMessage = (args: RouteArgsIface): RouteDecisionIface[] => {\n const { conversation, message, participants } = args;\n const agents = activeAgents(participants).filter(\n (agent) =>\n agent.id !== message.authorParticipantId &&\n (message.agentAuthorDefinitionId == null ||\n agent.definitionId !== message.agentAuthorDefinitionId),\n );\n if (agents.length === 0) {\n return [];\n }\n const mentioned = new Set(message.mentionedParticipantIds);\n const mayPrompt = message.hopDepth < MAX_AGENT_HOP_DEPTH;\n const promptDelivery = mayPrompt ? Delivery.Prompt : Delivery.Observe;\n\n if (conversation.kind === ConversationKind.Direct) {\n return agents.map((agent) => ({ participantId: agent.id, delivery: promptDelivery }));\n }\n\n const anyAgentMentioned = agents.some((agent) => mentioned.has(agent.id));\n const defaultAgent = effectiveDefaultAgent(participants);\n return agents.flatMap((agent): RouteDecisionIface[] => {\n if (mentioned.has(agent.id)) {\n return [{ participantId: agent.id, delivery: promptDelivery }];\n }\n if (agent.routingMode === RoutingMode.Mention || agent.routingMode == null) {\n return [];\n }\n if (!anyAgentMentioned && agent.id === defaultAgent?.id) {\n return [{ participantId: agent.id, delivery: promptDelivery }];\n }\n return [{ participantId: agent.id, delivery: Delivery.Observe }];\n });\n};\n"],
|
|
5
|
+
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAKA,MAAM,sBAAsB;AAEnC,MAAM,eAAe,CAAC,iBACpB,aAAa;AAAA,EACX,CAAC,gBAAgB,YAAY,SAAS,gBAAgB,SAAS,CAAC,YAAY;AAC9E;AAKK,MAAM,wBAAwB,CACnC,iBAC4B;AAC5B,QAAM,SAAS,aAAa,YAAY;AACxC,SACE,OAAO,KAAK,CAAC,UAAU,MAAM,gBAAgB,YAAY,OAAO,KAChE,OAAO,KAAK,CAAC,UAAU,MAAM,gBAAgB,YAAY,GAAG,KAC5D;AAEJ;AAGO,MAAM,eAAe,CAAC,SAA+C;AAC1E,QAAM,EAAE,cAAc,SAAS,aAAa,IAAI;AAChD,QAAM,SAAS,aAAa,YAAY,EAAE;AAAA,IACxC,CAAC,UACC,MAAM,OAAO,QAAQ,wBACpB,QAAQ,2BAA2B,QAClC,MAAM,iBAAiB,QAAQ;AAAA,EACrC;AACA,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,CAAC;AAAA,EACV;AACA,QAAM,YAAY,IAAI,IAAI,QAAQ,uBAAuB;AACzD,QAAM,YAAY,QAAQ,WAAW;AACrC,QAAM,iBAAiB,YAAY,SAAS,SAAS,SAAS;AAE9D,MAAI,aAAa,SAAS,iBAAiB,QAAQ;AACjD,WAAO,OAAO,IAAI,CAAC,WAAW,EAAE,eAAe,MAAM,IAAI,UAAU,eAAe,EAAE;AAAA,EACtF;AAEA,QAAM,oBAAoB,OAAO,KAAK,CAAC,UAAU,UAAU,IAAI,MAAM,EAAE,CAAC;AACxE,QAAM,eAAe,sBAAsB,YAAY;AACvD,SAAO,OAAO,QAAQ,CAAC,UAAgC;AACrD,QAAI,UAAU,IAAI,MAAM,EAAE,GAAG;AAC3B,aAAO,CAAC,EAAE,eAAe,MAAM,IAAI,UAAU,eAAe,CAAC;AAAA,IAC/D;AACA,QAAI,MAAM,gBAAgB,YAAY,WAAW,MAAM,eAAe,MAAM;AAC1E,aAAO,CAAC;AAAA,IACV;AACA,QAAI,CAAC,qBAAqB,MAAM,OAAO,cAAc,IAAI;AACvD,aAAO,CAAC,EAAE,eAAe,MAAM,IAAI,UAAU,eAAe,CAAC;AAAA,IAC/D;AACA,WAAO,CAAC,EAAE,eAAe,MAAM,IAAI,UAAU,SAAS,QAAQ,CAAC;AAAA,EACjE,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ContentBlock } from '@agentclientprotocol/sdk/experimental/v2';
|
|
2
|
+
import { ChannelPrincipalType, type ChannelAuthIface, type ChannelInputReceiptIface, type ChannelPlatformIface, type JsonObject, type TurnPolicy } from '@finalai/agent-provider';
|
|
3
|
+
export interface ServiceInputArgsIface {
|
|
4
|
+
room: string;
|
|
5
|
+
participantId?: string;
|
|
6
|
+
content: ContentBlock[];
|
|
7
|
+
principal: {
|
|
8
|
+
principalId: string;
|
|
9
|
+
principalType?: ChannelPrincipalType | `${ChannelPrincipalType}`;
|
|
10
|
+
authenticator: string;
|
|
11
|
+
attributes?: JsonObject;
|
|
12
|
+
};
|
|
13
|
+
displayName: string;
|
|
14
|
+
actingUserId?: string;
|
|
15
|
+
originTurnId?: string;
|
|
16
|
+
idempotencyKey: string;
|
|
17
|
+
label: string;
|
|
18
|
+
context?: string[];
|
|
19
|
+
turnPolicy?: TurnPolicy | `${TurnPolicy}`;
|
|
20
|
+
}
|
|
21
|
+
export declare const serviceAuthOf: (args: ServiceInputArgsIface) => ChannelAuthIface;
|
|
22
|
+
export declare const sendServiceInput: (platform: Pick<ChannelPlatformIface, "channelSessions">, args: ServiceInputArgsIface) => Promise<ChannelInputReceiptIface>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChannelPrincipalType
|
|
3
|
+
} from "@finalai/agent-provider";
|
|
4
|
+
const serviceAuthOf = (args) => ({
|
|
5
|
+
principalId: args.principal.principalId,
|
|
6
|
+
principalType: args.principal.principalType ?? ChannelPrincipalType.Service,
|
|
7
|
+
authenticator: args.principal.authenticator,
|
|
8
|
+
displayName: args.displayName,
|
|
9
|
+
attributes: args.principal.attributes ?? {}
|
|
10
|
+
});
|
|
11
|
+
const sendServiceInput = async (platform, args) => {
|
|
12
|
+
if (platform.channelSessions == null)
|
|
13
|
+
throw new Error("channel session operations are unavailable");
|
|
14
|
+
return platform.channelSessions.send(
|
|
15
|
+
{ conversationId: args.room, participantId: args.participantId },
|
|
16
|
+
{
|
|
17
|
+
content: args.content,
|
|
18
|
+
idempotencyKey: args.idempotencyKey,
|
|
19
|
+
auth: serviceAuthOf(args),
|
|
20
|
+
actingUserId: args.actingUserId,
|
|
21
|
+
originTurnId: args.originTurnId,
|
|
22
|
+
source: { label: args.label },
|
|
23
|
+
context: args.context,
|
|
24
|
+
turnPolicy: args.turnPolicy
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
export {
|
|
29
|
+
sendServiceInput,
|
|
30
|
+
serviceAuthOf
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=service-input.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/channels/service-input.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ContentBlock } from '@agentclientprotocol/sdk/experimental/v2';\nimport {\n ChannelPrincipalType,\n type ChannelAuthIface,\n type ChannelInputReceiptIface,\n type ChannelPlatformIface,\n type JsonObject,\n type TurnPolicy,\n} from '@finalai/agent-provider';\n\n// Input to a Final room's agent from no member of the room: a schedule\n// firing, a webhook, a linked conversation, a sandbox result, a handoff from\n// another channel. It is channel input from a principal, never a message: the\n// platform accepts it at once and prompts the agent; the agent's reply in the\n// room carries `label` as its caption.\n\nexport interface ServiceInputArgsIface {\n // The room (conversation id).\n room: string;\n // One agent of the room; the room's Default agent when absent.\n participantId?: string;\n content: ContentBlock[];\n // Who sent it. A service principal unless a channel authenticated a person.\n principal: {\n principalId: string;\n principalType?: ChannelPrincipalType | `${ChannelPrincipalType}`;\n authenticator: string;\n attributes?: JsonObject;\n };\n // How the agent and the room name the principal.\n displayName: string;\n // The person the input acts for: who pays for the turn and answers what it asks.\n actingUserId?: string;\n // The turn whose work produced the input, for loop limits and reply provenance.\n originTurnId?: string;\n idempotencyKey: string;\n // The reply's caption (\"After the 9:00 schedule\"), written by the producer.\n label: string;\n context?: string[];\n turnPolicy?: TurnPolicy | `${TurnPolicy}`;\n}\n\nexport const serviceAuthOf = (args: ServiceInputArgsIface): ChannelAuthIface => ({\n principalId: args.principal.principalId,\n principalType: args.principal.principalType ?? ChannelPrincipalType.Service,\n authenticator: args.principal.authenticator,\n displayName: args.displayName,\n attributes: args.principal.attributes ?? {},\n});\n\nexport const sendServiceInput = async (\n platform: Pick<ChannelPlatformIface, 'channelSessions'>,\n args: ServiceInputArgsIface,\n): Promise<ChannelInputReceiptIface> => {\n if (platform.channelSessions == null)\n throw new Error('channel session operations are unavailable');\n return platform.channelSessions.send(\n { conversationId: args.room, participantId: args.participantId },\n {\n content: args.content,\n idempotencyKey: args.idempotencyKey,\n auth: serviceAuthOf(args),\n actingUserId: args.actingUserId,\n originTurnId: args.originTurnId,\n source: { label: args.label },\n context: args.context,\n turnPolicy: args.turnPolicy,\n },\n );\n};\n"],
|
|
5
|
+
"mappings": "AACA;AAAA,EACE;AAAA,OAMK;AAkCA,MAAM,gBAAgB,CAAC,UAAmD;AAAA,EAC/E,aAAa,KAAK,UAAU;AAAA,EAC5B,eAAe,KAAK,UAAU,iBAAiB,qBAAqB;AAAA,EACpE,eAAe,KAAK,UAAU;AAAA,EAC9B,aAAa,KAAK;AAAA,EAClB,YAAY,KAAK,UAAU,cAAc,CAAC;AAC5C;AAEO,MAAM,mBAAmB,OAC9B,UACA,SACsC;AACtC,MAAI,SAAS,mBAAmB;AAC9B,UAAM,IAAI,MAAM,4CAA4C;AAC9D,SAAO,SAAS,gBAAgB;AAAA,IAC9B,EAAE,gBAAgB,KAAK,MAAM,eAAe,KAAK,cAAc;AAAA,IAC/D;AAAA,MACE,SAAS,KAAK;AAAA,MACd,gBAAgB,KAAK;AAAA,MACrB,MAAM,cAAc,IAAI;AAAA,MACxB,cAAc,KAAK;AAAA,MACnB,cAAc,KAAK;AAAA,MACnB,QAAQ,EAAE,OAAO,KAAK,MAAM;AAAA,MAC5B,SAAS,KAAK;AAAA,MACd,YAAY,KAAK;AAAA,IACnB;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -29,6 +29,8 @@ export interface ComputerSandboxProviderConfigIface {
|
|
|
29
29
|
buildPollMs?: number;
|
|
30
30
|
shelveWaitMs?: number;
|
|
31
31
|
shelvePollMs?: number;
|
|
32
|
+
runningWaitMs?: number;
|
|
33
|
+
runningPollMs?: number;
|
|
32
34
|
log?: ComputerSandboxLogIface;
|
|
33
35
|
randomUuid?: () => string;
|
|
34
36
|
now?: () => number;
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
PLATFORM_ENVIRONMENT_NAMES,
|
|
12
12
|
platformProcessEnvironment,
|
|
13
13
|
PortProtocol,
|
|
14
|
+
reportPlatformNetworkPolicy,
|
|
14
15
|
SandboxCapability,
|
|
15
16
|
sandboxSeedArtifact,
|
|
16
17
|
sandboxSeedArtifactSchema,
|
|
@@ -134,12 +135,12 @@ const CAPTURE_SCRUBBED_PATHS = [
|
|
|
134
135
|
"/home/user/.ssh"
|
|
135
136
|
];
|
|
136
137
|
const BUILDER_IDLE_TIMEOUT_SECONDS = 2 * 60 * 60;
|
|
137
|
-
const
|
|
138
|
-
const policy = withPlatformEgressHosts(ctx, requested);
|
|
138
|
+
const computerEgressOf = (policy) => {
|
|
139
139
|
if (policy.mode === NetworkPolicy.AllowAll) return { egress: ComputerEgressPolicy.Internet };
|
|
140
140
|
if (policy.mode === NetworkPolicy.DenyAll) return { egress: ComputerEgressPolicy.None };
|
|
141
141
|
return { egress: ComputerEgressPolicy.Allowlist, allow: policy.allow ?? [] };
|
|
142
142
|
};
|
|
143
|
+
const egressFor = (ctx, requested) => computerEgressOf(withPlatformEgressHosts(ctx, requested));
|
|
143
144
|
const LINEAGE_PREFIX = "lineage:";
|
|
144
145
|
const portOf = (url) => {
|
|
145
146
|
try {
|
|
@@ -157,6 +158,7 @@ const sandboxWorkspaceOf = {
|
|
|
157
158
|
[ComputerWorkspacePolicy.Ephemeral]: SandboxWorkspace.Ephemeral
|
|
158
159
|
};
|
|
159
160
|
const isRunningState = (state) => state === ComputerState.Running || state === ComputerState.Paused || state === ComputerState.Resuming;
|
|
161
|
+
const RUNNING_POLL_MAX_MS = 5e3;
|
|
160
162
|
const createComputerSandboxProvider = (config) => {
|
|
161
163
|
const log = config.log ?? (() => void 0);
|
|
162
164
|
const randomUuid = config.randomUuid ?? (() => crypto.randomUUID());
|
|
@@ -164,6 +166,7 @@ const createComputerSandboxProvider = (config) => {
|
|
|
164
166
|
const sleep = config.sleep ?? defaultSleep;
|
|
165
167
|
const defaultImage = config.defaultImage ?? DEFAULT_COMPUTER_IMAGE;
|
|
166
168
|
const credentials = createComputersApiCredentials(config.computersApi, now);
|
|
169
|
+
const runningWaitMs = config.runningWaitMs ?? 5 * 6e4;
|
|
167
170
|
const clientFor = (organizationId) => createComputersApiClient({ config: config.computersApi, credentials, organizationId });
|
|
168
171
|
const registerImage = (client, reference) => client.registerImage(
|
|
169
172
|
config.registryCredentialId == null ? { reference } : { reference, registryCredentialId: config.registryCredentialId }
|
|
@@ -435,6 +438,29 @@ ${commandHelp}`}`,
|
|
|
435
438
|
await sleep(config.buildPollMs ?? 5e3);
|
|
436
439
|
}
|
|
437
440
|
};
|
|
441
|
+
const awaitComputerRunning = async (client, computerId, timeoutMs) => {
|
|
442
|
+
const deadline = now() + timeoutMs;
|
|
443
|
+
let pollMs = config.runningPollMs ?? 250;
|
|
444
|
+
for (; ; ) {
|
|
445
|
+
const computer = await client.getComputer(computerId);
|
|
446
|
+
if (computer == null || computer.state === ComputerState.Deleted)
|
|
447
|
+
throw computerSandboxError("computer_gone", `computer ${computerId} is gone`);
|
|
448
|
+
if (computer.state === ComputerState.Error)
|
|
449
|
+
throw computerSandboxError(
|
|
450
|
+
"computer_gone",
|
|
451
|
+
`computer ${computer.name} is in the Error state`
|
|
452
|
+
);
|
|
453
|
+
if (computer.state === ComputerState.Running) return computer;
|
|
454
|
+
const left = deadline - now();
|
|
455
|
+
if (left <= 0)
|
|
456
|
+
throw computerSandboxError(
|
|
457
|
+
"computer_not_running",
|
|
458
|
+
`computer ${computer.name} was not running within ${Math.round(timeoutMs / 1e3)} s (it is ${ComputerState[computer.state]})`
|
|
459
|
+
);
|
|
460
|
+
await sleep(Math.min(pollMs, left));
|
|
461
|
+
pollMs = Math.min(pollMs * 2, RUNNING_POLL_MAX_MS);
|
|
462
|
+
}
|
|
463
|
+
};
|
|
438
464
|
const awaitShelved = async (client, computerId) => {
|
|
439
465
|
const deadline = now() + (config.shelveWaitMs ?? 5 * 6e4);
|
|
440
466
|
for (; ; ) {
|
|
@@ -475,6 +501,7 @@ ${commandHelp}`}`,
|
|
|
475
501
|
);
|
|
476
502
|
ctx.log?.(`building computer base ${key} on ${builder.name}`);
|
|
477
503
|
try {
|
|
504
|
+
await awaitComputerRunning(client, builder.id, runningWaitMs);
|
|
478
505
|
const sandbox = setupSession({ client, info: builder, env: async () => ({}) }).sandbox;
|
|
479
506
|
const prepared = await sandbox.run({
|
|
480
507
|
command: "/bin/sh",
|
|
@@ -579,6 +606,11 @@ ${step.run}`,
|
|
|
579
606
|
log
|
|
580
607
|
});
|
|
581
608
|
};
|
|
609
|
+
const awaitRunning = (ctx, rawState, options2) => awaitComputerRunning(
|
|
610
|
+
clientFor(ctx.session.organizationId),
|
|
611
|
+
computerStateSchema.pick({ computerId: true }).parse(rawState).computerId,
|
|
612
|
+
options2.timeoutMs
|
|
613
|
+
);
|
|
582
614
|
return {
|
|
583
615
|
processes: async (ctx, rawState, request) => {
|
|
584
616
|
const state = computerStateSchema.parse(rawState);
|
|
@@ -677,10 +709,14 @@ ${step.run}`,
|
|
|
677
709
|
base: { kind: "image", image: spec.image, imageVersionId }
|
|
678
710
|
};
|
|
679
711
|
},
|
|
712
|
+
awaitRunning: async (ctx, state, options2) => {
|
|
713
|
+
await awaitRunning(ctx, state, options2);
|
|
714
|
+
},
|
|
680
715
|
start: async (ctx, openOptions, rawArtifact) => {
|
|
681
716
|
const access = computerOpenOptionsSchema.parse(openOptions ?? {}).access;
|
|
682
717
|
const spec = await resolve(ctx.guides);
|
|
683
|
-
const
|
|
718
|
+
const policy = withPlatformEgressHosts(ctx, spec.parameters.networkPolicy);
|
|
719
|
+
const egress = computerEgressOf(policy);
|
|
684
720
|
const artifact = computerArtifactSchema.safeParse(rawArtifact);
|
|
685
721
|
if (!artifact.success) {
|
|
686
722
|
throw computerSandboxError(
|
|
@@ -736,8 +772,12 @@ ${step.run}`,
|
|
|
736
772
|
const idempotencyKey = `agent-sandbox-${(await sha256Hex(`${ctx.session.bindingId}:${name}:${source.baseId ?? source.imageVersionId}`)).slice(0, 32)}`;
|
|
737
773
|
info = await client.createComputer(body, idempotencyKey);
|
|
738
774
|
}
|
|
739
|
-
|
|
775
|
+
const reused = existing != null && existing.id === info.id;
|
|
776
|
+
log("computer_sandbox.started", { computerId: info.id, reused });
|
|
740
777
|
try {
|
|
778
|
+
info = await awaitRunning(ctx, { computerId: info.id }, { timeoutMs: runningWaitMs });
|
|
779
|
+
if (reused) await client.setNetworkPolicy(info.id, egress);
|
|
780
|
+
await reportPlatformNetworkPolicy(ctx, policy);
|
|
741
781
|
const setup = setupSession({ client, info, env });
|
|
742
782
|
await writePlatformFiles(ctx, setup.sandbox);
|
|
743
783
|
await mountRepositories({
|
|
@@ -774,7 +814,7 @@ ${step.run}`,
|
|
|
774
814
|
}
|
|
775
815
|
};
|
|
776
816
|
} catch (error) {
|
|
777
|
-
if (
|
|
817
|
+
if (!reused) {
|
|
778
818
|
try {
|
|
779
819
|
await client.action(info.id, "delete");
|
|
780
820
|
} catch (cleanupError) {
|
|
@@ -796,6 +836,7 @@ ${step.run}`,
|
|
|
796
836
|
"computer_not_running",
|
|
797
837
|
"computer is not running for passive access"
|
|
798
838
|
);
|
|
839
|
+
await awaitRunning(ctx, state, { timeoutMs: runningWaitMs });
|
|
799
840
|
const spec = await resolve(ctx.guides);
|
|
800
841
|
const env = platformProcessEnvironment(ctx, await loadVariables(ctx, spec));
|
|
801
842
|
return (await buildHandle({ ctx, client, info, spec, access: state.access, env })).handle;
|
|
@@ -810,16 +851,20 @@ ${step.run}`,
|
|
|
810
851
|
const rebooted = !isRunningState(info.state);
|
|
811
852
|
if (info.state === ComputerState.Paused) await client.action(info.id, "resume");
|
|
812
853
|
else if (rebooted) await client.action(info.id, "start");
|
|
854
|
+
const running = await awaitRunning(ctx, state, { timeoutMs: runningWaitMs });
|
|
813
855
|
const spec = await resolve(ctx.guides);
|
|
814
856
|
const env = platformProcessEnvironment(ctx, await loadVariables(ctx, spec));
|
|
815
|
-
const
|
|
857
|
+
const policy = withPlatformEgressHosts(ctx, spec.parameters.networkPolicy);
|
|
858
|
+
await client.setNetworkPolicy(running.id, computerEgressOf(policy));
|
|
859
|
+
await reportPlatformNetworkPolicy(ctx, policy);
|
|
860
|
+
const setup = setupSession({ client, info: running, env });
|
|
816
861
|
await writePlatformFiles(ctx, setup.sandbox);
|
|
817
862
|
await installGuideCommands({ sandbox: setup.sandbox, template: spec.template });
|
|
818
863
|
if (rebooted) await startServicesFor({ setup, spec, env });
|
|
819
864
|
const { handle } = await buildHandle({
|
|
820
865
|
ctx,
|
|
821
866
|
client,
|
|
822
|
-
info,
|
|
867
|
+
info: running,
|
|
823
868
|
spec,
|
|
824
869
|
access: state.access,
|
|
825
870
|
env
|