@telora/daemon 0.19.35 → 0.19.37

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.
Files changed (56) hide show
  1. package/build-info.json +4 -3
  2. package/dist/queries/code-metrics.d.ts +4 -0
  3. package/dist/queries/code-metrics.d.ts.map +1 -1
  4. package/dist/queries/code-metrics.js.map +1 -1
  5. package/dist/resolvers/compose/focus-injections-compose.d.ts +61 -0
  6. package/dist/resolvers/compose/focus-injections-compose.d.ts.map +1 -0
  7. package/dist/resolvers/compose/focus-injections-compose.js +72 -0
  8. package/dist/resolvers/compose/focus-injections-compose.js.map +1 -0
  9. package/dist/resolvers/compose/loop-context-compose.d.ts +51 -0
  10. package/dist/resolvers/compose/loop-context-compose.d.ts.map +1 -0
  11. package/dist/resolvers/compose/loop-context-compose.js +66 -0
  12. package/dist/resolvers/compose/loop-context-compose.js.map +1 -0
  13. package/dist/resolvers/compose/loop-frt-statement-compose.d.ts +24 -0
  14. package/dist/resolvers/compose/loop-frt-statement-compose.d.ts.map +1 -0
  15. package/dist/resolvers/compose/loop-frt-statement-compose.js +30 -0
  16. package/dist/resolvers/compose/loop-frt-statement-compose.js.map +1 -0
  17. package/dist/resolvers/compose/loop-injection-compose.d.ts +30 -0
  18. package/dist/resolvers/compose/loop-injection-compose.d.ts.map +1 -0
  19. package/dist/resolvers/compose/loop-injection-compose.js +40 -0
  20. package/dist/resolvers/compose/loop-injection-compose.js.map +1 -0
  21. package/dist/resolvers/compose/loop-persona-compose.d.ts +24 -0
  22. package/dist/resolvers/compose/loop-persona-compose.d.ts.map +1 -0
  23. package/dist/resolvers/compose/loop-persona-compose.js +24 -0
  24. package/dist/resolvers/compose/loop-persona-compose.js.map +1 -0
  25. package/dist/resolvers/compose/loop-upstream-udes-compose.d.ts +44 -0
  26. package/dist/resolvers/compose/loop-upstream-udes-compose.d.ts.map +1 -0
  27. package/dist/resolvers/compose/loop-upstream-udes-compose.js +75 -0
  28. package/dist/resolvers/compose/loop-upstream-udes-compose.js.map +1 -0
  29. package/dist/resolvers/focus-injections.d.ts +4 -0
  30. package/dist/resolvers/focus-injections.d.ts.map +1 -1
  31. package/dist/resolvers/focus-injections.js +6 -50
  32. package/dist/resolvers/focus-injections.js.map +1 -1
  33. package/dist/resolvers/loop-context.d.ts +12 -9
  34. package/dist/resolvers/loop-context.d.ts.map +1 -1
  35. package/dist/resolvers/loop-context.js +87 -102
  36. package/dist/resolvers/loop-context.js.map +1 -1
  37. package/dist/resolvers/loop-frt-statement.d.ts +3 -0
  38. package/dist/resolvers/loop-frt-statement.d.ts.map +1 -1
  39. package/dist/resolvers/loop-frt-statement.js +7 -8
  40. package/dist/resolvers/loop-frt-statement.js.map +1 -1
  41. package/dist/resolvers/loop-injection.d.ts +4 -0
  42. package/dist/resolvers/loop-injection.d.ts.map +1 -1
  43. package/dist/resolvers/loop-injection.js +8 -18
  44. package/dist/resolvers/loop-injection.js.map +1 -1
  45. package/dist/resolvers/loop-persona.d.ts.map +1 -1
  46. package/dist/resolvers/loop-persona.js +2 -3
  47. package/dist/resolvers/loop-persona.js.map +1 -1
  48. package/dist/resolvers/loop-upstream-udes.d.ts +5 -0
  49. package/dist/resolvers/loop-upstream-udes.d.ts.map +1 -1
  50. package/dist/resolvers/loop-upstream-udes.js +14 -51
  51. package/dist/resolvers/loop-upstream-udes.js.map +1 -1
  52. package/dist/sensor-sweep.d.ts +58 -6
  53. package/dist/sensor-sweep.d.ts.map +1 -1
  54. package/dist/sensor-sweep.js +95 -9
  55. package/dist/sensor-sweep.js.map +1 -1
  56. package/package.json +1 -1
