@falai/agent 4.0.0-alpha.8 → 4.0.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 +2 -0
- package/dist/cjs/core/Agent.d.ts +8 -1
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +18 -0
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/CompactionEngine.d.ts.map +1 -1
- package/dist/cjs/core/CompactionEngine.js +21 -7
- package/dist/cjs/core/CompactionEngine.js.map +1 -1
- package/dist/cjs/core/FlowSpec.d.ts +21 -2
- package/dist/cjs/core/FlowSpec.d.ts.map +1 -1
- package/dist/cjs/core/FlowSpec.js +297 -59
- package/dist/cjs/core/FlowSpec.js.map +1 -1
- package/dist/cjs/core/Migrate.d.ts.map +1 -1
- package/dist/cjs/core/Migrate.js +3 -1
- package/dist/cjs/core/Migrate.js.map +1 -1
- package/dist/cjs/core/Prompt.d.ts +16 -0
- package/dist/cjs/core/Prompt.d.ts.map +1 -1
- package/dist/cjs/core/Prompt.js +39 -0
- package/dist/cjs/core/Prompt.js.map +1 -1
- package/dist/cjs/core/Runner.d.ts +37 -4
- package/dist/cjs/core/Runner.d.ts.map +1 -1
- package/dist/cjs/core/Runner.js +281 -72
- package/dist/cjs/core/Runner.js.map +1 -1
- package/dist/cjs/core/Speak.d.ts.map +1 -1
- package/dist/cjs/core/Speak.js +63 -19
- package/dist/cjs/core/Speak.js.map +1 -1
- package/dist/cjs/core/Understand.d.ts +6 -3
- package/dist/cjs/core/Understand.d.ts.map +1 -1
- package/dist/cjs/core/Understand.js +18 -42
- package/dist/cjs/core/Understand.js.map +1 -1
- package/dist/cjs/core/contracts.d.ts +26 -6
- package/dist/cjs/core/contracts.d.ts.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/persistence/OpenSearchStore.d.ts +2 -1
- package/dist/cjs/persistence/OpenSearchStore.d.ts.map +1 -1
- package/dist/cjs/persistence/OpenSearchStore.js +2 -2
- package/dist/cjs/persistence/OpenSearchStore.js.map +1 -1
- package/dist/cjs/persistence/RedisStore.d.ts +1 -1
- package/dist/cjs/persistence/RedisStore.d.ts.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.d.ts +2 -5
- package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.js +3 -4
- package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
- package/dist/cjs/providers/DeepSeekProvider.d.ts.map +1 -1
- package/dist/cjs/providers/DeepSeekProvider.js +3 -4
- package/dist/cjs/providers/DeepSeekProvider.js.map +1 -1
- package/dist/cjs/providers/FallbackAiProvider.js +1 -1
- package/dist/cjs/providers/FallbackAiProvider.js.map +1 -1
- package/dist/cjs/providers/GeminiProvider.d.ts +1 -2
- package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/GeminiProvider.js +3 -4
- package/dist/cjs/providers/GeminiProvider.js.map +1 -1
- package/dist/cjs/providers/GenericOpenAICompatibleProvider.js +4 -4
- package/dist/cjs/providers/GenericOpenAICompatibleProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.js +3 -4
- package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
- package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenRouterProvider.js +4 -3
- package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
- package/dist/cjs/providers/ProviderAdapter.d.ts +26 -11
- package/dist/cjs/providers/ProviderAdapter.d.ts.map +1 -1
- package/dist/cjs/providers/ProviderAdapter.js +13 -13
- package/dist/cjs/providers/ProviderAdapter.js.map +1 -1
- package/dist/cjs/providers/ZaiProvider.js +1 -1
- package/dist/cjs/providers/ZaiProvider.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +14 -3
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/ai.d.ts +10 -6
- package/dist/cjs/types/ai.d.ts.map +1 -1
- package/dist/cjs/types/compaction.d.ts +1 -0
- package/dist/cjs/types/compaction.d.ts.map +1 -1
- package/dist/cjs/types/errors.d.ts +4 -9
- package/dist/cjs/types/errors.d.ts.map +1 -1
- package/dist/cjs/types/errors.js +12 -12
- package/dist/cjs/types/errors.js.map +1 -1
- package/dist/cjs/types/flow.d.ts +17 -1
- package/dist/cjs/types/flow.d.ts.map +1 -1
- package/dist/cjs/types/history.d.ts +0 -7
- package/dist/cjs/types/history.d.ts.map +1 -1
- package/dist/cjs/types/index.d.ts +2 -2
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/session.d.ts +4 -2
- package/dist/cjs/types/session.d.ts.map +1 -1
- package/dist/cjs/utils/clock.js +1 -1
- package/dist/cjs/utils/clock.js.map +1 -1
- package/dist/cjs/utils/outcomes.d.ts +2 -0
- package/dist/cjs/utils/outcomes.d.ts.map +1 -1
- package/dist/cjs/utils/outcomes.js +2 -0
- package/dist/cjs/utils/outcomes.js.map +1 -1
- package/dist/cjs/utils/schema.d.ts +3 -12
- package/dist/cjs/utils/schema.d.ts.map +1 -1
- package/dist/cjs/utils/schema.js +3 -43
- package/dist/cjs/utils/schema.js.map +1 -1
- package/dist/cjs/utils/template.d.ts.map +1 -1
- package/dist/cjs/utils/template.js +9 -2
- package/dist/cjs/utils/template.js.map +1 -1
- package/dist/core/Agent.d.ts +8 -1
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +19 -1
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/CompactionEngine.d.ts.map +1 -1
- package/dist/core/CompactionEngine.js +21 -7
- package/dist/core/CompactionEngine.js.map +1 -1
- package/dist/core/FlowSpec.d.ts +21 -2
- package/dist/core/FlowSpec.d.ts.map +1 -1
- package/dist/core/FlowSpec.js +296 -60
- package/dist/core/FlowSpec.js.map +1 -1
- package/dist/core/Migrate.d.ts.map +1 -1
- package/dist/core/Migrate.js +3 -1
- package/dist/core/Migrate.js.map +1 -1
- package/dist/core/Prompt.d.ts +16 -0
- package/dist/core/Prompt.d.ts.map +1 -1
- package/dist/core/Prompt.js +37 -0
- package/dist/core/Prompt.js.map +1 -1
- package/dist/core/Runner.d.ts +37 -4
- package/dist/core/Runner.d.ts.map +1 -1
- package/dist/core/Runner.js +282 -73
- package/dist/core/Runner.js.map +1 -1
- package/dist/core/Speak.d.ts.map +1 -1
- package/dist/core/Speak.js +64 -20
- package/dist/core/Speak.js.map +1 -1
- package/dist/core/Understand.d.ts +6 -3
- package/dist/core/Understand.d.ts.map +1 -1
- package/dist/core/Understand.js +18 -42
- package/dist/core/Understand.js.map +1 -1
- package/dist/core/contracts.d.ts +26 -6
- package/dist/core/contracts.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/persistence/OpenSearchStore.d.ts +2 -1
- package/dist/persistence/OpenSearchStore.d.ts.map +1 -1
- package/dist/persistence/OpenSearchStore.js +2 -2
- package/dist/persistence/OpenSearchStore.js.map +1 -1
- package/dist/persistence/RedisStore.d.ts +1 -1
- package/dist/persistence/RedisStore.d.ts.map +1 -1
- package/dist/providers/AnthropicProvider.d.ts +2 -5
- package/dist/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/providers/AnthropicProvider.js +3 -4
- package/dist/providers/AnthropicProvider.js.map +1 -1
- package/dist/providers/DeepSeekProvider.d.ts.map +1 -1
- package/dist/providers/DeepSeekProvider.js +3 -4
- package/dist/providers/DeepSeekProvider.js.map +1 -1
- package/dist/providers/FallbackAiProvider.js +1 -1
- package/dist/providers/FallbackAiProvider.js.map +1 -1
- package/dist/providers/GeminiProvider.d.ts +1 -2
- package/dist/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiProvider.js +3 -4
- package/dist/providers/GeminiProvider.js.map +1 -1
- package/dist/providers/GenericOpenAICompatibleProvider.js +4 -4
- package/dist/providers/GenericOpenAICompatibleProvider.js.map +1 -1
- package/dist/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/OpenAIProvider.js +3 -4
- package/dist/providers/OpenAIProvider.js.map +1 -1
- package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
- package/dist/providers/OpenRouterProvider.js +4 -3
- package/dist/providers/OpenRouterProvider.js.map +1 -1
- package/dist/providers/ProviderAdapter.d.ts +26 -11
- package/dist/providers/ProviderAdapter.d.ts.map +1 -1
- package/dist/providers/ProviderAdapter.js +14 -14
- package/dist/providers/ProviderAdapter.js.map +1 -1
- package/dist/providers/ZaiProvider.js +1 -1
- package/dist/providers/ZaiProvider.js.map +1 -1
- package/dist/types/agent.d.ts +14 -3
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/ai.d.ts +10 -6
- package/dist/types/ai.d.ts.map +1 -1
- package/dist/types/compaction.d.ts +1 -0
- package/dist/types/compaction.d.ts.map +1 -1
- package/dist/types/errors.d.ts +4 -9
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/errors.js +12 -12
- package/dist/types/errors.js.map +1 -1
- package/dist/types/flow.d.ts +17 -1
- package/dist/types/flow.d.ts.map +1 -1
- package/dist/types/history.d.ts +0 -7
- package/dist/types/history.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/session.d.ts +4 -2
- package/dist/types/session.d.ts.map +1 -1
- package/dist/utils/clock.js +1 -1
- package/dist/utils/clock.js.map +1 -1
- package/dist/utils/outcomes.d.ts +2 -0
- package/dist/utils/outcomes.d.ts.map +1 -1
- package/dist/utils/outcomes.js +2 -0
- package/dist/utils/outcomes.js.map +1 -1
- package/dist/utils/schema.d.ts +3 -12
- package/dist/utils/schema.d.ts.map +1 -1
- package/dist/utils/schema.js +3 -42
- package/dist/utils/schema.js.map +1 -1
- package/dist/utils/template.d.ts.map +1 -1
- package/dist/utils/template.js +9 -2
- package/dist/utils/template.js.map +1 -1
- package/docs/concepts/architecture.md +2 -2
- package/docs/concepts/collection.md +40 -5
- package/docs/concepts/pipeline.md +10 -7
- package/docs/concepts/runs-and-waits.md +2 -2
- package/docs/guides/actions-and-events.md +1 -1
- package/docs/guides/branching.md +4 -2
- package/docs/guides/compaction.md +2 -2
- package/docs/guides/error-handling.md +2 -2
- package/docs/guides/flow-control.md +4 -2
- package/docs/guides/persistence.md +2 -2
- package/docs/guides/testing.md +1 -1
- package/docs/guides/triggers.md +3 -3
- package/docs/migration/v3-to-v4.md +8 -7
- package/docs/reference/actions-events-conditions.md +1 -1
- package/docs/reference/agent.md +9 -5
- package/docs/reference/branches.md +1 -1
- package/docs/reference/errors.md +14 -11
- package/docs/reference/fields.md +5 -3
- package/docs/reference/flow-spec.md +35 -8
- package/docs/reference/flow.md +8 -3
- package/docs/reference/outcomes.md +4 -2
- package/docs/reference/providers.md +5 -3
- package/docs/reference/session.md +2 -0
- package/docs/reference/step.md +9 -5
- package/docs/reference/stores.md +5 -3
- package/docs/reference/trigger.md +2 -2
- package/docs/rfc/v4-one-flow.md +5 -3
- package/docs/start/01-install.md +2 -0
- package/docs/start/05-go-to-production.md +18 -1
- package/examples/05-branches.ts +1 -1
- package/examples/06-triggers-and-waits.ts +4 -3
- package/package.json +4 -3
- package/src/core/Agent.ts +23 -2
- package/src/core/CompactionEngine.ts +25 -9
- package/src/core/FlowSpec.ts +311 -72
- package/src/core/Migrate.ts +2 -1
- package/src/core/Prompt.ts +40 -0
- package/src/core/Runner.ts +279 -69
- package/src/core/Speak.ts +67 -19
- package/src/core/Understand.ts +24 -44
- package/src/core/contracts.ts +26 -4
- package/src/index.ts +1 -1
- package/src/persistence/OpenSearchStore.ts +3 -2
- package/src/persistence/RedisStore.ts +1 -1
- package/src/providers/AnthropicProvider.ts +4 -9
- package/src/providers/DeepSeekProvider.ts +2 -4
- package/src/providers/FallbackAiProvider.ts +1 -1
- package/src/providers/GeminiProvider.ts +3 -6
- package/src/providers/GenericOpenAICompatibleProvider.ts +4 -4
- package/src/providers/OpenAIProvider.ts +3 -4
- package/src/providers/OpenRouterProvider.ts +4 -2
- package/src/providers/ProviderAdapter.ts +35 -33
- package/src/providers/ZaiProvider.ts +1 -1
- package/src/types/agent.ts +15 -4
- package/src/types/ai.ts +10 -6
- package/src/types/compaction.ts +1 -0
- package/src/types/errors.ts +11 -12
- package/src/types/flow.ts +17 -1
- package/src/types/history.ts +0 -10
- package/src/types/index.ts +1 -1
- package/src/types/session.ts +5 -1
- package/src/utils/clock.ts +1 -1
- package/src/utils/outcomes.ts +2 -0
- package/src/utils/schema.ts +3 -48
- package/src/utils/template.ts +9 -2
- package/dist/cjs/providers/index.d.ts +0 -26
- package/dist/cjs/providers/index.d.ts.map +0 -1
- package/dist/cjs/providers/index.js +0 -30
- package/dist/cjs/providers/index.js.map +0 -1
- package/dist/cjs/utils/clone.d.ts +0 -8
- package/dist/cjs/utils/clone.d.ts.map +0 -1
- package/dist/cjs/utils/clone.js +0 -32
- package/dist/cjs/utils/clone.js.map +0 -1
- package/dist/cjs/utils/index.d.ts +0 -9
- package/dist/cjs/utils/index.d.ts.map +0 -1
- package/dist/cjs/utils/index.js +0 -29
- package/dist/cjs/utils/index.js.map +0 -1
- package/dist/providers/index.d.ts +0 -26
- package/dist/providers/index.d.ts.map +0 -1
- package/dist/providers/index.js +0 -17
- package/dist/providers/index.js.map +0 -1
- package/dist/utils/clone.d.ts +0 -8
- package/dist/utils/clone.d.ts.map +0 -1
- package/dist/utils/clone.js +0 -29
- package/dist/utils/clone.js.map +0 -1
- package/dist/utils/index.d.ts +0 -9
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -9
- package/dist/utils/index.js.map +0 -1
- package/src/providers/index.ts +0 -38
- package/src/utils/clone.ts +0 -34
- package/src/utils/index.ts +0 -18
|
@@ -17,9 +17,11 @@
|
|
|
17
17
|
* nulls dropped, and the identity on a spec that had none.
|
|
18
18
|
*/
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.BUILT_IN_CONDITIONS = void 0;
|
|
20
21
|
exports.fromSpec = fromSpec;
|
|
21
22
|
exports.toSpec = toSpec;
|
|
22
23
|
exports.validateFlow = validateFlow;
|
|
24
|
+
exports.checkPred = checkPred;
|
|
23
25
|
exports.flowSpecSchema = flowSpecSchema;
|
|
24
26
|
const errors_js_1 = require("../types/errors.js");
|
|
25
27
|
const duration_js_1 = require("../utils/duration.js");
|
|
@@ -34,9 +36,7 @@ const schema_js_1 = require("../utils/schema.js");
|
|
|
34
36
|
*/
|
|
35
37
|
function fromSpec(spec) {
|
|
36
38
|
const clean = stripNulls(spec);
|
|
37
|
-
|
|
38
|
-
throw problem(`flow "${clean.id}"`, "has no steps list", "Write steps as a list, even an empty one.");
|
|
39
|
-
}
|
|
39
|
+
checkShape(clean);
|
|
40
40
|
const { steps, ...rest } = clean;
|
|
41
41
|
const flow = { ...rest, steps: steps.map(fromStepSpec) };
|
|
42
42
|
return flow;
|
|
@@ -56,6 +56,7 @@ function toSpec(flow) {
|
|
|
56
56
|
on: flow.on?.map((trigger, i) => triggerToSpec(trigger, at(`trigger #${i + 1}`))),
|
|
57
57
|
anchor: flow.anchor,
|
|
58
58
|
while: jsonPred(flow.while, at("while")),
|
|
59
|
+
collect: flow.collect,
|
|
59
60
|
clearOnStart: flow.clearOnStart,
|
|
60
61
|
steps: flow.steps.map((step) => stepToSpec(step, at(`step "${step.id}"`))),
|
|
61
62
|
onEnd: flow.onEnd,
|
|
@@ -103,11 +104,13 @@ function stepToSpec(step, at) {
|
|
|
103
104
|
instructions: step.instructions?.map((ins, i) => instructionToSpec(ins, `${at} instructions[${i}]`)),
|
|
104
105
|
};
|
|
105
106
|
if (step.collect !== undefined) {
|
|
106
|
-
return compact({ ...base, kind: "collect", collect: step.collect, prompt: step.prompt, ...talk });
|
|
107
|
+
return compact({ ...base, kind: "collect", collect: step.collect, prompt: step.prompt, question: step.question, ...talk });
|
|
107
108
|
}
|
|
108
109
|
if (step.prompt === undefined) {
|
|
109
110
|
throw problem(at, "has neither prompt nor collect", "A talk step needs a guideline, fields to collect, or both.");
|
|
110
111
|
}
|
|
112
|
+
if (step.question !== undefined)
|
|
113
|
+
throw questionWithoutCollect(at);
|
|
111
114
|
return compact({ ...base, kind: "prompt", prompt: step.prompt, ...talk });
|
|
112
115
|
}
|
|
113
116
|
/** `typeof step.wait` alone does not narrow the union for tsc; this does. */
|
|
@@ -123,12 +126,12 @@ function jsonPredRequired(pred, at) {
|
|
|
123
126
|
}
|
|
124
127
|
return pred;
|
|
125
128
|
}
|
|
126
|
-
|
|
129
|
+
exports.BUILT_IN_CONDITIONS = ["equals", "known", "silenced"];
|
|
127
130
|
const DURATION_HINT = 'Write a number and a unit: "30s", "5m", "24h" or "3d".';
|
|
128
131
|
/** The four keys one of which makes an `on[]` entry a trigger. */
|
|
129
132
|
const TRIGGER_KINDS = ["message", "mention", "silence", "event"];
|
|
130
|
-
|
|
131
|
-
const
|
|
133
|
+
const ON_END = ["end", "stay", "reset"];
|
|
134
|
+
const INSTRUCTION_KINDS = ["must", "never", "should"];
|
|
132
135
|
/**
|
|
133
136
|
* Check a flow, typed or as a spec, against the agent's registries. Throws
|
|
134
137
|
* `FlowConfigurationError` on the first problem that would break at runtime;
|
|
@@ -137,16 +140,14 @@ const STEP_DOES = ["prompt", "collect", "say", "do", "wait", "if"];
|
|
|
137
140
|
function validateFlow(input, registries) {
|
|
138
141
|
// Nulls mean "not set" in a spec; a typed flow has none, so one pass serves both forms.
|
|
139
142
|
const flow = stripNulls(input);
|
|
140
|
-
|
|
143
|
+
checkShape(flow);
|
|
144
|
+
const { fields, actions = {}, events = {}, tools = [] } = registries;
|
|
141
145
|
const toolIds = new Set(tools.map((tool) => tool.id));
|
|
142
146
|
const warnings = [];
|
|
143
147
|
if (typeof flow.id !== "string" || flow.id === "") {
|
|
144
148
|
throw problem("flow", "has no id", "Give the flow a short unique id.");
|
|
145
149
|
}
|
|
146
150
|
const flowAt = `flow "${flow.id}"`;
|
|
147
|
-
if (!Array.isArray(flow.steps)) {
|
|
148
|
-
throw problem(flowAt, "has no steps list", "Write steps as a list, even an empty one.");
|
|
149
|
-
}
|
|
150
151
|
const index = new Map();
|
|
151
152
|
flow.steps.forEach((step, i) => {
|
|
152
153
|
if (typeof step.id !== "string" || step.id === "") {
|
|
@@ -163,48 +164,14 @@ function validateFlow(input, registries) {
|
|
|
163
164
|
if (flow.on?.length && flow.steps.length === 0) {
|
|
164
165
|
throw problem(flowAt, "has triggers but no steps", "Add at least one step or remove `on`.");
|
|
165
166
|
}
|
|
166
|
-
const slug = (name, at, where) =>
|
|
167
|
-
if (!own(fields, name)) {
|
|
168
|
-
throw problem(at, `unknown field "${name}" in ${where}`, "Add it to the agent's fields or fix the slug.");
|
|
169
|
-
}
|
|
170
|
-
};
|
|
167
|
+
const slug = (name, at, where) => checkSlug(fields, name, at, where);
|
|
171
168
|
const toolNames = (names, at) => {
|
|
172
169
|
for (const name of names ?? []) {
|
|
173
170
|
if (!toolIds.has(name))
|
|
174
171
|
throw problem(at, `unknown tool "${name}"`, "Register it in the agent's tools or fix the name.");
|
|
175
172
|
}
|
|
176
173
|
};
|
|
177
|
-
const pred = (value, at, where) =>
|
|
178
|
-
if (value === undefined || typeof value === "function")
|
|
179
|
-
return;
|
|
180
|
-
for (const [name, arg] of Object.entries(value)) {
|
|
181
|
-
if (name === "equals") {
|
|
182
|
-
if (arg === null || typeof arg !== "object" || Array.isArray(arg)) {
|
|
183
|
-
throw problem(at, `${where}.equals is not an object`, "Write equals as { field: value }.");
|
|
184
|
-
}
|
|
185
|
-
for (const [field, given] of Object.entries(arg)) {
|
|
186
|
-
slug(field, at, `${where}.equals`);
|
|
187
|
-
const def = fields[field];
|
|
188
|
-
if (!matches(def, given)) {
|
|
189
|
-
throw problem(at, `${where}.equals gives "${field}" a ${describe(given)}, but the field is a ${def.type}`, `Write a ${def.type}; values are not coerced.`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
else if (name === "known") {
|
|
194
|
-
if (!Array.isArray(arg))
|
|
195
|
-
throw problem(at, `${where}.known is not a list`, "Write known as [field, ...].");
|
|
196
|
-
for (const field of arg)
|
|
197
|
-
slug(String(field), at, `${where}.known`);
|
|
198
|
-
}
|
|
199
|
-
else if (name === "silenced") {
|
|
200
|
-
if (typeof arg !== "boolean")
|
|
201
|
-
throw problem(at, `${where}.silenced is not a boolean`, "Write true or false.");
|
|
202
|
-
}
|
|
203
|
-
else if (!own(conditions, name)) {
|
|
204
|
-
throw problem(at, `unknown condition "${name}" in ${where}`, `Register it in conditions or use ${BUILT_IN_CONDITIONS.join(", ")}.`);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
};
|
|
174
|
+
const pred = (value, at, where) => checkPred(value, at, where, registries);
|
|
208
175
|
const duration = (value, at, where) => {
|
|
209
176
|
if (value !== undefined && !(0, duration_js_1.isDuration)(value)) {
|
|
210
177
|
throw problem(at, `${where} has duration "${value}", which does not parse`, DURATION_HINT);
|
|
@@ -221,8 +188,16 @@ function validateFlow(input, registries) {
|
|
|
221
188
|
}
|
|
222
189
|
return to;
|
|
223
190
|
}
|
|
224
|
-
if ("flow" in next)
|
|
191
|
+
if ("flow" in next) {
|
|
192
|
+
// A templated id resolves per run; only a literal one can be checked now. A warning, not a throw:
|
|
193
|
+
// a host that drops one bad row keeps the rest of its agent, and the Runner skips this move as flow-gone.
|
|
194
|
+
const known = registries.flows?.map((f) => f.id);
|
|
195
|
+
if (known && !next.flow.includes("{{") && !known.includes(next.flow)) {
|
|
196
|
+
const fix = known.length ? `Use one of ${known.map((id) => `"${id}"`).join(", ")}, or add the flow.` : "Add the flow to the agent.";
|
|
197
|
+
warnings.push(`${at}: ${where} names flow "${next.flow}", which this agent does not have; a run skips this move with flow-gone. ${fix}`);
|
|
198
|
+
}
|
|
225
199
|
return undefined;
|
|
200
|
+
}
|
|
226
201
|
const to = index.get(next.step);
|
|
227
202
|
if (to === undefined) {
|
|
228
203
|
throw problem(at, `${where} points at step "${next.step}", which does not exist`, 'Use an existing step id or "end".');
|
|
@@ -252,7 +227,8 @@ function validateFlow(input, registries) {
|
|
|
252
227
|
continue;
|
|
253
228
|
}
|
|
254
229
|
if (!matchesParam(def, value)) {
|
|
255
|
-
|
|
230
|
+
const { expected, got, fix } = mismatch(def, value);
|
|
231
|
+
throw problem(at, `parameter "${param}" of action "${name}" must be ${expected}, got ${got}`, fix);
|
|
256
232
|
}
|
|
257
233
|
}
|
|
258
234
|
for (const param of Object.keys(given)) {
|
|
@@ -261,8 +237,18 @@ function validateFlow(input, registries) {
|
|
|
261
237
|
}
|
|
262
238
|
}
|
|
263
239
|
};
|
|
240
|
+
for (const field of flow.collect ?? [])
|
|
241
|
+
slug(field, flowAt, "collect");
|
|
264
242
|
for (const field of flow.clearOnStart ?? [])
|
|
265
243
|
slug(field, flowAt, "clearOnStart");
|
|
244
|
+
// A field taken only from the answer to a step that asks it can never be filled when no step asks it.
|
|
245
|
+
const asked = new Set(flow.steps.flatMap((step) => step.collect ?? []));
|
|
246
|
+
for (const field of flow.collect ?? []) {
|
|
247
|
+
if (!asked.has(field) && (0, schema_js_1.extractMode)(fields[field]) === "asked") {
|
|
248
|
+
warnings.push(`${flowAt}: collect lists "${field}", which is only taken from the answer to a step that asks it, and no ` +
|
|
249
|
+
"step does. Add it to a step's collect, or set extract: 'anywhere' on the field.");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
266
252
|
pred(flow.while, flowAt, "while");
|
|
267
253
|
toolNames(flow.tools, flowAt);
|
|
268
254
|
flow.instructions?.forEach((ins, i) => pred(ins.if, flowAt, `instructions[${i}].if`));
|
|
@@ -298,13 +284,6 @@ function validateFlow(input, registries) {
|
|
|
298
284
|
});
|
|
299
285
|
flow.steps.forEach((step, i) => {
|
|
300
286
|
const at = `${flowAt}, step "${step.id}"`;
|
|
301
|
-
// Same reasoning as the trigger above: a step that does none of the five
|
|
302
|
-
// things is a step the run walks straight past, silently. `kind` alone is
|
|
303
|
-
// not enough — the spec form drops it and keeps the body, so what counts
|
|
304
|
-
// is whether the body says what to do.
|
|
305
|
-
if (!STEP_DOES.some((key) => step[key] !== undefined)) {
|
|
306
|
-
throw problem(at, "does nothing", "A step talks (`prompt` / `collect`), says (`say`), acts (`do`), waits (`wait`) or forks (`if`).");
|
|
307
|
-
}
|
|
308
287
|
const thenTo = edge(i, step.then, at, "then");
|
|
309
288
|
edge(i, step.else, at, "else");
|
|
310
289
|
if (step.collect !== undefined || step.prompt !== undefined) {
|
|
@@ -312,8 +291,13 @@ function validateFlow(input, registries) {
|
|
|
312
291
|
slug(field, at, "collect");
|
|
313
292
|
for (const field of Object.keys(step.ask ?? {}))
|
|
314
293
|
slug(field, at, "ask");
|
|
294
|
+
if (step.question !== undefined && !step.collect?.length)
|
|
295
|
+
throw questionWithoutCollect(at);
|
|
315
296
|
const fields_ = step.collect ?? [];
|
|
316
|
-
if (step.prompt === undefined &&
|
|
297
|
+
if (step.prompt === undefined &&
|
|
298
|
+
step.question === undefined &&
|
|
299
|
+
fields_.length > 0 &&
|
|
300
|
+
fields_.every((f) => !step.ask?.[f] && !fields[f].ask)) {
|
|
317
301
|
warnings.push(`${at}: collects ${fields_.map((f) => `"${f}"`).join(", ")} with no prompt and no ask; the AI has nothing ` +
|
|
318
302
|
"to go on. Add a prompt or an ask per field.");
|
|
319
303
|
}
|
|
@@ -323,6 +307,12 @@ function validateFlow(input, registries) {
|
|
|
323
307
|
if (branch.when === undefined && branch.if === undefined) {
|
|
324
308
|
throw problem(at, `${where} has neither when nor if`, "Give the branch an AI condition (when) or a code one (if).");
|
|
325
309
|
}
|
|
310
|
+
// No understand call judges a wait, so an AI condition there never fires. A warning, not a throw: the
|
|
311
|
+
// flow still runs as it did, a reply goes to `else`, and editors that offered the branch keep their rows.
|
|
312
|
+
if (branch.when !== undefined && step.wait !== undefined) {
|
|
313
|
+
warnings.push(`${at}: ${where} is a "when" branch on a wait step, which no call judges, so a reply goes to else. ` +
|
|
314
|
+
'Use "if", or move the branch to a talk step.');
|
|
315
|
+
}
|
|
326
316
|
pred(branch.if, at, `${where}.if`);
|
|
327
317
|
edge(i, branch.then, at, `${where}.then`);
|
|
328
318
|
});
|
|
@@ -353,6 +343,232 @@ function validateFlow(input, registries) {
|
|
|
353
343
|
});
|
|
354
344
|
return { warnings };
|
|
355
345
|
}
|
|
346
|
+
function checkSlug(fields, name, at, where) {
|
|
347
|
+
if (!own(fields, name)) {
|
|
348
|
+
throw problem(at, `unknown field "${name}" in ${where}`, "Add it to the agent's fields or fix the slug.");
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* A predicate's names against the registries: the fields `equals` and `known`
|
|
353
|
+
* read, and every other key as one of the agent's conditions. A function is
|
|
354
|
+
* code and passes as is. The agent runs this on its own instructions too.
|
|
355
|
+
*/
|
|
356
|
+
function checkPred(value, at, where, registries) {
|
|
357
|
+
if (value === undefined || typeof value === "function")
|
|
358
|
+
return;
|
|
359
|
+
const { fields, conditions = {} } = registries;
|
|
360
|
+
for (const [name, arg] of Object.entries(value)) {
|
|
361
|
+
if (name === "equals") {
|
|
362
|
+
if (!isObject(arg)) {
|
|
363
|
+
throw problem(at, `${where}.equals is not an object`, "Write equals as { field: value }.");
|
|
364
|
+
}
|
|
365
|
+
for (const [field, given] of Object.entries(arg)) {
|
|
366
|
+
checkSlug(fields, field, at, `${where}.equals`);
|
|
367
|
+
const def = fields[field];
|
|
368
|
+
if (!matches(def, given)) {
|
|
369
|
+
const { listed, expected, got, fix } = mismatch(def, given);
|
|
370
|
+
throw listed
|
|
371
|
+
? problem(at, `${where}.equals gives "${field}" ${got}, which is not ${expected}`, fix)
|
|
372
|
+
: problem(at, `${where}.equals gives "${field}" ${article(got)} ${got}, but the field is ${expected}`, `Write ${expected}; values are not coerced.`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
else if (name === "known") {
|
|
377
|
+
if (!Array.isArray(arg))
|
|
378
|
+
throw problem(at, `${where}.known is not a list`, "Write known as [field, ...].");
|
|
379
|
+
for (const field of arg)
|
|
380
|
+
checkSlug(fields, String(field), at, `${where}.known`);
|
|
381
|
+
}
|
|
382
|
+
else if (name === "silenced") {
|
|
383
|
+
if (typeof arg !== "boolean")
|
|
384
|
+
throw problem(at, `${where}.silenced is not a boolean`, "Write true or false.");
|
|
385
|
+
}
|
|
386
|
+
else if (!own(conditions, name)) {
|
|
387
|
+
throw problem(at, `unknown condition "${name}" in ${where}`, `Register it in conditions or use ${exports.BUILT_IN_CONDITIONS.join(", ")}.`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// ── Shape ───────────────────────────────────────────────────────────────
|
|
392
|
+
/**
|
|
393
|
+
* The JSON shape, checked before any name is. Stored rows and generated specs
|
|
394
|
+
* are untrusted: a string where a list belongs used to crash with a raw
|
|
395
|
+
* TypeError naming no flow, or pass and misbehave at run time (`collect:
|
|
396
|
+
* "nome"` read as the fields "n", "o", "m", "e").
|
|
397
|
+
*/
|
|
398
|
+
function checkShape(value) {
|
|
399
|
+
if (!isObject(value))
|
|
400
|
+
throw problem("flow", `is ${show(value)}, not an object`, "Pass the flow itself: { id, name, steps }.");
|
|
401
|
+
const flowAt = typeof value.id === "string" ? `flow "${value.id}"` : "flow";
|
|
402
|
+
if (!Array.isArray(value.steps)) {
|
|
403
|
+
throw problem(flowAt, "has no steps list", "Write steps as a list, even an empty one.");
|
|
404
|
+
}
|
|
405
|
+
text(value, ["description", "anchor"], flowAt);
|
|
406
|
+
for (const key of ["collect", "clearOnStart", "tools"])
|
|
407
|
+
listOf(value[key], key, flowAt, "string");
|
|
408
|
+
oneOf(value.onEnd, "onEnd", ON_END, flowAt);
|
|
409
|
+
predShape(value.while, "while", flowAt);
|
|
410
|
+
instructionsShape(value.instructions, flowAt);
|
|
411
|
+
listOf(value.on, "on", flowAt, "object").forEach((trigger, i) => {
|
|
412
|
+
const at = `${flowAt}, trigger #${i + 1}`;
|
|
413
|
+
if (!isObject(trigger))
|
|
414
|
+
return;
|
|
415
|
+
listOf(trigger.message, "message", at, "string");
|
|
416
|
+
listOf(trigger.mention, "mention", at, "string");
|
|
417
|
+
predShape(trigger.if, "if", at);
|
|
418
|
+
const { repeat } = trigger;
|
|
419
|
+
if (repeat !== undefined && repeat !== "once" && repeat !== "always" && !(isObject(repeat) && typeof repeat.cooldown === "string")) {
|
|
420
|
+
throw problem(at, `repeat is ${show(repeat)}`, 'Use "once", "always" or { cooldown: "24h" }.');
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
listOf(value.steps, "steps", flowAt, "object").forEach((step, i) => {
|
|
424
|
+
if (!isObject(step))
|
|
425
|
+
return;
|
|
426
|
+
const at = typeof step.id === "string" ? `${flowAt}, step "${step.id}"` : `${flowAt}, step #${i + 1}`;
|
|
427
|
+
stepShape(step, at);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
/** One step: what it does, and every value the right kind of thing. */
|
|
431
|
+
function stepShape(step, at) {
|
|
432
|
+
// A step that does none of the five things is one the run walks straight
|
|
433
|
+
// past; one that does two runs only the first, and the other never happens.
|
|
434
|
+
const kinds = bodyKinds(step);
|
|
435
|
+
if (kinds.length === 0) {
|
|
436
|
+
throw problem(at, "does nothing", "A step talks (`prompt` / `collect`), says (`say`), acts (`do`), waits (`wait`) or forks (`if`).");
|
|
437
|
+
}
|
|
438
|
+
if (kinds.length > 1) {
|
|
439
|
+
throw problem(at, `mixes ${and(kinds.map((k) => `"${k}"`))}`, "A step does one thing. Split it into one step per kind.");
|
|
440
|
+
}
|
|
441
|
+
// The spec's `kind` must say what the body does; the Runner reads the body.
|
|
442
|
+
if (step.kind !== undefined && step.kind !== kinds[0]) {
|
|
443
|
+
throw problem(at, `has kind ${show(step.kind)}, but its body is a "${kinds[0]}" step`, `Set kind to "${kinds[0]}", or change the body to match.`);
|
|
444
|
+
}
|
|
445
|
+
text(step, ["say", "prompt", "question"], at);
|
|
446
|
+
listOf(step.collect, "collect", at, "string");
|
|
447
|
+
listOf(step.tools, "tools", at, "string");
|
|
448
|
+
if (step.ask !== undefined && !isObject(step.ask)) {
|
|
449
|
+
throw problem(at, `ask is ${show(step.ask)}, not an object`, 'Write ask as { field: "how to ask" }.');
|
|
450
|
+
}
|
|
451
|
+
if (step.with !== undefined && !isObject(step.with)) {
|
|
452
|
+
throw problem(at, `with is ${show(step.with)}, not an object`, "Write with as { parameter: value }.");
|
|
453
|
+
}
|
|
454
|
+
const { maxAsks } = step;
|
|
455
|
+
if (maxAsks !== undefined && !(typeof maxAsks === "number" && Number.isInteger(maxAsks) && maxAsks >= 1)) {
|
|
456
|
+
throw problem(at, `maxAsks is ${show(maxAsks)}, not a whole number of 1 or more`, "Write a number like 3.");
|
|
457
|
+
}
|
|
458
|
+
if (isObject(step.wait) && typeof step.wait.event !== "string") {
|
|
459
|
+
throw problem(at, "wait has no event", 'Write wait: { event: "name" } to wait for an event, or a duration like "1h".');
|
|
460
|
+
}
|
|
461
|
+
predShape(step.if, "if", at);
|
|
462
|
+
for (const key of ["then", "else", "onFail"])
|
|
463
|
+
nextShape(step[key], key, at);
|
|
464
|
+
instructionsShape(step.instructions, at);
|
|
465
|
+
listOf(step.branches, "branches", at, "object").forEach((branch, j) => {
|
|
466
|
+
if (!isObject(branch))
|
|
467
|
+
return;
|
|
468
|
+
const where = `branches[${j}]`;
|
|
469
|
+
if (branch.when !== undefined && typeof branch.when !== "string") {
|
|
470
|
+
throw problem(at, `${where}.when is ${show(branch.when)}, not text`, "Write the condition as one sentence.");
|
|
471
|
+
}
|
|
472
|
+
predShape(branch.if, `${where}.if`, at);
|
|
473
|
+
nextShape(branch.then, `${where}.then`, at);
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
/** The kinds a step's body carries, by its keys. A talk step is `collect` with a list, `prompt` with a guideline alone. */
|
|
477
|
+
function bodyKinds(step) {
|
|
478
|
+
const kinds = [];
|
|
479
|
+
if (step.collect !== undefined)
|
|
480
|
+
kinds.push("collect");
|
|
481
|
+
else if (step.prompt !== undefined)
|
|
482
|
+
kinds.push("prompt");
|
|
483
|
+
if (step.say !== undefined)
|
|
484
|
+
kinds.push("say");
|
|
485
|
+
if (step.do !== undefined)
|
|
486
|
+
kinds.push("do");
|
|
487
|
+
if (step.wait !== undefined)
|
|
488
|
+
kinds.push(typeof step.wait === "string" ? "wait" : "waitEvent");
|
|
489
|
+
if (step.if !== undefined)
|
|
490
|
+
kinds.push("if");
|
|
491
|
+
return kinds;
|
|
492
|
+
}
|
|
493
|
+
function instructionsShape(value, at) {
|
|
494
|
+
listOf(value, "instructions", at, "object").forEach((ins, i) => {
|
|
495
|
+
if (!isObject(ins))
|
|
496
|
+
return;
|
|
497
|
+
const where = `instructions[${i}]`;
|
|
498
|
+
oneOf(ins.kind, `${where}.kind`, INSTRUCTION_KINDS, at);
|
|
499
|
+
if (typeof ins.prompt !== "string") {
|
|
500
|
+
throw problem(at, `${where}.prompt is ${show(ins.prompt)}, not text`, "Write the rule as a sentence.");
|
|
501
|
+
}
|
|
502
|
+
if (ins.when !== undefined && typeof ins.when !== "string")
|
|
503
|
+
listOf(ins.when, `${where}.when`, at, "string");
|
|
504
|
+
predShape(ins.if, `${where}.if`, at);
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
/** A step id, `"end"`, `{ step, clear? }` or `{ flow, input? }`. */
|
|
508
|
+
function nextShape(value, where, at) {
|
|
509
|
+
if (value === undefined || typeof value === "string")
|
|
510
|
+
return;
|
|
511
|
+
if (isObject(value) && (typeof value.step === "string" || typeof value.flow === "string")) {
|
|
512
|
+
listOf(value.clear, `${where}.clear`, at, "string");
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
throw problem(at, `${where} is ${show(value)}, not a step id or a target`, 'Write a step id, "end", { step: "id" } or { flow: "id" }.');
|
|
516
|
+
}
|
|
517
|
+
/** A condition object, or code on a typed flow. */
|
|
518
|
+
function predShape(value, where, at) {
|
|
519
|
+
if (value === undefined || typeof value === "function" || isObject(value))
|
|
520
|
+
return;
|
|
521
|
+
throw problem(at, `${where} is ${show(value)}, not a condition`, 'Write it as an object, e.g. { known: ["nome"] }.');
|
|
522
|
+
}
|
|
523
|
+
/** Each named key, when set, must be text. */
|
|
524
|
+
function text(owner, keys, at) {
|
|
525
|
+
for (const key of keys) {
|
|
526
|
+
const value = owner[key];
|
|
527
|
+
if (value !== undefined && typeof value !== "string") {
|
|
528
|
+
throw problem(at, `${key} is ${show(value)}, not text`, `Write ${key} as a string.`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function oneOf(value, where, allowed, at) {
|
|
533
|
+
if (value === undefined || (typeof value === "string" && allowed.includes(value)))
|
|
534
|
+
return;
|
|
535
|
+
throw problem(at, `${where} is ${show(value)}, which is not one of ${allowed.map((v) => `"${v}"`).join(", ")}`, "Use one of them.");
|
|
536
|
+
}
|
|
537
|
+
/** A list whose items are all strings or all objects; absent reads as empty. */
|
|
538
|
+
function listOf(value, where, at, item) {
|
|
539
|
+
if (value === undefined)
|
|
540
|
+
return [];
|
|
541
|
+
if (!Array.isArray(value)) {
|
|
542
|
+
const example = item === "string" && typeof value === "string" ? `${where}: ${JSON.stringify([value])}` : `${where} as a list`;
|
|
543
|
+
throw problem(at, `${where} is ${show(value)}, not a list`, `Write ${example}.`);
|
|
544
|
+
}
|
|
545
|
+
value.forEach((entry, i) => {
|
|
546
|
+
if (item === "string" ? typeof entry !== "string" : !isObject(entry)) {
|
|
547
|
+
throw problem(at, `${where}[${i}] is ${show(entry)}, not ${item === "string" ? "text" : "an object"}`, `Write each entry of ${where} as ${item === "string" ? "a string" : "an object"}.`);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
return value;
|
|
551
|
+
}
|
|
552
|
+
/** A plain object: not null, not a list, not a function. */
|
|
553
|
+
function isObject(value) {
|
|
554
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
555
|
+
}
|
|
556
|
+
/** A wrong value as an error names it: short scalars verbatim, anything else by kind. */
|
|
557
|
+
function show(value) {
|
|
558
|
+
if (typeof value === "string")
|
|
559
|
+
return JSON.stringify(value.length > 40 ? `${value.slice(0, 37)}...` : value);
|
|
560
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
561
|
+
return String(value);
|
|
562
|
+
if (value === undefined)
|
|
563
|
+
return "missing";
|
|
564
|
+
if (value === null)
|
|
565
|
+
return "null";
|
|
566
|
+
return Array.isArray(value) ? "a list" : typeof value === "function" ? "a function" : "an object";
|
|
567
|
+
}
|
|
568
|
+
/** `"a"`, `"a" and "b"`, `"a", "b" and "c"`. */
|
|
569
|
+
function and(items) {
|
|
570
|
+
return items.length < 2 ? items.join("") : `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
|
|
571
|
+
}
|
|
356
572
|
function matchesParam(def, value) {
|
|
357
573
|
if (def.type === "array")
|
|
358
574
|
return Array.isArray(value) && value.every((item) => matches(def.items, item));
|
|
@@ -373,6 +589,21 @@ function matches(def, value) {
|
|
|
373
589
|
return true;
|
|
374
590
|
return typeof value !== "boolean" && def.enum.includes(value);
|
|
375
591
|
}
|
|
592
|
+
/**
|
|
593
|
+
* What a rejected value should have been and what it was. A value of the right type that is
|
|
594
|
+
* not a listed one names the listed values: "must be a string, got string" would say nothing.
|
|
595
|
+
*/
|
|
596
|
+
function mismatch(def, value) {
|
|
597
|
+
const scalar = def.type === "array" ? def.items : def;
|
|
598
|
+
const { enum: allowed, ...typeOnly } = scalar;
|
|
599
|
+
const items = def.type === "array" && Array.isArray(value) ? value : [value];
|
|
600
|
+
const off = allowed ? items.findIndex((item) => matches(typeOnly, item) && !matches(scalar, item)) : -1;
|
|
601
|
+
if (!allowed || off === -1) {
|
|
602
|
+
return { listed: false, expected: describeDef(def), got: describe(value), fix: "Values are not coerced; write the right type." };
|
|
603
|
+
}
|
|
604
|
+
const list = (v) => JSON.stringify(v);
|
|
605
|
+
return { listed: true, expected: `one of ${allowed.map(list).join(", ")}`, got: list(items[off]), fix: "Use one of the listed values." };
|
|
606
|
+
}
|
|
376
607
|
function describe(value) {
|
|
377
608
|
return Array.isArray(value) ? "list" : value === null ? "null" : typeof value;
|
|
378
609
|
}
|
|
@@ -439,6 +670,8 @@ function flowSpecSchema(registries) {
|
|
|
439
670
|
: undefined,
|
|
440
671
|
]);
|
|
441
672
|
const branches = orNull(list(union([closed({ when: STRING, then: next }), closed({ if: condition, then: next })]), "Exits judged while the step asks"));
|
|
673
|
+
// No understand call judges a wait, so its branches are code only.
|
|
674
|
+
const waitBranches = orNull(list(closed({ if: condition, then: next }), "Exits checked by code when the lead replies before the wait ends"));
|
|
442
675
|
const stepBase = { id: STRING, label: orNull(STRING), then: nextOrNull };
|
|
443
676
|
const step = union([
|
|
444
677
|
closed({ ...stepBase, kind: enumOf(["prompt"]), prompt: { type: "string", description: "Guideline for the AI's next message" }, branches }),
|
|
@@ -448,6 +681,7 @@ function flowSpecSchema(registries) {
|
|
|
448
681
|
kind: enumOf(["collect"]),
|
|
449
682
|
collect: { ...slugList, description: "Fields the AI asks for until they are known" },
|
|
450
683
|
prompt: orNull(STRING),
|
|
684
|
+
question: orNull({ type: "string", description: "A fixed first question, sent word for word; later asks are the AI's" }),
|
|
451
685
|
maxAsks: orNull({ ...INTEGER, description: "Times a field may be asked before it is skipped; default 3" }),
|
|
452
686
|
branches,
|
|
453
687
|
}),
|
|
@@ -465,7 +699,7 @@ function flowSpecSchema(registries) {
|
|
|
465
699
|
wait: { ...duration, description: "Park this long; then = time passed, else = the lead replied" },
|
|
466
700
|
businessHours: orNull(BOOLEAN),
|
|
467
701
|
else: nextOrNull,
|
|
468
|
-
branches,
|
|
702
|
+
branches: waitBranches,
|
|
469
703
|
}),
|
|
470
704
|
eventNames.length
|
|
471
705
|
? closed({
|
|
@@ -490,9 +724,10 @@ function flowSpecSchema(registries) {
|
|
|
490
724
|
on: orNull(list(trigger, "What starts a run; null = started by hand")),
|
|
491
725
|
anchor: orNull({ type: "string", description: "'session' (default) or a host anchor such as 'lead'" }),
|
|
492
726
|
while: orNull({ ...condition, description: "The run ends when this stops holding" }),
|
|
727
|
+
collect: slugList && orNull({ ...slugList, description: "The data this flow needs; its steps ask for it in order, and any of it the lead gives is noted" }),
|
|
493
728
|
clearOnStart: slugList && orNull({ ...slugList, description: "Fields to forget when a run starts" }),
|
|
494
729
|
steps: list(step, "In order; a run moves to the next step unless `then` says otherwise"),
|
|
495
|
-
onEnd: orNull(enumOf(["end", "stay", "reset"], "After the last step: end the run, stay on it, or reset to the first")),
|
|
730
|
+
onEnd: orNull(enumOf(["end", "stay", "reset"], "After the last step: end the run, stay on the last talk step it took answering every message, or reset to the first")),
|
|
496
731
|
instructions: orNull(list(instruction, "Rules that apply only inside this flow")),
|
|
497
732
|
});
|
|
498
733
|
}
|
|
@@ -533,6 +768,9 @@ function orNull(schema) {
|
|
|
533
768
|
return typeof schema.type === "string" ? { ...schema, type: [schema.type, "null"] } : { anyOf: [schema, NULL] };
|
|
534
769
|
}
|
|
535
770
|
// ── Shared helpers ──────────────────────────────────────────────────────
|
|
771
|
+
function questionWithoutCollect(at) {
|
|
772
|
+
return problem(at, "has a question but collects nothing", "A fixed question asks for fields: add collect, or send the text with a say step.");
|
|
773
|
+
}
|
|
536
774
|
function problem(at, what, fix) {
|
|
537
775
|
return new errors_js_1.FlowConfigurationError(`[FlowConfigurationError] ${at}: ${what}. ${fix}`);
|
|
538
776
|
}
|