@falai/agent 4.0.0-alpha.6 → 4.0.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/core/FlowSpec.d.ts.map +1 -1
- package/dist/cjs/core/FlowSpec.js +24 -1
- package/dist/cjs/core/FlowSpec.js.map +1 -1
- package/dist/cjs/core/Runner.d.ts +5 -0
- package/dist/cjs/core/Runner.d.ts.map +1 -1
- package/dist/cjs/core/Runner.js +9 -3
- package/dist/cjs/core/Runner.js.map +1 -1
- package/dist/cjs/core/Understand.d.ts.map +1 -1
- package/dist/cjs/core/Understand.js +4 -6
- package/dist/cjs/core/Understand.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +5 -1
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/core/FlowSpec.d.ts.map +1 -1
- package/dist/core/FlowSpec.js +24 -1
- package/dist/core/FlowSpec.js.map +1 -1
- package/dist/core/Runner.d.ts +5 -0
- package/dist/core/Runner.d.ts.map +1 -1
- package/dist/core/Runner.js +9 -3
- package/dist/core/Runner.js.map +1 -1
- package/dist/core/Understand.d.ts.map +1 -1
- package/dist/core/Understand.js +4 -6
- package/dist/core/Understand.js.map +1 -1
- package/dist/types/agent.d.ts +5 -1
- package/dist/types/agent.d.ts.map +1 -1
- package/docs/concepts/architecture.md +1 -1
- package/docs/concepts/pipeline.md +4 -4
- package/docs/guides/conditions.md +1 -1
- package/docs/guides/triggers.md +2 -2
- package/docs/reference/agent.md +2 -2
- package/docs/reference/flow-spec.md +1 -1
- package/docs/reference/trigger.md +2 -2
- package/docs/rfc/v4-one-flow.md +2 -2
- package/package.json +1 -1
- package/src/core/FlowSpec.ts +36 -1
- package/src/core/Runner.ts +10 -3
- package/src/core/Understand.ts +3 -6
- package/src/types/agent.ts +5 -1
package/src/core/FlowSpec.ts
CHANGED
|
@@ -251,6 +251,7 @@ interface LooseStep {
|
|
|
251
251
|
ask?: Partial<Record<string, string>>;
|
|
252
252
|
branches?: LooseBranch[];
|
|
253
253
|
instructions?: LooseInstruction[];
|
|
254
|
+
say?: Template;
|
|
254
255
|
do?: string;
|
|
255
256
|
with?: Record<string, unknown>;
|
|
256
257
|
wait?: Duration | { event: string; upTo?: Duration };
|
|
@@ -271,6 +272,12 @@ const BUILT_IN_CONDITIONS = ["equals", "known", "silenced"];
|
|
|
271
272
|
|
|
272
273
|
const DURATION_HINT = 'Write a number and a unit: "30s", "5m", "24h" or "3d".';
|
|
273
274
|
|
|
275
|
+
/** The four keys one of which makes an `on[]` entry a trigger. */
|
|
276
|
+
const TRIGGER_KINDS = ["message", "mention", "silence", "event"] as const;
|
|
277
|
+
|
|
278
|
+
/** The six keys one of which makes a step do something. */
|
|
279
|
+
const STEP_DOES = ["prompt", "collect", "say", "do", "wait", "if"] as const;
|
|
280
|
+
|
|
274
281
|
/**
|
|
275
282
|
* Check a flow, typed or as a spec, against the agent's registries. Throws
|
|
276
283
|
* `FlowConfigurationError` on the first problem that would break at runtime;
|
|
@@ -424,6 +431,18 @@ export function validateFlow<C = unknown, D = LooseData>(
|
|
|
424
431
|
|
|
425
432
|
flow.on?.forEach((trigger, i) => {
|
|
426
433
|
const at = `${flowAt}, trigger #${i + 1}`;
|
|
434
|
+
// A trigger that names no kind can never fire, and nothing downstream says
|
|
435
|
+
// so: the Runner simply never finds it eligible and the flow looks broken
|
|
436
|
+
// for some other reason. `{ kind: 'message', when: [...] }` — the v3 shape —
|
|
437
|
+
// lands here, and so does a typo in the one key that matters.
|
|
438
|
+
if (!TRIGGER_KINDS.some((key) => trigger[key] !== undefined)) {
|
|
439
|
+
throw problem(
|
|
440
|
+
at,
|
|
441
|
+
"names no trigger kind",
|
|
442
|
+
`A trigger is one of ${TRIGGER_KINDS.map((key) => `\`${key}\``).join(", ")}. ` +
|
|
443
|
+
"A flow the host starts itself has no `on` at all.",
|
|
444
|
+
);
|
|
445
|
+
}
|
|
427
446
|
if (trigger.event !== undefined && !own(events, trigger.event)) {
|
|
428
447
|
throw problem(at, `unknown event "${trigger.event}"`, "Register it in events or fix the name.");
|
|
429
448
|
}
|
|
@@ -450,6 +469,17 @@ export function validateFlow<C = unknown, D = LooseData>(
|
|
|
450
469
|
|
|
451
470
|
flow.steps.forEach((step, i) => {
|
|
452
471
|
const at = `${flowAt}, step "${step.id}"`;
|
|
472
|
+
// Same reasoning as the trigger above: a step that does none of the five
|
|
473
|
+
// things is a step the run walks straight past, silently. `kind` alone is
|
|
474
|
+
// not enough — the spec form drops it and keeps the body, so what counts
|
|
475
|
+
// is whether the body says what to do.
|
|
476
|
+
if (!STEP_DOES.some((key) => step[key] !== undefined)) {
|
|
477
|
+
throw problem(
|
|
478
|
+
at,
|
|
479
|
+
"does nothing",
|
|
480
|
+
"A step talks (`prompt` / `collect`), says (`say`), acts (`do`), waits (`wait`) or forks (`if`).",
|
|
481
|
+
);
|
|
482
|
+
}
|
|
453
483
|
const thenTo = edge(i, step.then, at, "then");
|
|
454
484
|
edge(i, step.else, at, "else");
|
|
455
485
|
|
|
@@ -529,7 +559,12 @@ function describe(value: unknown): string {
|
|
|
529
559
|
}
|
|
530
560
|
|
|
531
561
|
function describeDef(def: ParamDef): string {
|
|
532
|
-
return def.type === "array" ? `a list of ${def.items.type}` :
|
|
562
|
+
return def.type === "array" ? `a list of ${def.items.type}s` : `${article(def.type)} ${def.type}`;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** "an integer", "a string". A vowel test rather than one hard-coded type, so a new type reads right for free. */
|
|
566
|
+
function article(type: string): string {
|
|
567
|
+
return /^[aeiou]/.test(type) ? "an" : "a";
|
|
533
568
|
}
|
|
534
569
|
|
|
535
570
|
// ── flowSpecSchema ──────────────────────────────────────────────────────
|
package/src/core/Runner.ts
CHANGED
|
@@ -508,8 +508,7 @@ export class Runner<C = unknown, D = unknown> {
|
|
|
508
508
|
const floorRun = this.floorRun(turn);
|
|
509
509
|
const floorFlow = floorRun && this.flows.get(floorRun.flowId);
|
|
510
510
|
const eligible = this.eligibleMessageFlows(turn);
|
|
511
|
-
|
|
512
|
-
const messageFlows = !floorRun && eligible.length === 1 ? [] : eligible;
|
|
511
|
+
const messageFlows = !floorRun && this.startsUnscored(turn, eligible) ? [] : eligible;
|
|
513
512
|
const mentionFlows = this.mentionFlows()
|
|
514
513
|
.filter(({ flow, trigger }) => trigger.mention.length > 0 && this.repeatAllows(turn, flow, trigger, turn.triggerKey))
|
|
515
514
|
.map(({ flow }) => flow);
|
|
@@ -562,6 +561,14 @@ export class Runner<C = unknown, D = unknown> {
|
|
|
562
561
|
return this.messageFlows(turn, (list) => list.length > 0);
|
|
563
562
|
}
|
|
564
563
|
|
|
564
|
+
/**
|
|
565
|
+
* The lone eligible flow starts without a score only when a low score would have nowhere
|
|
566
|
+
* else to send the message: no catch-all passes and the idle speaker is silent (S9).
|
|
567
|
+
*/
|
|
568
|
+
private startsUnscored(turn: Turn<C, D>, eligible: Flow<C, D>[]): boolean {
|
|
569
|
+
return eligible.length === 1 && this.options.idle === "silent" && this.messageFlows(turn, (list) => list.length === 0).length === 0;
|
|
570
|
+
}
|
|
571
|
+
|
|
565
572
|
private messageFlows(turn: Turn<C, D>, accept: (list: string[]) => boolean): Flow<C, D>[] {
|
|
566
573
|
const out: Flow<C, D>[] = [];
|
|
567
574
|
for (const flow of this.flows.values()) {
|
|
@@ -605,7 +612,7 @@ export class Runner<C = unknown, D = unknown> {
|
|
|
605
612
|
const current = scores[asker.flowId] ?? 0;
|
|
606
613
|
const top = best(eligible.filter((f) => f.id !== asker.flowId));
|
|
607
614
|
if (top && top.score >= current + ROUTE_STICKY && top.score >= ROUTE_MIN) route = top.flow;
|
|
608
|
-
} else if (
|
|
615
|
+
} else if (this.startsUnscored(turn, eligible)) {
|
|
609
616
|
route = eligible[0];
|
|
610
617
|
} else {
|
|
611
618
|
const top = best(eligible);
|
package/src/core/Understand.ts
CHANGED
|
@@ -44,12 +44,9 @@ export class Understand<C = unknown, D = unknown> {
|
|
|
44
44
|
const candidates = candidateFlows(req);
|
|
45
45
|
const onlyRouting =
|
|
46
46
|
req.mentionFlows.length === 0 && req.branches.length === 0 && Object.keys(req.fields).length === 0;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const only = candidates[0];
|
|
51
|
-
return only && !req.floor ? { ...empty(0), flows: { [only.id]: 100 } } : empty(0);
|
|
52
|
-
}
|
|
47
|
+
// Nothing to compare or extract: no candidates, or only the floor's own flow. A lone
|
|
48
|
+
// candidate with nobody on the floor is here because the runner wants it scored.
|
|
49
|
+
if (onlyRouting && candidates.length <= (req.floor ? 1 : 0)) return empty(0);
|
|
53
50
|
|
|
54
51
|
const aliases = new Aliases();
|
|
55
52
|
const jsonSchema = buildEnvelope(req, candidates, aliases);
|
package/src/types/agent.ts
CHANGED
|
@@ -87,7 +87,11 @@ export type TurnBase<C = unknown, D = unknown> = ContextField<C> & {
|
|
|
87
87
|
sessionId: string;
|
|
88
88
|
/** Absent on a first turn. A wake never creates a session. */
|
|
89
89
|
session?: Session<D>;
|
|
90
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* The conversation BEFORE this input. Pass it on every input kind, wakes included.
|
|
92
|
+
* Leave out the message this turn carries: both calls quote it on their own, so a
|
|
93
|
+
* history that ends with it makes the model read it twice.
|
|
94
|
+
*/
|
|
91
95
|
history?: History;
|
|
92
96
|
silenced?: Silenced;
|
|
93
97
|
/** Host anchors this session belongs to, e.g. `{ lead: { key: 'lead:456', lastInboundAt } }`. */
|