@@ -0,0 +1,75 @@
1
+ /**
2
+ * GENERATED FILE -- DO NOT EDIT.
3
+ *
4
+ * Deno edge-function copy of src/lib/context-assembly/resolvers/loop-upstream-udes-compose.ts, produced by
5
+ * `npm run gen:shared-mirrors`. The canonical source is the ONLY hand-edited
6
+ * copy of this algorithm; edit it there and regenerate. The drift gate
7
+ * (`npm run lint:shared-mirrors`) fails CI when this file differs from what
8
+ * generation would produce, so the same algorithm runs in both runtimes.
9
+ *
10
+ * @module resolvers/compose/loop-upstream-udes-compose
11
+ */
12
+ /** The assembly source-reference this module composes. */
13
+ export const REF = 'loop.upstream_udes';
14
+ /** Maximum BFS depth for the upstream UDE walk. */
15
+ export const MAX_UPSTREAM_DEPTH = 3;
16
+ /**
17
+ * For each delivery's injection, seed from the nodes it targets (via 'targets'
18
+ * edges) and BFS upstream along causal edges ('sufficient' and 'and') up to
19
+ * MAX_UPSTREAM_DEPTH hops, returning a markdown list of the upstream UDE/entity
20
+ * nodes the injection indirectly addresses. Cycle-safe; seeds excluded from
21
+ * output. Returns '' when nothing upstream is found.
22
+ */
23
+ export function composeLoopUpstreamUdes(entries) {
24
+ const sections = [];
25
+ for (const { deliveryName, injectionId, nodes, edges } of entries) {
26
+ const nodeById = new Map();
27
+ for (const n of nodes)
28
+ nodeById.set(n.id, n);
29
+ // Seed: the nodes the injection targets directly.
30
+ const seeds = edges
31
+ .filter((e) => e.edgeType === 'targets' && e.fromNodeId === injectionId)
32
+ .map((e) => e.toNodeId);
33
+ if (seeds.length === 0)
34
+ continue;
35
+ // BFS upstream along causal edges: walk edges where to_node_id is in the
36
+ // current frontier; the from_node_id of those edges is the upstream node.
37
+ // Bounded by MAX_UPSTREAM_DEPTH and cycle-safe via visited set. Seeds
38
+ // themselves are excluded from output (we want their upstream, not the seeds).
39
+ const visited = new Set(seeds);
40
+ const collected = [];
41
+ let frontier = new Set(seeds);
42
+ for (let depth = 0; depth < MAX_UPSTREAM_DEPTH; depth++) {
43
+ if (frontier.size === 0)
44
+ break;
45
+ const next = new Set();
46
+ for (const edge of edges) {
47
+ if (edge.edgeType !== 'sufficient' && edge.edgeType !== 'and')
48
+ continue;
49
+ if (!frontier.has(edge.toNodeId))
50
+ continue;
51
+ const upId = edge.fromNodeId;
52
+ if (visited.has(upId))
53
+ continue;
54
+ visited.add(upId);
55
+ next.add(upId);
56
+ const upNode = nodeById.get(upId);
57
+ if (upNode)
58
+ collected.push(upNode);
59
+ }
60
+ frontier = next;
61
+ }
62
+ if (collected.length === 0)
63
+ continue;
64
+ collected.sort((a, b) => a.seq - b.seq);
65
+ const lines = collected.map((n) => {
66
+ const typeTag = n.nodeType !== 'entity' ? ` [${n.nodeType}]` : '';
67
+ return `- #${n.seq} ${n.statement}${typeTag}`;
68
+ });
69
+ sections.push(`### ${deliveryName}\n\n${lines.join('\n')}`);
70
+ }
71
+ if (sections.length === 0)
72
+ return '';
73
+ return `## Upstream UDEs (per delivery, max depth ${MAX_UPSTREAM_DEPTH})\n\n${sections.join('\n\n')}`;
74
+ }
75
+ //# sourceMappingURL=loop-upstream-udes-compose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop-upstream-udes-compose.js","sourceRoot":"","sources":["../../../src/resolvers/compose/loop-upstream-udes-compose.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,10 @@
8
8
  * deliveries; shared-target collisions to avoid two workers changing the same
9
9
  * node; and the root-cause clusters. Per-delivery full differentials are pulled
10
10
  * on demand via telora_reality_tree_injection_context(seq).
11
+ *
12
+ * THIN FETCH HALF: this resolver only fetches (callApi) the coordination-map
13
+ * data; the markdown is composed by the shared, drift-mirrored
14
+ * `composeFocusInjections` so the inspector's preview renders identically.
11
15
  */
12
16
  /** Register this module's resolver(s). Called from initResolvers() -- not an import side effect. */
13
17
  export declare function registerFocusInjectionsResolver(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"focus-injections.d.ts","sourceRoot":"","sources":["../../src/resolvers/focus-injections.ts"],"names":[],"mappings":"AA6CA;;;;;;;;;;GAUG;AACH,oGAAoG;AACpG,wBAAgB,+BAA+B,IAAI,IAAI,CAoEtD"}
