@orchestraworks/cli 0.5.1 → 0.5.3
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 +1 -1
- package/dist/cli/api/compute-provider-check.d.ts +29 -0
- package/dist/cli/api/compute-provider-check.js +207 -0
- package/dist/cli/api/tail.d.ts +66 -0
- package/dist/cli/api/tail.js +564 -0
- package/dist/cli/main.d.ts +5 -1
- package/dist/cli/main.js +22 -0
- package/dist/constructs/agent.d.ts +10 -0
- package/dist/constructs/agent.js +39 -4
- package/dist/constructs/environment.d.ts +29 -1
- package/dist/constructs/environment.js +28 -1
- package/dist/constructs/skill.d.ts +6 -1
- package/dist/constructs/skill.js +21 -2
- package/dist/engine/reconcile.js +15 -1
- package/dist/generated/cli-commands.json +876 -1
- package/dist/generated/managed-skills.d.ts +2 -0
- package/dist/generated/managed-skills.js +13 -0
- package/dist/index.d.ts +1 -1
- package/docs/REFERENCE.md +21 -1
- package/node_modules/@orchestraworks/client/dist/esm/models/ApiKey.d.ts +6 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/ApiKey.js +2 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/ComputeProvider.d.ts +3 -3
- package/node_modules/@orchestraworks/client/dist/esm/models/CreateApiKeyRequest.d.ts +7 -1
- package/node_modules/@orchestraworks/client/dist/esm/models/CreateApiKeyRequest.js +2 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/CreateEnvironmentRequest.d.ts +13 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/CreateEnvironmentRequest.js +5 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/Environment.d.ts +13 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/Environment.js +5 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/EnvironmentHook.d.ts +38 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/EnvironmentHook.js +45 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/EnvironmentHooks.d.ts +39 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/EnvironmentHooks.js +44 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/EnvironmentHooksRequest.d.ts +39 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/EnvironmentHooksRequest.js +44 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/EnvironmentProvisionSpec.d.ts +2 -1
- package/node_modules/@orchestraworks/client/dist/esm/models/EnvironmentProvisionSpecRequest.d.ts +2 -1
- package/node_modules/@orchestraworks/client/dist/esm/models/EnvironmentSnapshot.d.ts +19 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/EnvironmentSnapshot.js +7 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/Machine.d.ts +7 -1
- package/node_modules/@orchestraworks/client/dist/esm/models/Machine.js +2 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/UpdateEnvironmentRequest.d.ts +13 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/UpdateEnvironmentRequest.js +5 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/index.d.ts +3 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/index.js +3 -0
- package/node_modules/@orchestraworks/client/dist/models/ApiKey.d.ts +6 -0
- package/node_modules/@orchestraworks/client/dist/models/ApiKey.js +2 -0
- package/node_modules/@orchestraworks/client/dist/models/ComputeProvider.d.ts +3 -3
- package/node_modules/@orchestraworks/client/dist/models/CreateApiKeyRequest.d.ts +7 -1
- package/node_modules/@orchestraworks/client/dist/models/CreateApiKeyRequest.js +2 -0
- package/node_modules/@orchestraworks/client/dist/models/CreateEnvironmentRequest.d.ts +13 -0
- package/node_modules/@orchestraworks/client/dist/models/CreateEnvironmentRequest.js +5 -0
- package/node_modules/@orchestraworks/client/dist/models/Environment.d.ts +13 -0
- package/node_modules/@orchestraworks/client/dist/models/Environment.js +5 -0
- package/node_modules/@orchestraworks/client/dist/models/EnvironmentHook.d.ts +38 -0
- package/node_modules/@orchestraworks/client/dist/models/EnvironmentHook.js +52 -0
- package/node_modules/@orchestraworks/client/dist/models/EnvironmentHooks.d.ts +39 -0
- package/node_modules/@orchestraworks/client/dist/models/EnvironmentHooks.js +51 -0
- package/node_modules/@orchestraworks/client/dist/models/EnvironmentHooksRequest.d.ts +39 -0
- package/node_modules/@orchestraworks/client/dist/models/EnvironmentHooksRequest.js +51 -0
- package/node_modules/@orchestraworks/client/dist/models/EnvironmentProvisionSpec.d.ts +2 -1
- package/node_modules/@orchestraworks/client/dist/models/EnvironmentProvisionSpecRequest.d.ts +2 -1
- package/node_modules/@orchestraworks/client/dist/models/EnvironmentSnapshot.d.ts +19 -0
- package/node_modules/@orchestraworks/client/dist/models/EnvironmentSnapshot.js +7 -0
- package/node_modules/@orchestraworks/client/dist/models/Machine.d.ts +7 -1
- package/node_modules/@orchestraworks/client/dist/models/Machine.js +2 -0
- package/node_modules/@orchestraworks/client/dist/models/UpdateEnvironmentRequest.d.ts +13 -0
- package/node_modules/@orchestraworks/client/dist/models/UpdateEnvironmentRequest.js +5 -0
- package/node_modules/@orchestraworks/client/dist/models/index.d.ts +3 -0
- package/node_modules/@orchestraworks/client/dist/models/index.js +3 -0
- package/node_modules/@orchestraworks/client/package.json +1 -1
- package/package.json +4 -4
- package/skills/orchestra/SKILL.md +167 -42
- package/skills/orchestra-fdk/SKILL.md +112 -545
- package/skills/orchestra-fdk/references/authoring.md +130 -3
- package/skills/orchestra-fdk/references/environment.md +352 -43
- package/skills/orchestra-fdk/references/integrations/analytics.md +18 -0
- package/skills/orchestra-fdk/references/integrations/ci-cd.md +20 -0
- package/skills/orchestra-fdk/references/integrations/cloud.md +19 -0
- package/skills/orchestra-fdk/references/integrations/code-repo.md +76 -0
- package/skills/orchestra-fdk/references/integrations/databases.md +22 -0
- package/skills/orchestra-fdk/references/integrations/dependencies.md +29 -0
- package/skills/orchestra-fdk/references/integrations/design.md +18 -0
- package/skills/orchestra-fdk/references/integrations/feature-flags.md +19 -0
- package/skills/orchestra-fdk/references/integrations/incident.md +29 -0
- package/skills/orchestra-fdk/references/integrations/issue-tracker.md +65 -0
- package/skills/orchestra-fdk/references/integrations/knowledge-base.md +43 -0
- package/skills/orchestra-fdk/references/integrations/messaging.md +38 -0
- package/skills/orchestra-fdk/references/integrations/observability.md +36 -0
- package/skills/orchestra-fdk/references/integrations/secrets-managers.md +25 -0
- package/skills/orchestra-fdk/references/integrations/support-desk.md +31 -0
- package/skills/orchestra-fdk/references/phases/0-start.md +62 -0
- package/skills/orchestra-fdk/references/phases/1-discover.md +111 -0
- package/skills/orchestra-fdk/references/phases/2-plan.md +157 -0
- package/skills/orchestra-fdk/references/phases/3-connect.md +99 -0
- package/skills/orchestra-fdk/references/phases/4-build.md +159 -0
- package/skills/orchestra-fdk/references/phases/5-prove.md +107 -0
- package/skills/orchestra-fdk/references/phases/6-handover.md +42 -0
- package/skills/orchestra-fdk/references/troubleshooting.md +128 -0
- package/templates/bug-fix/agents/bugfix.md +1 -1
- package/templates/bug-fix/main.ts +1 -1
- package/templates/engineering-team/README.md +45 -181
- package/templates/engineering-team/agents/engineer-agent.md +10 -7
- package/templates/engineering-team/agents/manager-agent.md +23 -120
- package/templates/engineering-team/inputs.json +1 -3
- package/templates/engineering-team/lib/environment.ts +10 -90
- package/templates/engineering-team/lib/inputs.ts +5 -97
- package/templates/engineering-team/lib/setup.sh +5 -5
- package/templates/engineering-team/main.ts +22 -69
- package/templates/todo-factory/agents/bugfix.md +1 -1
- package/templates/todo-factory/main.ts +1 -1
- package/templates/engineering-team/agents/engineer-bare.md +0 -22
- package/templates/engineering-team/onboarding/create-score-metrics.sh +0 -33
|
@@ -23,6 +23,13 @@ export interface AgentProps {
|
|
|
23
23
|
harness: HarnessFamily;
|
|
24
24
|
instructions?: string;
|
|
25
25
|
skills?: ISkill[];
|
|
26
|
+
/**
|
|
27
|
+
* Whether the platform's managed skills are added after `skills`, which they are unless this is `false` or
|
|
28
|
+
* the harness is `script` or `custom`. Each managed id is added once, and an id `skills` already names stays
|
|
29
|
+
* where it is named there. A managed skill is left out when an authored `Skill` in `skills` declares the
|
|
30
|
+
* same frontmatter `name`, so the agent never installs two skills under one name.
|
|
31
|
+
*/
|
|
32
|
+
includeManagedSkills?: boolean;
|
|
26
33
|
memoryFiles?: MemoryFile[];
|
|
27
34
|
labels?: Record<string, string>;
|
|
28
35
|
modelSettings?: AgentModelSettings;
|
|
@@ -47,11 +54,14 @@ export declare class Agent extends Construct implements IAgent {
|
|
|
47
54
|
private readonly explicitSlug;
|
|
48
55
|
private readonly displayName;
|
|
49
56
|
private readonly harness;
|
|
57
|
+
private readonly managedSkillIds;
|
|
50
58
|
constructor(scope: Construct, id: string, props: AgentProps);
|
|
51
59
|
get slug(): string;
|
|
52
60
|
/** @internal */
|
|
53
61
|
_resourceId(): string;
|
|
54
62
|
_toPlanResource(): PlanResource;
|
|
63
|
+
private receivesManagedSkills;
|
|
64
|
+
private skillIds;
|
|
55
65
|
static fromSlug(scope: Construct, slug: string): IAgent;
|
|
56
66
|
/**
|
|
57
67
|
* Apply resolves the reference through the shape-opaque `findByIdOrSlug` lookup, so a slug passed here —
|
package/dist/constructs/agent.js
CHANGED
|
@@ -2,9 +2,13 @@ import { CreateAgentRequestSyncModeEnum, StandbyCapacityWindowDaysOfWeekEnum, }
|
|
|
2
2
|
import { Construct } from "../core/construct.js";
|
|
3
3
|
import { validateDeclaredLabels } from "../core/labels.js";
|
|
4
4
|
import { refToken } from "../core/tokens.js";
|
|
5
|
+
import { MANAGED_SKILL_IDS } from "../generated/managed-skills.js";
|
|
5
6
|
import { registerImport, resolveManagedSlug, validateExplicitSlug } from "./references.js";
|
|
7
|
+
import { Skill } from "./skill.js";
|
|
6
8
|
const HARNESS_FAMILIES = ["claude", "codex", "pi", "script", "custom"];
|
|
7
9
|
const SYNC_MODES = Object.values(CreateAgentRequestSyncModeEnum);
|
|
10
|
+
/** The platform serves each managed skill under `skill_<key>`, and `<key>` is that skill's frontmatter `name`. */
|
|
11
|
+
const MANAGED_ID_PREFIX = "skill_";
|
|
8
12
|
/**
|
|
9
13
|
* Checked at runtime as well as in the type: orchestra apps are spawned under `npx tsx`, which transpiles without
|
|
10
14
|
* typechecking, so an app that omits the prop reaches synth with `harness === undefined` and the TypeScript
|
|
@@ -75,6 +79,20 @@ function validateStandbyCapacity(label, capacity) {
|
|
|
75
79
|
}
|
|
76
80
|
});
|
|
77
81
|
}
|
|
82
|
+
/** Checked at runtime for the same reason as the harness: a transpiled app reaches synth untypechecked. */
|
|
83
|
+
function validateIncludeManagedSkills(label, include) {
|
|
84
|
+
if (include !== undefined && typeof include !== "boolean") {
|
|
85
|
+
throw new Error(`${label}: includeManagedSkills is ${JSON.stringify(include)}. Set it to true or false, or remove it ` +
|
|
86
|
+
`to add the managed skills.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function managedIdsWithoutAuthoredNames(skills) {
|
|
90
|
+
const authoredNames = new Set(skills.flatMap((skill) => {
|
|
91
|
+
const name = skill instanceof Skill ? skill._declaredName() : undefined;
|
|
92
|
+
return name === undefined ? [] : [name];
|
|
93
|
+
}));
|
|
94
|
+
return MANAGED_SKILL_IDS.filter((managed) => !authoredNames.has(managed.slice(MANAGED_ID_PREFIX.length)));
|
|
95
|
+
}
|
|
78
96
|
export class Agent extends Construct {
|
|
79
97
|
/** Accepts a platform id or a slug interchangeably — one rule, stated on `IResource`. */
|
|
80
98
|
agentId;
|
|
@@ -82,17 +100,20 @@ export class Agent extends Construct {
|
|
|
82
100
|
explicitSlug;
|
|
83
101
|
displayName;
|
|
84
102
|
harness;
|
|
103
|
+
managedSkillIds;
|
|
85
104
|
constructor(scope, id, props) {
|
|
86
105
|
super(scope, id);
|
|
87
106
|
validateDeclaredLabels(props.labels, `agent '${this.node.path.join("/")}' labels`);
|
|
88
107
|
validateExplicitSlug(this, props.slug, "agent");
|
|
89
108
|
this.harness = normalizeHarness(`agent '${this.node.path.join("/")}'`, props.harness);
|
|
90
109
|
validateSyncMode(`agent '${this.node.path.join("/")}'`, props.syncMode);
|
|
110
|
+
validateIncludeManagedSkills(`agent '${this.node.path.join("/")}'`, props.includeManagedSkills);
|
|
91
111
|
// Snapshot at construction so a later mutation of the caller's literal cannot change what synth emits —
|
|
92
112
|
// notably a `labels` key added after the `validateDeclaredLabels` gate above already passed. `skills`
|
|
93
|
-
// holds references (only each `skillId`
|
|
94
|
-
// also why the deep `structuredClone` `connection.ts` uses is unavailable here:
|
|
95
|
-
// `_resourceId()` method, which structured clone refuses — and copying an identity
|
|
113
|
+
// holds references (only each `skillId` and a `Skill`'s declared name are read), so copying the array is
|
|
114
|
+
// enough. Those references are also why the deep `structuredClone` `connection.ts` uses is unavailable here:
|
|
115
|
+
// an `ISkill` carries a `_resourceId()` method, which structured clone refuses — and copying an identity
|
|
116
|
+
// would be wrong anyway.
|
|
96
117
|
this.props = {
|
|
97
118
|
...props,
|
|
98
119
|
...(props.skills !== undefined ? { skills: [...props.skills] } : {}),
|
|
@@ -102,6 +123,11 @@ export class Agent extends Construct {
|
|
|
102
123
|
...(props.standbyCapacity !== undefined ? { standbyCapacity: structuredClone(props.standbyCapacity) } : {}),
|
|
103
124
|
};
|
|
104
125
|
validateStandbyCapacity(`agent '${this.node.path.join("/")}'`, this.props.standbyCapacity);
|
|
126
|
+
this.managedSkillIds = this.receivesManagedSkills() ? managedIdsWithoutAuthoredNames(this.props.skills ?? []) : [];
|
|
127
|
+
// Imports, as `Skill.fromId` registers one, so the pre-flight reads that the platform serves each id.
|
|
128
|
+
for (const managed of this.managedSkillIds) {
|
|
129
|
+
registerImport(this, { kind: "skill", ref: managed }, managed);
|
|
130
|
+
}
|
|
105
131
|
this.explicitSlug = props.slug;
|
|
106
132
|
this.displayName = props.name ?? id;
|
|
107
133
|
this.agentId = refToken("agent", this.node.path);
|
|
@@ -127,7 +153,7 @@ export class Agent extends Construct {
|
|
|
127
153
|
environmentId: this.props.environment.environmentId,
|
|
128
154
|
instructions: this.props.instructions ?? "",
|
|
129
155
|
harness: this.harness,
|
|
130
|
-
skillIds:
|
|
156
|
+
skillIds: this.skillIds(),
|
|
131
157
|
memoryFiles: (this.props.memoryFiles ?? []).map((file) => ({
|
|
132
158
|
path: file.path,
|
|
133
159
|
content: file.content,
|
|
@@ -148,6 +174,15 @@ export class Agent extends Construct {
|
|
|
148
174
|
};
|
|
149
175
|
return { kind: "agent", path: this.node.path.join("/"), slug, managed: true, payload };
|
|
150
176
|
}
|
|
177
|
+
// A script agent installs no skill, and the platform refuses `skillIds` on one; a custom agent starts no harness
|
|
178
|
+
// that reads a skill. Neither gets the managed set.
|
|
179
|
+
receivesManagedSkills() {
|
|
180
|
+
return this.harness !== "script" && this.harness !== "custom" && this.props.includeManagedSkills !== false;
|
|
181
|
+
}
|
|
182
|
+
skillIds() {
|
|
183
|
+
const authored = (this.props.skills ?? []).map((skill) => skill.skillId);
|
|
184
|
+
return [...authored, ...this.managedSkillIds.filter((managed) => !authored.includes(managed))];
|
|
185
|
+
}
|
|
151
186
|
static fromSlug(scope, slug) {
|
|
152
187
|
registerImport(scope, { kind: "agent", ref: slug }, slug);
|
|
153
188
|
return { agentId: slug, _resourceId: () => slug };
|
|
@@ -245,6 +245,24 @@ export interface ResourceSpec {
|
|
|
245
245
|
* exactly `2 × cpu`. */
|
|
246
246
|
memory?: string;
|
|
247
247
|
}
|
|
248
|
+
/** One hook: a script, at most 64 KiB, and how long it may run. Omitted, the hook's own default bound applies. */
|
|
249
|
+
export interface EnvironmentHook {
|
|
250
|
+
script: string;
|
|
251
|
+
timeoutSeconds?: number;
|
|
252
|
+
}
|
|
253
|
+
export interface EnvironmentHooks {
|
|
254
|
+
/**
|
|
255
|
+
* Run once as root on each new machine before it registers. Its bound is 1 to 1,800 seconds; omitted, the
|
|
256
|
+
* deployment's, which is 600 unless the deployment sets another. A machine keeps the hook it was created with.
|
|
257
|
+
*/
|
|
258
|
+
onStartMachine?: EnvironmentHook;
|
|
259
|
+
/**
|
|
260
|
+
* Run once for each agent placed on a machine, as the agent's account in `workingDirectory`, before that
|
|
261
|
+
* agent's first conversation there. Its bound is 1 to 240 seconds, 120 when omitted. A failure fails the
|
|
262
|
+
* conversation that waited on it, and the next one runs the hook again.
|
|
263
|
+
*/
|
|
264
|
+
onStartAgent?: EnvironmentHook;
|
|
265
|
+
}
|
|
248
266
|
export interface EnvironmentProps {
|
|
249
267
|
/** Omitted, each launch uses the compute provider's default image for the runtime. */
|
|
250
268
|
image?: IImage | string;
|
|
@@ -253,8 +271,18 @@ export interface EnvironmentProps {
|
|
|
253
271
|
* runs the worker; a command of your own must still start the worker, or the machine never dials in.
|
|
254
272
|
*/
|
|
255
273
|
command?: string[];
|
|
256
|
-
/**
|
|
274
|
+
/**
|
|
275
|
+
* A script run once as root on each new machine before it registers, at most 64 KiB.
|
|
276
|
+
* @deprecated Declare `hooks.onStartMachine`. Both together must name the same script.
|
|
277
|
+
*/
|
|
257
278
|
setup?: string;
|
|
279
|
+
/**
|
|
280
|
+
* The folder every conversation's agent starts in: an absolute path for the environment's OS, at most 1,024
|
|
281
|
+
* characters. The worker creates it for the agent's account when it is missing. Omitted, the agent starts in
|
|
282
|
+
* the worker's own folder for its harness.
|
|
283
|
+
*/
|
|
284
|
+
workingDirectory?: string;
|
|
285
|
+
hooks?: EnvironmentHooks;
|
|
258
286
|
egress: EgressPolicy;
|
|
259
287
|
platform?: PlatformSpec;
|
|
260
288
|
resources?: ResourceSpec;
|
|
@@ -499,6 +499,12 @@ const LADDER_MEMORY_BY_CPU = {
|
|
|
499
499
|
/** The Standard capacity — the emitted default when nothing is declared. */
|
|
500
500
|
const STANDARD_CPU = "2";
|
|
501
501
|
const STANDARD_MEMORY = "4G";
|
|
502
|
+
/** A timeout left undeclared is left out, so the platform's default applies and a read of it is no drift. */
|
|
503
|
+
function hookPayload(hook) {
|
|
504
|
+
if (hook === undefined)
|
|
505
|
+
return null;
|
|
506
|
+
return { script: hook.script, ...(hook.timeoutSeconds !== undefined ? { timeoutSeconds: hook.timeoutSeconds } : {}) };
|
|
507
|
+
}
|
|
502
508
|
export class Environment extends Construct {
|
|
503
509
|
/** Accepts a platform id or a slug interchangeably — one rule, stated on `IResource`. */
|
|
504
510
|
environmentId;
|
|
@@ -508,6 +514,11 @@ export class Environment extends Construct {
|
|
|
508
514
|
constructor(scope, id, props) {
|
|
509
515
|
super(scope, id);
|
|
510
516
|
validateExplicitSlug(this, props.slug, "environment");
|
|
517
|
+
const declaredOnStartMachine = props.hooks?.onStartMachine?.script;
|
|
518
|
+
if (props.setup !== undefined && declaredOnStartMachine !== undefined && props.setup !== declaredOnStartMachine) {
|
|
519
|
+
throw new Error(`Environment '${id}' declares both setup and hooks.onStartMachine with different scripts. ` +
|
|
520
|
+
"Declare hooks.onStartMachine alone: setup is its older name.");
|
|
521
|
+
}
|
|
511
522
|
// Snapshot at construction so a later mutation of the caller's literal cannot change what synth emits.
|
|
512
523
|
// Each nested plain-data structure is copied one level deeper than the spread; a mount's `volume` stays
|
|
513
524
|
// the same IVolume, since a reference is shared by identity and only its `volumeId` is ever read. Those
|
|
@@ -524,6 +535,14 @@ export class Environment extends Construct {
|
|
|
524
535
|
: {}),
|
|
525
536
|
...(props.disks !== undefined ? { disks: props.disks.map((disk) => ({ ...disk })) } : {}),
|
|
526
537
|
...(props.command !== undefined ? { command: [...props.command] } : {}),
|
|
538
|
+
...(props.hooks !== undefined
|
|
539
|
+
? {
|
|
540
|
+
hooks: {
|
|
541
|
+
...(props.hooks.onStartMachine !== undefined ? { onStartMachine: { ...props.hooks.onStartMachine } } : {}),
|
|
542
|
+
...(props.hooks.onStartAgent !== undefined ? { onStartAgent: { ...props.hooks.onStartAgent } } : {}),
|
|
543
|
+
},
|
|
544
|
+
}
|
|
545
|
+
: {}),
|
|
527
546
|
// Copied so a key added after the construct accepted the literal cannot ride a later synth into a
|
|
528
547
|
// machine's env.
|
|
529
548
|
...(props.env !== undefined ? { env: { ...props.env } } : {}),
|
|
@@ -555,6 +574,7 @@ export class Environment extends Construct {
|
|
|
555
574
|
platform.runtime = platformProps.runtime;
|
|
556
575
|
const resources = this.props.resources ?? {};
|
|
557
576
|
const cpu = resources.cpu ?? STANDARD_CPU;
|
|
577
|
+
const onStartMachine = this.props.hooks?.onStartMachine ?? (this.props.setup ? { script: this.props.setup } : undefined);
|
|
558
578
|
// The egress rules, `ingressPolicy.endpoints`, `volumeMounts` and `env` are ALWAYS emitted — empty when
|
|
559
579
|
// unset — so clearing any of them reconciles through the platform's whole-object PATCH replace instead of
|
|
560
580
|
// being masked as omitted. `env` inherits the known map residual: emptying a declared map as the SOLE
|
|
@@ -573,6 +593,12 @@ export class Environment extends Construct {
|
|
|
573
593
|
access: endpoint.access,
|
|
574
594
|
})),
|
|
575
595
|
},
|
|
596
|
+
// Sent empty when undeclared, and each hook as null, so removing one clears what an earlier apply set.
|
|
597
|
+
workingDirectory: this.props.workingDirectory ?? "",
|
|
598
|
+
hooks: {
|
|
599
|
+
onStartMachine: hookPayload(onStartMachine),
|
|
600
|
+
onStartAgent: hookPayload(this.props.hooks?.onStartAgent),
|
|
601
|
+
},
|
|
576
602
|
// The launch facts are declared here and nowhere else: the platform ignores one spelled at the top
|
|
577
603
|
// level of the environment body.
|
|
578
604
|
provisionSpec: {
|
|
@@ -580,7 +606,8 @@ export class Environment extends Construct {
|
|
|
580
606
|
// leaves the stored value, and only an empty one clears it.
|
|
581
607
|
image,
|
|
582
608
|
command: [...(this.props.command ?? [])],
|
|
583
|
-
setup
|
|
609
|
+
// The platform refuses a setup that names another script than hooks.onStartMachine in the same body.
|
|
610
|
+
setup: onStartMachine?.script ?? "",
|
|
584
611
|
resources: {
|
|
585
612
|
cpu,
|
|
586
613
|
// A cpu that names no rung has no `2 x cpu` to look up, so it falls back to the Standard memory: the
|
|
@@ -2,7 +2,7 @@ import { Construct } from "../core/construct.js";
|
|
|
2
2
|
import type { PlanResource } from "../core/plan.js";
|
|
3
3
|
import type { ISkill } from "./interfaces.js";
|
|
4
4
|
export interface SkillProps {
|
|
5
|
-
/** Read at synth; text-only. */
|
|
5
|
+
/** Read whole at synth, and its `SKILL.md` name when an agent is constructed; text-only. */
|
|
6
6
|
source: string;
|
|
7
7
|
/** When omitted a factory-namespaced slug is derived. */
|
|
8
8
|
slug?: string;
|
|
@@ -25,6 +25,11 @@ export declare class Skill extends Construct implements ISkill {
|
|
|
25
25
|
_toPlanResource(): PlanResource;
|
|
26
26
|
/** Sorted by path for a deterministic plan. The missing-root-SKILL.md check only pre-empts a server 400. */
|
|
27
27
|
private readSourceFiles;
|
|
28
|
+
/**
|
|
29
|
+
* @internal The `name` the root `SKILL.md` frontmatter declares, or `undefined` when the file or the field
|
|
30
|
+
* cannot be read; synth reports an unreadable source itself.
|
|
31
|
+
*/
|
|
32
|
+
_declaredName(): string | undefined;
|
|
28
33
|
static fromSlug(scope: Construct, slug: string): ISkill;
|
|
29
34
|
static fromId(scope: Construct, id: string): ISkill;
|
|
30
35
|
}
|
package/dist/constructs/skill.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The payload mirrors the platform's create request EXCEPT the wire's `files: [{path, fileId}]`: the plan
|
|
2
2
|
// carries the ergonomic authoring shape `{path, content}` and the apply engine does the file choreography.
|
|
3
|
-
// The root `SKILL.md` frontmatter is
|
|
4
|
-
//
|
|
3
|
+
// The root `SKILL.md` frontmatter is validated SERVER-side, so the construct checks only that the file set
|
|
4
|
+
// is text, in-cap, and carries a root `SKILL.md`; it reads the frontmatter's `name` alone, for `Agent`.
|
|
5
5
|
import { readdirSync, readFileSync } from "node:fs";
|
|
6
6
|
import { relative, resolve, sep } from "node:path";
|
|
7
7
|
import { Construct } from "../core/construct.js";
|
|
@@ -73,6 +73,25 @@ export class Skill extends Construct {
|
|
|
73
73
|
files.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
74
74
|
return files;
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* @internal The `name` the root `SKILL.md` frontmatter declares, or `undefined` when the file or the field
|
|
78
|
+
* cannot be read; synth reports an unreadable source itself.
|
|
79
|
+
*/
|
|
80
|
+
_declaredName() {
|
|
81
|
+
let text;
|
|
82
|
+
try {
|
|
83
|
+
text = readFileSync(resolve(this.props.source, ROOT_SKILL_PATH), "utf8");
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text);
|
|
89
|
+
const name = frontmatter === null ? null : /^name[ \t]*:[ \t]*(.*?)[ \t]*$/m.exec(frontmatter[1]);
|
|
90
|
+
if (name === null)
|
|
91
|
+
return undefined;
|
|
92
|
+
const quoted = /^(["'])(.*)\1$/.exec(name[1]);
|
|
93
|
+
return quoted === null ? name[1] : quoted[2];
|
|
94
|
+
}
|
|
76
95
|
static fromSlug(scope, slug) {
|
|
77
96
|
registerImport(scope, { kind: "skill", ref: slug }, slug);
|
|
78
97
|
return { skillId: slug, _resourceId: () => slug };
|
package/dist/engine/reconcile.js
CHANGED
|
@@ -1026,6 +1026,8 @@ async function applyEnvironment(resource, apis, outcomes, applied, reserved, con
|
|
|
1026
1026
|
outcomes.push({ path: resource.path, kind: "environment", slug: resource.slug, id, action });
|
|
1027
1027
|
applied.set(refToken("environment", resource.path.split("/")), id);
|
|
1028
1028
|
}
|
|
1029
|
+
/** The bound the platform stores for an `onStartAgent` hook that declares none. */
|
|
1030
|
+
const ON_START_AGENT_DEFAULT_TIMEOUT_SECONDS = 120;
|
|
1029
1031
|
/**
|
|
1030
1032
|
* `policyVersion` is server-managed, so it is excluded. The platform leaves an empty `image`, `command` or
|
|
1031
1033
|
* `setup` out of a read, so the empty value orchestra sends for an undeclared one matches an absent live one.
|
|
@@ -1046,7 +1048,19 @@ export function environmentUnchanged(desired, live) {
|
|
|
1046
1048
|
}
|
|
1047
1049
|
}
|
|
1048
1050
|
}
|
|
1049
|
-
|
|
1051
|
+
if (isEmptyLaunchValue(compare.workingDirectory) && isEmptyLaunchValue(live.workingDirectory)) {
|
|
1052
|
+
delete compare.workingDirectory;
|
|
1053
|
+
}
|
|
1054
|
+
// An undeclared bound is compared as the one the platform stores for it, so removing a declared bound is a
|
|
1055
|
+
// change: the agent hook's default, and no bound for the machine hook, whose bound is then the deployment's.
|
|
1056
|
+
const hooks = compare.hooks;
|
|
1057
|
+
for (const [name, undeclared] of [["onStartAgent", ON_START_AGENT_DEFAULT_TIMEOUT_SECONDS], ["onStartMachine", null]]) {
|
|
1058
|
+
const hook = hooks?.[name];
|
|
1059
|
+
if (hook && hook.timeoutSeconds === undefined)
|
|
1060
|
+
hook.timeoutSeconds = undeclared;
|
|
1061
|
+
}
|
|
1062
|
+
// A read leaves out `hooks` when it holds none, which the declared nulls match.
|
|
1063
|
+
return subsetDeepEqual(compare, { ...live, hooks: live.hooks ?? {} });
|
|
1050
1064
|
}
|
|
1051
1065
|
function isEmptyLaunchValue(value) {
|
|
1052
1066
|
return value === undefined || value === null || value === "" || (Array.isArray(value) && value.length === 0);
|