@tangle-network/agent-runtime 0.314.2 → 0.317.0
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/README.md +6 -0
- package/dist/{activation-BpMnOlw5.d.ts → activation-BFDmjaKP.d.ts} +2 -2
- package/dist/{activation-Ci20OAsG.js → activation-CvdE2HIU.js} +3 -3
- package/dist/{activation-Ci20OAsG.js.map → activation-CvdE2HIU.js.map} +1 -1
- package/dist/agent.d.ts +2 -1
- package/dist/agent.js +2 -2
- package/dist/{bundle-BK3XBY6q.js → bundle-RgHpAFDY.js} +2 -2
- package/dist/{bundle-BK3XBY6q.js.map → bundle-RgHpAFDY.js.map} +1 -1
- package/dist/candidate-execution/index.js +5 -5
- package/dist/{coordination-driver-CyJF37L8.js → coordination-driver-DGhN8GO9.js} +3 -3
- package/dist/{coordination-driver-CyJF37L8.js.map → coordination-driver-DGhN8GO9.js.map} +1 -1
- package/dist/{delegate-CTezpiM2.js → delegate-B5pmSbQa.js} +2 -2
- package/dist/{delegate-CTezpiM2.js.map → delegate-B5pmSbQa.js.map} +1 -1
- package/dist/durable.d.ts +1 -1
- package/dist/durable.js +4 -4
- package/dist/{graph-DGDrO_ZW.js → graph-G0j1ysqe.js} +4 -4
- package/dist/{graph-DGDrO_ZW.js.map → graph-G0j1ysqe.js.map} +1 -1
- package/dist/{improvement-cycle-DReg-Qi4.js → improvement-cycle-D7yR6GC2.js} +6 -6
- package/dist/{improvement-cycle-DReg-Qi4.js.map → improvement-cycle-D7yR6GC2.js.map} +1 -1
- package/dist/{index-B89bMKhD.d.ts → index-CUh40yUJ.d.ts} +13 -90
- package/dist/index.d.ts +4 -4
- package/dist/index.js +9 -9
- package/dist/integrations-DopfCC2a.d.ts +190 -0
- package/dist/integrations-dFi-ALk7.js +132 -0
- package/dist/integrations-dFi-ALk7.js.map +1 -0
- package/dist/intelligence.d.ts +2 -2
- package/dist/intelligence.js +7 -7
- package/dist/kernel.d.ts +2 -2
- package/dist/kernel.js +9 -9
- package/dist/{loop-runner-bin-eoycPwY3.js → loop-runner-bin-CFVX4oi0.js} +3 -3
- package/dist/{loop-runner-bin-eoycPwY3.js.map → loop-runner-bin-CFVX4oi0.js.map} +1 -1
- package/dist/{loop-runner-bin-BM1in-RT.d.ts → loop-runner-bin-DQKLUt0l.d.ts} +2 -2
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +74 -33
- package/dist/mcp/index.js +129 -6
- package/dist/mcp/index.js.map +1 -1
- package/dist/{openai-tools-xPO6dzf4.d.ts → openai-tools-u3gWBi3x.d.ts} +2 -2
- package/dist/placement.d.ts +132 -0
- package/dist/placement.js +319 -0
- package/dist/placement.js.map +1 -0
- package/dist/platform.d.ts +1 -188
- package/dist/platform.js +1 -129
- package/dist/platform.js.map +1 -1
- package/dist/{prepare-8A1CAWG8.js → prepare-CD9kR0gS.js} +3 -3
- package/dist/{prepare-8A1CAWG8.js.map → prepare-CD9kR0gS.js.map} +1 -1
- package/dist/{profile-BhQShsM1.js → profile-CHXYmpGd.js} +2 -2
- package/dist/{profile-BhQShsM1.js.map → profile-CHXYmpGd.js.map} +1 -1
- package/dist/profile-materialization-Cca615eu.d.ts +90 -0
- package/dist/profiles.d.ts +1 -1
- package/dist/{protected-model-port-ug52OmvB.js → protected-model-port-B0pHd65J.js} +4 -4
- package/dist/{protected-model-port-ug52OmvB.js.map → protected-model-port-B0pHd65J.js.map} +1 -1
- package/dist/{provision-supervisor-B6D3XRbL.js → provision-supervisor-BQLLrUtn.js} +4 -4
- package/dist/{provision-supervisor-B6D3XRbL.js.map → provision-supervisor-BQLLrUtn.js.map} +1 -1
- package/dist/{runtime-NlIVxRXu.js → runtime-B9BOFyEp.js} +10 -9
- package/dist/runtime-B9BOFyEp.js.map +1 -0
- package/dist/{search-executor-DFs-79tK.js → search-executor-B5ZVFsDV.js} +3 -3
- package/dist/{search-executor-DFs-79tK.js.map → search-executor-B5ZVFsDV.js.map} +1 -1
- package/dist/{server-B8ubWXpW.js → server-TwpH9aik.js} +4 -4
- package/dist/{server-B8ubWXpW.js.map → server-TwpH9aik.js.map} +1 -1
- package/dist/{supervise-BE9ta8bl.js → supervise-CU6AuxWY.js} +119 -29
- package/dist/supervise-CU6AuxWY.js.map +1 -0
- package/dist/{supervisor-DmSuKksw.js → supervisor-CbFyf48U.js} +3 -3
- package/dist/{supervisor-DmSuKksw.js.map → supervisor-CbFyf48U.js.map} +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +14 -14
- package/dist/tool-loop.d.ts +45 -6
- package/dist/tool-loop.js +49 -21
- package/dist/tool-loop.js.map +1 -1
- package/dist/tui/index.d.ts +1 -1
- package/dist/tui/index.js +1 -1
- package/dist/workspace-store/index.d.ts +206 -0
- package/dist/workspace-store/index.js +855 -0
- package/dist/workspace-store/index.js.map +1 -0
- package/dist/{workspace-streams-BEvSX97P.js → workspace-streams-DkijayqX.js} +16 -3
- package/dist/workspace-streams-DkijayqX.js.map +1 -0
- package/package.json +11 -1
- package/skills/profile-authoring/SKILL.md +114 -171
- package/dist/runtime-NlIVxRXu.js.map +0 -1
- package/dist/supervise-BE9ta8bl.js.map +0 -1
- package/dist/workspace-streams-BEvSX97P.js.map +0 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { n as AgentProfileMaterializationAxis, s as ProfileMaterializationContract } from "./profile-materialization-Cca615eu.js";
|
|
2
|
+
import { AgentProfile } from "@tangle-network/agent-interface";
|
|
3
|
+
//#region src/placement/index.d.ts
|
|
4
|
+
/** The policy version every {@link PlacementRecord} carries. */
|
|
5
|
+
declare const PLACEMENT_POLICY = "agent-placement/3";
|
|
6
|
+
/** Where one unit of agent work runs, cheapest first. */
|
|
7
|
+
type ExecutionTier = 'inference' | 'loop' | 'sandbox';
|
|
8
|
+
/** What was chosen, under which policy, why, and why each other tier was refused. */
|
|
9
|
+
interface PlacementRecord {
|
|
10
|
+
chosen: ExecutionTier;
|
|
11
|
+
policy: typeof PLACEMENT_POLICY;
|
|
12
|
+
reason: string;
|
|
13
|
+
/**
|
|
14
|
+
* The profile's declared materialization axes the chosen tier carries. A tier that
|
|
15
|
+
* cannot carry a declared axis is rejected, so on a fresh decision this is every
|
|
16
|
+
* declared axis; an authored or resumed record makes no coverage decision and is empty.
|
|
17
|
+
*/
|
|
18
|
+
coveredAxes: AgentProfileMaterializationAxis[];
|
|
19
|
+
rejected: Array<{
|
|
20
|
+
placement: ExecutionTier;
|
|
21
|
+
reason: string;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The declared requirements of the work, beside its profile. Field names follow
|
|
26
|
+
* Platform's `agent.run` action, the first consumer; an app's chat turn usually
|
|
27
|
+
* declares none of them.
|
|
28
|
+
*/
|
|
29
|
+
interface PlacementRequirements {
|
|
30
|
+
/** A pinned harness. Always runs in the sandbox. */
|
|
31
|
+
harness?: string;
|
|
32
|
+
/** A model other than the profile's own needs the sandbox to choose between them. */
|
|
33
|
+
model?: string;
|
|
34
|
+
source?: unknown;
|
|
35
|
+
/** Skill ids resolved into a box. Profile skills are `profile.resources.skills`. */
|
|
36
|
+
skills?: unknown;
|
|
37
|
+
env?: unknown;
|
|
38
|
+
image?: unknown;
|
|
39
|
+
size?: unknown;
|
|
40
|
+
resources?: unknown;
|
|
41
|
+
fromSnapshot?: unknown;
|
|
42
|
+
fromSandboxId?: unknown;
|
|
43
|
+
resume?: unknown;
|
|
44
|
+
maxRounds?: number;
|
|
45
|
+
ask?: boolean;
|
|
46
|
+
/** Hub connections: tools the loop holds. */
|
|
47
|
+
connections?: readonly string[];
|
|
48
|
+
/** Owner-object grants: tools only the loop holds. */
|
|
49
|
+
objects?: unknown;
|
|
50
|
+
/** Public HTTPS reads: a tool only the loop holds. */
|
|
51
|
+
http?: unknown;
|
|
52
|
+
/** Tools the caller binds into the loop itself, such as an app's product tools. */
|
|
53
|
+
tools?: readonly string[];
|
|
54
|
+
}
|
|
55
|
+
/** Which tiers this deployment has wired. */
|
|
56
|
+
interface PlacementContext {
|
|
57
|
+
interactive: boolean;
|
|
58
|
+
inferenceAvailable: boolean;
|
|
59
|
+
loopAvailable: boolean;
|
|
60
|
+
/** Why the loop cannot take this work, when it cannot. */
|
|
61
|
+
loopUnavailableReason?: string;
|
|
62
|
+
}
|
|
63
|
+
/** Profile axes a tier may count as covered when their declared value grants nothing. */
|
|
64
|
+
type VacuousAxis = 'permissions';
|
|
65
|
+
interface PlaceExecutionInput {
|
|
66
|
+
requirements?: PlacementRequirements;
|
|
67
|
+
/** The RESOLVED profile: a named profile is read before the decision. */
|
|
68
|
+
profile?: AgentProfile | object;
|
|
69
|
+
context: PlacementContext;
|
|
70
|
+
/**
|
|
71
|
+
* Axes whose deny-only value counts as covered on `inference` and `loop`.
|
|
72
|
+
* `permissions` qualifies when every rule is `deny`: a tier without native tools
|
|
73
|
+
* already denies them. Deny-only `tools` are covered without opting in.
|
|
74
|
+
*/
|
|
75
|
+
vacuousAxes?: readonly VacuousAxis[];
|
|
76
|
+
/**
|
|
77
|
+
* The caller inlines the profile's skill content into the loop's context, so
|
|
78
|
+
* `resources.skills` (with a strict `resources.failOnError`) and the native `skill`
|
|
79
|
+
* tool and permission that load them count as covered on `loop`. Off by default:
|
|
80
|
+
* the skills then keep the work in the sandbox.
|
|
81
|
+
*/
|
|
82
|
+
inlineSkills?: boolean;
|
|
83
|
+
}
|
|
84
|
+
/** No tier can meet every declared requirement. Raised before any spend. */
|
|
85
|
+
declare class PlacementRefusedError extends Error {
|
|
86
|
+
readonly reason: string;
|
|
87
|
+
constructor(reason: string);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The profile axes `inference` and `loop` carry: identity, prompt, model controls and
|
|
91
|
+
* metadata. No harness, native tools, files, MCP, subagents, hooks or modes.
|
|
92
|
+
*/
|
|
93
|
+
declare const loopTierProfileMaterialization: ProfileMaterializationContract;
|
|
94
|
+
/** Options for {@link loopProfileRefusal}. */
|
|
95
|
+
interface LoopProfileRefusalOptions {
|
|
96
|
+
vacuousAxes?: readonly VacuousAxis[];
|
|
97
|
+
inlineSkills?: boolean;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Why `inference` and `loop` cannot run this profile, or undefined when they can.
|
|
101
|
+
* A profile that needs a harness, files, harness built-in tools, MCP servers,
|
|
102
|
+
* subagents, hooks or modes needs a sandbox; the loop never drops one silently.
|
|
103
|
+
* Platform's `workerProfileRefusal`, read through {@link loopTierProfileMaterialization}.
|
|
104
|
+
*/
|
|
105
|
+
declare function loopProfileRefusal(profile: object, options?: LoopProfileRefusalOptions): string | undefined;
|
|
106
|
+
/** Options for {@link sandboxOnlyRequirements}. */
|
|
107
|
+
interface SandboxOnlyRequirementsOptions extends LoopProfileRefusalOptions {
|
|
108
|
+
requirements?: PlacementRequirements;
|
|
109
|
+
profile?: AgentProfile | object;
|
|
110
|
+
interactive: boolean;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Every declared requirement only a sandbox can meet. A caller resuming work in the
|
|
114
|
+
* loop it began in checks this is empty before it continues there.
|
|
115
|
+
*/
|
|
116
|
+
declare function sandboxOnlyRequirements(options: SandboxOnlyRequirementsOptions): string[];
|
|
117
|
+
/**
|
|
118
|
+
* Choose where work runs: the cheapest tier every declared requirement allows.
|
|
119
|
+
* Throws {@link PlacementRefusedError} when no tier can meet every requirement.
|
|
120
|
+
*/
|
|
121
|
+
declare function placeExecution(input: PlaceExecutionInput): PlacementRecord;
|
|
122
|
+
/** The record for a tier the author named: nothing was chosen or refused. */
|
|
123
|
+
declare function authoredPlacement(chosen: ExecutionTier): PlacementRecord;
|
|
124
|
+
/**
|
|
125
|
+
* The record for work that resumes under the tier it began with. A retry reuses the
|
|
126
|
+
* recorded tier and never re-decides, so one turn never runs on two tiers; a caller
|
|
127
|
+
* resuming on `loop` first checks {@link sandboxOnlyRequirements} is empty.
|
|
128
|
+
*/
|
|
129
|
+
declare function resumedPlacement(chosen: ExecutionTier): PlacementRecord;
|
|
130
|
+
//#endregion
|
|
131
|
+
export { ExecutionTier, LoopProfileRefusalOptions, PLACEMENT_POLICY, PlaceExecutionInput, PlacementContext, PlacementRecord, PlacementRefusedError, PlacementRequirements, SandboxOnlyRequirementsOptions, VacuousAxis, authoredPlacement, loopProfileRefusal, loopTierProfileMaterialization, placeExecution, resumedPlacement, sandboxOnlyRequirements };
|
|
132
|
+
//# sourceMappingURL=placement.d.ts.map
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
//#region src/placement/index.ts
|
|
2
|
+
/** The policy version every {@link PlacementRecord} carries. */
|
|
3
|
+
const PLACEMENT_POLICY = "agent-placement/3";
|
|
4
|
+
/** No tier can meet every declared requirement. Raised before any spend. */
|
|
5
|
+
var PlacementRefusedError = class extends Error {
|
|
6
|
+
reason;
|
|
7
|
+
constructor(reason) {
|
|
8
|
+
super(`placement cannot run this work — ${reason}`);
|
|
9
|
+
this.reason = reason;
|
|
10
|
+
this.name = "PlacementRefusedError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The profile axes `inference` and `loop` carry: identity, prompt, model controls and
|
|
15
|
+
* metadata. No harness, native tools, files, MCP, subagents, hooks or modes.
|
|
16
|
+
*/
|
|
17
|
+
const loopTierProfileMaterialization = {
|
|
18
|
+
name: "placement-loop-tier",
|
|
19
|
+
axes: [
|
|
20
|
+
"name",
|
|
21
|
+
"description",
|
|
22
|
+
"version",
|
|
23
|
+
"tags",
|
|
24
|
+
"systemPrompt",
|
|
25
|
+
"appendSystemPrompt",
|
|
26
|
+
"instructions",
|
|
27
|
+
"modelDefault",
|
|
28
|
+
"modelSmall",
|
|
29
|
+
"modelProvider",
|
|
30
|
+
"modelReasoningEffort",
|
|
31
|
+
"modelMaxVisibleOutputTokens",
|
|
32
|
+
"modelMaxTotalOutputTokens",
|
|
33
|
+
"modelMetadata",
|
|
34
|
+
"metadata"
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
/** Compound profile properties and the axis each child maps to. */
|
|
38
|
+
const COMPOUND_FIELDS = {
|
|
39
|
+
prompt: {
|
|
40
|
+
systemPrompt: "systemPrompt",
|
|
41
|
+
appendSystemPrompt: "appendSystemPrompt",
|
|
42
|
+
instructions: "instructions"
|
|
43
|
+
},
|
|
44
|
+
model: {
|
|
45
|
+
default: "modelDefault",
|
|
46
|
+
small: "modelSmall",
|
|
47
|
+
provider: "modelProvider",
|
|
48
|
+
reasoningEffort: "modelReasoningEffort",
|
|
49
|
+
maxVisibleOutputTokens: "modelMaxVisibleOutputTokens",
|
|
50
|
+
maxReasoningTokens: "modelMaxReasoningTokens",
|
|
51
|
+
maxTotalOutputTokens: "modelMaxTotalOutputTokens",
|
|
52
|
+
metadata: "modelMetadata"
|
|
53
|
+
},
|
|
54
|
+
resources: {
|
|
55
|
+
files: "files",
|
|
56
|
+
tools: "resourceTools",
|
|
57
|
+
skills: "skills",
|
|
58
|
+
agents: "resourceAgents",
|
|
59
|
+
commands: "commands",
|
|
60
|
+
instructions: "resourceInstructions",
|
|
61
|
+
failOnError: "resourceFailOnError"
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
/** Top-level profile properties that are themselves an axis (the rest are compounds). */
|
|
65
|
+
const TOP_LEVEL_AXES = /* @__PURE__ */ new Set([
|
|
66
|
+
"name",
|
|
67
|
+
"description",
|
|
68
|
+
"version",
|
|
69
|
+
"tags",
|
|
70
|
+
"harness",
|
|
71
|
+
"seats",
|
|
72
|
+
"permissions",
|
|
73
|
+
"tools",
|
|
74
|
+
"mcp",
|
|
75
|
+
"connections",
|
|
76
|
+
"subagents",
|
|
77
|
+
"hooks",
|
|
78
|
+
"modes",
|
|
79
|
+
"confidential",
|
|
80
|
+
"metadata",
|
|
81
|
+
"extensions"
|
|
82
|
+
]);
|
|
83
|
+
/**
|
|
84
|
+
* Why `inference` and `loop` cannot run this profile, or undefined when they can.
|
|
85
|
+
* A profile that needs a harness, files, harness built-in tools, MCP servers,
|
|
86
|
+
* subagents, hooks or modes needs a sandbox; the loop never drops one silently.
|
|
87
|
+
* Platform's `workerProfileRefusal`, read through {@link loopTierProfileMaterialization}.
|
|
88
|
+
*/
|
|
89
|
+
function loopProfileRefusal(profile, options = {}) {
|
|
90
|
+
const fields = profile;
|
|
91
|
+
const vacuousPermissions = options.vacuousAxes?.includes("permissions") === true;
|
|
92
|
+
const inlineSkills = options.inlineSkills === true;
|
|
93
|
+
const carried = new Set(loopTierProfileMaterialization.axes);
|
|
94
|
+
if (inlineSkills) carried.add("skills");
|
|
95
|
+
const childCovered = (field, child, value) => {
|
|
96
|
+
const axis = COMPOUND_FIELDS[field]?.[child];
|
|
97
|
+
if (axis === void 0) return false;
|
|
98
|
+
if (axis === "resourceFailOnError") return inlineSkills && value === true;
|
|
99
|
+
return carried.has(axis);
|
|
100
|
+
};
|
|
101
|
+
const reasons = [];
|
|
102
|
+
for (const [field, value] of Object.entries(fields)) {
|
|
103
|
+
if (value === void 0) continue;
|
|
104
|
+
if (field === "tools") continue;
|
|
105
|
+
if (field === "permissions" && vacuousPermissions && permissionsGrantNothing(value, inlineSkills)) continue;
|
|
106
|
+
if (TOP_LEVEL_AXES.has(field) && carried.has(field)) continue;
|
|
107
|
+
const children = COMPOUND_FIELDS[field];
|
|
108
|
+
if (children && Object.values(children).some((axis) => carried.has(axis))) continue;
|
|
109
|
+
reasons.push(`profile ${field} needs a sandbox`);
|
|
110
|
+
}
|
|
111
|
+
for (const field of Object.keys(COMPOUND_FIELDS)) {
|
|
112
|
+
const value = fields[field];
|
|
113
|
+
if (typeof value !== "object" || value === null) continue;
|
|
114
|
+
if (!Object.values(COMPOUND_FIELDS[field] ?? {}).some((axis) => carried.has(axis))) continue;
|
|
115
|
+
for (const [child, inner] of Object.entries(value)) if (inner !== void 0 && !childCovered(field, child, inner)) reasons.push(`profile ${field}.${child} is not supported by the worker`);
|
|
116
|
+
}
|
|
117
|
+
const tools = fields.tools;
|
|
118
|
+
if (typeof tools === "object" && tools !== null) {
|
|
119
|
+
const enabled = Object.entries(tools).filter(([name, on]) => on === true && !(inlineSkills && name === "skill")).map(([name]) => name);
|
|
120
|
+
if (enabled.length > 0) reasons.push(`profile tools ${enabled.join(", ")} need a sandbox`);
|
|
121
|
+
}
|
|
122
|
+
return reasons.length > 0 ? reasons.join("; ") : void 0;
|
|
123
|
+
}
|
|
124
|
+
/** True when every permission rule denies, or allows only `skill` under inlined skills. */
|
|
125
|
+
function permissionsGrantNothing(value, inlineSkills) {
|
|
126
|
+
if (typeof value !== "object" || value === null) return false;
|
|
127
|
+
return Object.entries(value).every(([tool, rule]) => {
|
|
128
|
+
if (inlineSkills && tool === "skill") return true;
|
|
129
|
+
if (rule === "deny") return true;
|
|
130
|
+
if (typeof rule !== "object" || rule === null) return false;
|
|
131
|
+
return Object.values(rule).every((inner) => inner === "deny");
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/** Requirement fields that only mean something inside a provisioned box. */
|
|
135
|
+
const SANDBOX_FIELDS = [
|
|
136
|
+
"source",
|
|
137
|
+
"skills",
|
|
138
|
+
"env",
|
|
139
|
+
"image",
|
|
140
|
+
"size",
|
|
141
|
+
"resources",
|
|
142
|
+
"fromSnapshot",
|
|
143
|
+
"fromSandboxId",
|
|
144
|
+
"resume"
|
|
145
|
+
];
|
|
146
|
+
/** Every declared requirement only a sandbox can meet, in field order. */
|
|
147
|
+
function sandboxRequirements(options) {
|
|
148
|
+
const { requirements: cfg, profile } = options;
|
|
149
|
+
const needs = [];
|
|
150
|
+
if (options.interactive) needs.push("interactive run needs a session");
|
|
151
|
+
if (cfg.harness !== void 0) needs.push(`harness ${cfg.harness} is pinned`);
|
|
152
|
+
for (const field of SANDBOX_FIELDS) if (cfg[field] !== void 0) needs.push(`${field} needs a sandbox`);
|
|
153
|
+
if (cfg.maxRounds !== void 0 && cfg.maxRounds > 1) needs.push("maxRounds > 1 needs a multi-round session");
|
|
154
|
+
if (cfg.ask === true) needs.push("ask needs a session to answer into");
|
|
155
|
+
const refusal = profile ? loopProfileRefusal(profile, options) : void 0;
|
|
156
|
+
if (refusal) needs.push(refusal);
|
|
157
|
+
const profileModel = profileDefaultModel(profile);
|
|
158
|
+
if (cfg.model !== void 0 && profileModel !== void 0 && cfg.model !== profileModel) needs.push(`the action model ${cfg.model} differs from the profile model ${String(profileModel)}`);
|
|
159
|
+
return needs;
|
|
160
|
+
}
|
|
161
|
+
function profileDefaultModel(profile) {
|
|
162
|
+
return profile?.model?.default;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Every declared requirement only a sandbox can meet. A caller resuming work in the
|
|
166
|
+
* loop it began in checks this is empty before it continues there.
|
|
167
|
+
*/
|
|
168
|
+
function sandboxOnlyRequirements(options) {
|
|
169
|
+
return sandboxRequirements({
|
|
170
|
+
...options,
|
|
171
|
+
requirements: options.requirements ?? {}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/** Tools the work grants, each of which the loop can hold. */
|
|
175
|
+
function toolRequirements(cfg) {
|
|
176
|
+
const tools = [];
|
|
177
|
+
if ((cfg.connections ?? []).length > 0) tools.push("connections need Hub tools");
|
|
178
|
+
if (cfg.objects !== void 0) tools.push("objects need object tools");
|
|
179
|
+
if (cfg.http !== void 0) tools.push("http needs a fetch tool");
|
|
180
|
+
if ((cfg.tools ?? []).length > 0) tools.push(`tools ${cfg.tools?.join(", ")} need the loop`);
|
|
181
|
+
return tools;
|
|
182
|
+
}
|
|
183
|
+
/** Grants only the loop holds: a sandbox cannot honour them. */
|
|
184
|
+
function loopOnlyGrants(cfg) {
|
|
185
|
+
return [...cfg.objects !== void 0 ? ["objects"] : [], ...cfg.http !== void 0 ? ["http"] : []];
|
|
186
|
+
}
|
|
187
|
+
/** True when the profile declares skills that only an inliner on the loop can carry. */
|
|
188
|
+
function declaresProfileSkills(profile) {
|
|
189
|
+
const fields = profile ?? {};
|
|
190
|
+
const skills = fields.resources?.skills;
|
|
191
|
+
return Array.isArray(skills) && skills.length > 0 || fields.tools?.skill === true || fields.permissions?.skill !== void 0 && fields.permissions.skill !== "deny";
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Choose where work runs: the cheapest tier every declared requirement allows.
|
|
195
|
+
* Throws {@link PlacementRefusedError} when no tier can meet every requirement.
|
|
196
|
+
*/
|
|
197
|
+
function placeExecution(input) {
|
|
198
|
+
const cfg = input.requirements ?? {};
|
|
199
|
+
const { context, profile } = input;
|
|
200
|
+
const opts = {
|
|
201
|
+
...input.vacuousAxes ? { vacuousAxes: input.vacuousAxes } : {},
|
|
202
|
+
...input.inlineSkills ? { inlineSkills: true } : {}
|
|
203
|
+
};
|
|
204
|
+
const sandboxNeeds = sandboxRequirements({
|
|
205
|
+
requirements: cfg,
|
|
206
|
+
profile,
|
|
207
|
+
interactive: context.interactive,
|
|
208
|
+
...opts
|
|
209
|
+
});
|
|
210
|
+
const tools = toolRequirements(cfg);
|
|
211
|
+
if (input.inlineSkills && declaresProfileSkills(profile)) tools.push("skills are inlined by the loop");
|
|
212
|
+
const loopOnly = loopOnlyGrants(cfg);
|
|
213
|
+
const coveredAxes = profile ? declaredAxes(profile) : [];
|
|
214
|
+
const record = (chosen, reason, rejected) => ({
|
|
215
|
+
chosen,
|
|
216
|
+
policy: PLACEMENT_POLICY,
|
|
217
|
+
reason,
|
|
218
|
+
coveredAxes,
|
|
219
|
+
rejected
|
|
220
|
+
});
|
|
221
|
+
if (sandboxNeeds.length > 0) {
|
|
222
|
+
const reason = sandboxNeeds.join("; ");
|
|
223
|
+
if (loopOnly.length > 0) throw new PlacementRefusedError(`${loopOnly.join(" and ")} run only in the worker, but ${reason}`);
|
|
224
|
+
return record("sandbox", reason, [{
|
|
225
|
+
placement: "inference",
|
|
226
|
+
reason
|
|
227
|
+
}, {
|
|
228
|
+
placement: "loop",
|
|
229
|
+
reason
|
|
230
|
+
}]);
|
|
231
|
+
}
|
|
232
|
+
if (tools.length > 0) {
|
|
233
|
+
const reason = `${tools.join("; ")}, which the worker holds without a sandbox`;
|
|
234
|
+
if (!context.loopAvailable) {
|
|
235
|
+
const unavailable = context.loopUnavailableReason ?? "worker placement is not wired on this deployment";
|
|
236
|
+
if (loopOnly.length > 0) throw new PlacementRefusedError(`${loopOnly.join(" and ")} need the worker, but ${unavailable}`);
|
|
237
|
+
return record("sandbox", unavailable, [{
|
|
238
|
+
placement: "inference",
|
|
239
|
+
reason: tools.join("; ")
|
|
240
|
+
}, {
|
|
241
|
+
placement: "loop",
|
|
242
|
+
reason: unavailable
|
|
243
|
+
}]);
|
|
244
|
+
}
|
|
245
|
+
return record("loop", reason, [{
|
|
246
|
+
placement: "inference",
|
|
247
|
+
reason: tools.join("; ")
|
|
248
|
+
}, {
|
|
249
|
+
placement: "sandbox",
|
|
250
|
+
reason: "costs more and no declared requirement needs it"
|
|
251
|
+
}]);
|
|
252
|
+
}
|
|
253
|
+
if (context.inferenceAvailable) return record("inference", "no declared requirement needs tools, files or a session", [{
|
|
254
|
+
placement: "loop",
|
|
255
|
+
reason: "no declared tool needs a loop"
|
|
256
|
+
}, {
|
|
257
|
+
placement: "sandbox",
|
|
258
|
+
reason: "costs more and no declared requirement needs it"
|
|
259
|
+
}]);
|
|
260
|
+
const unwired = "inference is not wired on this deployment";
|
|
261
|
+
if (context.loopAvailable) return record("loop", `${unwired}; one tool-free worker turn`, [{
|
|
262
|
+
placement: "inference",
|
|
263
|
+
reason: unwired
|
|
264
|
+
}, {
|
|
265
|
+
placement: "sandbox",
|
|
266
|
+
reason: "costs more and no declared requirement needs it"
|
|
267
|
+
}]);
|
|
268
|
+
return record("sandbox", unwired, [{
|
|
269
|
+
placement: "inference",
|
|
270
|
+
reason: unwired
|
|
271
|
+
}, {
|
|
272
|
+
placement: "loop",
|
|
273
|
+
reason: "worker placement is not wired on this deployment"
|
|
274
|
+
}]);
|
|
275
|
+
}
|
|
276
|
+
/** The materialization axes a profile declares, in its own field order. Read from the
|
|
277
|
+
* same field map as {@link loopProfileRefusal}, so the module has no runtime import. */
|
|
278
|
+
function declaredAxes(profile) {
|
|
279
|
+
const axes = [];
|
|
280
|
+
for (const [field, value] of Object.entries(profile)) {
|
|
281
|
+
if (value === void 0) continue;
|
|
282
|
+
if (TOP_LEVEL_AXES.has(field)) axes.push(field);
|
|
283
|
+
const children = COMPOUND_FIELDS[field];
|
|
284
|
+
if (!children || typeof value !== "object" || value === null) continue;
|
|
285
|
+
for (const [child, inner] of Object.entries(value)) {
|
|
286
|
+
const axis = children[child];
|
|
287
|
+
if (axis !== void 0 && inner !== void 0) axes.push(axis);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return axes;
|
|
291
|
+
}
|
|
292
|
+
/** The record for a tier the author named: nothing was chosen or refused. */
|
|
293
|
+
function authoredPlacement(chosen) {
|
|
294
|
+
return {
|
|
295
|
+
chosen,
|
|
296
|
+
policy: PLACEMENT_POLICY,
|
|
297
|
+
reason: `authored placement: ${chosen}`,
|
|
298
|
+
coveredAxes: [],
|
|
299
|
+
rejected: []
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* The record for work that resumes under the tier it began with. A retry reuses the
|
|
304
|
+
* recorded tier and never re-decides, so one turn never runs on two tiers; a caller
|
|
305
|
+
* resuming on `loop` first checks {@link sandboxOnlyRequirements} is empty.
|
|
306
|
+
*/
|
|
307
|
+
function resumedPlacement(chosen) {
|
|
308
|
+
return {
|
|
309
|
+
chosen,
|
|
310
|
+
policy: PLACEMENT_POLICY,
|
|
311
|
+
reason: `resumed under the placement it began with: ${chosen}`,
|
|
312
|
+
coveredAxes: [],
|
|
313
|
+
rejected: []
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
//#endregion
|
|
317
|
+
export { PLACEMENT_POLICY, PlacementRefusedError, authoredPlacement, loopProfileRefusal, loopTierProfileMaterialization, placeExecution, resumedPlacement, sandboxOnlyRequirements };
|
|
318
|
+
|
|
319
|
+
//# sourceMappingURL=placement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"placement.js","names":[],"sources":["../src/placement/index.ts"],"sourcesContent":["/**\n * Execution placement: choose the cheapest executor whose declared capabilities\n * cover the work, before any key, sandbox or model call.\n *\n * A pure decision over DECLARED data — the work's requirements and its resolved\n * {@link AgentProfile}. It never reads the prompt and does no I/O, so it is safe\n * in a Worker or Durable Object and costs nothing on the path to the first word.\n * Tiers, cheapest first:\n * - `inference`: one Router call, when nothing needs a tool, a file or a session;\n * - `loop`: the Runtime tool loop in the caller's own process (a Worker or the\n * Platform workflow worker), when the only tools are ones that loop holds;\n * - `sandbox`: anything that needs a harness, a filesystem, a shell, files, MCP\n * servers, subagents, hooks or an interactive session.\n * A requirement no tier can meet refuses the work by name\n * ({@link PlacementRefusedError}); a capability is never dropped silently, and a\n * pinned harness always runs in the sandbox.\n *\n * This is Platform's `agent-placement/2` (`selectAgentPlacement` and\n * `workerProfileRefusal`) moved here unchanged in semantics. Its reason strings\n * are kept byte-identical so a shadow comparison against Platform's copy reads\n * equal; only the tier name changes (`worker` → `loop`). `agent-placement/3`\n * adds two caller opt-ins, both off by default:\n * - {@link PlaceExecutionInput.vacuousAxes}: a deny-only `permissions` map asks\n * for nothing a tier lacks, so it counts as covered;\n * - {@link PlaceExecutionInput.inlineSkills}: profile skills count as covered on\n * `loop` when the caller inlines their content.\n */\n\nimport type { AgentProfile } from '@tangle-network/agent-interface'\nimport type {\n AgentProfileMaterializationAxis,\n ProfileMaterializationContract,\n} from '../agent/profile-materialization'\n\n/** The policy version every {@link PlacementRecord} carries. */\nexport const PLACEMENT_POLICY = 'agent-placement/3'\n\n/** Where one unit of agent work runs, cheapest first. */\nexport type ExecutionTier = 'inference' | 'loop' | 'sandbox'\n\n/** What was chosen, under which policy, why, and why each other tier was refused. */\nexport interface PlacementRecord {\n chosen: ExecutionTier\n policy: typeof PLACEMENT_POLICY\n reason: string\n /**\n * The profile's declared materialization axes the chosen tier carries. A tier that\n * cannot carry a declared axis is rejected, so on a fresh decision this is every\n * declared axis; an authored or resumed record makes no coverage decision and is empty.\n */\n coveredAxes: AgentProfileMaterializationAxis[]\n rejected: Array<{ placement: ExecutionTier; reason: string }>\n}\n\n/**\n * The declared requirements of the work, beside its profile. Field names follow\n * Platform's `agent.run` action, the first consumer; an app's chat turn usually\n * declares none of them.\n */\nexport interface PlacementRequirements {\n /** A pinned harness. Always runs in the sandbox. */\n harness?: string\n /** A model other than the profile's own needs the sandbox to choose between them. */\n model?: string\n source?: unknown\n /** Skill ids resolved into a box. Profile skills are `profile.resources.skills`. */\n skills?: unknown\n env?: unknown\n image?: unknown\n size?: unknown\n resources?: unknown\n fromSnapshot?: unknown\n fromSandboxId?: unknown\n resume?: unknown\n maxRounds?: number\n ask?: boolean\n /** Hub connections: tools the loop holds. */\n connections?: readonly string[]\n /** Owner-object grants: tools only the loop holds. */\n objects?: unknown\n /** Public HTTPS reads: a tool only the loop holds. */\n http?: unknown\n /** Tools the caller binds into the loop itself, such as an app's product tools. */\n tools?: readonly string[]\n}\n\n/** Which tiers this deployment has wired. */\nexport interface PlacementContext {\n interactive: boolean\n inferenceAvailable: boolean\n loopAvailable: boolean\n /** Why the loop cannot take this work, when it cannot. */\n loopUnavailableReason?: string\n}\n\n/** Profile axes a tier may count as covered when their declared value grants nothing. */\nexport type VacuousAxis = 'permissions'\n\nexport interface PlaceExecutionInput {\n requirements?: PlacementRequirements\n /** The RESOLVED profile: a named profile is read before the decision. */\n profile?: AgentProfile | object\n context: PlacementContext\n /**\n * Axes whose deny-only value counts as covered on `inference` and `loop`.\n * `permissions` qualifies when every rule is `deny`: a tier without native tools\n * already denies them. Deny-only `tools` are covered without opting in.\n */\n vacuousAxes?: readonly VacuousAxis[]\n /**\n * The caller inlines the profile's skill content into the loop's context, so\n * `resources.skills` (with a strict `resources.failOnError`) and the native `skill`\n * tool and permission that load them count as covered on `loop`. Off by default:\n * the skills then keep the work in the sandbox.\n */\n inlineSkills?: boolean\n}\n\n/** No tier can meet every declared requirement. Raised before any spend. */\nexport class PlacementRefusedError extends Error {\n constructor(readonly reason: string) {\n super(`placement cannot run this work — ${reason}`)\n this.name = 'PlacementRefusedError'\n }\n}\n\n/**\n * The profile axes `inference` and `loop` carry: identity, prompt, model controls and\n * metadata. No harness, native tools, files, MCP, subagents, hooks or modes.\n */\nexport const loopTierProfileMaterialization: ProfileMaterializationContract = {\n name: 'placement-loop-tier',\n axes: [\n 'name',\n 'description',\n 'version',\n 'tags',\n 'systemPrompt',\n 'appendSystemPrompt',\n 'instructions',\n 'modelDefault',\n 'modelSmall',\n 'modelProvider',\n 'modelReasoningEffort',\n 'modelMaxVisibleOutputTokens',\n 'modelMaxTotalOutputTokens',\n 'modelMetadata',\n 'metadata',\n ],\n}\n\n/** Compound profile properties and the axis each child maps to. */\nconst COMPOUND_FIELDS: Record<string, Record<string, AgentProfileMaterializationAxis>> = {\n prompt: {\n systemPrompt: 'systemPrompt',\n appendSystemPrompt: 'appendSystemPrompt',\n instructions: 'instructions',\n },\n model: {\n default: 'modelDefault',\n small: 'modelSmall',\n provider: 'modelProvider',\n reasoningEffort: 'modelReasoningEffort',\n maxVisibleOutputTokens: 'modelMaxVisibleOutputTokens',\n maxReasoningTokens: 'modelMaxReasoningTokens',\n maxTotalOutputTokens: 'modelMaxTotalOutputTokens',\n metadata: 'modelMetadata',\n },\n resources: {\n files: 'files',\n tools: 'resourceTools',\n skills: 'skills',\n agents: 'resourceAgents',\n commands: 'commands',\n instructions: 'resourceInstructions',\n failOnError: 'resourceFailOnError',\n },\n}\n\n/** Top-level profile properties that are themselves an axis (the rest are compounds). */\nconst TOP_LEVEL_AXES: ReadonlySet<string> = new Set([\n 'name',\n 'description',\n 'version',\n 'tags',\n 'harness',\n 'seats',\n 'permissions',\n 'tools',\n 'mcp',\n 'connections',\n 'subagents',\n 'hooks',\n 'modes',\n 'confidential',\n 'metadata',\n 'extensions',\n])\n\n/** Options for {@link loopProfileRefusal}. */\nexport interface LoopProfileRefusalOptions {\n vacuousAxes?: readonly VacuousAxis[]\n inlineSkills?: boolean\n}\n\n/**\n * Why `inference` and `loop` cannot run this profile, or undefined when they can.\n * A profile that needs a harness, files, harness built-in tools, MCP servers,\n * subagents, hooks or modes needs a sandbox; the loop never drops one silently.\n * Platform's `workerProfileRefusal`, read through {@link loopTierProfileMaterialization}.\n */\nexport function loopProfileRefusal(\n profile: object,\n options: LoopProfileRefusalOptions = {},\n): string | undefined {\n const fields = profile as Record<string, unknown>\n const vacuousPermissions = options.vacuousAxes?.includes('permissions') === true\n const inlineSkills = options.inlineSkills === true\n const carried = new Set<string>(loopTierProfileMaterialization.axes)\n if (inlineSkills) carried.add('skills')\n const childCovered = (field: string, child: string, value: unknown): boolean => {\n const axis = COMPOUND_FIELDS[field]?.[child]\n if (axis === undefined) return false\n // A strict failure policy is honoured by an inliner that fails closed; a best-effort\n // one would have to report a skipped skill, which the loop has no channel for.\n if (axis === 'resourceFailOnError') return inlineSkills && value === true\n return carried.has(axis)\n }\n const reasons: string[] = []\n for (const [field, value] of Object.entries(fields)) {\n if (value === undefined) continue\n if (field === 'tools') continue\n if (\n field === 'permissions' &&\n vacuousPermissions &&\n permissionsGrantNothing(value, inlineSkills)\n )\n continue\n if (TOP_LEVEL_AXES.has(field) && carried.has(field)) continue\n const children = COMPOUND_FIELDS[field]\n if (children && Object.values(children).some((axis) => carried.has(axis))) continue\n reasons.push(`profile ${field} needs a sandbox`)\n }\n for (const field of Object.keys(COMPOUND_FIELDS)) {\n const value = fields[field]\n if (typeof value !== 'object' || value === null) continue\n // A compound the loop carries none of was refused whole above.\n if (!Object.values(COMPOUND_FIELDS[field] ?? {}).some((axis) => carried.has(axis))) continue\n for (const [child, inner] of Object.entries(value)) {\n if (inner !== undefined && !childCovered(field, child, inner)) {\n reasons.push(`profile ${field}.${child} is not supported by the worker`)\n }\n }\n }\n // Harness built-in tools (shell, edit, browser) exist only in a sandbox. A profile may\n // still switch them off. The `skill` tool only loads skills, which an inliner supplies.\n const tools = fields.tools\n if (typeof tools === 'object' && tools !== null) {\n const enabled = Object.entries(tools)\n .filter(([name, on]) => on === true && !(inlineSkills && name === 'skill'))\n .map(([name]) => name)\n if (enabled.length > 0) reasons.push(`profile tools ${enabled.join(', ')} need a sandbox`)\n }\n return reasons.length > 0 ? reasons.join('; ') : undefined\n}\n\n/** True when every permission rule denies, or allows only `skill` under inlined skills. */\nfunction permissionsGrantNothing(value: unknown, inlineSkills: boolean): boolean {\n if (typeof value !== 'object' || value === null) return false\n return Object.entries(value).every(([tool, rule]) => {\n if (inlineSkills && tool === 'skill') return true\n if (rule === 'deny') return true\n if (typeof rule !== 'object' || rule === null) return false\n return Object.values(rule).every((inner) => inner === 'deny')\n })\n}\n\n/** Requirement fields that only mean something inside a provisioned box. */\nconst SANDBOX_FIELDS = [\n 'source',\n 'skills',\n 'env',\n 'image',\n 'size',\n 'resources',\n 'fromSnapshot',\n 'fromSandboxId',\n 'resume',\n] as const satisfies ReadonlyArray<keyof PlacementRequirements>\n\ninterface RequirementOptions extends LoopProfileRefusalOptions {\n requirements: PlacementRequirements\n profile?: object\n interactive: boolean\n}\n\n/** Every declared requirement only a sandbox can meet, in field order. */\nfunction sandboxRequirements(options: RequirementOptions): string[] {\n const { requirements: cfg, profile } = options\n const needs: string[] = []\n if (options.interactive) needs.push('interactive run needs a session')\n if (cfg.harness !== undefined) needs.push(`harness ${cfg.harness} is pinned`)\n for (const field of SANDBOX_FIELDS) {\n if (cfg[field] !== undefined) needs.push(`${field} needs a sandbox`)\n }\n if (cfg.maxRounds !== undefined && cfg.maxRounds > 1)\n needs.push('maxRounds > 1 needs a multi-round session')\n if (cfg.ask === true) needs.push('ask needs a session to answer into')\n const refusal = profile ? loopProfileRefusal(profile, options) : undefined\n if (refusal) needs.push(refusal)\n // Inference and the loop run the profile's own model and refuse another; only the\n // sandbox decides between a requested model and the profile's.\n const profileModel = profileDefaultModel(profile)\n if (cfg.model !== undefined && profileModel !== undefined && cfg.model !== profileModel) {\n needs.push(\n `the action model ${cfg.model} differs from the profile model ${String(profileModel)}`,\n )\n }\n return needs\n}\n\nfunction profileDefaultModel(profile: object | undefined): unknown {\n return (profile as { model?: { default?: unknown } } | undefined)?.model?.default\n}\n\n/** Options for {@link sandboxOnlyRequirements}. */\nexport interface SandboxOnlyRequirementsOptions extends LoopProfileRefusalOptions {\n requirements?: PlacementRequirements\n profile?: AgentProfile | object\n interactive: boolean\n}\n\n/**\n * Every declared requirement only a sandbox can meet. A caller resuming work in the\n * loop it began in checks this is empty before it continues there.\n */\nexport function sandboxOnlyRequirements(options: SandboxOnlyRequirementsOptions): string[] {\n return sandboxRequirements({ ...options, requirements: options.requirements ?? {} })\n}\n\n/** Tools the work grants, each of which the loop can hold. */\nfunction toolRequirements(cfg: PlacementRequirements): string[] {\n const tools: string[] = []\n if ((cfg.connections ?? []).length > 0) tools.push('connections need Hub tools')\n if (cfg.objects !== undefined) tools.push('objects need object tools')\n if (cfg.http !== undefined) tools.push('http needs a fetch tool')\n if ((cfg.tools ?? []).length > 0) tools.push(`tools ${cfg.tools?.join(', ')} need the loop`)\n return tools\n}\n\n/** Grants only the loop holds: a sandbox cannot honour them. */\nfunction loopOnlyGrants(cfg: PlacementRequirements): string[] {\n return [\n ...(cfg.objects !== undefined ? ['objects'] : []),\n ...(cfg.http !== undefined ? ['http'] : []),\n ]\n}\n\n/** True when the profile declares skills that only an inliner on the loop can carry. */\nfunction declaresProfileSkills(profile: object | undefined): boolean {\n const fields = (profile ?? {}) as {\n resources?: { skills?: unknown }\n tools?: Record<string, unknown>\n permissions?: Record<string, unknown>\n }\n const skills = fields.resources?.skills\n return (\n (Array.isArray(skills) && skills.length > 0) ||\n fields.tools?.skill === true ||\n (fields.permissions?.skill !== undefined && fields.permissions.skill !== 'deny')\n )\n}\n\n/**\n * Choose where work runs: the cheapest tier every declared requirement allows.\n * Throws {@link PlacementRefusedError} when no tier can meet every requirement.\n */\nexport function placeExecution(input: PlaceExecutionInput): PlacementRecord {\n const cfg = input.requirements ?? {}\n const { context, profile } = input\n const opts: LoopProfileRefusalOptions = {\n ...(input.vacuousAxes ? { vacuousAxes: input.vacuousAxes } : {}),\n ...(input.inlineSkills ? { inlineSkills: true } : {}),\n }\n const sandboxNeeds = sandboxRequirements({\n requirements: cfg,\n profile,\n interactive: context.interactive,\n ...opts,\n })\n const tools = toolRequirements(cfg)\n if (input.inlineSkills && declaresProfileSkills(profile))\n tools.push('skills are inlined by the loop')\n const loopOnly = loopOnlyGrants(cfg)\n const coveredAxes = profile ? declaredAxes(profile) : []\n const record = (\n chosen: ExecutionTier,\n reason: string,\n rejected: PlacementRecord['rejected'],\n ): PlacementRecord => ({ chosen, policy: PLACEMENT_POLICY, reason, coveredAxes, rejected })\n\n if (sandboxNeeds.length > 0) {\n const reason = sandboxNeeds.join('; ')\n if (loopOnly.length > 0) {\n throw new PlacementRefusedError(\n `${loopOnly.join(' and ')} run only in the worker, but ${reason}`,\n )\n }\n return record('sandbox', reason, [\n { placement: 'inference', reason },\n { placement: 'loop', reason },\n ])\n }\n\n // Past the sandbox check the profile is one the loop (and a single Router call) can\n // honour, so only granted tools separate the two.\n if (tools.length > 0) {\n const reason = `${tools.join('; ')}, which the worker holds without a sandbox`\n if (!context.loopAvailable) {\n const unavailable =\n context.loopUnavailableReason ?? 'worker placement is not wired on this deployment'\n if (loopOnly.length > 0) {\n throw new PlacementRefusedError(\n `${loopOnly.join(' and ')} need the worker, but ${unavailable}`,\n )\n }\n // Inlined skills are the one loop need a sandbox also meets, natively.\n return record('sandbox', unavailable, [\n { placement: 'inference', reason: tools.join('; ') },\n { placement: 'loop', reason: unavailable },\n ])\n }\n return record('loop', reason, [\n { placement: 'inference', reason: tools.join('; ') },\n { placement: 'sandbox', reason: 'costs more and no declared requirement needs it' },\n ])\n }\n\n if (context.inferenceAvailable) {\n return record('inference', 'no declared requirement needs tools, files or a session', [\n { placement: 'loop', reason: 'no declared tool needs a loop' },\n { placement: 'sandbox', reason: 'costs more and no declared requirement needs it' },\n ])\n }\n const unwired = 'inference is not wired on this deployment'\n if (context.loopAvailable) {\n return record('loop', `${unwired}; one tool-free worker turn`, [\n { placement: 'inference', reason: unwired },\n { placement: 'sandbox', reason: 'costs more and no declared requirement needs it' },\n ])\n }\n return record('sandbox', unwired, [\n { placement: 'inference', reason: unwired },\n { placement: 'loop', reason: 'worker placement is not wired on this deployment' },\n ])\n}\n\n/** The materialization axes a profile declares, in its own field order. Read from the\n * same field map as {@link loopProfileRefusal}, so the module has no runtime import. */\nfunction declaredAxes(profile: object): AgentProfileMaterializationAxis[] {\n const axes: AgentProfileMaterializationAxis[] = []\n for (const [field, value] of Object.entries(profile)) {\n if (value === undefined) continue\n if (TOP_LEVEL_AXES.has(field)) axes.push(field as AgentProfileMaterializationAxis)\n const children = COMPOUND_FIELDS[field]\n if (!children || typeof value !== 'object' || value === null) continue\n for (const [child, inner] of Object.entries(value)) {\n const axis = children[child]\n if (axis !== undefined && inner !== undefined) axes.push(axis)\n }\n }\n return axes\n}\n\n/** The record for a tier the author named: nothing was chosen or refused. */\nexport function authoredPlacement(chosen: ExecutionTier): PlacementRecord {\n return {\n chosen,\n policy: PLACEMENT_POLICY,\n reason: `authored placement: ${chosen}`,\n coveredAxes: [],\n rejected: [],\n }\n}\n\n/**\n * The record for work that resumes under the tier it began with. A retry reuses the\n * recorded tier and never re-decides, so one turn never runs on two tiers; a caller\n * resuming on `loop` first checks {@link sandboxOnlyRequirements} is empty.\n */\nexport function resumedPlacement(chosen: ExecutionTier): PlacementRecord {\n return {\n chosen,\n policy: PLACEMENT_POLICY,\n reason: `resumed under the placement it began with: ${chosen}`,\n coveredAxes: [],\n rejected: [],\n }\n}\n"],"mappings":"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"}
|