1
+ {"version":3,"file":"focus-injections.d.ts","sourceRoot":"","sources":["../../src/resolvers/focus-injections.ts"],"names":[],"mappings":"AA+CA;;;;;;;;;;;;;;GAcG;AACH,oGAAoG;AACpG,wBAAgB,+BAA+B,IAAI,IAAI,CAYtD"}
@@ -1,5 +1,6 @@
1
1
  import { registerSourceResolver } from '../assembly-engine.js';
2
2
  import { callApi } from '../queries/shared.js';
3
+ import { composeFocusInjections } from './compose/focus-injections-compose.js';
3
4
  /**
4
5
  * focus.injections
5
6
  *
@@ -10,6 +11,10 @@ import { callApi } from '../queries/shared.js';
10
11
  * deliveries; shared-target collisions to avoid two workers changing the same
11
12
  * node; and the root-cause clusters. Per-delivery full differentials are pulled
12
13
  * on demand via telora_reality_tree_injection_context(seq).
14
+ *
15
+ * THIN FETCH HALF: this resolver only fetches (callApi) the coordination-map
16
+ * data; the markdown is composed by the shared, drift-mirrored
17
+ * `composeFocusInjections` so the inspector's preview renders identically.
13
18
  */
14
19
  /** Register this module's resolver(s). Called from initResolvers() -- not an import side effect. */
