@frontera-sdk/automation 1.45.10 → 1.45.12
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/package.json +2 -2
- package/src/manifest.ts +63 -4
- package/src/types.ts +16 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontera-sdk/automation",
|
|
3
|
-
"version": "1.45.
|
|
3
|
+
"version": "1.45.12",
|
|
4
4
|
"description": "Author Frontera automations: manifest, triggers and the typed handler contract.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"frontera",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"typescript": "^5.9.3"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@frontera-sdk/blueprint": "1.45.
|
|
45
|
+
"@frontera-sdk/blueprint": "1.45.12",
|
|
46
46
|
"cron-parser": "^5.0.6"
|
|
47
47
|
}
|
|
48
48
|
}
|
package/src/manifest.ts
CHANGED
|
@@ -71,6 +71,17 @@ const TYPED_NAMESPACES: Record<string, { re: RegExp; hint: string }> = {
|
|
|
71
71
|
},
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Shortest description an agent-callable automation may carry.
|
|
76
|
+
*
|
|
77
|
+
* Not a round number picked for looks: it is about the length of one honest
|
|
78
|
+
* clause ("Reconcile open invoices against the settlement file"), and it is
|
|
79
|
+
* chosen to be long enough that the slug restated as a sentence — "reconcile
|
|
80
|
+
* invoices" — does not clear it. A description that only repeats the name
|
|
81
|
+
* tells a model nothing it did not already have from the tool name.
|
|
82
|
+
*/
|
|
83
|
+
export const AGENT_DESCRIPTION_MIN_CHARS = 24
|
|
84
|
+
|
|
74
85
|
const KNOWN_KEYS = new Set([
|
|
75
86
|
'name', 'trigger', 'grants', 'inputs', 'concurrency', 'retries', 'description',
|
|
76
87
|
])
|
|
@@ -111,6 +122,7 @@ export function validateManifest(input: unknown): ValidationResult {
|
|
|
111
122
|
inputs?: unknown
|
|
112
123
|
concurrency?: unknown
|
|
113
124
|
retries?: unknown
|
|
125
|
+
description?: unknown
|
|
114
126
|
}
|
|
115
127
|
|
|
116
128
|
if (typeof m.name !== 'string' || !NAME_RE.test(m.name)) {
|
|
@@ -119,10 +131,27 @@ export function validateManifest(input: unknown): ValidationResult {
|
|
|
119
131
|
errors.push('name must be 64 characters or fewer')
|
|
120
132
|
}
|
|
121
133
|
|
|
122
|
-
const trigger = m.trigger as
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
134
|
+
const trigger = m.trigger as
|
|
135
|
+
| { cron?: string; manual?: boolean; agent?: boolean }
|
|
136
|
+
| undefined
|
|
137
|
+
if (
|
|
138
|
+
!trigger
|
|
139
|
+
|| (trigger.cron === undefined && trigger.manual !== true && trigger.agent !== true)
|
|
140
|
+
) {
|
|
141
|
+
errors.push('trigger must be { cron }, { manual: true }, or { agent: true }')
|
|
142
|
+
} else if (trigger.agent !== undefined && trigger.agent !== true) {
|
|
143
|
+
// Not folded into the arm above: `{ manual: true, agent: false }` is a
|
|
144
|
+
// legal-looking manifest that means nothing. `agent` is a permission, and
|
|
145
|
+
// the way to withhold a permission is to omit it, not to write it false —
|
|
146
|
+
// the same rule the grant list follows.
|
|
147
|
+
errors.push(
|
|
148
|
+
'trigger.agent must be true when present — omit the key to mean "not agent-callable"',
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
// Separate `if`, not the old `else if`: with three arms the cron check has to
|
|
152
|
+
// run whenever a cron is present, including on `{ cron, agent: true }`, and
|
|
153
|
+
// an `else if` chained off the acceptance test above would skip it there.
|
|
154
|
+
if (trigger?.cron !== undefined) {
|
|
126
155
|
if (typeof trigger.cron !== 'string') {
|
|
127
156
|
errors.push('invalid cron expression: must be a string')
|
|
128
157
|
} else {
|
|
@@ -252,6 +281,36 @@ export function validateManifest(input: unknown): ValidationResult {
|
|
|
252
281
|
}
|
|
253
282
|
}
|
|
254
283
|
|
|
284
|
+
// `trigger: { agent: true }` turns this manifest into the source of a tool
|
|
285
|
+
// definition a language model reads and decides from. Two fields that are
|
|
286
|
+
// courtesies everywhere else become load-bearing here, so they are errors
|
|
287
|
+
// rather than warnings: a model handed an undescribed tool, or an undescribed
|
|
288
|
+
// argument, does not fail loudly — it guesses, and the guess starts a real
|
|
289
|
+
// run against real systems. Checked at deploy, where the author still has the
|
|
290
|
+
// file open, rather than at bind time in a Console someone else is using.
|
|
291
|
+
if (trigger?.agent === true) {
|
|
292
|
+
const description = m.description
|
|
293
|
+
if (typeof description !== 'string' || description.trim().length < AGENT_DESCRIPTION_MIN_CHARS) {
|
|
294
|
+
errors.push(
|
|
295
|
+
`trigger { agent: true } requires a description of at least ${AGENT_DESCRIPTION_MIN_CHARS} `
|
|
296
|
+
+ 'characters — it becomes the tool description an agent reads before calling this '
|
|
297
|
+
+ 'automation.',
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
const agentInputs = m.inputs
|
|
301
|
+
if (agentInputs && typeof agentInputs === 'object' && !Array.isArray(agentInputs)) {
|
|
302
|
+
for (const [key, raw] of Object.entries(agentInputs as Record<string, unknown>)) {
|
|
303
|
+
const spec = raw as { description?: unknown } | null
|
|
304
|
+
if (typeof spec?.description !== 'string' || spec.description.trim().length === 0) {
|
|
305
|
+
errors.push(
|
|
306
|
+
`input "${key}" needs a description: trigger { agent: true } publishes every input as `
|
|
307
|
+
+ 'a tool argument, and an agent cannot fill an argument it has no description for.',
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
255
314
|
if (input && typeof input === 'object' && !Array.isArray(input)) {
|
|
256
315
|
for (const key of Object.keys(input)) {
|
|
257
316
|
if (!KNOWN_KEYS.has(key)) {
|
package/src/types.ts
CHANGED
|
@@ -7,16 +7,29 @@
|
|
|
7
7
|
import type { WhereNode } from '@frontera-sdk/blueprint/types'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Cron and
|
|
10
|
+
* Cron, manual, and agent for now; event and webhook land with Event Triggers.
|
|
11
11
|
*
|
|
12
12
|
* The `?: never` members are load-bearing. Without them `{ cron, manual }`
|
|
13
13
|
* typechecks — TypeScript's excess-property check admits any key present on
|
|
14
14
|
* *some* member of a union — and the runner would have to decide at runtime
|
|
15
15
|
* what a both-shaped trigger means.
|
|
16
|
+
*
|
|
17
|
+
* `agent` is deliberately NOT part of that exclusion. Cron and manual answer
|
|
18
|
+
* "what fires this on its own"; `agent: true` answers "may a bound agent call
|
|
19
|
+
* this", which is an orthogonal question — a nightly reconciliation that an
|
|
20
|
+
* analyst can also ask an agent to run on demand is one automation, not two.
|
|
21
|
+
* So `agent` rides alongside either, and the third arm exists for the
|
|
22
|
+
* agent-only automation, which has no self-starting trigger at all.
|
|
23
|
+
*
|
|
24
|
+
* Declaring it is only the AUTHOR's half of the permission. A workspace
|
|
25
|
+
* operator must still bind the automation to one named agent before any tool
|
|
26
|
+
* is projected; see the design in
|
|
27
|
+
* docs/superpowers/specs/2026-08-27-agent-callable-automations-design.md.
|
|
16
28
|
*/
|
|
17
29
|
export type AutomationTrigger =
|
|
18
|
-
| { cron: string; manual?: never }
|
|
19
|
-
| { cron?: never; manual: true }
|
|
30
|
+
| { cron: string; manual?: never; agent?: true }
|
|
31
|
+
| { cron?: never; manual: true; agent?: true }
|
|
32
|
+
| { cron?: never; manual?: never; agent: true }
|
|
20
33
|
|
|
21
34
|
/**
|
|
22
35
|
* An author-time affordance, not a validation gate.
|