agentopology 0.3.0 → 0.4.1
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 +53 -5
- package/dist/analyzer/__tests__/analyzer.test.js +36 -9
- package/dist/analyzer/__tests__/analyzer.test.js.map +1 -1
- package/dist/analyzer/__tests__/observability-gap.test.js +40 -10
- package/dist/analyzer/__tests__/observability-gap.test.js.map +1 -1
- package/dist/analyzer/index.d.ts +2 -1
- package/dist/analyzer/index.d.ts.map +1 -1
- package/dist/analyzer/index.js +1 -1
- package/dist/analyzer/index.js.map +1 -1
- package/dist/bindings/__tests__/bindings.test.js +20 -5
- package/dist/bindings/__tests__/bindings.test.js.map +1 -1
- package/dist/bindings/__tests__/claude-workflow.test.js +12 -3
- package/dist/bindings/__tests__/claude-workflow.test.js.map +1 -1
- package/dist/bindings/__tests__/defaults-agreement.test.d.ts +16 -0
- package/dist/bindings/__tests__/defaults-agreement.test.d.ts.map +1 -0
- package/dist/bindings/__tests__/defaults-agreement.test.js +80 -0
- package/dist/bindings/__tests__/defaults-agreement.test.js.map +1 -0
- package/dist/bindings/__tests__/observability.test.js +8 -2
- package/dist/bindings/__tests__/observability.test.js.map +1 -1
- package/dist/bindings/__tests__/stub-generation.test.js +8 -2
- package/dist/bindings/__tests__/stub-generation.test.js.map +1 -1
- package/dist/bindings/claude-code.d.ts.map +1 -1
- package/dist/bindings/claude-code.js +2 -1
- package/dist/bindings/claude-code.js.map +1 -1
- package/dist/bindings/claude-workflow.d.ts.map +1 -1
- package/dist/bindings/claude-workflow.js +2 -1
- package/dist/bindings/claude-workflow.js.map +1 -1
- package/dist/bindings/codex.d.ts.map +1 -1
- package/dist/bindings/codex.js +6 -2
- package/dist/bindings/codex.js.map +1 -1
- package/dist/bindings/gemini-cli.d.ts.map +1 -1
- package/dist/bindings/gemini-cli.js +2 -1
- package/dist/bindings/gemini-cli.js.map +1 -1
- package/dist/bindings/kiro.d.ts.map +1 -1
- package/dist/bindings/kiro.js +2 -1
- package/dist/bindings/kiro.js.map +1 -1
- package/dist/bindings/openclaw.d.ts.map +1 -1
- package/dist/bindings/openclaw.js +2 -1
- package/dist/bindings/openclaw.js.map +1 -1
- package/dist/cli/__tests__/cli.test.d.ts +12 -0
- package/dist/cli/__tests__/cli.test.d.ts.map +1 -0
- package/dist/cli/__tests__/cli.test.js +171 -0
- package/dist/cli/__tests__/cli.test.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +149 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/docs/content.d.ts.map +1 -1
- package/dist/docs/content.js +112 -0
- package/dist/docs/content.js.map +1 -1
- package/dist/exporters/ascii.d.ts +13 -0
- package/dist/exporters/ascii.d.ts.map +1 -0
- package/dist/exporters/ascii.js +26 -0
- package/dist/exporters/ascii.js.map +1 -0
- package/dist/exporters/brief.d.ts +14 -0
- package/dist/exporters/brief.d.ts.map +1 -0
- package/dist/exporters/brief.js +37 -0
- package/dist/exporters/brief.js.map +1 -0
- package/dist/exporters/index.d.ts +2 -0
- package/dist/exporters/index.d.ts.map +1 -1
- package/dist/exporters/index.js +6 -0
- package/dist/exporters/index.js.map +1 -1
- package/dist/exporters/markdown.d.ts.map +1 -1
- package/dist/exporters/markdown.js +2 -1
- package/dist/exporters/markdown.js.map +1 -1
- package/dist/exporters/mermaid.d.ts.map +1 -1
- package/dist/exporters/mermaid.js +2 -1
- package/dist/exporters/mermaid.js.map +1 -1
- package/dist/import/__tests__/import.test.js +8 -2
- package/dist/import/__tests__/import.test.js.map +1 -1
- package/dist/parser/__tests__/parser.test.js +1386 -210
- package/dist/parser/__tests__/parser.test.js.map +1 -1
- package/dist/parser/__tests__/spec-sync.test.d.ts +10 -0
- package/dist/parser/__tests__/spec-sync.test.d.ts.map +1 -0
- package/dist/parser/__tests__/spec-sync.test.js +60 -0
- package/dist/parser/__tests__/spec-sync.test.js.map +1 -0
- package/dist/parser/index.d.ts +50 -6
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +183 -28
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/validator.d.ts +7 -1
- package/dist/parser/validator.d.ts.map +1 -1
- package/dist/parser/validator.js +77 -2
- package/dist/parser/validator.js.map +1 -1
- package/dist/plan/__tests__/brief.test.d.ts +2 -0
- package/dist/plan/__tests__/brief.test.d.ts.map +1 -0
- package/dist/plan/__tests__/brief.test.js +442 -0
- package/dist/plan/__tests__/brief.test.js.map +1 -0
- package/dist/plan/brief.d.ts +207 -0
- package/dist/plan/brief.d.ts.map +1 -0
- package/dist/plan/brief.js +342 -0
- package/dist/plan/brief.js.map +1 -0
- package/dist/plan/render.d.ts +20 -0
- package/dist/plan/render.d.ts.map +1 -0
- package/dist/plan/render.js +425 -0
- package/dist/plan/render.js.map +1 -0
- package/dist/render/__tests__/ascii.test.d.ts +2 -0
- package/dist/render/__tests__/ascii.test.d.ts.map +1 -0
- package/dist/render/__tests__/ascii.test.js +187 -0
- package/dist/render/__tests__/ascii.test.js.map +1 -0
- package/dist/render/ascii.d.ts +23 -0
- package/dist/render/ascii.d.ts.map +1 -0
- package/dist/render/ascii.js +187 -0
- package/dist/render/ascii.js.map +1 -0
- package/dist/resolve/__tests__/defaults.test.d.ts +2 -0
- package/dist/resolve/__tests__/defaults.test.d.ts.map +1 -0
- package/dist/resolve/__tests__/defaults.test.js +109 -0
- package/dist/resolve/__tests__/defaults.test.js.map +1 -0
- package/dist/resolve/__tests__/order.test.d.ts +2 -0
- package/dist/resolve/__tests__/order.test.d.ts.map +1 -0
- package/dist/resolve/__tests__/order.test.js +238 -0
- package/dist/resolve/__tests__/order.test.js.map +1 -0
- package/dist/resolve/__tests__/phase.test.d.ts +12 -0
- package/dist/resolve/__tests__/phase.test.d.ts.map +1 -0
- package/dist/resolve/__tests__/phase.test.js +81 -0
- package/dist/resolve/__tests__/phase.test.js.map +1 -0
- package/dist/resolve/defaults.d.ts +51 -0
- package/dist/resolve/defaults.d.ts.map +1 -0
- package/dist/resolve/defaults.js +109 -0
- package/dist/resolve/defaults.js.map +1 -0
- package/dist/resolve/order.d.ts +73 -0
- package/dist/resolve/order.d.ts.map +1 -0
- package/dist/resolve/order.js +208 -0
- package/dist/resolve/order.js.map +1 -0
- package/dist/resolve/phase.d.ts +57 -0
- package/dist/resolve/phase.d.ts.map +1 -0
- package/dist/resolve/phase.js +60 -0
- package/dist/resolve/phase.js.map +1 -0
- package/dist/scaffold/__tests__/scaffold.test.js +8 -2
- package/dist/scaffold/__tests__/scaffold.test.js.map +1 -1
- package/dist/scaffold/__tests__/settings-merge.test.js +95 -0
- package/dist/scaffold/__tests__/settings-merge.test.js.map +1 -1
- package/dist/scaffold/incremental.d.ts.map +1 -1
- package/dist/scaffold/incremental.js +13 -2
- package/dist/scaffold/incremental.js.map +1 -1
- package/dist/visualizer/__tests__/topology-brain-link.test.js +19 -4
- package/dist/visualizer/__tests__/topology-brain-link.test.js.map +1 -1
- package/examples/code-review.at +32 -4
- package/examples/data-processing.at +33 -4
- package/package.json +1 -1
- package/spec/grammar.md +25 -1
- package/spec/validation.md +103 -35
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An unphased agent must sort the same way on every target.
|
|
3
|
+
*
|
|
4
|
+
* Regression: six consumers each picked their own sentinel — MAX_SAFE_INTEGER
|
|
5
|
+
* in claude-code and claude-workflow, 999 in gemini-cli, 0 in openclaw, kiro,
|
|
6
|
+
* mermaid and the markdown exporter. So the same topology put an unphased
|
|
7
|
+
* agent LAST on three targets and FIRST on three others. That is exactly the
|
|
8
|
+
* failure "write once, deploy to any platform" exists to prevent, and nothing
|
|
9
|
+
* compared them.
|
|
10
|
+
*/
|
|
11
|
+
import { describe, it, expect } from "vitest";
|
|
12
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
13
|
+
import { resolve, dirname, join } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { parse } from "../../parser/index.js";
|
|
16
|
+
import { phaseOf, byPhase, UNPHASED } from "../phase.js";
|
|
17
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
|
|
18
|
+
const MIXED = `topology t : [pipeline] {
|
|
19
|
+
meta {
|
|
20
|
+
version: "1.0.0"
|
|
21
|
+
description: "one phased agent, one not"
|
|
22
|
+
}
|
|
23
|
+
agent early {
|
|
24
|
+
model: sonnet
|
|
25
|
+
description: "e"
|
|
26
|
+
phase: 1
|
|
27
|
+
}
|
|
28
|
+
agent unphased {
|
|
29
|
+
model: sonnet
|
|
30
|
+
description: "u"
|
|
31
|
+
}
|
|
32
|
+
action i {
|
|
33
|
+
kind: inline
|
|
34
|
+
description: "in"
|
|
35
|
+
}
|
|
36
|
+
flow { i -> early -> unphased }
|
|
37
|
+
}`;
|
|
38
|
+
describe("phase resolution is defined in exactly one place", () => {
|
|
39
|
+
it("gives an unphased agent a key no real declaration can reach", () => {
|
|
40
|
+
// 999 collides with `phase: 999`; 0 collides with `phase: 0`. This cannot
|
|
41
|
+
// collide, because `phase` is parseFloat'd from a source literal.
|
|
42
|
+
expect(UNPHASED).toBe(Number.MAX_SAFE_INTEGER);
|
|
43
|
+
});
|
|
44
|
+
it("trails unphased agents rather than leading them", () => {
|
|
45
|
+
const agents = parse(MIXED).nodes.filter((n) => n.type === "agent");
|
|
46
|
+
const sorted = [...agents].sort(byPhase).map((a) => a.id);
|
|
47
|
+
expect(sorted).toEqual(["early", "unphased"]);
|
|
48
|
+
});
|
|
49
|
+
it("leaves declaration order intact when nobody declares a phase", () => {
|
|
50
|
+
const src = MIXED.replace(" phase: 1\n", "");
|
|
51
|
+
const agents = parse(src).nodes.filter((n) => n.type === "agent");
|
|
52
|
+
expect(agents.every((a) => phaseOf(a) === UNPHASED)).toBe(true);
|
|
53
|
+
expect([...agents].sort(byPhase).map((a) => a.id)).toEqual(["early", "unphased"]);
|
|
54
|
+
});
|
|
55
|
+
it("no consumer defines its own phase fallback", () => {
|
|
56
|
+
// The guard that actually prevents the regression: grep the source. A new
|
|
57
|
+
// `agent.phase ?? <n>` anywhere reintroduces the divergence silently.
|
|
58
|
+
const offenders = [];
|
|
59
|
+
const walk = (dir) => {
|
|
60
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
61
|
+
const p = join(dir, entry.name);
|
|
62
|
+
if (entry.isDirectory()) {
|
|
63
|
+
if (entry.name === "__tests__" || entry.name === "node_modules")
|
|
64
|
+
continue;
|
|
65
|
+
walk(p);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (!entry.name.endsWith(".ts"))
|
|
69
|
+
continue;
|
|
70
|
+
if (p.endsWith(join("resolve", "phase.ts")))
|
|
71
|
+
continue;
|
|
72
|
+
const text = readFileSync(p, "utf8");
|
|
73
|
+
if (/\bphase\s*\?\?\s*/.test(text))
|
|
74
|
+
offenders.push(p.slice(root.length + 1));
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
walk(join(root, "src"));
|
|
78
|
+
expect(offenders, `these define their own phase fallback instead of using resolve/phase.ts: ${offenders.join(", ")}`).toEqual([]);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
//# sourceMappingURL=phase.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phase.test.js","sourceRoot":"","sources":["../../../src/resolve/__tests__/phase.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defaults resolver — the first code in the package that reads the spec's
|
|
3
|
+
* defaults table (`spec/grammar.md` §7).
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS
|
|
6
|
+
* ---------------
|
|
7
|
+
* The parser writes a field only when it is present in the source, so every
|
|
8
|
+
* optional field on a node arrives `undefined`. Until now each binding filled
|
|
9
|
+
* the blanks itself, and they disagree: `agent.permissions` defaults to
|
|
10
|
+
* `"supervised"` in `bindings/codex.ts`, `"auto"` in `bindings/openclaw.ts`,
|
|
11
|
+
* while the spec says `autonomous`. Interpreted mode cannot tolerate that —
|
|
12
|
+
* an execution brief must state one resolved value and be able to name where
|
|
13
|
+
* it came from.
|
|
14
|
+
*
|
|
15
|
+
* WHAT THIS DOES NOT DO
|
|
16
|
+
* ---------------------
|
|
17
|
+
* It does not mutate the input and no binding consumes it. Wiring it into the
|
|
18
|
+
* bindings would silently change their output for three targets, which is a
|
|
19
|
+
* separate, deliberate change. This resolver serves `agentopology plan`.
|
|
20
|
+
*
|
|
21
|
+
* Only defaults that are a CONCRETE VALUE are applied. Table rows whose default
|
|
22
|
+
* is "-- (none)" stay `undefined`, because absence is their meaning.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
import type { TopologyAST } from "../parser/ast.js";
|
|
27
|
+
/** One resolved field, and the fact that it was not authored. */
|
|
28
|
+
export interface ResolvedDefault {
|
|
29
|
+
/** Node id, or `"<topology>"` for topology-level fields. */
|
|
30
|
+
node: string;
|
|
31
|
+
/** Field name as it appears in the grammar (kebab-case). */
|
|
32
|
+
field: string;
|
|
33
|
+
/** The value the spec says to use. */
|
|
34
|
+
value: unknown;
|
|
35
|
+
}
|
|
36
|
+
/** Result of resolving defaults: a new AST plus the list of what was filled. */
|
|
37
|
+
export interface DefaultsResolution {
|
|
38
|
+
ast: TopologyAST;
|
|
39
|
+
applied: ResolvedDefault[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Fill every omitted optional field that the spec gives a concrete default.
|
|
43
|
+
*
|
|
44
|
+
* Returns a NEW ast — the input is not mutated — plus the list of fields that
|
|
45
|
+
* were filled, so a caller can report which values the author never wrote.
|
|
46
|
+
*
|
|
47
|
+
* @param ast - A parsed topology.
|
|
48
|
+
* @returns The resolved AST and the record of what was applied.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveDefaults(ast: TopologyAST): DefaultsResolution;
|
|
51
|
+
//# sourceMappingURL=defaults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/resolve/defaults.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAA4C,MAAM,kBAAkB,CAAC;AAE9F,iEAAiE;AACjE,MAAM,WAAW,eAAe;IAC9B,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,gFAAgF;AAChF,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,WAAW,CAAC;IACjB,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AAyDD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,WAAW,GAAG,kBAAkB,CA4BpE"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defaults resolver — the first code in the package that reads the spec's
|
|
3
|
+
* defaults table (`spec/grammar.md` §7).
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS
|
|
6
|
+
* ---------------
|
|
7
|
+
* The parser writes a field only when it is present in the source, so every
|
|
8
|
+
* optional field on a node arrives `undefined`. Until now each binding filled
|
|
9
|
+
* the blanks itself, and they disagree: `agent.permissions` defaults to
|
|
10
|
+
* `"supervised"` in `bindings/codex.ts`, `"auto"` in `bindings/openclaw.ts`,
|
|
11
|
+
* while the spec says `autonomous`. Interpreted mode cannot tolerate that —
|
|
12
|
+
* an execution brief must state one resolved value and be able to name where
|
|
13
|
+
* it came from.
|
|
14
|
+
*
|
|
15
|
+
* WHAT THIS DOES NOT DO
|
|
16
|
+
* ---------------------
|
|
17
|
+
* It does not mutate the input and no binding consumes it. Wiring it into the
|
|
18
|
+
* bindings would silently change their output for three targets, which is a
|
|
19
|
+
* separate, deliberate change. This resolver serves `agentopology plan`.
|
|
20
|
+
*
|
|
21
|
+
* Only defaults that are a CONCRETE VALUE are applied. Table rows whose default
|
|
22
|
+
* is "-- (none)" stay `undefined`, because absence is their meaning.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Concrete agent defaults from `spec/grammar.md` §7. Keys are AST field names;
|
|
28
|
+
* `grammarName` is the kebab-case spelling used in `.at` source and in reports.
|
|
29
|
+
*/
|
|
30
|
+
const AGENT_DEFAULTS = [
|
|
31
|
+
{ key: "permissions", grammarName: "permissions", value: "autonomous" },
|
|
32
|
+
{ key: "disallowedTools", grammarName: "disallowed-tools", value: [] },
|
|
33
|
+
{ key: "reads", grammarName: "reads", value: [] },
|
|
34
|
+
{ key: "writes", grammarName: "writes", value: [] },
|
|
35
|
+
{ key: "outputs", grammarName: "outputs", value: {} },
|
|
36
|
+
{ key: "retry", grammarName: "retry", value: 0 },
|
|
37
|
+
{ key: "invocation", grammarName: "invocation", value: "auto" },
|
|
38
|
+
{ key: "behavior", grammarName: "behavior", value: "blocking" },
|
|
39
|
+
{ key: "memory", grammarName: "memory", value: [] },
|
|
40
|
+
{ key: "skills", grammarName: "skills", value: [] },
|
|
41
|
+
{ key: "mcpServers", grammarName: "mcp-servers", value: [] },
|
|
42
|
+
{ key: "background", grammarName: "background", value: false },
|
|
43
|
+
{ key: "fallbackChain", grammarName: "fallback-chain", value: [] },
|
|
44
|
+
];
|
|
45
|
+
const GATE_DEFAULTS = [
|
|
46
|
+
{ key: "checks", grammarName: "checks", value: [] },
|
|
47
|
+
{ key: "retry", grammarName: "retry", value: 0 },
|
|
48
|
+
{ key: "onFail", grammarName: "on-fail", value: "halt" },
|
|
49
|
+
{ key: "behavior", grammarName: "behavior", value: "blocking" },
|
|
50
|
+
];
|
|
51
|
+
const ACTION_DEFAULTS = [
|
|
52
|
+
{ key: "commands", grammarName: "commands", value: [] },
|
|
53
|
+
];
|
|
54
|
+
/** Deep-ish clone that is enough for an AST of plain data. */
|
|
55
|
+
function cloneValue(v) {
|
|
56
|
+
if (Array.isArray(v))
|
|
57
|
+
return [...v];
|
|
58
|
+
if (v && typeof v === "object")
|
|
59
|
+
return { ...v };
|
|
60
|
+
return v;
|
|
61
|
+
}
|
|
62
|
+
function applyTo(node, id, table, applied) {
|
|
63
|
+
for (const { key, grammarName, value } of table) {
|
|
64
|
+
if (node[key] === undefined) {
|
|
65
|
+
node[key] = cloneValue(value);
|
|
66
|
+
// Clone for the REPORT too. Pushing `value` handed the caller a reference
|
|
67
|
+
// to the module-level table, so one mutation of a reported value poisoned
|
|
68
|
+
// the default for every later topology in the process.
|
|
69
|
+
applied.push({ node: id, field: grammarName, value: cloneValue(value) });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Fill every omitted optional field that the spec gives a concrete default.
|
|
75
|
+
*
|
|
76
|
+
* Returns a NEW ast — the input is not mutated — plus the list of fields that
|
|
77
|
+
* were filled, so a caller can report which values the author never wrote.
|
|
78
|
+
*
|
|
79
|
+
* @param ast - A parsed topology.
|
|
80
|
+
* @returns The resolved AST and the record of what was applied.
|
|
81
|
+
*/
|
|
82
|
+
export function resolveDefaults(ast) {
|
|
83
|
+
const applied = [];
|
|
84
|
+
const nodes = ast.nodes.map((n) => {
|
|
85
|
+
const copy = { ...n };
|
|
86
|
+
switch (n.type) {
|
|
87
|
+
case "agent":
|
|
88
|
+
applyTo(copy, n.id, AGENT_DEFAULTS, applied);
|
|
89
|
+
break;
|
|
90
|
+
case "gate":
|
|
91
|
+
applyTo(copy, n.id, GATE_DEFAULTS, applied);
|
|
92
|
+
break;
|
|
93
|
+
case "action":
|
|
94
|
+
applyTo(copy, n.id, ACTION_DEFAULTS, applied);
|
|
95
|
+
break;
|
|
96
|
+
case "orchestrator":
|
|
97
|
+
if (copy.outputs === undefined) {
|
|
98
|
+
copy.outputs = {};
|
|
99
|
+
applied.push({ node: n.id, field: "outputs", value: {} });
|
|
100
|
+
}
|
|
101
|
+
break;
|
|
102
|
+
default:
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
return copy;
|
|
106
|
+
});
|
|
107
|
+
return { ast: { ...ast, nodes }, applied };
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/resolve/defaults.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution order resolver.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS
|
|
5
|
+
* ---------------
|
|
6
|
+
* `computeLayers` in `../analyzer` is a correct Kahn topological rank, but it
|
|
7
|
+
* cannot be used directly for execution order. Gates bind to the graph through
|
|
8
|
+
* `after:` / `before:` fields, never through edges, so they have in-degree 0
|
|
9
|
+
* and Kahn ranks them as SOURCES. Measured on `examples/code-review.at`, the
|
|
10
|
+
* gate `human-approval` lands in layer 0 next to the entry action.
|
|
11
|
+
*
|
|
12
|
+
* So: rank the flow-participating nodes only, then splice gates back in at the
|
|
13
|
+
* position their `after` / `before` actually names.
|
|
14
|
+
*
|
|
15
|
+
* The orchestrator is deliberately NOT a step. In interpreted mode the host
|
|
16
|
+
* coding agent IS the orchestrator — it does not spawn one.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import type { TopologyAST, EdgeDef } from "../parser/ast.js";
|
|
21
|
+
/** What the host does at one step. */
|
|
22
|
+
export type StepKind = "spawn" | "gate" | "action" | "human" | "group";
|
|
23
|
+
/** One step of the resolved execution order. */
|
|
24
|
+
export interface OrderStep {
|
|
25
|
+
/** 1-based position. */
|
|
26
|
+
index: number;
|
|
27
|
+
kind: StepKind;
|
|
28
|
+
/** Node ids. More than one means they run in parallel, mutually blind. */
|
|
29
|
+
ids: string[];
|
|
30
|
+
/** Graph rank, or `null` for a gate (spliced by position, never ranked). */
|
|
31
|
+
depth: number | null;
|
|
32
|
+
/**
|
|
33
|
+
* True when the ids are mutually EXCLUSIVE branches, not a parallel fan-out:
|
|
34
|
+
* every one is reached only by a conditional edge from the same source.
|
|
35
|
+
* Exactly one runs.
|
|
36
|
+
*
|
|
37
|
+
* Without this the renderer called them "parallel" and the brief marked them
|
|
38
|
+
* mutually blind and told the host to dispatch all of them in one message —
|
|
39
|
+
* running every branch of a decision instead of taking one.
|
|
40
|
+
*/
|
|
41
|
+
exclusive: boolean;
|
|
42
|
+
/** For an exclusive step, the condition that selects each id. */
|
|
43
|
+
branchOn?: Array<{
|
|
44
|
+
id: string;
|
|
45
|
+
condition: string;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
/** A back-edge with its iteration budget. */
|
|
49
|
+
export interface LoopInfo {
|
|
50
|
+
from: string;
|
|
51
|
+
to: string;
|
|
52
|
+
condition: string | null;
|
|
53
|
+
/** `[max N]` — the number of TRAVERSALS allowed, so the target runs N+1 times. */
|
|
54
|
+
budget: number | null;
|
|
55
|
+
}
|
|
56
|
+
export interface ResolvedOrder {
|
|
57
|
+
steps: OrderStep[];
|
|
58
|
+
loops: LoopInfo[];
|
|
59
|
+
/** Nodes Kahn could not reach — cycle members. `computeLayers` gives depth -1. */
|
|
60
|
+
unreachable: string[];
|
|
61
|
+
/** The orchestrator's id, if declared. It is the host, not a step. */
|
|
62
|
+
orchestrator: string | null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the order in which a host agent should enact a topology.
|
|
66
|
+
*
|
|
67
|
+
* @param ast - A parsed topology. Order does not depend on defaults resolution.
|
|
68
|
+
* @returns Ordered steps, loop budgets, unreachable nodes, and the orchestrator id.
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolveOrder(ast: TopologyAST): ResolvedOrder;
|
|
71
|
+
/** All outbound edges from a node, in declaration order. */
|
|
72
|
+
export declare function edgesFrom(ast: TopologyAST, id: string): EdgeDef[];
|
|
73
|
+
//# sourceMappingURL=order.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order.d.ts","sourceRoot":"","sources":["../../src/resolve/order.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAqB,MAAM,kBAAkB,CAAC;AAGhF,sCAAsC;AACtC,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;AAEvE,gDAAgD;AAChD,MAAM,WAAW,SAAS;IACxB,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,0EAA0E;IAC1E,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,4EAA4E;IAC5E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;;;;;;;;OAQG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrD;AAED,6CAA6C;AAC7C,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,kFAAkF;IAClF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,kFAAkF;IAClF,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,sEAAsE;IACtE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAkDD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,aAAa,CAyI5D;AAED,4DAA4D;AAC5D,wBAAgB,SAAS,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,EAAE,CAEjE"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution order resolver.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS
|
|
5
|
+
* ---------------
|
|
6
|
+
* `computeLayers` in `../analyzer` is a correct Kahn topological rank, but it
|
|
7
|
+
* cannot be used directly for execution order. Gates bind to the graph through
|
|
8
|
+
* `after:` / `before:` fields, never through edges, so they have in-degree 0
|
|
9
|
+
* and Kahn ranks them as SOURCES. Measured on `examples/code-review.at`, the
|
|
10
|
+
* gate `human-approval` lands in layer 0 next to the entry action.
|
|
11
|
+
*
|
|
12
|
+
* So: rank the flow-participating nodes only, then splice gates back in at the
|
|
13
|
+
* position their `after` / `before` actually names.
|
|
14
|
+
*
|
|
15
|
+
* The orchestrator is deliberately NOT a step. In interpreted mode the host
|
|
16
|
+
* coding agent IS the orchestrator — it does not spawn one.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import { computeLayers } from "../analyzer/index.js";
|
|
21
|
+
/**
|
|
22
|
+
* True back-edges, found by DFS over the FULL edge set.
|
|
23
|
+
*
|
|
24
|
+
* `findBackEdges` in the validator cannot be reused here: it strips every edge
|
|
25
|
+
* carrying `[max N]` first, because its job is to find loops the author has NOT
|
|
26
|
+
* acknowledged (that is V6). We need the opposite — the acknowledged ones too.
|
|
27
|
+
*
|
|
28
|
+
* And `computeLayers` strips them as well, so a plain FORWARD edge that happens
|
|
29
|
+
* to carry a bound both corrupts the ranking and gets reported as a loop.
|
|
30
|
+
* Classifying first, then ranking with only the real back-edges removed, fixes
|
|
31
|
+
* both.
|
|
32
|
+
*/
|
|
33
|
+
function trueBackEdges(edges, nodeIds) {
|
|
34
|
+
const out = new Map();
|
|
35
|
+
for (const e of edges) {
|
|
36
|
+
if (e.isError)
|
|
37
|
+
continue;
|
|
38
|
+
if (!nodeIds.has(e.from) || !nodeIds.has(e.to))
|
|
39
|
+
continue;
|
|
40
|
+
if (!out.has(e.from))
|
|
41
|
+
out.set(e.from, []);
|
|
42
|
+
out.get(e.from).push(e.to);
|
|
43
|
+
}
|
|
44
|
+
const WHITE = 0, GREY = 1, BLACK = 2;
|
|
45
|
+
const colour = new Map([...nodeIds].map((id) => [id, WHITE]));
|
|
46
|
+
const back = new Set();
|
|
47
|
+
const visit = (id) => {
|
|
48
|
+
colour.set(id, GREY);
|
|
49
|
+
for (const to of out.get(id) ?? []) {
|
|
50
|
+
const c = colour.get(to) ?? WHITE;
|
|
51
|
+
// An edge into a node still on the DFS stack closes a cycle.
|
|
52
|
+
if (c === GREY)
|
|
53
|
+
back.add(`${id} ${to}`);
|
|
54
|
+
else if (c === WHITE)
|
|
55
|
+
visit(to);
|
|
56
|
+
}
|
|
57
|
+
colour.set(id, BLACK);
|
|
58
|
+
};
|
|
59
|
+
for (const id of nodeIds)
|
|
60
|
+
if (colour.get(id) === WHITE)
|
|
61
|
+
visit(id);
|
|
62
|
+
return back;
|
|
63
|
+
}
|
|
64
|
+
const KIND_BY_TYPE = {
|
|
65
|
+
agent: "spawn",
|
|
66
|
+
action: "action",
|
|
67
|
+
human: "human",
|
|
68
|
+
group: "group",
|
|
69
|
+
gate: "gate",
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Resolve the order in which a host agent should enact a topology.
|
|
73
|
+
*
|
|
74
|
+
* @param ast - A parsed topology. Order does not depend on defaults resolution.
|
|
75
|
+
* @returns Ordered steps, loop budgets, unreachable nodes, and the orchestrator id.
|
|
76
|
+
*/
|
|
77
|
+
export function resolveOrder(ast) {
|
|
78
|
+
const byId = new Map(ast.nodes.map((n) => [n.id, n]));
|
|
79
|
+
const orchestrator = ast.nodes.find((n) => n.type === "orchestrator")?.id ?? null;
|
|
80
|
+
const gates = ast.nodes.filter((n) => n.type === "gate");
|
|
81
|
+
const gateIds = new Set(gates.map((g) => g.id));
|
|
82
|
+
// Rank only what actually participates in the flow graph. Gates are excluded
|
|
83
|
+
// because they have no edges; the orchestrator because it is the host.
|
|
84
|
+
const flowIds = new Set(ast.nodes.filter((n) => !gateIds.has(n.id) && n.type !== "orchestrator").map((n) => n.id));
|
|
85
|
+
// Classify first: rank with only the REAL back-edges removed, so a forward
|
|
86
|
+
// edge carrying `[max N]` still advances the depth of its target.
|
|
87
|
+
const backKeys = trueBackEdges(ast.edges, flowIds);
|
|
88
|
+
const forwardEdges = ast.edges.filter((e) => !backKeys.has(`${e.from} ${e.to}`));
|
|
89
|
+
const layers = computeLayers(
|
|
90
|
+
// computeLayers strips `maxIterations` itself, so clear it on the edges we
|
|
91
|
+
// have already established are forward — otherwise they vanish again.
|
|
92
|
+
forwardEdges.map((e) => (e.maxIterations ? { ...e, maxIterations: null } : e)), flowIds);
|
|
93
|
+
// Build the ranked spine, splitting each layer by node kind so a step is
|
|
94
|
+
// never a mix of "spawn three agents" and "run a shell action".
|
|
95
|
+
const spine = [];
|
|
96
|
+
const unreachable = [];
|
|
97
|
+
for (const layer of layers) {
|
|
98
|
+
if (layer.depth === -1) {
|
|
99
|
+
unreachable.push(...layer.nodes);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const grouped = new Map();
|
|
103
|
+
for (const id of layer.nodes) {
|
|
104
|
+
const node = byId.get(id);
|
|
105
|
+
if (!node)
|
|
106
|
+
continue;
|
|
107
|
+
const kind = KIND_BY_TYPE[node.type] ?? "spawn";
|
|
108
|
+
if (!grouped.has(kind))
|
|
109
|
+
grouped.set(kind, []);
|
|
110
|
+
grouped.get(kind).push(id);
|
|
111
|
+
}
|
|
112
|
+
// Deterministic kind order within a layer: work first, then human gates.
|
|
113
|
+
for (const kind of ["action", "spawn", "group", "human"]) {
|
|
114
|
+
const ids = grouped.get(kind);
|
|
115
|
+
if (ids && ids.length)
|
|
116
|
+
spine.push({ kind, ids, depth: layer.depth });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Splice gates. `after: X` inserts immediately after the step containing X.
|
|
120
|
+
// `before: Y` inserts immediately before the step containing Y. A gate with
|
|
121
|
+
// both prefers `after`, since that is when its check can actually run.
|
|
122
|
+
const withGates = [...spine];
|
|
123
|
+
// Splice in declaration order, tracking how many gates already landed on each
|
|
124
|
+
// anchor. Without the offset each new gate inserted at the same index and
|
|
125
|
+
// pushed its predecessor down, so two gates after the same agent ran in
|
|
126
|
+
// reverse declaration order.
|
|
127
|
+
const placedPerAnchor = new Map();
|
|
128
|
+
// A gate may anchor to ANOTHER gate. Splicing in declaration order meant a
|
|
129
|
+
// gate whose anchor was declared later found nothing, fell through to "runs
|
|
130
|
+
// last", and silently skipped past the step it was meant to guard. Order the
|
|
131
|
+
// gates so every anchor is already placed: repeat until no progress, then
|
|
132
|
+
// append whatever still cannot resolve.
|
|
133
|
+
const placedIds = new Set();
|
|
134
|
+
const pending = [...gates];
|
|
135
|
+
const ordered = [];
|
|
136
|
+
for (let pass = 0; pass < gates.length + 1 && pending.length; pass++) {
|
|
137
|
+
let moved = false;
|
|
138
|
+
for (let i = 0; i < pending.length;) {
|
|
139
|
+
const g = pending[i];
|
|
140
|
+
const anchors = [g.after, g.before].filter((a) => !!a);
|
|
141
|
+
const waitingOnGate = anchors.some((a) => gateIds.has(a) && !placedIds.has(a));
|
|
142
|
+
if (waitingOnGate) {
|
|
143
|
+
i++;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
ordered.push(g);
|
|
147
|
+
placedIds.add(g.id);
|
|
148
|
+
pending.splice(i, 1);
|
|
149
|
+
moved = true;
|
|
150
|
+
}
|
|
151
|
+
if (!moved)
|
|
152
|
+
break; // a cycle among gate anchors — append them as-is
|
|
153
|
+
}
|
|
154
|
+
ordered.push(...pending);
|
|
155
|
+
for (const gate of ordered) {
|
|
156
|
+
let at = -1;
|
|
157
|
+
if (gate.after) {
|
|
158
|
+
const i = withGates.findIndex((s) => s.ids.includes(gate.after));
|
|
159
|
+
if (i >= 0) {
|
|
160
|
+
const already = placedPerAnchor.get(gate.after) ?? 0;
|
|
161
|
+
at = i + 1 + already;
|
|
162
|
+
placedPerAnchor.set(gate.after, already + 1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (at === -1 && gate.before) {
|
|
166
|
+
const i = withGates.findIndex((s) => s.ids.includes(gate.before));
|
|
167
|
+
if (i >= 0)
|
|
168
|
+
at = i;
|
|
169
|
+
}
|
|
170
|
+
// A gate anchored to nothing resolvable runs last — visible, not dropped.
|
|
171
|
+
if (at === -1)
|
|
172
|
+
at = withGates.length;
|
|
173
|
+
withGates.splice(at, 0, { kind: "gate", ids: [gate.id], depth: null });
|
|
174
|
+
}
|
|
175
|
+
// Classify each multi-node step: parallel fan-out, or exclusive branch?
|
|
176
|
+
const steps = withGates.map((s, i) => {
|
|
177
|
+
const base = { index: i + 1, ...s, exclusive: false };
|
|
178
|
+
if (s.ids.length < 2)
|
|
179
|
+
return base;
|
|
180
|
+
const inbound = s.ids.map((id) => ast.edges.filter((e) => e.to === id && !e.isError));
|
|
181
|
+
// Every id reached ONLY by conditional edges, and every one of those edges
|
|
182
|
+
// from the same single source → this is one decision, not a fan-out.
|
|
183
|
+
const allConditional = inbound.every((es) => es.length > 0 && es.every((e) => !!e.condition));
|
|
184
|
+
const sources = new Set(inbound.flat().map((e) => e.from));
|
|
185
|
+
if (!allConditional || sources.size !== 1)
|
|
186
|
+
return base;
|
|
187
|
+
base.exclusive = true;
|
|
188
|
+
base.branchOn = s.ids.map((id) => ({
|
|
189
|
+
id,
|
|
190
|
+
condition: ast.edges.find((e) => e.to === id && e.condition)?.condition ?? "",
|
|
191
|
+
}));
|
|
192
|
+
return base;
|
|
193
|
+
});
|
|
194
|
+
const loops = ast.edges
|
|
195
|
+
.filter((e) => backKeys.has(`${e.from} ${e.to}`))
|
|
196
|
+
.map((e) => ({
|
|
197
|
+
from: e.from,
|
|
198
|
+
to: e.to,
|
|
199
|
+
condition: e.condition,
|
|
200
|
+
budget: e.maxIterations ?? null,
|
|
201
|
+
}));
|
|
202
|
+
return { steps, loops, unreachable, orchestrator };
|
|
203
|
+
}
|
|
204
|
+
/** All outbound edges from a node, in declaration order. */
|
|
205
|
+
export function edgesFrom(ast, id) {
|
|
206
|
+
return ast.edges.filter((e) => e.from === id);
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=order.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order.js","sourceRoot":"","sources":["../../src/resolve/order.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single answer to "where does an agent with no declared `phase` go?"
|
|
3
|
+
*
|
|
4
|
+
* THE BUG THIS FIXES
|
|
5
|
+
* ------------------
|
|
6
|
+
* Six consumers each picked their own sentinel, so the same topology ordered
|
|
7
|
+
* an unphased agent differently on every target:
|
|
8
|
+
*
|
|
9
|
+
* claude-code, claude-workflow → Number.MAX_SAFE_INTEGER → runs LAST
|
|
10
|
+
* gemini-cli → 999 → runs near-last
|
|
11
|
+
* openclaw, kiro, mermaid → 0 → runs FIRST
|
|
12
|
+
*
|
|
13
|
+
* An agent that runs first on three targets and last on three others is
|
|
14
|
+
* precisely the failure "write once, deploy to any platform" exists to prevent.
|
|
15
|
+
* Found by an audit topology run through `agentopology plan`.
|
|
16
|
+
*
|
|
17
|
+
* WHY TRAILING, NOT LEADING
|
|
18
|
+
* -------------------------
|
|
19
|
+
* `spec/grammar.md` §7 gives `agent.phase` the default "-- (none, unordered)",
|
|
20
|
+
* so there is no declared answer — a sort simply needs one. Trailing is the
|
|
21
|
+
* safer reading: an author who phases *some* agents is expressing "these come
|
|
22
|
+
* first, in this order", and the ones they did not mention should not leap
|
|
23
|
+
* ahead of them. A stable sort then preserves declaration order among the
|
|
24
|
+
* unphased, which is the only ordering information they carry.
|
|
25
|
+
*
|
|
26
|
+
* When NO agent declares a phase, every agent takes the same sentinel and the
|
|
27
|
+
* stable sort leaves declaration order intact — the same result all six
|
|
28
|
+
* sentinels used to give, which is why this went unnoticed.
|
|
29
|
+
*
|
|
30
|
+
* @module
|
|
31
|
+
*/
|
|
32
|
+
import type { AgentNode } from "../parser/ast.js";
|
|
33
|
+
/**
|
|
34
|
+
* Sort key for an agent with no declared `phase`.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately `Number.MAX_SAFE_INTEGER` rather than a smaller magic number:
|
|
37
|
+
* `999` collides with a legitimately declared `phase: 999`, and `0` collides
|
|
38
|
+
* with `phase: 0`. This one cannot be reached by a real declaration, since
|
|
39
|
+
* `phase` is parsed with `parseFloat` from a source literal.
|
|
40
|
+
*/
|
|
41
|
+
export declare const UNPHASED: number;
|
|
42
|
+
/**
|
|
43
|
+
* The ordering value for an agent — its declared `phase`, or {@link UNPHASED}.
|
|
44
|
+
*
|
|
45
|
+
* Every consumer that sorts by phase must use this. Sorting by
|
|
46
|
+
* `agent.phase ?? <anything else>` reintroduces the divergence.
|
|
47
|
+
*
|
|
48
|
+
* @param agent - Any agent node.
|
|
49
|
+
* @returns The numeric sort key.
|
|
50
|
+
*/
|
|
51
|
+
export declare function phaseOf(agent: Pick<AgentNode, "phase">): number;
|
|
52
|
+
/**
|
|
53
|
+
* Comparator for `Array.prototype.sort`, stable on ties so declaration order
|
|
54
|
+
* survives among agents sharing a phase.
|
|
55
|
+
*/
|
|
56
|
+
export declare function byPhase(a: Pick<AgentNode, "phase">, b: Pick<AgentNode, "phase">): number;
|
|
57
|
+
//# sourceMappingURL=phase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phase.d.ts","sourceRoot":"","sources":["../../src/resolve/phase.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,QAA0B,CAAC;AAEhD;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,MAAM,CAE/D;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,MAAM,CAExF"}
|