15
20
  export function registerFocusInjectionsResolver() {
@@ -18,56 +23,7 @@ export function registerFocusInjectionsResolver() {
18
23
  const result = await callApi('reality_tree_focus_injections', {
19
24
  focusId: context.focusId,
20
25
  });
21
- if (!result.items || result.items.length === 0)
22
- return '';
23
- const structure = result.structure ?? {
24
- dependencyOrder: [],
25
- sharedTargetCollisions: [],
26
- rootCauseClusters: [],
27
- };
28
- const lines = [
29
- '## Focus Coordination Map',
30
- '',
31
- 'The focus reality-tree structure -- your planning surface. Each injection moves a',
32
- 'Now (a current UDE) to a Target (its future desired effect). Use the dependency',
33
- 'order to sequence deliveries, the collisions to avoid two workers changing the same',
34
- 'node, and the clusters to see which root causes drive which problems. Pull a',
35
- "delivery's full differential on demand with telora_reality_tree_injection_context",
36
- '(by injection seq).',
37
- '',
38
- '### Injections (Now -> Target)',
39
- '',
40
- ];
41
- for (const item of result.items) {
42
- const inj = item.injection;
43
- const statusTag = inj.injectionStatus ? ` [${inj.injectionStatus}]` : '';
44
- lines.push(`- #${inj.seq} ${inj.statement}${statusTag}`);
45
- for (const nt of item.nowTargets ?? []) {
46
- const target = nt.target && nt.target.trim().length > 0 ? nt.target : '(no FRT overlay yet)';
47
- lines.push(` - Now: #${nt.seq} ${nt.now} -> Target: ${target}`);
48
- }
49
- }
50
- if (structure.dependencyOrder.length > 0) {
51
- lines.push('', '### Dependency Order (sequence deliveries this way)', '');
52
- structure.dependencyOrder.forEach((d, i) => {
53
- lines.push(`${i + 1}. #${d.seq} ${d.statement}`);
54
- });
55
- }
56
- if (structure.sharedTargetCollisions.length > 0) {
57
- lines.push('', '### Shared-Target Collisions (avoid two workers changing the same node)', '');
58
- for (const c of structure.sharedTargetCollisions) {
59
- const injList = c.injectionSeqs.map((s) => `#${s}`).join(', ');
60
- lines.push(`- #${c.targetSeq} "${c.targetStatement}" <- injections ${injList}`);
61
- }
62
- }
63
- if (structure.rootCauseClusters.length > 0) {
64
- lines.push('', '### Root-Cause Clusters (which roots drive which UDEs)', '');
65
- for (const cl of structure.rootCauseClusters) {
66
- const udeList = cl.udeSeqs.map((s) => `#${s}`).join(', ');
67
- lines.push(`- #${cl.rootSeq} ${cl.rootStatement} (${cl.rootNodeType}) drives UDEs: ${udeList}`);
68
- }
69
- }
70
- return lines.join('\n');
26
+ return composeFocusInjections(result);
71
27
  }
72
28
  catch (err) {
73
29
  console.warn(`[assembly-engine] focus.injections: failed: ${err.message}`);
@@ -1 +1 @@
1
- {"version":3,"file":"focus-injections.js","sourceRoot":"","sources":["../../src/resolvers/focus-injections.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"focus-injections.js","sourceRoot":"","sources":["../../src/resolvers/focus-injections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AA4C/E;;;;;;;;;;;;;;GAcG;AACH,oGAAoG;AACpG,MAAM,UAAU,+BAA+B;IAC7C,sBAAsB,CAAC,kBAAkB,EAAE,KAAK,EAAE,OAAwB,EAAmB,EAAE;QAC7F,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAA2B,+BAA+B,EAAE;gBACtF,OAAO,EAAE,OAAO,CAAC,OAAO;aACzB,CAAC,CAAC;YACH,OAAO,sBAAsB,CAAC,MAA8B,CAAC,CAAC;QAChE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,+CAAgD,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YACtF,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -1,17 +1,15 @@
1
+ import { reorderDocsByRank } from './compose/loop-context-compose.js';
1
2
  /**
2
- * Reorder documents by a semantic-rank id ordering. Documents whose id appears
3
- * in `rankedIds` are ordered by that rank; documents absent from the ranking
4
- * (no text/embedding match) keep their original relative order AFTER the ranked
5
- * ones (sort_order is the floor). Pure -- exported for tests.
3
+ * Back-compat re-export. `reorderDocsByRank` historically lived in this module
4
+ * (and is imported by loop-context-semantic.test.ts); its canonical home is now
5
+ * the shared compose mirror, but the re-export keeps existing importers working.
6
6
  */
7
- export declare function reorderDocsByRank<T extends {
8
- id: string;
9
- }>(docs: T[], rankedIds: string[]): T[];
7
+ export { reorderDocsByRank };
10
8
  /**
11
9
  * loop.context
12
10
  *
13
- * Composite resolver that fetches persona + force-ranked documents + Q&A
14
- * for one or more loop layers. Provides complete loop context for agent
11
+ * Composite resolver that fetches persona + force-/semantic-ranked documents +
12
+ * Q&A for one or more loop layers. Provides complete loop context for agent
15
13
  * prompt injection.
16
14
  *
17
15
  * Optional param: layer
@@ -22,6 +20,11 @@ export declare function reorderDocsByRank<T extends {
22
20
  *
23
21
  * Loop data at focus and delivery layers is scoped to the product ID
24
22
  * (layer-based architecture, not per-record).
23
+ *
24
+ * THIN FETCH HALF: the per-layer reads are batched up front and the
25
+ * per-question loop_answer_get N+1 is collapsed into a single loop_answer_list
26
+ * call; rendering is delegated to the shared, drift-mirrored composeLoopContext
27
+ * so the inspector's preview matches a live spawn.
25
28
  */
26
29
  /** Register this module's resolver(s). Called from initResolvers() -- not an import side effect. */
27
30
  export declare function registerLoopContextResolver(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"loop-context.d.ts","sourceRoot":"","sources":["../../src/resolvers/loop-context.ts"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EACxD,IAAI,EAAE,CAAC,EAAE,EACT,SAAS,EAAE,MAAM,EAAE,GAClB,CAAC,EAAE,CAQL;AA0GD;;;;;;;;;;;;;;;GAeG;AACH,oGAAoG;AACpG,wBAAgB,2BAA2B,IAAI,IAAI,CA4BlD"}
1
+ {"version":3,"file":"loop-context.d.ts","sourceRoot":"","sources":["../../src/resolvers/loop-context.ts"],"names":[],"mappings":"AAQA,OAAO,EAAsB,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAG1F;;;;GAIG;AACH,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAuF7B;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,oGAAoG;AACpG,wBAAgB,2BAA2B,IAAI,IAAI,CAsDlD"}
@@ -1,119 +1,72 @@
1
1
  import { registerSourceResolver } from '../assembly-engine.js';
2
2
  import { callApi } from '../queries/shared.js';
3
+ import { composeLoopContext, reorderDocsByRank } from './compose/loop-context-compose.js';
3
4
  /**
4
- * Reorder documents by a semantic-rank id ordering. Documents whose id appears
5
- * in `rankedIds` are ordered by that rank; documents absent from the ranking
6
- * (no text/embedding match) keep their original relative order AFTER the ranked
7
- * ones (sort_order is the floor). Pure -- exported for tests.
5
+ * Back-compat re-export. `reorderDocsByRank` historically lived in this module
6
+ * (and is imported by loop-context-semantic.test.ts); its canonical home is now
7
+ * the shared compose mirror, but the re-export keeps existing importers working.
8
8
  */
9
- export function reorderDocsByRank(docs, rankedIds) {
10
- if (rankedIds.length === 0)
11
- return docs;
12
- const rankOf = new Map(rankedIds.map((id, i) => [id, i]));
13
- return [...docs].sort((a, b) => (rankOf.get(a.id) ?? Number.MAX_SAFE_INTEGER) -
14
- (rankOf.get(b.id) ?? Number.MAX_SAFE_INTEGER));
15
- }
9
+ export { reorderDocsByRank };
16
10
  /**
17
- * Fetch loop data (persona + documents + Q&A) for a single layer.
18
- * Returns formatted markdown sections, or empty string if no data exists.
11
+ * THIN FETCH HALF for one layer: batches persona + documents (+ optional
12
+ * semantic search ranking) + questions. Answers are NOT fetched here -- they
13
+ * are batched once across all layers by the caller. Every read degrades to
14
+ * empty on failure (missing persona/docs/questions are not errors).
19
15
  */
20
- async function fetchLoopLayerContext(entityType, entityId, layerLabel, query) {
21
- const parts = [];
22
- // Persona
23
- try {
24
- const personaResult = await callApi('loop_persona_get', {
25
- entityType,
26
- entityId,
27
- });
28
- if (personaResult.persona?.content) {
29
- parts.push(`**Persona:** ${personaResult.persona.content}`);
30
- }
31
- }
32
- catch {
33
- // No persona -- not an error
34
- }
35
- // Documents. By default these are force-ranked by sort_order. When a relevance
36
- // query is supplied, we reorder the SAME full documents by semantic similarity
37
- // (loop_document_search blends text rank + cosine over the embedding). The
38
- // sort_order order is preserved as the floor: if search fails or returns
39
- // nothing, or for documents the search did not rank, we keep sort_order.
40
- try {
41
- const docsResult = await callApi('loop_document_list', {
16
+ async function fetchLayerRaw(layer, query) {
17
+ const { entityType, entityId, label } = layer;
18
+ let persona = null;
19
+ let docs = [];
20
+ let rankedDocIds = [];
21
+ let questions = [];
22
+ const [personaSettled, docsSettled, questionsSettled] = await Promise.allSettled([
23
+ callApi('loop_persona_get', { entityType, entityId }),
24
+ callApi('loop_document_list', {
42
25
  entityType,
43
26
  entityId,
44
27
  detail: 'full',
45
28
  limit: 50,
46
- });
47
- let docs = docsResult.items ?? [];
48
- const trimmedQuery = query?.trim();
49
- if (trimmedQuery && docs.length > 1) {
50
- try {
51
- const search = await callApi('loop_document_search', {
52
- entityType,
53
- entityId,
54
- query: trimmedQuery,
55
- limit: docs.length,
56
- });
57
- docs = reorderDocsByRank(docs, search.items.map((it) => it.id));
58
- }
59
- catch {
60
- // Search unavailable -- keep sort_order ordering.
61
- }
62
- }
63
- if (docs.length > 0) {
64
- const docLines = docs.map((doc, i) => `#### ${i + 1}. ${doc.title}\n\n${doc.content}`);
65
- parts.push(`### Documents\n\n${docLines.join('\n\n')}`);
66
- }
67
- }
68
- catch {
69
- // No documents -- not an error
70
- }
71
- // Questions + answers
72
- try {
73
- const qResult = await callApi('loop_question_list', {
29
+ }),
30
+ callApi('loop_question_list', {
74
31
  entityType,
75
32
  entityId,
76
33
  detail: 'full',
77
- });
78
- if (qResult.items?.length > 0) {
79
- const fixed = qResult.items.filter((q) => q.questionType === 'fixed');
80
- const generated = qResult.items.filter((q) => q.questionType === 'generated');
81
- const formatQ = async (q) => {
82
- try {
83
- const aResult = await callApi('loop_answer_get', { questionId: q.id });
84
- const answer = aResult.answer?.content || '_No answer yet._';
85
- return `**Q:** ${q.text}\n**A:** ${answer}`;
86
- }
87
- catch {
88
- return `**Q:** ${q.text}\n**A:** _No answer yet._`;
89
- }
90
- };
91
- const qParts = [];
92
- if (fixed.length > 0) {
93
- const fixedLines = await Promise.all(fixed.map(formatQ));
94
- qParts.push(`#### Fixed Questions\n\n${fixedLines.join('\n\n')}`);
95
- }
96
- if (generated.length > 0) {
97
- const genLines = await Promise.all(generated.map(formatQ));
98
- qParts.push(`#### Agent-Generated Questions\n\n${genLines.join('\n\n')}`);
99
- }
100
- if (qParts.length > 0) {
101
- parts.push(`### Questions & Answers\n\n${qParts.join('\n\n')}`);
102
- }
103
- }
34
+ }),
35
+ ]);
36
+ if (personaSettled.status === 'fulfilled') {
37
+ persona = personaSettled.value.persona?.content ?? null;
104
38
  }
105
- catch {
106
- // No questions -- not an error
39
+ if (docsSettled.status === 'fulfilled') {
40
+ docs = docsSettled.value.items ?? [];
41
+ }
42
+ if (questionsSettled.status === 'fulfilled') {
43
+ questions = questionsSettled.value.items ?? [];
44
+ }
45
+ // Semantic ranking depends on the fetched docs, so it runs after loop_document_list.
46
+ // The rank is applied in the (pure) compose module via reorderDocsByRank; here we
47
+ // only fetch the id ordering. sort_order remains the floor when search is absent.
48
+ const trimmedQuery = query?.trim();
49
+ if (trimmedQuery && docs.length > 1) {
50
+ try {
51
+ const search = await callApi('loop_document_search', {
52
+ entityType,
53
+ entityId,
54
+ query: trimmedQuery,
55
+ limit: docs.length,
56
+ });
57
+ rankedDocIds = (search.items ?? []).map((it) => it.id);
58
+ }
59
+ catch {
60
+ // Search unavailable -- keep sort_order ordering.
61
+ }
107
62
  }
108
- if (parts.length === 0)
109
- return '';
110
- return `## ${layerLabel} Loop\n\n${parts.join('\n\n')}`;
63
+ return { label, persona, docs, rankedDocIds, questions };
111
64
  }
112
65
  /**
113
66
  * loop.context
114
67
  *
115
- * Composite resolver that fetches persona + force-ranked documents + Q&A
116
- * for one or more loop layers. Provides complete loop context for agent
68
+ * Composite resolver that fetches persona + force-/semantic-ranked documents +
69
+ * Q&A for one or more loop layers. Provides complete loop context for agent
117
70
  * prompt injection.
118
71
  *
119
72
  * Optional param: layer
@@ -124,6 +77,11 @@ async function fetchLoopLayerContext(entityType, entityId, layerLabel, query) {
124
77
  *
125
78
  * Loop data at focus and delivery layers is scoped to the product ID
126
79
  * (layer-based architecture, not per-record).
80
+ *
81
+ * THIN FETCH HALF: the per-layer reads are batched up front and the
82
+ * per-question loop_answer_get N+1 is collapsed into a single loop_answer_list
83
+ * call; rendering is delegated to the shared, drift-mirrored composeLoopContext
84
+ * so the inspector's preview matches a live spawn.
127
85
  */
128
86
  /** Register this module's resolver(s). Called from initResolvers() -- not an import side effect. */
129
87
  export function registerLoopContextResolver() {
@@ -142,11 +100,38 @@ export function registerLoopContextResolver() {
142
100
  if (!layerFilter || layerFilter === 'delivery') {
143
101
  layers.push({ entityType: 'delivery', entityId: context.productId, label: 'Delivery' });
144
102
  }
145
- const sections = await Promise.all(layers.map(l => fetchLoopLayerContext(l.entityType, l.entityId, l.label, query)));
146
- const nonEmpty = sections.filter(s => s.trim());
147
- if (nonEmpty.length === 0)
148
- return '';
149
- return nonEmpty.join('\n\n');
103
+ // Wave 1: fetch persona + documents (+ ranking) + questions for every layer.
104
+ const raw = await Promise.all(layers.map((l) => fetchLayerRaw(l, query)));
105
+ // Wave 2: collapse the answer N+1 -- one batched read for every question id
106
+ // across all layers.
107
+ const allQuestionIds = raw.flatMap((r) => r.questions.map((q) => q.id));
108
+ const answersByQuestion = new Map();
109
+ if (allQuestionIds.length > 0) {
110
+ try {
111
+ const resp = await callApi('loop_answer_list', {
112
+ questionIds: allQuestionIds,
113
+ });
114
+ for (const item of resp.items ?? []) {
115
+ answersByQuestion.set(item.questionId, item.content ?? null);
116
+ }
117
+ }
118
+ catch {
119
+ // Answers unavailable -- each question renders "_No answer yet._".
120
+ }
121
+ }
122
+ const toQA = (q) => ({
123
+ question: q.text,
124
+ answer: answersByQuestion.get(q.id) ?? null,
125
+ });
126
+ const composeLayers = raw.map((r) => ({
127
+ label: r.label,
128
+ persona: r.persona,
129
+ docs: r.docs.map((d) => ({ id: d.id, title: d.title, content: d.content })),
130
+ rankedDocIds: r.rankedDocIds,
131
+ fixedQuestions: r.questions.filter((q) => q.questionType === 'fixed').map(toQA),
132
+ generatedQuestions: r.questions.filter((q) => q.questionType === 'generated').map(toQA),
133
+ }));
134
+ return composeLoopContext(composeLayers);
150
135
  });
151
136
  }
152
137
  //# sourceMappingURL=loop-context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"loop-context.js","sourceRoot":"","sources":["../../src/resolvers/loop-context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"loop-context.js","sourceRoot":"","sources":["../../src/resolvers/loop-context.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,9 @@
6
6
  * statements are joined with blank lines so the agent sees each
7
7
  * delivery's target state. Empty string when no delivery has an
8
8
  * injection link or no injection has an FRT statement set.
9
+ *
10
+ * THIN FETCH HALF: resolves each delivery's injection bundle, then delegates
11
+ * rendering to the shared, drift-mirrored `composeLoopFrtStatement`.
9
12
  */
10
13
  /** Register this module's resolver(s). Called from initResolvers() -- not an import side effect. */
11
14
  export declare function registerLoopFrtStatementResolver(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"loop-frt-statement.d.ts","sourceRoot":"","sources":["../../src/resolvers/loop-frt-statement.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AACH,oGAAoG;AACpG,wBAAgB,gCAAgC,IAAI,IAAI,CAwBvD"}
1
+ {"version":3,"file":"loop-frt-statement.d.ts","sourceRoot":"","sources":["../../src/resolvers/loop-frt-statement.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;GAWG;AACH,oGAAoG;AACpG,wBAAgB,gCAAgC,IAAI,IAAI,CAsBvD"}
@@ -1,5 +1,6 @@
1
1
  import { registerSourceResolver } from '../assembly-engine.js';
2
2
  import { getDeliveryInjectionPairs, fetchInjectionBundles } from './shared/loop.js';
3
+ import { composeLoopFrtStatement } from './compose/loop-frt-statement-compose.js';
3
4
  /**
4
5
  * loop.frt_statement
5
6
  *
@@ -8,6 +9,9 @@ import { getDeliveryInjectionPairs, fetchInjectionBundles } from './shared/loop.
8
9
  * statements are joined with blank lines so the agent sees each
9
10
  * delivery's target state. Empty string when no delivery has an
10
11
  * injection link or no injection has an FRT statement set.
12
+ *
13
+ * THIN FETCH HALF: resolves each delivery's injection bundle, then delegates
14
+ * rendering to the shared, drift-mirrored `composeLoopFrtStatement`.
11
15
  */
12
16
  /** Register this module's resolver(s). Called from initResolvers() -- not an import side effect. */
13
17
  export function registerLoopFrtStatementResolver() {
@@ -19,19 +23,14 @@ export function registerLoopFrtStatementResolver() {
19
23
  const bundles = await fetchInjectionBundles(pairs.map((p) => p.injectionId));
20
24
  if (bundles.size === 0)
21
25
  return '';
22
- const lines = [];
26
+ const entries = [];
23
27
  for (const { injectionId } of pairs) {
24
28
  const bundle = bundles.get(injectionId);
25
29
  if (!bundle)
26
30
  continue;
27
- const frt = bundle.injection.frtStatement;
28
- if (typeof frt !== 'string' || frt.trim().length === 0)
29
- continue;
30
- lines.push(frt.trim());
31
+ entries.push({ frtStatement: bundle.injection.frtStatement });
31
32
  }
32
- if (lines.length === 0)
33
- return '';
34
- return lines.join('\n\n');
33
+ return composeLoopFrtStatement(entries);
35
34
  }
36
35
  catch (err) {
37
36
  console.warn(`[assembly] loop.frt_statement failed: ${err.message}`);
@@ -1 +1 @@
1
- {"version":3,"file":"loop-frt-statement.js","sourceRoot":"","sources":["../../src/resolvers/loop-frt-statement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAEpF;;;;;;;;GAQG;AACH,oGAAoG;AACpG,MAAM,UAAU,gCAAgC;IAC9C,sBAAsB,CAAC,oBAAoB,EAAE,KAAK,EAAE,OAAwB,EAAmB,EAAE;QAC/F,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,yBAAyB,CAAC,OAAO,CAAC,CAAC;YACvD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YAElC,MAAM,OAAO,GAAG,MAAM,qBAAqB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;YAC7E,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YAElC,MAAM,KAAK,GAAa,EAAE,CAAC;YAC3B,KAAK,MAAM,EAAE,WAAW,EAAE,IAAI,KAAK,EAAE,CAAC;gBACpC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;gBACxC,IAAI,CAAC,MAAM;oBAAE,SAAS;gBACtB,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC;gBAC1C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;oBAAE,SAAS;gBACjE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YACzB,CAAC;YACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YAClC,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,yCAA0C,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YAChF,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"loop-frt-statement.js","sourceRoot":"","sources":["../../src/resolvers/loop-frt-statement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACpF,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAGlF;;;;;;;;;;;GAWG;AACH,oGAAoG;AACpG,MAAM,UAAU,gCAAgC;IAC9C,sBAAsB,CAAC,oBAAoB,EAAE,KAAK,EAAE,OAAwB,EAAmB,EAAE;QAC/F,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,yBAAyB,CAAC,OAAO,CAAC,CAAC;YACvD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YAElC,MAAM,OAAO,GAAG,MAAM,qBAAqB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;YAC7E,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YAElC,MAAM,OAAO,GAA4B,EAAE,CAAC;YAC5C,KAAK,MAAM,EAAE,WAAW,EAAE,IAAI,KAAK,EAAE,CAAC;gBACpC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;gBACxC,IAAI,CAAC,MAAM;oBAAE,SAAS;gBACtB,OAAO,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,SAAS,CAAC,YAAY,EAAE,CAAC,CAAC;YAChE,CAAC;YAED,OAAO,uBAAuB,CAAC,OAAO,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,yCAA0C,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YAChF,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -5,6 +5,10 @@
5
5
  * the injection node: statement, lifecycle status, what obstacle it
6
6
  * dissolves. Plain markdown; no upstream/downstream walk (that lives in
7
7
  * loop.upstream_udes / loop.expected_effects).
8
+ *
9
+ * THIN FETCH HALF: resolves each delivery's injection bundle, then delegates
10
+ * rendering to the shared, drift-mirrored `composeLoopInjection` so the
11
+ * inspector's preview matches a live spawn.
8
12
  */
9
13
  /** Register this module's resolver(s). Called from initResolvers() -- not an import side effect. */
10
14
  export declare function registerLoopInjectionResolver(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"loop-injection.d.ts","sourceRoot":"","sources":["../../src/resolvers/loop-injection.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,oGAAoG;AACpG,wBAAgB,6BAA6B,IAAI,IAAI,CAoCpD"}
1
+ {"version":3,"file":"loop-injection.d.ts","sourceRoot":"","sources":["../../src/resolvers/loop-injection.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;GAWG;AACH,oGAAoG;AACpG,wBAAgB,6BAA6B,IAAI,IAAI,CAsBpD"}
@@ -1,5 +1,6 @@
1
1
  import { registerSourceResolver } from '../assembly-engine.js';
2
2
  import { getDeliveryInjectionPairs, fetchInjectionBundles } from './shared/loop.js';
3
+ import { composeLoopInjection } from './compose/loop-injection-compose.js';
3
4
  /**
4
5
  * loop.injection
5
6
  *
@@ -7,6 +8,10 @@ import { getDeliveryInjectionPairs, fetchInjectionBundles } from './shared/loop.
7
8
  * the injection node: statement, lifecycle status, what obstacle it
8
9
  * dissolves. Plain markdown; no upstream/downstream walk (that lives in
9
10
  * loop.upstream_udes / loop.expected_effects).
11
+ *
12
+ * THIN FETCH HALF: resolves each delivery's injection bundle, then delegates
13
+ * rendering to the shared, drift-mirrored `composeLoopInjection` so the
14
+ * inspector's preview matches a live spawn.
10
15
  */
11
16
  /** Register this module's resolver(s). Called from initResolvers() -- not an import side effect. */
12
17
  export function registerLoopInjectionResolver() {
@@ -18,29 +23,14 @@ export function registerLoopInjectionResolver() {
18
23
  const bundles = await fetchInjectionBundles(pairs.map((p) => p.injectionId));
19
24
  if (bundles.size === 0)
20
25
  return '';
21
- const sections = [];
26
+ const entries = [];
22
27
  for (const { deliveryName, injectionId } of pairs) {
23
28
  const bundle = bundles.get(injectionId);
24
29
  if (!bundle)
25
30
  continue;
26
- const inj = bundle.injection;
27
- const statusTag = inj.injectionStatus ? ` [${inj.injectionStatus}]` : '';
28
- const dissolvesLine = inj.dissolvesObstacle
29
- ? `- **Dissolves obstacle:** ${inj.dissolvesObstacle}`
30
- : null;
31
- const lines = [
32
- `### ${deliveryName}`,
33
- '',
34
- `- **Injection:** ${inj.statement}${statusTag}`,
35
- `- **Injection status:** ${inj.injectionStatus ?? '_not set_'}`,
36
- ];
37
- if (dissolvesLine)
38
- lines.push(dissolvesLine);
39
- sections.push(lines.join('\n'));
31
+ entries.push({ deliveryName, injection: bundle.injection });
40
32
  }
41
- if (sections.length === 0)
42
- return '';
43
- return `## Loop Injection (per delivery)\n\n${sections.join('\n\n')}`;
33
+ return composeLoopInjection(entries);
44
34
  }
45
35
  catch (err) {
46
36
  console.warn(`[assembly] loop.injection failed: ${err.message}`);