@telora/daemon 0.16.33 → 0.16.42
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/build-info.json +2 -2
- package/dist/assembly-resolvers.d.ts +13 -65
- package/dist/assembly-resolvers.d.ts.map +1 -1
- package/dist/assembly-resolvers.js +15 -1915
- package/dist/assembly-resolvers.js.map +1 -1
- package/dist/audit-phase.d.ts +2 -2
- package/dist/audit-phase.d.ts.map +1 -1
- package/dist/audit-phase.js +1 -1
- package/dist/audit-phase.js.map +1 -1
- package/dist/{completion-handler.d.ts → completion/agent-completion.d.ts} +3 -3
- package/dist/completion/agent-completion.d.ts.map +1 -0
- package/dist/{completion-handler.js → completion/agent-completion.js} +7 -7
- package/dist/completion/agent-completion.js.map +1 -0
- package/dist/{completion-detector.d.ts → completion/detector.d.ts} +1 -1
- package/dist/completion/detector.d.ts.map +1 -0
- package/dist/{completion-detector.js → completion/detector.js} +1 -1
- package/dist/completion/detector.js.map +1 -0
- package/dist/{focus-completion-event.d.ts → completion/event.d.ts} +21 -3
- package/dist/completion/event.d.ts.map +1 -0
- package/dist/{focus-completion-event.js → completion/event.js} +55 -9
- package/dist/completion/event.js.map +1 -0
- package/dist/completion/index.d.ts +24 -0
- package/dist/completion/index.d.ts.map +1 -0
- package/dist/completion/index.js +25 -0
- package/dist/completion/index.js.map +1 -0
- package/dist/{focus-completion.d.ts → completion/team-completion.d.ts} +11 -11
- package/dist/completion/team-completion.d.ts.map +1 -0
- package/dist/{focus-completion.js → completion/team-completion.js} +37 -26
- package/dist/completion/team-completion.js.map +1 -0
- package/dist/{focus-team-lifecycle.d.ts → completion/team-lifecycle.d.ts} +2 -2
- package/dist/completion/team-lifecycle.d.ts.map +1 -0
- package/dist/{focus-team-lifecycle.js → completion/team-lifecycle.js} +12 -12
- package/dist/completion/team-lifecycle.js.map +1 -0
- package/dist/directive-executor.d.ts +30 -0
- package/dist/directive-executor.d.ts.map +1 -1
- package/dist/directive-executor.js +46 -0
- package/dist/directive-executor.js.map +1 -1
- package/dist/focus-engine.d.ts.map +1 -1
- package/dist/focus-engine.js +1 -2
- package/dist/focus-engine.js.map +1 -1
- package/dist/focus-executor.d.ts +4 -4
- package/dist/focus-executor.d.ts.map +1 -1
- package/dist/focus-executor.js +5 -5
- package/dist/focus-executor.js.map +1 -1
- package/dist/focus-merge.js +1 -1
- package/dist/focus-merge.js.map +1 -1
- package/dist/focus-prompt-builder.d.ts.map +1 -1
- package/dist/focus-prompt-builder.js +5 -39
- package/dist/focus-prompt-builder.js.map +1 -1
- package/dist/listener.js +1 -1
- package/dist/listener.js.map +1 -1
- package/dist/output-monitor.d.ts +1 -1
- package/dist/output-monitor.d.ts.map +1 -1
- package/dist/output-monitor.js +1 -1
- package/dist/output-monitor.js.map +1 -1
- package/dist/prompt-listing.d.ts +26 -0
- package/dist/prompt-listing.d.ts.map +1 -0
- package/dist/prompt-listing.js +55 -0
- package/dist/prompt-listing.js.map +1 -0
- package/dist/queries/index.d.ts +13 -0
- package/dist/queries/index.d.ts.map +1 -1
- package/dist/queries/index.js +9 -0
- package/dist/queries/index.js.map +1 -1
- package/dist/resolvers/agent-escalations.d.ts +2 -0
- package/dist/resolvers/agent-escalations.d.ts.map +1 -0
- package/dist/resolvers/agent-escalations.js +33 -0
- package/dist/resolvers/agent-escalations.js.map +1 -0
- package/dist/resolvers/agent-session-summaries.d.ts +2 -0
- package/dist/resolvers/agent-session-summaries.d.ts.map +1 -0
- package/dist/resolvers/agent-session-summaries.js +28 -0
- package/dist/resolvers/agent-session-summaries.js.map +1 -0
- package/dist/resolvers/delivery-acceptance-criteria.d.ts +2 -0
- package/dist/resolvers/delivery-acceptance-criteria.d.ts.map +1 -0
- package/dist/resolvers/delivery-acceptance-criteria.js +19 -0
- package/dist/resolvers/delivery-acceptance-criteria.js.map +1 -0
- package/dist/resolvers/delivery-description.d.ts +2 -0
- package/dist/resolvers/delivery-description.d.ts.map +1 -0
- package/dist/resolvers/delivery-description.js +19 -0
- package/dist/resolvers/delivery-description.js.map +1 -0
- package/dist/resolvers/delivery-issues.d.ts +2 -0
- package/dist/resolvers/delivery-issues.d.ts.map +1 -0
- package/dist/resolvers/delivery-issues.js +58 -0
- package/dist/resolvers/delivery-issues.js.map +1 -0
- package/dist/resolvers/delivery-listing.d.ts +2 -0
- package/dist/resolvers/delivery-listing.d.ts.map +1 -0
- package/dist/resolvers/delivery-listing.js +54 -0
- package/dist/resolvers/delivery-listing.js.map +1 -0
- package/dist/resolvers/delivery-tech-context.d.ts +2 -0
- package/dist/resolvers/delivery-tech-context.d.ts.map +1 -0
- package/dist/resolvers/delivery-tech-context.js +19 -0
- package/dist/resolvers/delivery-tech-context.js.map +1 -0
- package/dist/resolvers/deployment-profile.d.ts +2 -0
- package/dist/resolvers/deployment-profile.d.ts.map +1 -0
- package/dist/resolvers/deployment-profile.js +28 -0
- package/dist/resolvers/deployment-profile.js.map +1 -0
- package/dist/resolvers/focus-anchoring-injections.d.ts +2 -0
- package/dist/resolvers/focus-anchoring-injections.d.ts.map +1 -0
- package/dist/resolvers/focus-anchoring-injections.js +123 -0
- package/dist/resolvers/focus-anchoring-injections.js.map +1 -0
- package/dist/resolvers/focus-context.d.ts +2 -0
- package/dist/resolvers/focus-context.d.ts.map +1 -0
- package/dist/resolvers/focus-context.js +47 -0
- package/dist/resolvers/focus-context.js.map +1 -0
- package/dist/resolvers/focus-injections.d.ts +2 -0
- package/dist/resolvers/focus-injections.d.ts.map +1 -0
- package/dist/resolvers/focus-injections.js +75 -0
- package/dist/resolvers/focus-injections.js.map +1 -0
- package/dist/resolvers/focus-last-review-report.d.ts +2 -0
- package/dist/resolvers/focus-last-review-report.d.ts.map +1 -0
- package/dist/resolvers/focus-last-review-report.js +46 -0
- package/dist/resolvers/focus-last-review-report.js.map +1 -0
- package/dist/resolvers/focus-reality-tree.d.ts +2 -0
- package/dist/resolvers/focus-reality-tree.d.ts.map +1 -0
- package/dist/resolvers/focus-reality-tree.js +50 -0
- package/dist/resolvers/focus-reality-tree.js.map +1 -0
- package/dist/resolvers/git-diff-against-base.d.ts +2 -0
- package/dist/resolvers/git-diff-against-base.d.ts.map +1 -0
- package/dist/resolvers/git-diff-against-base.js +39 -0
- package/dist/resolvers/git-diff-against-base.js.map +1 -0
- package/dist/resolvers/guards-evaluation-results.d.ts +2 -0
- package/dist/resolvers/guards-evaluation-results.d.ts.map +1 -0
- package/dist/resolvers/guards-evaluation-results.js +31 -0
- package/dist/resolvers/guards-evaluation-results.js.map +1 -0
- package/dist/resolvers/index.d.ts +50 -0
- package/dist/resolvers/index.d.ts.map +1 -0
- package/dist/resolvers/index.js +51 -0
- package/dist/resolvers/index.js.map +1 -0
- package/dist/resolvers/loop-context.d.ts +2 -0
- package/dist/resolvers/loop-context.d.ts.map +1 -0
- package/dist/resolvers/loop-context.js +113 -0
- package/dist/resolvers/loop-context.js.map +1 -0
- package/dist/resolvers/loop-delivery-index.d.ts +2 -0
- package/dist/resolvers/loop-delivery-index.d.ts.map +1 -0
- package/dist/resolvers/loop-delivery-index.js +69 -0
- package/dist/resolvers/loop-delivery-index.js.map +1 -0
- package/dist/resolvers/loop-documents.d.ts +2 -0
- package/dist/resolvers/loop-documents.d.ts.map +1 -0
- package/dist/resolvers/loop-documents.js +30 -0
- package/dist/resolvers/loop-documents.js.map +1 -0
- package/dist/resolvers/loop-expected-effects.d.ts +2 -0
- package/dist/resolvers/loop-expected-effects.d.ts.map +1 -0
- package/dist/resolvers/loop-expected-effects.js +68 -0
- package/dist/resolvers/loop-expected-effects.js.map +1 -0
- package/dist/resolvers/loop-frt-statement.d.ts +2 -0
- package/dist/resolvers/loop-frt-statement.d.ts.map +1 -0
- package/dist/resolvers/loop-frt-statement.js +39 -0
- package/dist/resolvers/loop-frt-statement.js.map +1 -0
- package/dist/resolvers/loop-injection.d.ts +2 -0
- package/dist/resolvers/loop-injection.d.ts.map +1 -0
- package/dist/resolvers/loop-injection.js +48 -0
- package/dist/resolvers/loop-injection.js.map +1 -0
- package/dist/resolvers/loop-persona.d.ts +2 -0
- package/dist/resolvers/loop-persona.d.ts.map +1 -0
- package/dist/resolvers/loop-persona.js +40 -0
- package/dist/resolvers/loop-persona.js.map +1 -0
- package/dist/resolvers/loop-questions.d.ts +2 -0
- package/dist/resolvers/loop-questions.d.ts.map +1 -0
- package/dist/resolvers/loop-questions.js +49 -0
- package/dist/resolvers/loop-questions.js.map +1 -0
- package/dist/resolvers/loop-upstream-udes.d.ts +2 -0
- package/dist/resolvers/loop-upstream-udes.d.ts.map +1 -0
- package/dist/resolvers/loop-upstream-udes.js +88 -0
- package/dist/resolvers/loop-upstream-udes.js.map +1 -0
- package/dist/resolvers/reality-metrics.d.ts +2 -0
- package/dist/resolvers/reality-metrics.d.ts.map +1 -0
- package/dist/resolvers/reality-metrics.js +86 -0
- package/dist/resolvers/reality-metrics.js.map +1 -0
- package/dist/resolvers/reality-projections.d.ts +2 -0
- package/dist/resolvers/reality-projections.d.ts.map +1 -0
- package/dist/resolvers/reality-projections.js +44 -0
- package/dist/resolvers/reality-projections.js.map +1 -0
- package/dist/resolvers/reality-tree-snapshot.d.ts +2 -0
- package/dist/resolvers/reality-tree-snapshot.d.ts.map +1 -0
- package/dist/resolvers/reality-tree-snapshot.js +45 -0
- package/dist/resolvers/reality-tree-snapshot.js.map +1 -0
- package/dist/resolvers/retired-injections.d.ts +2 -0
- package/dist/resolvers/retired-injections.d.ts.map +1 -0
- package/dist/resolvers/retired-injections.js +83 -0
- package/dist/resolvers/retired-injections.js.map +1 -0
- package/dist/resolvers/review-outcomes.d.ts +2 -0
- package/dist/resolvers/review-outcomes.d.ts.map +1 -0
- package/dist/resolvers/review-outcomes.js +38 -0
- package/dist/resolvers/review-outcomes.js.map +1 -0
- package/dist/resolvers/security-advisory.d.ts +2 -0
- package/dist/resolvers/security-advisory.d.ts.map +1 -0
- package/dist/resolvers/security-advisory.js +97 -0
- package/dist/resolvers/security-advisory.js.map +1 -0
- package/dist/resolvers/shared/audit.d.ts +19 -0
- package/dist/resolvers/shared/audit.d.ts.map +1 -0
- package/dist/resolvers/shared/audit.js +28 -0
- package/dist/resolvers/shared/audit.js.map +1 -0
- package/dist/resolvers/shared/format.d.ts +6 -0
- package/dist/resolvers/shared/format.d.ts.map +1 -0
- package/dist/resolvers/shared/format.js +17 -0
- package/dist/resolvers/shared/format.js.map +1 -0
- package/dist/resolvers/shared/git-diff.d.ts +14 -0
- package/dist/resolvers/shared/git-diff.d.ts.map +1 -0
- package/dist/resolvers/shared/git-diff.js +30 -0
- package/dist/resolvers/shared/git-diff.js.map +1 -0
- package/dist/resolvers/shared/loop-items.d.ts +30 -0
- package/dist/resolvers/shared/loop-items.d.ts.map +1 -0
- package/dist/resolvers/shared/loop-items.js +7 -0
- package/dist/resolvers/shared/loop-items.js.map +1 -0
- package/dist/resolvers/shared/loop.d.ts +48 -0
- package/dist/resolvers/shared/loop.d.ts.map +1 -0
- package/dist/resolvers/shared/loop.js +38 -0
- package/dist/resolvers/shared/loop.js.map +1 -0
- package/dist/resolvers/shared/reality-metrics.d.ts +63 -0
- package/dist/resolvers/shared/reality-metrics.d.ts.map +1 -0
- package/dist/resolvers/shared/reality-metrics.js +6 -0
- package/dist/resolvers/shared/reality-metrics.js.map +1 -0
- package/dist/resolvers/shared/reality-tree.d.ts +51 -0
- package/dist/resolvers/shared/reality-tree.d.ts.map +1 -0
- package/dist/resolvers/shared/reality-tree.js +125 -0
- package/dist/resolvers/shared/reality-tree.js.map +1 -0
- package/dist/resolvers/shared/wiki.d.ts +60 -0
- package/dist/resolvers/shared/wiki.d.ts.map +1 -0
- package/dist/resolvers/shared/wiki.js +122 -0
- package/dist/resolvers/shared/wiki.js.map +1 -0
- package/dist/resolvers/wiki-search.d.ts +2 -0
- package/dist/resolvers/wiki-search.d.ts.map +1 -0
- package/dist/resolvers/wiki-search.js +23 -0
- package/dist/resolvers/wiki-search.js.map +1 -0
- package/dist/resolvers/wiki-topic.d.ts +2 -0
- package/dist/resolvers/wiki-topic.d.ts.map +1 -0
- package/dist/resolvers/wiki-topic.js +29 -0
- package/dist/resolvers/wiki-topic.js.map +1 -0
- package/dist/resolvers/workflow-stages.d.ts +2 -0
- package/dist/resolvers/workflow-stages.d.ts.map +1 -0
- package/dist/resolvers/workflow-stages.js +26 -0
- package/dist/resolvers/workflow-stages.js.map +1 -0
- package/dist/session-lifecycle.d.ts +2 -2
- package/dist/session-lifecycle.d.ts.map +1 -1
- package/dist/session-lifecycle.js +3 -3
- package/dist/session-lifecycle.js.map +1 -1
- package/dist/{pending-spawn-guard.d.ts → spawner/guards.d.ts} +1 -1
- package/dist/spawner/guards.d.ts.map +1 -0
- package/dist/{pending-spawn-guard.js → spawner/guards.js} +2 -2
- package/dist/spawner/guards.js.map +1 -0
- package/dist/{focus-spawn-helpers.d.ts → spawner/helpers.d.ts} +6 -6
- package/dist/spawner/helpers.d.ts.map +1 -0
- package/dist/{focus-spawn-helpers.js → spawner/helpers.js} +8 -8
- package/dist/spawner/helpers.js.map +1 -0
- package/dist/spawner/index.d.ts +30 -0
- package/dist/spawner/index.d.ts.map +1 -0
- package/dist/spawner/index.js +32 -0
- package/dist/spawner/index.js.map +1 -0
- package/dist/{spawner-lifecycle.d.ts → spawner/lifecycle.d.ts} +3 -3
- package/dist/spawner/lifecycle.d.ts.map +1 -0
- package/dist/{spawner-lifecycle.js → spawner/lifecycle.js} +6 -6
- package/dist/spawner/lifecycle.js.map +1 -0
- package/dist/{spawner-liveness.d.ts → spawner/liveness.d.ts} +2 -2
- package/dist/spawner/liveness.d.ts.map +1 -0
- package/dist/{spawner-liveness.js → spawner/liveness.js} +4 -4
- package/dist/spawner/liveness.js.map +1 -0
- package/dist/{spawner-resolution.d.ts → spawner/resolution.d.ts} +2 -2
- package/dist/spawner/resolution.d.ts.map +1 -0
- package/dist/{spawner-resolution.js → spawner/resolution.js} +2 -2
- package/dist/spawner/resolution.js.map +1 -0
- package/dist/{team-spawner.d.ts → spawner/spawn-team.d.ts} +4 -4
- package/dist/spawner/spawn-team.d.ts.map +1 -0
- package/dist/{team-spawner.js → spawner/spawn-team.js} +19 -19
- package/dist/spawner/spawn-team.js.map +1 -0
- package/dist/{spawner.d.ts → spawner/state.d.ts} +8 -8
- package/dist/spawner/state.d.ts.map +1 -0
- package/dist/{spawner.js → spawner/state.js} +8 -8
- package/dist/spawner/state.js.map +1 -0
- package/dist/{spawner-stream-handlers.d.ts → spawner/stream-handlers.d.ts} +4 -4
- package/dist/spawner/stream-handlers.d.ts.map +1 -0
- package/dist/{spawner-stream-handlers.js → spawner/stream-handlers.js} +7 -7
- package/dist/spawner/stream-handlers.js.map +1 -0
- package/dist/{spawner-timeout.d.ts → spawner/timeout.d.ts} +2 -2
- package/dist/spawner/timeout.d.ts.map +1 -0
- package/dist/{spawner-timeout.js → spawner/timeout.js} +1 -1
- package/dist/spawner/timeout.js.map +1 -0
- package/dist/team-prompt-base.d.ts.map +1 -1
- package/dist/team-prompt-base.js +4 -27
- package/dist/team-prompt-base.js.map +1 -1
- package/dist/types/focus.d.ts +1 -1
- package/dist/types/focus.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/completion-detector.d.ts.map +0 -1
- package/dist/completion-detector.js.map +0 -1
- package/dist/completion-handler.d.ts.map +0 -1
- package/dist/completion-handler.js.map +0 -1
- package/dist/focus-completion-event.d.ts.map +0 -1
- package/dist/focus-completion-event.js.map +0 -1
- package/dist/focus-completion.d.ts.map +0 -1
- package/dist/focus-completion.js.map +0 -1
- package/dist/focus-spawn-helpers.d.ts.map +0 -1
- package/dist/focus-spawn-helpers.js.map +0 -1
- package/dist/focus-team-lifecycle.d.ts.map +0 -1
- package/dist/focus-team-lifecycle.js.map +0 -1
- package/dist/pending-spawn-guard.d.ts.map +0 -1
- package/dist/pending-spawn-guard.js.map +0 -1
- package/dist/prompt-builder.d.ts +0 -14
- package/dist/prompt-builder.d.ts.map +0 -1
- package/dist/prompt-builder.js +0 -174
- package/dist/prompt-builder.js.map +0 -1
- package/dist/spawner-lifecycle.d.ts.map +0 -1
- package/dist/spawner-lifecycle.js.map +0 -1
- package/dist/spawner-liveness.d.ts.map +0 -1
- package/dist/spawner-liveness.js.map +0 -1
- package/dist/spawner-resolution.d.ts.map +0 -1
- package/dist/spawner-resolution.js.map +0 -1
- package/dist/spawner-stream-handlers.d.ts.map +0 -1
- package/dist/spawner-stream-handlers.js.map +0 -1
- package/dist/spawner-timeout.d.ts.map +0 -1
- package/dist/spawner-timeout.js.map +0 -1
- package/dist/spawner.d.ts.map +0 -1
- package/dist/spawner.js.map +0 -1
- package/dist/team-spawner.d.ts.map +0 -1
- package/dist/team-spawner.js.map +0 -1
|
@@ -1,1919 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Source resolver implementations for the Context Assembly Engine.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* module
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
async function searchWiki(productId, query, limit = 8) {
|
|
20
|
-
try {
|
|
21
|
-
const result = await callApi('wiki_search', {
|
|
22
|
-
productId,
|
|
23
|
-
query,
|
|
24
|
-
limit,
|
|
25
|
-
});
|
|
26
|
-
return result.pages ?? [];
|
|
27
|
-
}
|
|
28
|
-
catch (err) {
|
|
29
|
-
console.warn(`[assembly-engine] wiki_search failed for query "${query}": ${err.message}`);
|
|
30
|
-
return [];
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Fetch the deduplicated union of ancestors for a list of hit page ids.
|
|
35
|
-
* Returns root-first ordering (depthFromRoot ascending). Empty array on
|
|
36
|
-
* failure or when no ancestors exist (all hits are roots).
|
|
37
|
-
*/
|
|
38
|
-
async function fetchAncestorsForHits(productId, pageIds) {
|
|
39
|
-
if (pageIds.length === 0)
|
|
40
|
-
return [];
|
|
41
|
-
try {
|
|
42
|
-
const result = await callApi('wiki_ancestors_for_pages', {
|
|
43
|
-
productId,
|
|
44
|
-
pageIds,
|
|
45
|
-
});
|
|
46
|
-
return result.ancestors ?? [];
|
|
47
|
-
}
|
|
48
|
-
catch (err) {
|
|
49
|
-
console.warn(`[assembly-engine] wiki_ancestors_for_pages failed: ${err.message}`);
|
|
50
|
-
return [];
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Render hits with their content. Performs a context_assemble call -- not
|
|
55
|
-
* for layered assembly, but to surface page bodies via the same edge
|
|
56
|
-
* function endpoint used by the human MCP profile. The caller supplies the
|
|
57
|
-
* queries explicitly.
|
|
58
|
-
*/
|
|
59
|
-
async function fetchPageBodies(productId, ids) {
|
|
60
|
-
const bodies = new Map();
|
|
61
|
-
if (ids.length === 0)
|
|
62
|
-
return bodies;
|
|
63
|
-
for (const id of ids) {
|
|
64
|
-
try {
|
|
65
|
-
const result = await callApi('wiki_page_get', {
|
|
66
|
-
productId,
|
|
67
|
-
pageId: id,
|
|
68
|
-
});
|
|
69
|
-
if (result.page)
|
|
70
|
-
bodies.set(result.page.id, result.page);
|
|
71
|
-
}
|
|
72
|
-
catch (err) {
|
|
73
|
-
console.warn(`[assembly-engine] wiki_page_get failed for ${id}: ${err.message}`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return bodies;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Render a wiki retrieval section. When `ancestors` is non-empty, prepend a
|
|
80
|
-
* "Background Context" block (title + summary only, root-first) above the
|
|
81
|
-
* leaf hits. The ancestors block consumes up to WIKI_ANCESTORS_BUDGET_FRACTION
|
|
82
|
-
* of the total budget; the rest goes to leaf bodies.
|
|
83
|
-
*
|
|
84
|
-
* Design choice: ancestors render as a SEPARATE section above leaves rather
|
|
85
|
-
* than inline with each hit. Inline duplicates ancestors when multiple hits
|
|
86
|
-
* share them, and the agent can reason about the topical neighborhood once
|
|
87
|
-
* rather than per-hit.
|
|
88
|
-
*/
|
|
89
|
-
function renderWikiSection(heading, hits, bodies, budgetChars, ancestors = []) {
|
|
90
|
-
if (hits.length === 0)
|
|
91
|
-
return '';
|
|
92
|
-
const out = [`## ${heading}`];
|
|
93
|
-
let used = out[0].length;
|
|
94
|
-
// Ancestors block. Cumulative budget across the whole block (heading + all
|
|
95
|
-
// ancestor lines), not per-block — so a deep chain can't sneak past the cap
|
|
96
|
-
// by keeping each individual line small.
|
|
97
|
-
if (ancestors.length > 0) {
|
|
98
|
-
const ancestorBudget = Math.floor(budgetChars * WIKI_ANCESTORS_BUDGET_FRACTION);
|
|
99
|
-
const lines = ['\n\n### Background Context', ''];
|
|
100
|
-
let ancestorsUsed = lines[0].length + 1 + lines[1].length; // heading + join separator
|
|
101
|
-
for (const a of ancestors) {
|
|
102
|
-
const summary = (a.summary ?? '').trim();
|
|
103
|
-
const line = summary
|
|
104
|
-
? `- **${a.title}** (${a.path}) — ${summary}`
|
|
105
|
-
: `- **${a.title}** (${a.path})`;
|
|
106
|
-
const next = lines.length === 2 ? line : '\n' + line;
|
|
107
|
-
if (ancestorsUsed + next.length > ancestorBudget)
|
|
108
|
-
break;
|
|
109
|
-
ancestorsUsed += next.length;
|
|
110
|
-
lines.push(next);
|
|
111
|
-
}
|
|
112
|
-
if (lines.length > 2) {
|
|
113
|
-
const block = lines.join('\n');
|
|
114
|
-
out.push(block);
|
|
115
|
-
used += block.length;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
// Leaf bodies.
|
|
119
|
-
for (const hit of hits) {
|
|
120
|
-
const body = bodies.get(hit.id);
|
|
121
|
-
const content = body?.content?.trim() || hit.summary?.trim() || '';
|
|
122
|
-
const breadcrumb = hit.path && hit.path !== hit.slug ? `\n*path: ${hit.path}*` : '';
|
|
123
|
-
const block = `\n\n### ${hit.title}\n*slug: ${hit.slug}*${breadcrumb}\n\n${content || '_No content yet._'}`;
|
|
124
|
-
if (used + block.length > budgetChars && out.length > 1)
|
|
125
|
-
break;
|
|
126
|
-
out.push(block);
|
|
127
|
-
used += block.length;
|
|
128
|
-
}
|
|
129
|
-
return out.join('');
|
|
130
|
-
}
|
|
131
|
-
// ── Helpers ──────────────────────────────────────────────────────
|
|
132
|
-
export const MAX_DIFF_BYTES = 50 * 1024; // 50 KB truncation limit for git diffs
|
|
133
|
-
/** Format an issue status as a checkbox marker. */
|
|
134
|
-
function statusCheckbox(status) {
|
|
135
|
-
const lower = status.toLowerCase();
|
|
136
|
-
if (lower === 'done')
|
|
137
|
-
return '[x]';
|
|
138
|
-
if (lower === 'verified')
|
|
139
|
-
return '[v]';
|
|
140
|
-
if (lower === 'in review')
|
|
141
|
-
return '[?]';
|
|
142
|
-
if (lower === 'in progress')
|
|
143
|
-
return '[-]';
|
|
144
|
-
return '[ ]';
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Run git diff in a worktree against the base branch.
|
|
148
|
-
* Returns the raw diff output, or empty string on failure.
|
|
149
|
-
*/
|
|
150
|
-
export function getGitDiff(worktreePath, baseBranch) {
|
|
151
|
-
try {
|
|
152
|
-
const diff = execSync(`git diff ${baseBranch}...HEAD`, { cwd: worktreePath, maxBuffer: 10 * 1024 * 1024, encoding: 'utf-8' });
|
|
153
|
-
return diff;
|
|
154
|
-
}
|
|
155
|
-
catch {
|
|
156
|
-
return '';
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Get a summary of files changed in a diff (for truncation header).
|
|
161
|
-
*/
|
|
162
|
-
function getDiffStatSummary(worktreePath, baseBranch) {
|
|
163
|
-
try {
|
|
164
|
-
return execSync(`git diff --stat ${baseBranch}...HEAD`, { cwd: worktreePath, maxBuffer: 1024 * 1024, encoding: 'utf-8' });
|
|
165
|
-
}
|
|
166
|
-
catch {
|
|
167
|
-
return '';
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
// ── Resolvers ────────────────────────────────────────────────────
|
|
171
|
-
/**
|
|
172
|
-
* delivery.acceptance_criteria
|
|
173
|
-
*
|
|
174
|
-
* Fetches all deliveries for the focus and formats acceptance criteria.
|
|
175
|
-
*/
|
|
176
|
-
registerSourceResolver('delivery.acceptance_criteria', async (context) => {
|
|
177
|
-
const deliveries = await getFocusDeliveries(context.focusId);
|
|
178
|
-
if (deliveries.length === 0)
|
|
179
|
-
return '';
|
|
180
|
-
const sections = deliveries
|
|
181
|
-
.filter((d) => d.acceptanceCriteria)
|
|
182
|
-
.map((d) => `### ${d.name}\n\n${d.acceptanceCriteria}`);
|
|
183
|
-
if (sections.length === 0)
|
|
184
|
-
return '';
|
|
185
|
-
return `## Acceptance Criteria\n\n${sections.join('\n\n')}`;
|
|
186
|
-
});
|
|
187
|
-
/**
|
|
188
|
-
* delivery.description
|
|
189
|
-
*
|
|
190
|
-
* Fetches all deliveries for the focus and formats descriptions.
|
|
191
|
-
*/
|
|
192
|
-
registerSourceResolver('delivery.description', async (context) => {
|
|
193
|
-
const deliveries = await getFocusDeliveries(context.focusId);
|
|
194
|
-
if (deliveries.length === 0)
|
|
195
|
-
return '';
|
|
196
|
-
const sections = deliveries
|
|
197
|
-
.filter((d) => d.description)
|
|
198
|
-
.map((d) => `### ${d.name}\n\n${d.description}`);
|
|
199
|
-
if (sections.length === 0)
|
|
200
|
-
return '';
|
|
201
|
-
return `## Delivery Descriptions\n\n${sections.join('\n\n')}`;
|
|
202
|
-
});
|
|
203
|
-
/**
|
|
204
|
-
* delivery.issues
|
|
205
|
-
*
|
|
206
|
-
* Fetches all issues across deliveries and formats as a checklist.
|
|
207
|
-
* Optional param: status (filter by issue status, e.g. "done").
|
|
208
|
-
*/
|
|
209
|
-
registerSourceResolver('delivery.issues', async (context, params) => {
|
|
210
|
-
const issues = await getFocusIssues(context.focusId);
|
|
211
|
-
if (issues.length === 0)
|
|
212
|
-
return '';
|
|
213
|
-
const statusFilter = params.status?.toLowerCase();
|
|
214
|
-
// When filtering by 'done', also include 'Verified' issues — they are
|
|
215
|
-
// semantically closed (confirmed-addressed by a review agent) and should
|
|
216
|
-
// appear in review-context done-lists so the agent knows they were handled.
|
|
217
|
-
const filtered = statusFilter
|
|
218
|
-
? issues.filter((i) => {
|
|
219
|
-
const s = i.status.toLowerCase();
|
|
220
|
-
return s === statusFilter || (statusFilter === 'done' && s === 'verified');
|
|
221
|
-
})
|
|
222
|
-
: issues;
|
|
223
|
-
if (filtered.length === 0)
|
|
224
|
-
return '';
|
|
225
|
-
// Group issues by delivery
|
|
226
|
-
const byDelivery = {};
|
|
227
|
-
for (const issue of filtered) {
|
|
228
|
-
const group = byDelivery[issue.deliveryId] ?? [];
|
|
229
|
-
group.push(issue);
|
|
230
|
-
byDelivery[issue.deliveryId] = group;
|
|
231
|
-
}
|
|
232
|
-
const sections = [];
|
|
233
|
-
for (const deliveryId of Object.keys(byDelivery)) {
|
|
234
|
-
const deliveryIssues = byDelivery[deliveryId];
|
|
235
|
-
const lines = deliveryIssues.map((i) => {
|
|
236
|
-
const check = statusCheckbox(i.status);
|
|
237
|
-
const typeTag = i.issueType !== 'task' ? ` [${i.issueType}]` : '';
|
|
238
|
-
return `- ${check} ${i.title}${typeTag} (${i.status})`;
|
|
239
|
-
});
|
|
240
|
-
sections.push(`### Delivery ${deliveryId.slice(0, 8)}\n\n${lines.join('\n')}`);
|
|
241
|
-
}
|
|
242
|
-
const header = statusFilter
|
|
243
|
-
? `## Issues (${statusFilter})`
|
|
244
|
-
: '## Issues';
|
|
245
|
-
const hasVerified = filtered.some((i) => i.status.toLowerCase() === 'verified');
|
|
246
|
-
const hasInReview = filtered.some((i) => i.status.toLowerCase() === 'in review');
|
|
247
|
-
const legendLines = [];
|
|
248
|
-
if (hasVerified)
|
|
249
|
-
legendLines.push('`[v]` = Verified — confirmed-addressed in a prior review pass. Skip on re-review.');
|
|
250
|
-
if (hasInReview)
|
|
251
|
-
legendLines.push('`[?]` = In Review — dev-complete, awaiting review-agent confirmation. **This is the review agent\'s working set.**');
|
|
252
|
-
const legend = legendLines.length > 0
|
|
253
|
-
? '\n\n' + legendLines.map(l => `> ${l}`).join('\n>\n> ')
|
|
254
|
-
: '';
|
|
255
|
-
return `${header}${legend}\n\n${sections.join('\n\n')}`;
|
|
256
|
-
});
|
|
257
|
-
/**
|
|
258
|
-
* focus.context
|
|
259
|
-
*
|
|
260
|
-
* Pulls wiki pages relevant to the focus via wiki_search. Queries are
|
|
261
|
-
* derived from the focus name + the first sentence of its description;
|
|
262
|
-
* deliveries below the focus contribute additional queries when present.
|
|
263
|
-
*/
|
|
264
|
-
registerSourceResolver('focus.context', async (context) => {
|
|
265
|
-
const deliveries = await getFocusDeliveries(context.focusId);
|
|
266
|
-
const queries = [];
|
|
267
|
-
const seen = new Set();
|
|
268
|
-
const addQuery = (q) => {
|
|
269
|
-
const trimmed = (q ?? '').trim();
|
|
270
|
-
if (!trimmed || seen.has(trimmed))
|
|
271
|
-
return;
|
|
272
|
-
seen.add(trimmed);
|
|
273
|
-
queries.push(trimmed);
|
|
274
|
-
};
|
|
275
|
-
// Use the first delivery's name+description plus subsequent delivery
|
|
276
|
-
// names to seed the search. This keeps queries focused on the work in
|
|
277
|
-
// flight rather than dumping every delivery description.
|
|
278
|
-
for (const d of deliveries) {
|
|
279
|
-
addQuery(d.name);
|
|
280
|
-
}
|
|
281
|
-
if (queries.length === 0) {
|
|
282
|
-
addQuery(''); // recently-edited fallback
|
|
283
|
-
}
|
|
284
|
-
const aggregated = new Map();
|
|
285
|
-
for (const q of queries.slice(0, 4)) {
|
|
286
|
-
const hits = await searchWiki(context.productId, q, 4);
|
|
287
|
-
for (const hit of hits) {
|
|
288
|
-
const existing = aggregated.get(hit.id);
|
|
289
|
-
if (!existing || hit.rank > existing.rank)
|
|
290
|
-
aggregated.set(hit.id, hit);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
const sorted = Array.from(aggregated.values()).sort((a, b) => b.rank - a.rank).slice(0, 8);
|
|
294
|
-
const [bodies, ancestors] = await Promise.all([
|
|
295
|
-
fetchPageBodies(context.productId, sorted.map((h) => h.id)),
|
|
296
|
-
fetchAncestorsForHits(context.productId, sorted.map((h) => h.id)),
|
|
297
|
-
]);
|
|
298
|
-
return renderWikiSection('Wiki', sorted, bodies, WIKI_RETRIEVAL_BUDGET_CHARS, ancestors);
|
|
299
|
-
});
|
|
300
|
-
/**
|
|
301
|
-
* focus.last_review_report
|
|
302
|
-
*
|
|
303
|
-
* Read the most recent completed review report for the focus and render it
|
|
304
|
-
* as a markdown section. Returns an empty string when no completed report
|
|
305
|
-
* exists yet.
|
|
306
|
-
*
|
|
307
|
-
* The dev-stage assembly directive (see migration
|
|
308
|
-
* 20260512_*_add_last_review_report_to_dev_stage_assembly.sql) inserts this
|
|
309
|
-
* resolver immediately after focus.context so remediation work is informed
|
|
310
|
-
* by the reviewer's narrative, not just by individual issue descriptions.
|
|
311
|
-
*/
|
|
312
|
-
const FOCUS_LAST_REVIEW_REPORT_BUDGET_CHARS = 10_000;
|
|
313
|
-
registerSourceResolver('focus.last_review_report', async (context) => {
|
|
314
|
-
let review;
|
|
315
|
-
try {
|
|
316
|
-
review = await getLatestCompletedFocusReview(context.focusId);
|
|
317
|
-
}
|
|
318
|
-
catch (err) {
|
|
319
|
-
console.warn(`[assembly-engine] focus.last_review_report failed: ${err.message}`);
|
|
320
|
-
return '';
|
|
321
|
-
}
|
|
322
|
-
if (!review || !review.completedAt)
|
|
323
|
-
return '';
|
|
324
|
-
const heading = '## Last Review Report';
|
|
325
|
-
const outcomeLine = `**Outcome:** ${review.outcome ?? 'unknown'}`;
|
|
326
|
-
const completedLine = `**Completed:** ${review.completedAt}`;
|
|
327
|
-
const fixedPrefix = `${heading}\n\n${outcomeLine}\n${completedLine}\n\n`;
|
|
328
|
-
const summary = (review.summary ?? '').trim();
|
|
329
|
-
if (!summary) {
|
|
330
|
-
return `${fixedPrefix}_No summary provided._`;
|
|
331
|
-
}
|
|
332
|
-
const remaining = FOCUS_LAST_REVIEW_REPORT_BUDGET_CHARS - fixedPrefix.length;
|
|
333
|
-
if (remaining <= 0) {
|
|
334
|
-
// Prefix alone exceeds the budget (unlikely with a fixed-size heading +
|
|
335
|
-
// outcome + ISO timestamp). Drop the summary to stay under the cap.
|
|
336
|
-
return fixedPrefix.trimEnd();
|
|
337
|
-
}
|
|
338
|
-
const body = summary.length > remaining
|
|
339
|
-
? `${summary.slice(0, remaining)}\n\n_[truncated]_`
|
|
340
|
-
: summary;
|
|
341
|
-
return `${fixedPrefix}${body}`;
|
|
342
|
-
});
|
|
343
|
-
function pickString(payload, keys) {
|
|
344
|
-
for (const key of keys) {
|
|
345
|
-
const v = payload[key];
|
|
346
|
-
if (typeof v === 'string' && v.trim().length > 0)
|
|
347
|
-
return v.trim();
|
|
348
|
-
}
|
|
349
|
-
return null;
|
|
350
|
-
}
|
|
351
|
-
const ADVISORY_EVIDENCE_BUDGET = 500;
|
|
352
|
-
const SECURITY_ADVISORY_SECTION_BUDGET_CHARS = 10_000;
|
|
353
|
-
const SECURITY_ADVISORY_TRUNCATION_FOOTER = '\n\n_[truncated: section budget reached]_';
|
|
354
|
-
function formatAdvisorySection(advisory) {
|
|
355
|
-
const payload = advisory.payload ?? {};
|
|
356
|
-
const lines = [];
|
|
357
|
-
lines.push(`### ${advisory.identifier}`);
|
|
358
|
-
lines.push('');
|
|
359
|
-
lines.push(`- **IOC class:** ${advisory.iocClass}`);
|
|
360
|
-
lines.push(`- **Severity:** ${advisory.severity}`);
|
|
361
|
-
const pkg = pickString(payload, ['package', 'affected_package']);
|
|
362
|
-
if (pkg) {
|
|
363
|
-
const currentVersion = pickString(payload, ['current_version', 'installed_version']);
|
|
364
|
-
lines.push(`- **Package:** ${pkg}${currentVersion ? ` @ ${currentVersion}` : ''}`);
|
|
365
|
-
}
|
|
366
|
-
const fixVersion = pickString(payload, ['fix_version', 'fixed_version', 'patched_version']);
|
|
367
|
-
if (fixVersion)
|
|
368
|
-
lines.push(`- **Fix version:** ${fixVersion}`);
|
|
369
|
-
const advisoryUrl = pickString(payload, ['advisory_url', 'url', 'reference']);
|
|
370
|
-
if (advisoryUrl)
|
|
371
|
-
lines.push(`- **Advisory:** ${advisoryUrl}`);
|
|
372
|
-
const exploitClass = pickString(payload, ['exploit_class', 'cwe', 'vulnerability_class']);
|
|
373
|
-
if (exploitClass)
|
|
374
|
-
lines.push(`- **Exploit class:** ${exploitClass}`);
|
|
375
|
-
if (advisory.firstSeenAt)
|
|
376
|
-
lines.push(`- **First seen:** ${advisory.firstSeenAt}`);
|
|
377
|
-
if (advisory.injectionStatement) {
|
|
378
|
-
lines.push('');
|
|
379
|
-
lines.push(`**Injection statement:** ${advisory.injectionStatement}`);
|
|
380
|
-
}
|
|
381
|
-
if (advisory.injectionEvidence) {
|
|
382
|
-
const ev = advisory.injectionEvidence.length > ADVISORY_EVIDENCE_BUDGET
|
|
383
|
-
? `${advisory.injectionEvidence.slice(0, ADVISORY_EVIDENCE_BUDGET)}...`
|
|
384
|
-
: advisory.injectionEvidence;
|
|
385
|
-
lines.push('');
|
|
386
|
-
lines.push(`**Evidence:** ${ev}`);
|
|
387
|
-
}
|
|
388
|
-
return lines.join('\n');
|
|
389
|
-
}
|
|
390
|
-
registerSourceResolver('security.advisory', async (context) => {
|
|
391
|
-
if (context.deliveryIds.length === 0)
|
|
392
|
-
return '';
|
|
393
|
-
const header = '## Security Advisory\n\n';
|
|
394
|
-
// Reserve the footer length so that even the worst-case truncated output
|
|
395
|
-
// (header + body filled to sectionsBudget + footer) stays within the 10k cap.
|
|
396
|
-
const sectionsBudget = SECURITY_ADVISORY_SECTION_BUDGET_CHARS - header.length - SECURITY_ADVISORY_TRUNCATION_FOOTER.length;
|
|
397
|
-
const sections = [];
|
|
398
|
-
let used = 0;
|
|
399
|
-
let truncated = false;
|
|
400
|
-
for (const deliveryId of context.deliveryIds) {
|
|
401
|
-
if (used >= sectionsBudget) {
|
|
402
|
-
truncated = true;
|
|
403
|
-
break;
|
|
404
|
-
}
|
|
405
|
-
try {
|
|
406
|
-
const result = await callApi('daemon_get_security_advisory_for_delivery', { deliveryId });
|
|
407
|
-
const advisories = result.advisories ?? [];
|
|
408
|
-
if (advisories.length === 0)
|
|
409
|
-
continue;
|
|
410
|
-
let hitBudget = false;
|
|
411
|
-
for (const adv of advisories) {
|
|
412
|
-
const formatted = formatAdvisorySection(adv);
|
|
413
|
-
// Account for the inter-section separator that will be inserted.
|
|
414
|
-
const separator = sections.length > 0 ? 2 : 0;
|
|
415
|
-
if (used + separator + formatted.length > sectionsBudget) {
|
|
416
|
-
truncated = true;
|
|
417
|
-
hitBudget = true;
|
|
418
|
-
break;
|
|
419
|
-
}
|
|
420
|
-
sections.push(formatted);
|
|
421
|
-
used += separator + formatted.length;
|
|
422
|
-
}
|
|
423
|
-
if (hitBudget)
|
|
424
|
-
break;
|
|
425
|
-
}
|
|
426
|
-
catch (err) {
|
|
427
|
-
console.warn(`[assembly-engine] security.advisory for delivery ${deliveryId} failed: ${err.message}`);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
if (sections.length === 0)
|
|
431
|
-
return '';
|
|
432
|
-
const body = sections.join('\n\n');
|
|
433
|
-
return truncated
|
|
434
|
-
? `${header}${body}${SECURITY_ADVISORY_TRUNCATION_FOOTER}`
|
|
435
|
-
: `${header}${body}`;
|
|
436
|
-
});
|
|
437
|
-
/**
|
|
438
|
-
* wiki.search(query=...)
|
|
439
|
-
*
|
|
440
|
-
* Run a free-text wiki search and return ranked, body-included markdown.
|
|
441
|
-
* Stage directives use this to pull in topic-specific pages.
|
|
442
|
-
*/
|
|
443
|
-
registerSourceResolver('wiki.search', async (context, params) => {
|
|
444
|
-
const query = (params.query ?? '').trim();
|
|
445
|
-
const limit = params.limit ? Math.max(1, parseInt(params.limit, 10)) : 6;
|
|
446
|
-
if (!query)
|
|
447
|
-
return '';
|
|
448
|
-
const hits = await searchWiki(context.productId, query, limit);
|
|
449
|
-
if (hits.length === 0)
|
|
450
|
-
return '';
|
|
451
|
-
const [bodies, ancestors] = await Promise.all([
|
|
452
|
-
fetchPageBodies(context.productId, hits.map((h) => h.id)),
|
|
453
|
-
fetchAncestorsForHits(context.productId, hits.map((h) => h.id)),
|
|
454
|
-
]);
|
|
455
|
-
return renderWikiSection(`Wiki: ${query}`, hits, bodies, WIKI_RETRIEVAL_BUDGET_CHARS, ancestors);
|
|
456
|
-
});
|
|
457
|
-
/**
|
|
458
|
-
* wiki.topic(slug=...)
|
|
459
|
-
*
|
|
460
|
-
* Hard-pin a single wiki page by slug. No retrieval risk -- if the slug
|
|
461
|
-
* exists, the page is included verbatim. Used by stage directives that
|
|
462
|
-
* need a specific page (e.g., a security checklist).
|
|
463
|
-
*/
|
|
464
|
-
registerSourceResolver('wiki.topic', async (context, params) => {
|
|
465
|
-
const slug = (params.slug ?? '').trim();
|
|
466
|
-
if (!slug)
|
|
467
|
-
return '';
|
|
468
|
-
try {
|
|
469
|
-
const result = await callApi('wiki_page_get', {
|
|
470
|
-
productId: context.productId,
|
|
471
|
-
slug,
|
|
472
|
-
});
|
|
473
|
-
if (!result.page)
|
|
474
|
-
return '';
|
|
475
|
-
const page = result.page;
|
|
476
|
-
return `## Wiki: ${page.title}\n*slug: ${page.slug}*\n\n${page.content || '_No content yet._'}`;
|
|
477
|
-
}
|
|
478
|
-
catch (err) {
|
|
479
|
-
console.warn(`[assembly-engine] wiki.topic(slug=${slug}) failed: ${err.message}`);
|
|
480
|
-
return '';
|
|
481
|
-
}
|
|
482
|
-
});
|
|
483
|
-
/**
|
|
484
|
-
* git.diff_against_base
|
|
485
|
-
*
|
|
486
|
-
* Runs git diff against the integration branch in the worktree.
|
|
487
|
-
* Truncates large diffs and includes a file change summary.
|
|
488
|
-
*/
|
|
489
|
-
registerSourceResolver('git.diff_against_base', async (context) => {
|
|
490
|
-
if (!context.worktreePath) {
|
|
491
|
-
console.warn('[assembly-engine] git.diff_against_base: no worktree path available');
|
|
492
|
-
return '';
|
|
493
|
-
}
|
|
494
|
-
const baseBranch = context.config.integrationBranch;
|
|
495
|
-
const diff = getGitDiff(context.worktreePath, baseBranch);
|
|
496
|
-
if (!diff.trim())
|
|
497
|
-
return '';
|
|
498
|
-
if (diff.length > MAX_DIFF_BYTES) {
|
|
499
|
-
const stat = getDiffStatSummary(context.worktreePath, baseBranch);
|
|
500
|
-
const truncated = diff.slice(0, MAX_DIFF_BYTES);
|
|
501
|
-
return [
|
|
502
|
-
`## Git Diff (against ${baseBranch})`,
|
|
503
|
-
'',
|
|
504
|
-
`> **Truncated** -- diff is ${Math.round(diff.length / 1024)}KB, showing first ${Math.round(MAX_DIFF_BYTES / 1024)}KB.`,
|
|
505
|
-
'',
|
|
506
|
-
'### File Summary',
|
|
507
|
-
'',
|
|
508
|
-
'```',
|
|
509
|
-
stat.trim(),
|
|
510
|
-
'```',
|
|
511
|
-
'',
|
|
512
|
-
'```diff',
|
|
513
|
-
truncated,
|
|
514
|
-
'```',
|
|
515
|
-
].join('\n');
|
|
516
|
-
}
|
|
517
|
-
return `## Git Diff (against ${baseBranch})\n\n\`\`\`diff\n${diff}\n\`\`\``;
|
|
518
|
-
});
|
|
519
|
-
/**
|
|
520
|
-
* guards.evaluation_results
|
|
521
|
-
*
|
|
522
|
-
* Fetches the most recently resolved transition block for each delivery.
|
|
523
|
-
*/
|
|
524
|
-
registerSourceResolver('guards.evaluation_results', async (context) => {
|
|
525
|
-
if (context.deliveryIds.length === 0)
|
|
526
|
-
return '';
|
|
527
|
-
const results = [];
|
|
528
|
-
for (const deliveryId of context.deliveryIds) {
|
|
529
|
-
try {
|
|
530
|
-
const resolved = await getResolvedTransitionBlockForDelivery(deliveryId);
|
|
531
|
-
if (!resolved)
|
|
532
|
-
continue;
|
|
533
|
-
const path = resolved.block.decision_path ?? 'none';
|
|
534
|
-
const directive = resolved.directive_template ?? 'No directive template';
|
|
535
|
-
results.push(`### Delivery ${deliveryId.slice(0, 8)}\n\n` +
|
|
536
|
-
`- **Decision path**: ${path}\n` +
|
|
537
|
-
`- **Directive**: ${directive}`);
|
|
538
|
-
}
|
|
539
|
-
catch (err) {
|
|
540
|
-
console.warn(`[assembly-engine] guards.evaluation_results: failed for delivery ${deliveryId.slice(0, 8)}: ${err.message}`);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
if (results.length === 0)
|
|
544
|
-
return '';
|
|
545
|
-
return `## Guard Evaluation Results\n\n${results.join('\n\n')}`;
|
|
546
|
-
});
|
|
547
|
-
/**
|
|
548
|
-
* workflow.stages
|
|
549
|
-
*
|
|
550
|
-
* Fetches the effective workflow for the focus and lists all stage metadata.
|
|
551
|
-
*/
|
|
552
|
-
registerSourceResolver('workflow.stages', async (context) => {
|
|
553
|
-
try {
|
|
554
|
-
const workflow = await fetchFocusWorkflow(context.focusId);
|
|
555
|
-
if (!workflow || workflow.stages.length === 0)
|
|
556
|
-
return '';
|
|
557
|
-
const stageList = workflow.stages
|
|
558
|
-
.sort((a, b) => a.sort_order - b.sort_order)
|
|
559
|
-
.map((s) => {
|
|
560
|
-
const desc = s.description ? ` -- ${s.description}` : '';
|
|
561
|
-
return `- **${s.name}**${desc}`;
|
|
562
|
-
});
|
|
563
|
-
return `## Workflow Stages\n\nWorkflow: \`${workflow.id}\`\n\n${stageList.join('\n')}`;
|
|
564
|
-
}
|
|
565
|
-
catch (err) {
|
|
566
|
-
console.warn(`[assembly-engine] workflow.stages: failed: ${err.message}`);
|
|
567
|
-
return '';
|
|
568
|
-
}
|
|
569
|
-
});
|
|
570
|
-
/**
|
|
571
|
-
* deployment.profile
|
|
572
|
-
*
|
|
573
|
-
* Fetches the deployment profile snapshot for the product and formats
|
|
574
|
-
* the inception prompt and guidelines as markdown.
|
|
575
|
-
*/
|
|
576
|
-
registerSourceResolver('deployment.profile', async (context) => {
|
|
577
|
-
try {
|
|
578
|
-
const snapshot = await getProductDeploymentProfileSnapshot(context.productId);
|
|
579
|
-
if (!snapshot)
|
|
580
|
-
return '';
|
|
581
|
-
const parts = [];
|
|
582
|
-
if (snapshot.inceptionPrompt) {
|
|
583
|
-
parts.push(`## Deployment Profile: Operational Context\n\n${snapshot.inceptionPrompt}`);
|
|
584
|
-
}
|
|
585
|
-
if (snapshot.guidelines) {
|
|
586
|
-
parts.push(`## Deployment Constraints\n\n${snapshot.guidelines}`);
|
|
587
|
-
}
|
|
588
|
-
return parts.join('\n\n');
|
|
589
|
-
}
|
|
590
|
-
catch (err) {
|
|
591
|
-
console.warn(`[assembly-engine] deployment.profile: failed: ${err.message}`);
|
|
592
|
-
return '';
|
|
593
|
-
}
|
|
594
|
-
});
|
|
595
|
-
/**
|
|
596
|
-
* delivery.listing
|
|
597
|
-
*
|
|
598
|
-
* Fetches deliveries and issues for the focus and formats as a
|
|
599
|
-
* detailed listing using the same format as the team prompt builder.
|
|
600
|
-
*
|
|
601
|
-
* Optional param: scope
|
|
602
|
-
* - "team" (default): queued + coding deliveries only (building phase)
|
|
603
|
-
* - "all": all deliveries with status
|
|
604
|
-
* - "verify": only verify deliveries (review phase)
|
|
605
|
-
* - "done_since_last_audit": deliveries with executionStatus=done whose
|
|
606
|
-
* updatedAt is at or after the previous audit cycle's completed_at.
|
|
607
|
-
* When no prior audit exists, returns all done deliveries.
|
|
608
|
-
*/
|
|
609
|
-
registerSourceResolver('delivery.listing', async (context, params) => {
|
|
610
|
-
const deliveries = await getFocusDeliveries(context.focusId);
|
|
611
|
-
if (deliveries.length === 0)
|
|
612
|
-
return '';
|
|
613
|
-
const issues = await getFocusIssues(context.focusId);
|
|
614
|
-
const scope = params.scope ?? 'team';
|
|
615
|
-
if (scope === 'verify') {
|
|
616
|
-
// Filter to only verify deliveries for review context
|
|
617
|
-
const verifyDeliveries = deliveries.filter(d => d.executionStatus === 'verify');
|
|
618
|
-
if (verifyDeliveries.length === 0)
|
|
619
|
-
return '';
|
|
620
|
-
const lines = buildDeliveryListingSection(verifyDeliveries, issues);
|
|
621
|
-
return lines.join('\n');
|
|
622
|
-
}
|
|
623
|
-
if (scope === 'done_since_last_audit') {
|
|
624
|
-
const since = await getAuditCycleLowerBound(context.focusId);
|
|
625
|
-
const doneDeliveries = deliveries.filter(d => {
|
|
626
|
-
if (d.executionStatus !== 'done')
|
|
627
|
-
return false;
|
|
628
|
-
if (!since)
|
|
629
|
-
return true;
|
|
630
|
-
// updatedAt is the closest proxy for "moved to done at" -- when the
|
|
631
|
-
// daemon advances a delivery to done, that bumps updated_at. Missing
|
|
632
|
-
// timestamps are included so we don't silently drop a real completion.
|
|
633
|
-
return !d.updatedAt || d.updatedAt >= since;
|
|
634
|
-
});
|
|
635
|
-
if (doneDeliveries.length === 0)
|
|
636
|
-
return '';
|
|
637
|
-
const lines = buildDeliveryListingSection(doneDeliveries, issues);
|
|
638
|
-
return lines.join('\n');
|
|
639
|
-
}
|
|
640
|
-
// Default: use buildDeliveryListingSection which partitions into team/other
|
|
641
|
-
const lines = buildDeliveryListingSection(deliveries, issues);
|
|
642
|
-
return lines.join('\n');
|
|
643
|
-
});
|
|
644
|
-
/**
|
|
645
|
-
* focus.injections
|
|
646
|
-
*
|
|
647
|
-
* Renders the focus's reality-tree structure as the LEAD COORDINATION MAP --
|
|
648
|
-
* the lead's planning surface. It is structure, not per-delivery detail: for
|
|
649
|
-
* the whole focus it shows, per injection, a one-line Now->Target (the current
|
|
650
|
-
* UDE and the desired effect it becomes); the dependency order to sequence
|
|
651
|
-
* deliveries; shared-target collisions to avoid two workers changing the same
|
|
652
|
-
* node; and the root-cause clusters. Per-delivery full differentials are pulled
|
|
653
|
-
* on demand via telora_reality_tree_injection_context(seq).
|
|
654
|
-
*/
|
|
655
|
-
registerSourceResolver('focus.injections', async (context) => {
|
|
656
|
-
try {
|
|
657
|
-
const result = await callApi('reality_tree_focus_injections', {
|
|
658
|
-
focusId: context.focusId,
|
|
659
|
-
});
|
|
660
|
-
if (!result.items || result.items.length === 0)
|
|
661
|
-
return '';
|
|
662
|
-
const structure = result.structure ?? {
|
|
663
|
-
dependencyOrder: [],
|
|
664
|
-
sharedTargetCollisions: [],
|
|
665
|
-
rootCauseClusters: [],
|
|
666
|
-
};
|
|
667
|
-
const lines = [
|
|
668
|
-
'## Focus Coordination Map',
|
|
669
|
-
'',
|
|
670
|
-
'The focus reality-tree structure -- your planning surface. Each injection moves a',
|
|
671
|
-
'Now (a current UDE) to a Target (its future desired effect). Use the dependency',
|
|
672
|
-
'order to sequence deliveries, the collisions to avoid two workers changing the same',
|
|
673
|
-
'node, and the clusters to see which root causes drive which problems. Pull a',
|
|
674
|
-
"delivery's full differential on demand with telora_reality_tree_injection_context",
|
|
675
|
-
'(by injection seq).',
|
|
676
|
-
'',
|
|
677
|
-
'### Injections (Now -> Target)',
|
|
678
|
-
'',
|
|
679
|
-
];
|
|
680
|
-
for (const item of result.items) {
|
|
681
|
-
const inj = item.injection;
|
|
682
|
-
const statusTag = inj.injectionStatus ? ` [${inj.injectionStatus}]` : '';
|
|
683
|
-
lines.push(`- #${inj.seq} ${inj.statement}${statusTag}`);
|
|
684
|
-
for (const nt of item.nowTargets ?? []) {
|
|
685
|
-
const target = nt.target && nt.target.trim().length > 0 ? nt.target : '(no FRT overlay yet)';
|
|
686
|
-
lines.push(` - Now: #${nt.seq} ${nt.now} -> Target: ${target}`);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
if (structure.dependencyOrder.length > 0) {
|
|
690
|
-
lines.push('', '### Dependency Order (sequence deliveries this way)', '');
|
|
691
|
-
structure.dependencyOrder.forEach((d, i) => {
|
|
692
|
-
lines.push(`${i + 1}. #${d.seq} ${d.statement}`);
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
if (structure.sharedTargetCollisions.length > 0) {
|
|
696
|
-
lines.push('', '### Shared-Target Collisions (avoid two workers changing the same node)', '');
|
|
697
|
-
for (const c of structure.sharedTargetCollisions) {
|
|
698
|
-
const injList = c.injectionSeqs.map((s) => `#${s}`).join(', ');
|
|
699
|
-
lines.push(`- #${c.targetSeq} "${c.targetStatement}" <- injections ${injList}`);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
if (structure.rootCauseClusters.length > 0) {
|
|
703
|
-
lines.push('', '### Root-Cause Clusters (which roots drive which UDEs)', '');
|
|
704
|
-
for (const cl of structure.rootCauseClusters) {
|
|
705
|
-
const udeList = cl.udeSeqs.map((s) => `#${s}`).join(', ');
|
|
706
|
-
lines.push(`- #${cl.rootSeq} ${cl.rootStatement} (${cl.rootNodeType}) drives UDEs: ${udeList}`);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
return lines.join('\n');
|
|
710
|
-
}
|
|
711
|
-
catch (err) {
|
|
712
|
-
console.warn(`[assembly-engine] focus.injections: failed: ${err.message}`);
|
|
713
|
-
return '';
|
|
714
|
-
}
|
|
715
|
-
});
|
|
716
|
-
/**
|
|
717
|
-
* review.outcomes
|
|
718
|
-
*
|
|
719
|
-
* Thin wrapper around the most recent completed focus_review row. Surfaces
|
|
720
|
-
* outcome (approved / gaps_found) and the summary so the close_loop
|
|
721
|
-
* bookkeeper can correlate review findings with the anchored injection
|
|
722
|
-
* sweep. Fails soft -- returns empty string when no completed review
|
|
723
|
-
* exists yet.
|
|
724
|
-
*/
|
|
725
|
-
registerSourceResolver('review.outcomes', async (context) => {
|
|
726
|
-
try {
|
|
727
|
-
const review = await getLatestCompletedFocusReview(context.focusId);
|
|
728
|
-
if (!review || !review.completedAt)
|
|
729
|
-
return '';
|
|
730
|
-
const outcome = review.outcome ?? 'unknown';
|
|
731
|
-
const summary = (review.summary ?? '').trim();
|
|
732
|
-
const lines = [
|
|
733
|
-
'## Review Outcomes',
|
|
734
|
-
'',
|
|
735
|
-
`**Outcome:** ${outcome}`,
|
|
736
|
-
`**Completed:** ${review.completedAt}`,
|
|
737
|
-
];
|
|
738
|
-
if (summary.length > 0) {
|
|
739
|
-
lines.push('', summary);
|
|
740
|
-
}
|
|
741
|
-
else {
|
|
742
|
-
lines.push('', '_No summary provided._');
|
|
743
|
-
}
|
|
744
|
-
return lines.join('\n');
|
|
745
|
-
}
|
|
746
|
-
catch (err) {
|
|
747
|
-
console.warn(`[assembly-engine] review.outcomes: failed: ${err.message}`);
|
|
748
|
-
return '';
|
|
749
|
-
}
|
|
750
|
-
});
|
|
751
|
-
/**
|
|
752
|
-
* focus.anchoring_injections
|
|
753
|
-
*
|
|
754
|
-
* Per-delivery report of every injection anchored to a delivery in the
|
|
755
|
-
* focus. Used by the close_loop stage assembly so the bookkeeper team
|
|
756
|
-
* lead can sweep each injection on its own merits:
|
|
757
|
-
*
|
|
758
|
-
* - the delivery the injection is anchored to (name + id)
|
|
759
|
-
* - the injection statement, lifecycle status, and seq number
|
|
760
|
-
* - the UDE(s) the injection targets (via `targets` edges)
|
|
761
|
-
* - the FRT overlay statement on each UDE (what it becomes once verified)
|
|
762
|
-
* - the FRT overlay node type (e.g., UDE -> DE flip)
|
|
763
|
-
* - "expected reality change" -- diff between current statement and FRT
|
|
764
|
-
* overlay statement, surfaced per target so the agent can check whether
|
|
765
|
-
* the worktree evidence supports the flip
|
|
766
|
-
*
|
|
767
|
-
* Query path:
|
|
768
|
-
* org_nodes (type=delivery, focus_id=X) -> injection_id ->
|
|
769
|
-
* reality_tree_nodes (injection) -> reality_tree_edges (targets) ->
|
|
770
|
-
* reality_tree_nodes (UDE / target)
|
|
771
|
-
*
|
|
772
|
-
* Returns empty string when the focus has no deliveries with injection
|
|
773
|
-
* anchors. Fails soft on API errors.
|
|
774
|
-
*/
|
|
775
|
-
registerSourceResolver('focus.anchoring_injections', async (context) => {
|
|
776
|
-
try {
|
|
777
|
-
const deliveries = await getFocusDeliveries(context.focusId);
|
|
778
|
-
const anchored = deliveries.filter((d) => typeof d.injectionId === 'string' && d.injectionId.length > 0);
|
|
779
|
-
if (anchored.length === 0)
|
|
780
|
-
return '';
|
|
781
|
-
const injectionIds = anchored.map((d) => d.injectionId);
|
|
782
|
-
const bundles = await fetchInjectionBundles(injectionIds);
|
|
783
|
-
if (bundles.size === 0)
|
|
784
|
-
return '';
|
|
785
|
-
// Cache nodes+edges per tree so we don't fetch them once per delivery
|
|
786
|
-
// when multiple injections share a tree.
|
|
787
|
-
const treeNodesCache = new Map();
|
|
788
|
-
const treeEdgesCache = new Map();
|
|
789
|
-
async function loadTree(treeId) {
|
|
790
|
-
let nodes = treeNodesCache.get(treeId);
|
|
791
|
-
let edges = treeEdgesCache.get(treeId);
|
|
792
|
-
if (!nodes || !edges) {
|
|
793
|
-
const [nodesResp, edgesResp] = await Promise.all([
|
|
794
|
-
callApi('reality_tree_node_list', { treeId, status: 'active' }),
|
|
795
|
-
callApi('reality_tree_edge_list', { treeId }),
|
|
796
|
-
]);
|
|
797
|
-
nodes = nodesResp.items ?? [];
|
|
798
|
-
edges = edgesResp.items ?? [];
|
|
799
|
-
treeNodesCache.set(treeId, nodes);
|
|
800
|
-
treeEdgesCache.set(treeId, edges);
|
|
801
|
-
}
|
|
802
|
-
return { nodes, edges };
|
|
803
|
-
}
|
|
804
|
-
const sections = [];
|
|
805
|
-
for (const delivery of anchored) {
|
|
806
|
-
const injectionId = delivery.injectionId;
|
|
807
|
-
const bundle = bundles.get(injectionId);
|
|
808
|
-
if (!bundle)
|
|
809
|
-
continue;
|
|
810
|
-
const inj = bundle.injection;
|
|
811
|
-
const treeId = inj.realityTreeId;
|
|
812
|
-
const { nodes, edges } = await loadTree(treeId);
|
|
813
|
-
const nodeById = new Map();
|
|
814
|
-
for (const n of nodes)
|
|
815
|
-
nodeById.set(n.id, n);
|
|
816
|
-
const statusTag = inj.injectionStatus ? ` [${inj.injectionStatus}]` : '';
|
|
817
|
-
const lines = [
|
|
818
|
-
`### ${delivery.name}`,
|
|
819
|
-
'',
|
|
820
|
-
`- **Anchored delivery:** ${delivery.name} (\`${delivery.id}\`)`,
|
|
821
|
-
`- **Injection:** #${inj.seq} ${inj.statement}${statusTag}`,
|
|
822
|
-
`- **Tree:** ${bundle.treeName}`,
|
|
823
|
-
];
|
|
824
|
-
if (inj.dissolvesObstacle) {
|
|
825
|
-
lines.push(`- **Dissolves obstacle:** ${inj.dissolvesObstacle}`);
|
|
826
|
-
}
|
|
827
|
-
// Walk targets edges from this injection -> target UDE/entity nodes.
|
|
828
|
-
const targetIds = edges
|
|
829
|
-
.filter((e) => e.edgeType === 'targets' && e.fromNodeId === injectionId)
|
|
830
|
-
.map((e) => e.toNodeId);
|
|
831
|
-
if (targetIds.length === 0) {
|
|
832
|
-
lines.push('', '_No target UDEs declared. Injection has no `targets` edges._');
|
|
833
|
-
}
|
|
834
|
-
else {
|
|
835
|
-
lines.push('', '**Target UDEs and expected reality change:**');
|
|
836
|
-
for (const targetId of targetIds) {
|
|
837
|
-
const target = nodeById.get(targetId);
|
|
838
|
-
if (!target)
|
|
839
|
-
continue;
|
|
840
|
-
const currentType = target.nodeType;
|
|
841
|
-
const frtStatement = target.frtStatement?.trim() ?? '';
|
|
842
|
-
const frtType = target.frtNodeType?.trim() ?? '';
|
|
843
|
-
const targetHeader = `- **#${target.seq} Target UDE (${currentType}):** ${target.statement}`;
|
|
844
|
-
lines.push(targetHeader);
|
|
845
|
-
if (frtStatement.length > 0) {
|
|
846
|
-
const overlayType = frtType.length > 0 ? frtType : currentType;
|
|
847
|
-
lines.push(` - **FRT overlay (${overlayType}):** ${frtStatement}`);
|
|
848
|
-
lines.push(` - **Expected reality change:** "${target.statement}" -> "${frtStatement}"`);
|
|
849
|
-
}
|
|
850
|
-
else if (frtType.length > 0) {
|
|
851
|
-
lines.push(` - **FRT overlay (${frtType}):** _(type flip only, no statement rewrite)_`);
|
|
852
|
-
}
|
|
853
|
-
else {
|
|
854
|
-
lines.push(' - _No FRT overlay set on this target._');
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
sections.push(lines.join('\n'));
|
|
859
|
-
}
|
|
860
|
-
if (sections.length === 0)
|
|
861
|
-
return '';
|
|
862
|
-
return `## Anchoring Injections (close-loop sweep)\n\n${sections.join('\n\n')}`;
|
|
863
|
-
}
|
|
864
|
-
catch (err) {
|
|
865
|
-
console.warn(`[assembly-engine] focus.anchoring_injections: failed: ${err.message}`);
|
|
866
|
-
return '';
|
|
867
|
-
}
|
|
868
|
-
});
|
|
869
|
-
/**
|
|
870
|
-
* reality.metrics
|
|
871
|
-
*
|
|
872
|
-
* Fetches focus reality metrics + trajectory projections from the edge function.
|
|
873
|
-
* Formats as markdown for agent consumption (Wire B of the differential pair).
|
|
874
|
-
*/
|
|
875
|
-
registerSourceResolver('reality.metrics', async (context) => {
|
|
876
|
-
try {
|
|
877
|
-
const result = await callApi('reality_metrics_focus', {
|
|
878
|
-
focusId: context.focusId,
|
|
879
|
-
});
|
|
880
|
-
const m = result.metrics;
|
|
881
|
-
if (!m)
|
|
882
|
-
return '';
|
|
883
|
-
const parts = ['## Execution Reality\n'];
|
|
884
|
-
// Delivery status breakdown
|
|
885
|
-
parts.push('### Delivery Status');
|
|
886
|
-
parts.push(`- Planning: ${m.deliveries_planning} | Queued: ${m.deliveries_queued} | Coding: ${m.deliveries_coding} | Verify: ${m.deliveries_verify} | Done: ${m.deliveries_done} (${m.total_deliveries} total)`);
|
|
887
|
-
// Issue throughput
|
|
888
|
-
parts.push('\n### Issue Throughput');
|
|
889
|
-
parts.push(`- Total: ${m.total_issues} | To Do: ${m.issues_todo} | In Progress: ${m.issues_in_progress} | Done: ${m.issues_done} | Blocked: ${m.issues_blocked}`);
|
|
890
|
-
parts.push(`- Completed: ${m.issues_done_7d} (7d) | ${m.issues_done_14d} (14d) | ${m.issues_done_30d} (30d)`);
|
|
891
|
-
if (m.avg_cycle_time_days !== null) {
|
|
892
|
-
parts.push(`- Avg cycle time: ${m.avg_cycle_time_days} days`);
|
|
893
|
-
}
|
|
894
|
-
if (m.blocked_count > 0) {
|
|
895
|
-
parts.push(`- Blocked: ${m.blocked_count} issues${m.avg_blocked_duration_days !== null ? `, avg duration ${m.avg_blocked_duration_days} days` : ''}`);
|
|
896
|
-
}
|
|
897
|
-
// Agent cost
|
|
898
|
-
parts.push('\n### Agent Activity');
|
|
899
|
-
parts.push(`- Sessions: ${m.session_count} | Cost: $${m.total_agent_cost.toFixed(2)} | Escalations: ${m.escalation_count}`);
|
|
900
|
-
if (m.last_session_at) {
|
|
901
|
-
parts.push(`- Last session: ${m.last_session_at}`);
|
|
902
|
-
}
|
|
903
|
-
// Git merge metrics
|
|
904
|
-
if (m.merge_count !== undefined) {
|
|
905
|
-
parts.push('\n### Git Activity');
|
|
906
|
-
parts.push(`- Total merges: ${m.merge_count} | Last 7d: ${m.merges_7d ?? 0} | Last 30d: ${m.merges_30d ?? 0}`);
|
|
907
|
-
if (m.last_merge_at) {
|
|
908
|
-
parts.push(`- Last merge: ${m.last_merge_at}`);
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
// Trajectory projections
|
|
912
|
-
const comp = result.projections?.completion;
|
|
913
|
-
const cost = result.projections?.cost;
|
|
914
|
-
if (comp || cost) {
|
|
915
|
-
parts.push('\n## Trajectory Projections\n');
|
|
916
|
-
}
|
|
917
|
-
if (comp) {
|
|
918
|
-
parts.push('### Completion Forecast');
|
|
919
|
-
if (comp.status === 'complete') {
|
|
920
|
-
parts.push('- Status: **complete** (all issues done)');
|
|
921
|
-
}
|
|
922
|
-
else if (comp.status === 'stalled') {
|
|
923
|
-
parts.push(`- Status: **stalled** -- ${comp.stalledReason || 'no recent throughput'}`);
|
|
924
|
-
parts.push(`- Remaining issues: ${comp.remainingIssues}`);
|
|
925
|
-
}
|
|
926
|
-
else {
|
|
927
|
-
parts.push(`- Projected completion: ${comp.projectedCompletionDate ? new Date(comp.projectedCompletionDate).toISOString().split('T')[0] : 'unknown'}`);
|
|
928
|
-
parts.push(`- Days remaining: ${comp.daysRemaining}`);
|
|
929
|
-
parts.push(`- Velocity: ${comp.velocity} issues/day (${comp.windowDays}d window, ${comp.completedInWindow} completed)`);
|
|
930
|
-
parts.push(`- Confidence: ${comp.confidence}`);
|
|
931
|
-
parts.push(`- Remaining issues: ${comp.remainingIssues}`);
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
if (cost) {
|
|
935
|
-
parts.push('\n### Cost Forecast');
|
|
936
|
-
parts.push(`- Cost to date: $${cost.costToDate.toFixed(2)}`);
|
|
937
|
-
parts.push(`- Burn rate: $${cost.burnRatePerDay.toFixed(2)}/day`);
|
|
938
|
-
if (cost.avgCostPerIssue !== null) {
|
|
939
|
-
parts.push(`- Avg cost per issue: $${cost.avgCostPerIssue.toFixed(2)}`);
|
|
940
|
-
}
|
|
941
|
-
if (cost.projectedTotalCost !== null) {
|
|
942
|
-
parts.push(`- Projected total: $${cost.projectedTotalCost.toFixed(2)} ($${cost.projectedRemainingCost?.toFixed(2)} remaining)`);
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
return parts.join('\n');
|
|
946
|
-
}
|
|
947
|
-
catch (err) {
|
|
948
|
-
console.warn(`[assembly-engine] reality.metrics: failed: ${err.message}`);
|
|
949
|
-
return '';
|
|
950
|
-
}
|
|
951
|
-
});
|
|
952
|
-
/**
|
|
953
|
-
* reality.projections
|
|
954
|
-
*
|
|
955
|
-
* Compact format: just the forecasted outcomes for quick delta computation.
|
|
956
|
-
*/
|
|
957
|
-
registerSourceResolver('reality.projections', async (context) => {
|
|
958
|
-
try {
|
|
959
|
-
const result = await callApi('reality_metrics_focus', {
|
|
960
|
-
focusId: context.focusId,
|
|
961
|
-
});
|
|
962
|
-
const comp = result.projections?.completion;
|
|
963
|
-
const cost = result.projections?.cost;
|
|
964
|
-
if (!comp && !cost)
|
|
965
|
-
return '';
|
|
966
|
-
const lines = ['## Trajectory Projections\n'];
|
|
967
|
-
if (comp) {
|
|
968
|
-
if (comp.status === 'complete') {
|
|
969
|
-
lines.push('- Completion: **complete**');
|
|
970
|
-
}
|
|
971
|
-
else if (comp.status === 'stalled') {
|
|
972
|
-
lines.push(`- Completion: **stalled** (${comp.remainingIssues} remaining)`);
|
|
973
|
-
}
|
|
974
|
-
else {
|
|
975
|
-
lines.push(`- Completion: ${comp.projectedCompletionDate ? new Date(comp.projectedCompletionDate).toISOString().split('T')[0] : '?'} (${comp.daysRemaining}d, confidence ${comp.confidence})`);
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
if (cost) {
|
|
979
|
-
if (cost.projectedTotalCost !== null) {
|
|
980
|
-
lines.push(`- Cost: $${cost.costToDate.toFixed(2)} spent, $${cost.projectedTotalCost.toFixed(2)} projected total`);
|
|
981
|
-
}
|
|
982
|
-
else {
|
|
983
|
-
lines.push(`- Cost: $${cost.costToDate.toFixed(2)} spent (insufficient data to project)`);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
return lines.join('\n');
|
|
987
|
-
}
|
|
988
|
-
catch (err) {
|
|
989
|
-
console.warn(`[assembly-engine] reality.projections: failed: ${err.message}`);
|
|
990
|
-
return '';
|
|
991
|
-
}
|
|
992
|
-
});
|
|
993
|
-
/**
|
|
994
|
-
* loop.documents
|
|
995
|
-
*
|
|
996
|
-
* Fetches force-ranked loop documents for the focus's entity layer.
|
|
997
|
-
* Returns formatted markdown with ranked documents.
|
|
998
|
-
* Optional param: count (limit to top N).
|
|
999
|
-
*/
|
|
1000
|
-
registerSourceResolver('loop.documents', async (context, params) => {
|
|
1001
|
-
try {
|
|
1002
|
-
const limit = params.count ? parseInt(params.count, 10) : 50;
|
|
1003
|
-
const result = await callApi('loop_document_list', {
|
|
1004
|
-
entityType: 'focus',
|
|
1005
|
-
entityId: context.focusId,
|
|
1006
|
-
detail: 'full',
|
|
1007
|
-
limit,
|
|
1008
|
-
});
|
|
1009
|
-
const docs = result.items;
|
|
1010
|
-
if (!docs || docs.length === 0)
|
|
1011
|
-
return '';
|
|
1012
|
-
const sections = docs.map((doc, i) => `### ${i + 1}. ${doc.title}\n\n${doc.content}`);
|
|
1013
|
-
return `## Loop Documents\n\n${sections.join('\n\n')}`;
|
|
1014
|
-
}
|
|
1015
|
-
catch (err) {
|
|
1016
|
-
console.warn(`[assembly-engine] loop.documents: failed: ${err.message}`);
|
|
1017
|
-
return '';
|
|
1018
|
-
}
|
|
1019
|
-
});
|
|
1020
|
-
/**
|
|
1021
|
-
* loop.questions
|
|
1022
|
-
*
|
|
1023
|
-
* Fetches fixed + generated questions with current answers for the entity layer.
|
|
1024
|
-
* Returns formatted markdown: fixed questions first, then generated in rank order.
|
|
1025
|
-
*/
|
|
1026
|
-
registerSourceResolver('loop.questions', async (context) => {
|
|
1027
|
-
try {
|
|
1028
|
-
const result = await callApi('loop_question_list', {
|
|
1029
|
-
entityType: 'focus',
|
|
1030
|
-
entityId: context.focusId,
|
|
1031
|
-
detail: 'full',
|
|
1032
|
-
});
|
|
1033
|
-
const questions = result.items;
|
|
1034
|
-
if (!questions || questions.length === 0)
|
|
1035
|
-
return '';
|
|
1036
|
-
const fixed = questions.filter((q) => q.questionType === 'fixed');
|
|
1037
|
-
const generated = questions.filter((q) => q.questionType === 'generated');
|
|
1038
|
-
const formatQuestion = async (q) => {
|
|
1039
|
-
try {
|
|
1040
|
-
const answerResult = await callApi('loop_answer_get', {
|
|
1041
|
-
questionId: q.id,
|
|
1042
|
-
});
|
|
1043
|
-
const answer = answerResult.answer?.content || '_No answer yet._';
|
|
1044
|
-
return `**Q:** ${q.text}\n**A:** ${answer}`;
|
|
1045
|
-
}
|
|
1046
|
-
catch {
|
|
1047
|
-
return `**Q:** ${q.text}\n**A:** _No answer yet._`;
|
|
1048
|
-
}
|
|
1049
|
-
};
|
|
1050
|
-
const parts = [];
|
|
1051
|
-
if (fixed.length > 0) {
|
|
1052
|
-
const fixedLines = await Promise.all(fixed.map(formatQuestion));
|
|
1053
|
-
parts.push(`### Fixed Questions\n\n${fixedLines.join('\n\n')}`);
|
|
1054
|
-
}
|
|
1055
|
-
if (generated.length > 0) {
|
|
1056
|
-
const generatedLines = await Promise.all(generated.map(formatQuestion));
|
|
1057
|
-
parts.push(`### Generated Questions\n\n${generatedLines.join('\n\n')}`);
|
|
1058
|
-
}
|
|
1059
|
-
return `## Loop Questions\n\n${parts.join('\n\n')}`;
|
|
1060
|
-
}
|
|
1061
|
-
catch (err) {
|
|
1062
|
-
console.warn(`[assembly-engine] loop.questions: failed: ${err.message}`);
|
|
1063
|
-
return '';
|
|
1064
|
-
}
|
|
1065
|
-
});
|
|
1066
|
-
/**
|
|
1067
|
-
* loop.persona
|
|
1068
|
-
*
|
|
1069
|
-
* Fetches the active persona for the entity's loop layer.
|
|
1070
|
-
* Returns the raw "You are..." prompt text, or empty string if none set.
|
|
1071
|
-
*
|
|
1072
|
-
* Stage variants:
|
|
1073
|
-
* - `loop.persona(variant=close_loop)` -- returns the close-loop bookkeeper
|
|
1074
|
-
* persona (from team-prompt-base.buildCloseLoopBookkeeperPersona). This
|
|
1075
|
-
* is a built-in variant; the stored persona on the focus is ignored so
|
|
1076
|
-
* close_loop stage spawns always carry the asymmetric, no-third-option
|
|
1077
|
-
* bookkeeper framing regardless of what the human persona on the focus
|
|
1078
|
-
* looks like. The close_loop stage dispatcher composes its assembly
|
|
1079
|
-
* recipe with `loop.persona(variant=close_loop)` so the team lead
|
|
1080
|
-
* receives this exact text.
|
|
1081
|
-
*
|
|
1082
|
-
* No-variant call returns the human-authored persona stored on the focus.
|
|
1083
|
-
*/
|
|
1084
|
-
registerSourceResolver('loop.persona', async (context, params) => {
|
|
1085
|
-
if (params.variant === 'close_loop') {
|
|
1086
|
-
return buildCloseLoopBookkeeperPersona();
|
|
1087
|
-
}
|
|
1088
|
-
try {
|
|
1089
|
-
const result = await callApi('loop_persona_get', {
|
|
1090
|
-
entityType: 'focus',
|
|
1091
|
-
entityId: context.focusId,
|
|
1092
|
-
});
|
|
1093
|
-
if (!result.persona || !result.persona.content)
|
|
1094
|
-
return '';
|
|
1095
|
-
return result.persona.content;
|
|
1096
|
-
}
|
|
1097
|
-
catch (err) {
|
|
1098
|
-
console.warn(`[assembly-engine] loop.persona: failed: ${err.message}`);
|
|
1099
|
-
return '';
|
|
1100
|
-
}
|
|
1101
|
-
});
|
|
1102
|
-
/**
|
|
1103
|
-
* Fetch loop data (persona + documents + Q&A) for a single layer.
|
|
1104
|
-
* Returns formatted markdown sections, or empty string if no data exists.
|
|
1105
|
-
*/
|
|
1106
|
-
async function fetchLoopLayerContext(entityType, entityId, layerLabel) {
|
|
1107
|
-
const parts = [];
|
|
1108
|
-
// Persona
|
|
1109
|
-
try {
|
|
1110
|
-
const personaResult = await callApi('loop_persona_get', {
|
|
1111
|
-
entityType,
|
|
1112
|
-
entityId,
|
|
1113
|
-
});
|
|
1114
|
-
if (personaResult.persona?.content) {
|
|
1115
|
-
parts.push(`**Persona:** ${personaResult.persona.content}`);
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
catch {
|
|
1119
|
-
// No persona -- not an error
|
|
1120
|
-
}
|
|
1121
|
-
// Force-ranked documents
|
|
1122
|
-
try {
|
|
1123
|
-
const docsResult = await callApi('loop_document_list', {
|
|
1124
|
-
entityType,
|
|
1125
|
-
entityId,
|
|
1126
|
-
detail: 'full',
|
|
1127
|
-
limit: 50,
|
|
1128
|
-
});
|
|
1129
|
-
if (docsResult.items?.length > 0) {
|
|
1130
|
-
const docLines = docsResult.items.map((doc, i) => `#### ${i + 1}. ${doc.title}\n\n${doc.content}`);
|
|
1131
|
-
parts.push(`### Documents\n\n${docLines.join('\n\n')}`);
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
catch {
|
|
1135
|
-
// No documents -- not an error
|
|
1136
|
-
}
|
|
1137
|
-
// Questions + answers
|
|
1138
|
-
try {
|
|
1139
|
-
const qResult = await callApi('loop_question_list', {
|
|
1140
|
-
entityType,
|
|
1141
|
-
entityId,
|
|
1142
|
-
detail: 'full',
|
|
1143
|
-
});
|
|
1144
|
-
if (qResult.items?.length > 0) {
|
|
1145
|
-
const fixed = qResult.items.filter((q) => q.questionType === 'fixed');
|
|
1146
|
-
const generated = qResult.items.filter((q) => q.questionType === 'generated');
|
|
1147
|
-
const formatQ = async (q) => {
|
|
1148
|
-
try {
|
|
1149
|
-
const aResult = await callApi('loop_answer_get', { questionId: q.id });
|
|
1150
|
-
const answer = aResult.answer?.content || '_No answer yet._';
|
|
1151
|
-
return `**Q:** ${q.text}\n**A:** ${answer}`;
|
|
1152
|
-
}
|
|
1153
|
-
catch {
|
|
1154
|
-
return `**Q:** ${q.text}\n**A:** _No answer yet._`;
|
|
1155
|
-
}
|
|
1156
|
-
};
|
|
1157
|
-
const qParts = [];
|
|
1158
|
-
if (fixed.length > 0) {
|
|
1159
|
-
const fixedLines = await Promise.all(fixed.map(formatQ));
|
|
1160
|
-
qParts.push(`#### Fixed Questions\n\n${fixedLines.join('\n\n')}`);
|
|
1161
|
-
}
|
|
1162
|
-
if (generated.length > 0) {
|
|
1163
|
-
const genLines = await Promise.all(generated.map(formatQ));
|
|
1164
|
-
qParts.push(`#### Agent-Generated Questions\n\n${genLines.join('\n\n')}`);
|
|
1165
|
-
}
|
|
1166
|
-
if (qParts.length > 0) {
|
|
1167
|
-
parts.push(`### Questions & Answers\n\n${qParts.join('\n\n')}`);
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
catch {
|
|
1172
|
-
// No questions -- not an error
|
|
1173
|
-
}
|
|
1174
|
-
if (parts.length === 0)
|
|
1175
|
-
return '';
|
|
1176
|
-
return `## ${layerLabel} Loop\n\n${parts.join('\n\n')}`;
|
|
1177
|
-
}
|
|
1178
|
-
/**
|
|
1179
|
-
* loop.context
|
|
1180
|
-
*
|
|
1181
|
-
* Composite resolver that fetches persona + force-ranked documents + Q&A
|
|
1182
|
-
* for one or more loop layers. Provides complete loop context for agent
|
|
1183
|
-
* prompt injection.
|
|
1184
|
-
*
|
|
1185
|
-
* Optional param: layer
|
|
1186
|
-
* - "product": product-level loop only
|
|
1187
|
-
* - "focus": focus-level loop only
|
|
1188
|
-
* - "delivery": delivery-level loop only
|
|
1189
|
-
* - omitted: all three layers (product + focus + delivery)
|
|
1190
|
-
*
|
|
1191
|
-
* Loop data at focus and delivery layers is scoped to the product ID
|
|
1192
|
-
* (layer-based architecture, not per-record).
|
|
1193
|
-
*/
|
|
1194
|
-
registerSourceResolver('loop.context', async (context, params) => {
|
|
1195
|
-
const layerFilter = params.layer;
|
|
1196
|
-
const layers = [];
|
|
1197
|
-
if (!layerFilter || layerFilter === 'product') {
|
|
1198
|
-
layers.push({ entityType: 'product', entityId: context.productId, label: 'Product' });
|
|
1199
|
-
}
|
|
1200
|
-
if (!layerFilter || layerFilter === 'focus') {
|
|
1201
|
-
layers.push({ entityType: 'focus', entityId: context.productId, label: 'Focus' });
|
|
1202
|
-
}
|
|
1203
|
-
if (!layerFilter || layerFilter === 'delivery') {
|
|
1204
|
-
layers.push({ entityType: 'delivery', entityId: context.productId, label: 'Delivery' });
|
|
1205
|
-
}
|
|
1206
|
-
const sections = await Promise.all(layers.map(l => fetchLoopLayerContext(l.entityType, l.entityId, l.label)));
|
|
1207
|
-
const nonEmpty = sections.filter(s => s.trim());
|
|
1208
|
-
if (nonEmpty.length === 0)
|
|
1209
|
-
return '';
|
|
1210
|
-
return nonEmpty.join('\n\n');
|
|
1211
|
-
});
|
|
1212
|
-
/**
|
|
1213
|
-
* delivery.tech_context
|
|
1214
|
-
*
|
|
1215
|
-
* Concatenates the techContext field of every delivery in the focus.
|
|
1216
|
-
*/
|
|
1217
|
-
registerSourceResolver('delivery.tech_context', async (context) => {
|
|
1218
|
-
const deliveries = await getFocusDeliveries(context.focusId);
|
|
1219
|
-
if (deliveries.length === 0)
|
|
1220
|
-
return '';
|
|
1221
|
-
const sections = deliveries
|
|
1222
|
-
.filter((d) => d.techContext)
|
|
1223
|
-
.map((d) => `### ${d.name}\n\n${d.techContext}`);
|
|
1224
|
-
if (sections.length === 0)
|
|
1225
|
-
return '';
|
|
1226
|
-
return `## Technical Context\n\n${sections.join('\n\n')}`;
|
|
1227
|
-
});
|
|
1228
|
-
export const CRT_DUMP_BUDGET_CHARS = 10_000;
|
|
1229
|
-
const NODE_TYPE_HEADINGS = {
|
|
1230
|
-
entity: 'Entities',
|
|
1231
|
-
undesired_effect: 'Undesired Effects (UDEs)',
|
|
1232
|
-
desired_effect: 'Desired Effects (FRT)',
|
|
1233
|
-
injection: 'Injections (FRT)',
|
|
1234
|
-
};
|
|
1235
|
-
const NODE_TYPE_ORDER = [
|
|
1236
|
-
'entity',
|
|
1237
|
-
'undesired_effect',
|
|
1238
|
-
'desired_effect',
|
|
1239
|
-
'injection',
|
|
1240
|
-
];
|
|
1241
|
-
const INJECTION_STATUS_ORDER = [
|
|
1242
|
-
'proposed',
|
|
1243
|
-
'in_progress',
|
|
1244
|
-
'realized',
|
|
1245
|
-
'verified',
|
|
1246
|
-
];
|
|
1247
|
-
const INJECTION_STATUS_HEADINGS = {
|
|
1248
|
-
proposed: 'Injections - Proposed',
|
|
1249
|
-
in_progress: 'Injections - In Progress',
|
|
1250
|
-
realized: 'Injections - Realized',
|
|
1251
|
-
verified: 'Injections - Verified',
|
|
1252
|
-
};
|
|
1253
|
-
function renderNodeLine(n, opts = {}) {
|
|
1254
|
-
const tags = [];
|
|
1255
|
-
if (n.injectionStatus && !opts.suppressInjectionStatusTag)
|
|
1256
|
-
tags.push(n.injectionStatus);
|
|
1257
|
-
if (n.viewScope !== 'both')
|
|
1258
|
-
tags.push(n.viewScope);
|
|
1259
|
-
const tagSuffix = tags.length > 0 ? ` [${tags.join(', ')}]` : '';
|
|
1260
|
-
const evidence = n.evidence ? `\n evidence: ${n.evidence.replace(/\n/g, ' ')}` : '';
|
|
1261
|
-
const dissolves = n.dissolvesObstacle ? `\n dissolves: ${n.dissolvesObstacle.replace(/\n/g, ' ')}` : '';
|
|
1262
|
-
const frtOverlay = n.frtStatement && n.frtStatement !== n.statement
|
|
1263
|
-
? `\n frt: ${n.frtStatement.replace(/\n/g, ' ')}`
|
|
1264
|
-
: '';
|
|
1265
|
-
return `- #${n.seq} ${n.statement}${tagSuffix}${evidence}${dissolves}${frtOverlay}`;
|
|
1266
|
-
}
|
|
1267
|
-
function renderEdgeLine(e, nodeBySeq) {
|
|
1268
|
-
const from = nodeBySeq.get(e.fromNodeId);
|
|
1269
|
-
const to = nodeBySeq.get(e.toNodeId);
|
|
1270
|
-
const fromRef = from ? `#${from.seq}` : '?';
|
|
1271
|
-
const toRef = to ? `#${to.seq}` : '?';
|
|
1272
|
-
return `- ${fromRef} →[${e.edgeType}]→ ${toRef}`;
|
|
1273
|
-
}
|
|
1274
|
-
export function renderRealityTreeDump(tree, nodes, edges, budgetChars) {
|
|
1275
|
-
if (nodes.length === 0)
|
|
1276
|
-
return '';
|
|
1277
|
-
const nodeById = new Map();
|
|
1278
|
-
for (const n of nodes)
|
|
1279
|
-
nodeById.set(n.id, n);
|
|
1280
|
-
const sections = [];
|
|
1281
|
-
sections.push(`### Tree: ${tree.name}`);
|
|
1282
|
-
if (tree.description)
|
|
1283
|
-
sections.push(tree.description);
|
|
1284
|
-
const grouped = {
|
|
1285
|
-
entity: [], undesired_effect: [], desired_effect: [], injection: [],
|
|
1286
|
-
};
|
|
1287
|
-
for (const n of nodes)
|
|
1288
|
-
grouped[n.nodeType].push(n);
|
|
1289
|
-
for (const list of Object.values(grouped))
|
|
1290
|
-
list.sort((a, b) => a.seq - b.seq);
|
|
1291
|
-
for (const t of NODE_TYPE_ORDER) {
|
|
1292
|
-
const list = grouped[t];
|
|
1293
|
-
if (list.length === 0)
|
|
1294
|
-
continue;
|
|
1295
|
-
if (t === 'injection') {
|
|
1296
|
-
const byStatus = new Map();
|
|
1297
|
-
const unstatused = [];
|
|
1298
|
-
for (const inj of list) {
|
|
1299
|
-
if (inj.injectionStatus) {
|
|
1300
|
-
const bucket = byStatus.get(inj.injectionStatus) ?? [];
|
|
1301
|
-
bucket.push(inj);
|
|
1302
|
-
byStatus.set(inj.injectionStatus, bucket);
|
|
1303
|
-
}
|
|
1304
|
-
else {
|
|
1305
|
-
unstatused.push(inj);
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
for (const status of INJECTION_STATUS_ORDER) {
|
|
1309
|
-
const bucket = byStatus.get(status);
|
|
1310
|
-
if (!bucket || bucket.length === 0)
|
|
1311
|
-
continue;
|
|
1312
|
-
sections.push(`\n#### ${INJECTION_STATUS_HEADINGS[status]} (${bucket.length})`);
|
|
1313
|
-
sections.push(bucket.map((n) => renderNodeLine(n, { suppressInjectionStatusTag: true })).join('\n'));
|
|
1314
|
-
}
|
|
1315
|
-
if (unstatused.length > 0) {
|
|
1316
|
-
sections.push(`\n#### Injections - Unstatused (${unstatused.length})`);
|
|
1317
|
-
sections.push(unstatused.map((n) => renderNodeLine(n)).join('\n'));
|
|
1318
|
-
}
|
|
1319
|
-
continue;
|
|
1320
|
-
}
|
|
1321
|
-
sections.push(`\n#### ${NODE_TYPE_HEADINGS[t]} (${list.length})`);
|
|
1322
|
-
sections.push(list.map((n) => renderNodeLine(n)).join('\n'));
|
|
1323
|
-
}
|
|
1324
|
-
if (edges.length > 0) {
|
|
1325
|
-
const causal = edges.filter((e) => e.edgeType === 'sufficient' || e.edgeType === 'and' || e.edgeType === 'targets');
|
|
1326
|
-
if (causal.length > 0) {
|
|
1327
|
-
sections.push(`\n#### Causal Edges (${causal.length})`);
|
|
1328
|
-
sections.push(causal.map((e) => renderEdgeLine(e, nodeById)).join('\n'));
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
// Truncate to budget — preserve the heading + at least one section.
|
|
1332
|
-
let acc = '';
|
|
1333
|
-
for (const section of sections) {
|
|
1334
|
-
const candidate = acc.length === 0 ? section : `${acc}\n${section}`;
|
|
1335
|
-
if (candidate.length > budgetChars && acc.length > 0) {
|
|
1336
|
-
acc += '\n\n_(truncated for token budget)_';
|
|
1337
|
-
break;
|
|
1338
|
-
}
|
|
1339
|
-
acc = candidate;
|
|
1340
|
-
}
|
|
1341
|
-
return acc;
|
|
1342
|
-
}
|
|
1343
|
-
/**
|
|
1344
|
-
* focus.reality_tree
|
|
1345
|
-
*
|
|
1346
|
-
* Renders the focus's reality tree(s) as markdown for static-inspection
|
|
1347
|
-
* agents (ai_inspection verification, drift detectors). Groups nodes by
|
|
1348
|
-
* type, lists causal edges, surfaces injection lifecycle status. Token
|
|
1349
|
-
* budget ~10k chars; truncates with a marker if exceeded.
|
|
1350
|
-
*
|
|
1351
|
-
* This is the CRT dump the ai_inspection runner consumes alongside the
|
|
1352
|
-
* injection's frt_statement and the delivery diff. It is intentionally
|
|
1353
|
-
* verbose so the runner can reason about which CRT statements are at
|
|
1354
|
-
* stake without re-querying.
|
|
1355
|
-
*/
|
|
1356
|
-
export const FOCUS_REALITY_TREE_EMPTY_STATE = '## Focus Reality Tree\n\n_No active reality tree for this focus._ ' +
|
|
1357
|
-
'The intent-vs-reality graph (CRT + FRT overlays) has not been built yet -- ' +
|
|
1358
|
-
'walk the focus statement and capture UDEs + entities before scoping further work.';
|
|
1359
|
-
registerSourceResolver('focus.reality_tree', async (context) => {
|
|
1360
|
-
try {
|
|
1361
|
-
const treesResp = await callApi('reality_tree_list', {
|
|
1362
|
-
focusId: context.focusId,
|
|
1363
|
-
});
|
|
1364
|
-
const trees = (treesResp.items ?? []).filter((t) => t.status === 'active');
|
|
1365
|
-
if (trees.length === 0)
|
|
1366
|
-
return FOCUS_REALITY_TREE_EMPTY_STATE;
|
|
1367
|
-
const sections = [];
|
|
1368
|
-
let used = 0;
|
|
1369
|
-
for (const tree of trees) {
|
|
1370
|
-
if (used >= CRT_DUMP_BUDGET_CHARS)
|
|
1371
|
-
break;
|
|
1372
|
-
const remaining = CRT_DUMP_BUDGET_CHARS - used;
|
|
1373
|
-
const [nodesResp, edgesResp] = await Promise.all([
|
|
1374
|
-
callApi('reality_tree_node_list', { treeId: tree.id, status: 'active' }),
|
|
1375
|
-
callApi('reality_tree_edge_list', { treeId: tree.id }),
|
|
1376
|
-
]);
|
|
1377
|
-
const dump = renderRealityTreeDump(tree, nodesResp.items ?? [], edgesResp.items ?? [], remaining);
|
|
1378
|
-
if (dump.length === 0)
|
|
1379
|
-
continue;
|
|
1380
|
-
sections.push(dump);
|
|
1381
|
-
used += dump.length;
|
|
1382
|
-
}
|
|
1383
|
-
if (sections.length === 0)
|
|
1384
|
-
return FOCUS_REALITY_TREE_EMPTY_STATE;
|
|
1385
|
-
return `## Focus Reality Tree (CRT + FRT overlays)\n\n${sections.join('\n\n')}`;
|
|
1386
|
-
}
|
|
1387
|
-
catch (err) {
|
|
1388
|
-
console.warn(`[assembly-engine] focus.reality_tree: failed: ${err.message}`);
|
|
1389
|
-
return '';
|
|
1390
|
-
}
|
|
1391
|
-
});
|
|
1392
|
-
// ── Per-delivery FRT context resolvers (loop.*) ─────────────────────
|
|
1393
|
-
//
|
|
1394
|
-
// These resolvers operate per-delivery: they look up each delivery in
|
|
1395
|
-
// context.deliveryIds, follow its delivery.injection_id to the
|
|
1396
|
-
// reality-tree injection node, and render the surrounding FRT context
|
|
1397
|
-
// the agent needs to know "what FRT statement this delivery is meant
|
|
1398
|
-
// to flip, and which UDEs flip upstream as a result."
|
|
1399
|
-
//
|
|
1400
|
-
// All resolvers fail-soft: errors return '' and log to console.warn.
|
|
1401
|
-
// When a delivery has no injection link (injectionId is null/empty),
|
|
1402
|
-
// it contributes nothing to the output -- the resolver returns ''
|
|
1403
|
-
// rather than rendering an empty section.
|
|
1404
|
-
/** Maximum BFS depth for upstream UDE walk in loop.upstream_udes. */
|
|
1405
|
-
const MAX_UPSTREAM_DEPTH = 3;
|
|
1406
|
-
/**
|
|
1407
|
-
* Walk context.deliveryIds, return [deliveryId, injectionId] pairs for those
|
|
1408
|
-
* with non-empty injection links. Empty array on lookup failure.
|
|
1409
|
-
*/
|
|
1410
|
-
async function getDeliveryInjectionPairs(context) {
|
|
1411
|
-
const deliveries = await getFocusDeliveries(context.focusId);
|
|
1412
|
-
const wantedIds = new Set(context.deliveryIds);
|
|
1413
|
-
return deliveries
|
|
1414
|
-
.filter((d) => wantedIds.has(d.id))
|
|
1415
|
-
.filter((d) => typeof d.injectionId === 'string' && d.injectionId.length > 0)
|
|
1416
|
-
.map((d) => ({ deliveryId: d.id, deliveryName: d.name, injectionId: d.injectionId }));
|
|
1417
|
-
}
|
|
1418
|
-
/**
|
|
1419
|
-
* Fetch a batch of injection nodes by their IDs via reality_tree_injection_context.
|
|
1420
|
-
* Returns a Map keyed by injection node id.
|
|
1421
|
-
*/
|
|
1422
|
-
async function fetchInjectionBundles(injectionIds) {
|
|
1423
|
-
const out = new Map();
|
|
1424
|
-
if (injectionIds.length === 0)
|
|
1425
|
-
return out;
|
|
1426
|
-
const resp = await callApi('reality_tree_injection_context', {
|
|
1427
|
-
nodeIds: injectionIds,
|
|
1428
|
-
});
|
|
1429
|
-
for (const item of resp.items ?? []) {
|
|
1430
|
-
out.set(item.injection.id, item);
|
|
1431
|
-
}
|
|
1432
|
-
return out;
|
|
1433
|
-
}
|
|
1434
|
-
/**
|
|
1435
|
-
* loop.injection
|
|
1436
|
-
*
|
|
1437
|
-
* For each delivery in context.deliveryIds with a linked injection, render
|
|
1438
|
-
* the injection node: statement, lifecycle status, what obstacle it
|
|
1439
|
-
* dissolves. Plain markdown; no upstream/downstream walk (that lives in
|
|
1440
|
-
* loop.upstream_udes / loop.expected_effects).
|
|
1441
|
-
*/
|
|
1442
|
-
registerSourceResolver('loop.injection', async (context) => {
|
|
1443
|
-
try {
|
|
1444
|
-
const pairs = await getDeliveryInjectionPairs(context);
|
|
1445
|
-
if (pairs.length === 0)
|
|
1446
|
-
return '';
|
|
1447
|
-
const bundles = await fetchInjectionBundles(pairs.map((p) => p.injectionId));
|
|
1448
|
-
if (bundles.size === 0)
|
|
1449
|
-
return '';
|
|
1450
|
-
const sections = [];
|
|
1451
|
-
for (const { deliveryName, injectionId } of pairs) {
|
|
1452
|
-
const bundle = bundles.get(injectionId);
|
|
1453
|
-
if (!bundle)
|
|
1454
|
-
continue;
|
|
1455
|
-
const inj = bundle.injection;
|
|
1456
|
-
const statusTag = inj.injectionStatus ? ` [${inj.injectionStatus}]` : '';
|
|
1457
|
-
const dissolvesLine = inj.dissolvesObstacle
|
|
1458
|
-
? `- **Dissolves obstacle:** ${inj.dissolvesObstacle}`
|
|
1459
|
-
: null;
|
|
1460
|
-
const lines = [
|
|
1461
|
-
`### ${deliveryName}`,
|
|
1462
|
-
'',
|
|
1463
|
-
`- **Injection:** ${inj.statement}${statusTag}`,
|
|
1464
|
-
`- **Injection status:** ${inj.injectionStatus ?? '_not set_'}`,
|
|
1465
|
-
];
|
|
1466
|
-
if (dissolvesLine)
|
|
1467
|
-
lines.push(dissolvesLine);
|
|
1468
|
-
sections.push(lines.join('\n'));
|
|
1469
|
-
}
|
|
1470
|
-
if (sections.length === 0)
|
|
1471
|
-
return '';
|
|
1472
|
-
return `## Loop Injection (per delivery)\n\n${sections.join('\n\n')}`;
|
|
1473
|
-
}
|
|
1474
|
-
catch (err) {
|
|
1475
|
-
console.warn(`[assembly] loop.injection failed: ${err.message}`);
|
|
1476
|
-
return '';
|
|
1477
|
-
}
|
|
1478
|
-
});
|
|
1479
|
-
/**
|
|
1480
|
-
* loop.frt_statement
|
|
1481
|
-
*
|
|
1482
|
-
* Returns the linked injection's frt_statement field as plain text
|
|
1483
|
-
* (no markdown headers). When multiple deliveries are in scope, the
|
|
1484
|
-
* statements are joined with blank lines so the agent sees each
|
|
1485
|
-
* delivery's target state. Empty string when no delivery has an
|
|
1486
|
-
* injection link or no injection has an FRT statement set.
|
|
1487
|
-
*/
|
|
1488
|
-
registerSourceResolver('loop.frt_statement', async (context) => {
|
|
1489
|
-
try {
|
|
1490
|
-
const pairs = await getDeliveryInjectionPairs(context);
|
|
1491
|
-
if (pairs.length === 0)
|
|
1492
|
-
return '';
|
|
1493
|
-
const bundles = await fetchInjectionBundles(pairs.map((p) => p.injectionId));
|
|
1494
|
-
if (bundles.size === 0)
|
|
1495
|
-
return '';
|
|
1496
|
-
const lines = [];
|
|
1497
|
-
for (const { injectionId } of pairs) {
|
|
1498
|
-
const bundle = bundles.get(injectionId);
|
|
1499
|
-
if (!bundle)
|
|
1500
|
-
continue;
|
|
1501
|
-
const frt = bundle.injection.frtStatement;
|
|
1502
|
-
if (typeof frt !== 'string' || frt.trim().length === 0)
|
|
1503
|
-
continue;
|
|
1504
|
-
lines.push(frt.trim());
|
|
1505
|
-
}
|
|
1506
|
-
if (lines.length === 0)
|
|
1507
|
-
return '';
|
|
1508
|
-
return lines.join('\n\n');
|
|
1509
|
-
}
|
|
1510
|
-
catch (err) {
|
|
1511
|
-
console.warn(`[assembly] loop.frt_statement failed: ${err.message}`);
|
|
1512
|
-
return '';
|
|
1513
|
-
}
|
|
1514
|
-
});
|
|
1515
|
-
/**
|
|
1516
|
-
* loop.expected_effects
|
|
1517
|
-
*
|
|
1518
|
-
* For each delivery with a linked injection, walk the 'targets' edges
|
|
1519
|
-
* from injection -> targeted UDE/obstacle nodes; for each target with an
|
|
1520
|
-
* FRT overlay (frt_statement and/or frt_node_type), render its current
|
|
1521
|
-
* statement and what it becomes in the FRT. Targets lacking overlays are
|
|
1522
|
-
* skipped (no expected effect to advertise).
|
|
1523
|
-
*/
|
|
1524
|
-
registerSourceResolver('loop.expected_effects', async (context) => {
|
|
1525
|
-
try {
|
|
1526
|
-
const pairs = await getDeliveryInjectionPairs(context);
|
|
1527
|
-
if (pairs.length === 0)
|
|
1528
|
-
return '';
|
|
1529
|
-
const bundles = await fetchInjectionBundles(pairs.map((p) => p.injectionId));
|
|
1530
|
-
if (bundles.size === 0)
|
|
1531
|
-
return '';
|
|
1532
|
-
const sections = [];
|
|
1533
|
-
for (const { deliveryName, injectionId } of pairs) {
|
|
1534
|
-
const bundle = bundles.get(injectionId);
|
|
1535
|
-
if (!bundle)
|
|
1536
|
-
continue;
|
|
1537
|
-
// Fetch nodes + edges for this injection's tree so we can walk
|
|
1538
|
-
// targets edges and inspect the targeted nodes' overlays.
|
|
1539
|
-
const treeId = bundle.injection.realityTreeId;
|
|
1540
|
-
const [nodesResp, edgesResp] = await Promise.all([
|
|
1541
|
-
callApi('reality_tree_node_list', { treeId, status: 'active' }),
|
|
1542
|
-
callApi('reality_tree_edge_list', { treeId }),
|
|
1543
|
-
]);
|
|
1544
|
-
const nodes = nodesResp.items ?? [];
|
|
1545
|
-
const edges = edgesResp.items ?? [];
|
|
1546
|
-
const nodeById = new Map();
|
|
1547
|
-
for (const n of nodes)
|
|
1548
|
-
nodeById.set(n.id, n);
|
|
1549
|
-
const targetIds = edges
|
|
1550
|
-
.filter((e) => e.edgeType === 'targets' && e.fromNodeId === injectionId)
|
|
1551
|
-
.map((e) => e.toNodeId);
|
|
1552
|
-
const targetLines = [];
|
|
1553
|
-
for (const targetId of targetIds) {
|
|
1554
|
-
const node = nodeById.get(targetId);
|
|
1555
|
-
if (!node)
|
|
1556
|
-
continue;
|
|
1557
|
-
const hasOverlay = (node.frtStatement && node.frtStatement.trim().length > 0)
|
|
1558
|
-
|| (node.frtNodeType && node.frtNodeType.trim().length > 0);
|
|
1559
|
-
if (!hasOverlay)
|
|
1560
|
-
continue;
|
|
1561
|
-
const currentType = node.nodeType;
|
|
1562
|
-
const frtType = node.frtNodeType ?? currentType;
|
|
1563
|
-
const frtStatement = node.frtStatement ?? node.statement;
|
|
1564
|
-
targetLines.push(`- #${node.seq} **Current (${currentType}):** ${node.statement}\n **FRT (${frtType}):** ${frtStatement}`);
|
|
1565
|
-
}
|
|
1566
|
-
if (targetLines.length === 0)
|
|
1567
|
-
continue;
|
|
1568
|
-
sections.push(`### ${deliveryName}\n\n${targetLines.join('\n')}`);
|
|
1569
|
-
}
|
|
1570
|
-
if (sections.length === 0)
|
|
1571
|
-
return '';
|
|
1572
|
-
return `## Expected Effects (per delivery)\n\n${sections.join('\n\n')}`;
|
|
1573
|
-
}
|
|
1574
|
-
catch (err) {
|
|
1575
|
-
console.warn(`[assembly] loop.expected_effects failed: ${err.message}`);
|
|
1576
|
-
return '';
|
|
1577
|
-
}
|
|
1578
|
-
});
|
|
1579
|
-
/**
|
|
1580
|
-
* loop.upstream_udes
|
|
1581
|
-
*
|
|
1582
|
-
* For each delivery's injection, find the nodes that the injection
|
|
1583
|
-
* targets (via 'targets' edges), then BFS upstream from those targets
|
|
1584
|
-
* along causal edges ('sufficient' and 'and' edge kinds) up to
|
|
1585
|
-
* MAX_UPSTREAM_DEPTH hops. Returns a markdown list of upstream UDE/
|
|
1586
|
-
* entity nodes the injection is indirectly meant to address. Cycle-safe.
|
|
1587
|
-
*/
|
|
1588
|
-
registerSourceResolver('loop.upstream_udes', async (context) => {
|
|
1589
|
-
try {
|
|
1590
|
-
const pairs = await getDeliveryInjectionPairs(context);
|
|
1591
|
-
if (pairs.length === 0)
|
|
1592
|
-
return '';
|
|
1593
|
-
const bundles = await fetchInjectionBundles(pairs.map((p) => p.injectionId));
|
|
1594
|
-
if (bundles.size === 0)
|
|
1595
|
-
return '';
|
|
1596
|
-
const sections = [];
|
|
1597
|
-
for (const { deliveryName, injectionId } of pairs) {
|
|
1598
|
-
const bundle = bundles.get(injectionId);
|
|
1599
|
-
if (!bundle)
|
|
1600
|
-
continue;
|
|
1601
|
-
const treeId = bundle.injection.realityTreeId;
|
|
1602
|
-
const [nodesResp, edgesResp] = await Promise.all([
|
|
1603
|
-
callApi('reality_tree_node_list', { treeId, status: 'active' }),
|
|
1604
|
-
callApi('reality_tree_edge_list', { treeId }),
|
|
1605
|
-
]);
|
|
1606
|
-
const nodes = nodesResp.items ?? [];
|
|
1607
|
-
const edges = edgesResp.items ?? [];
|
|
1608
|
-
const nodeById = new Map();
|
|
1609
|
-
for (const n of nodes)
|
|
1610
|
-
nodeById.set(n.id, n);
|
|
1611
|
-
// Seed: the nodes the injection targets directly.
|
|
1612
|
-
const seeds = edges
|
|
1613
|
-
.filter((e) => e.edgeType === 'targets' && e.fromNodeId === injectionId)
|
|
1614
|
-
.map((e) => e.toNodeId);
|
|
1615
|
-
if (seeds.length === 0)
|
|
1616
|
-
continue;
|
|
1617
|
-
// BFS upstream along causal edges: walk edges where to_node_id is
|
|
1618
|
-
// in the current frontier; the from_node_id of those edges is the
|
|
1619
|
-
// upstream node. Bounded by MAX_UPSTREAM_DEPTH and cycle-safe via
|
|
1620
|
-
// visited set. Seeds themselves are excluded from output (we want
|
|
1621
|
-
// their upstream, not the seeds).
|
|
1622
|
-
const visited = new Set(seeds);
|
|
1623
|
-
const collected = [];
|
|
1624
|
-
let frontier = new Set(seeds);
|
|
1625
|
-
for (let depth = 0; depth < MAX_UPSTREAM_DEPTH; depth++) {
|
|
1626
|
-
if (frontier.size === 0)
|
|
1627
|
-
break;
|
|
1628
|
-
const next = new Set();
|
|
1629
|
-
for (const edge of edges) {
|
|
1630
|
-
if (edge.edgeType !== 'sufficient' && edge.edgeType !== 'and')
|
|
1631
|
-
continue;
|
|
1632
|
-
if (!frontier.has(edge.toNodeId))
|
|
1633
|
-
continue;
|
|
1634
|
-
const upId = edge.fromNodeId;
|
|
1635
|
-
if (visited.has(upId))
|
|
1636
|
-
continue;
|
|
1637
|
-
visited.add(upId);
|
|
1638
|
-
next.add(upId);
|
|
1639
|
-
const upNode = nodeById.get(upId);
|
|
1640
|
-
if (upNode)
|
|
1641
|
-
collected.push(upNode);
|
|
1642
|
-
}
|
|
1643
|
-
frontier = next;
|
|
1644
|
-
}
|
|
1645
|
-
if (collected.length === 0)
|
|
1646
|
-
continue;
|
|
1647
|
-
collected.sort((a, b) => a.seq - b.seq);
|
|
1648
|
-
const lines = collected.map((n) => {
|
|
1649
|
-
const typeTag = n.nodeType !== 'entity' ? ` [${n.nodeType}]` : '';
|
|
1650
|
-
return `- #${n.seq} ${n.statement}${typeTag}`;
|
|
1651
|
-
});
|
|
1652
|
-
sections.push(`### ${deliveryName}\n\n${lines.join('\n')}`);
|
|
1653
|
-
}
|
|
1654
|
-
if (sections.length === 0)
|
|
1655
|
-
return '';
|
|
1656
|
-
return `## Upstream UDEs (per delivery, max depth ${MAX_UPSTREAM_DEPTH})\n\n${sections.join('\n\n')}`;
|
|
1657
|
-
}
|
|
1658
|
-
catch (err) {
|
|
1659
|
-
console.warn(`[assembly] loop.upstream_udes failed: ${err.message}`);
|
|
1660
|
-
return '';
|
|
1661
|
-
}
|
|
1662
|
-
});
|
|
1663
|
-
/**
|
|
1664
|
-
* loop.delivery_index
|
|
1665
|
-
*
|
|
1666
|
-
* The lead's per-delivery INDEX: one line per in-focus delivery with a linked
|
|
1667
|
-
* injection, of the form
|
|
1668
|
-
* - <delivery> -> injection #<seq> -> Now: <ude> -> Target: <frt> -> pull: <call>
|
|
1669
|
-
* where the pull handle names telora_reality_tree_injection_context(seq=<seq>),
|
|
1670
|
-
* the on-demand call that returns the delivery's full differential.
|
|
1671
|
-
*
|
|
1672
|
-
* This REPLACES the bulk per-delivery push (loop.injection / loop.expected_effects /
|
|
1673
|
-
* loop.upstream_udes, each of which renders a full section per delivery-injection
|
|
1674
|
-
* pair) so the lead's context is flat in focus size -- one line per delivery, not
|
|
1675
|
-
* N full differentials. It is also the structural guarantee for the worker handoff:
|
|
1676
|
-
* the lead is always staring at each delivery's slice handle.
|
|
1677
|
-
*/
|
|
1678
|
-
registerSourceResolver('loop.delivery_index', async (context) => {
|
|
1679
|
-
try {
|
|
1680
|
-
const pairs = await getDeliveryInjectionPairs(context);
|
|
1681
|
-
if (pairs.length === 0)
|
|
1682
|
-
return '';
|
|
1683
|
-
const bundles = await fetchInjectionBundles(pairs.map((p) => p.injectionId));
|
|
1684
|
-
if (bundles.size === 0)
|
|
1685
|
-
return '';
|
|
1686
|
-
const lines = [];
|
|
1687
|
-
for (const { deliveryName, injectionId } of pairs) {
|
|
1688
|
-
const bundle = bundles.get(injectionId);
|
|
1689
|
-
if (!bundle)
|
|
1690
|
-
continue;
|
|
1691
|
-
const seq = bundle.injection.seq;
|
|
1692
|
-
const nowTargets = bundle.nowTargets ?? [];
|
|
1693
|
-
let nowTarget;
|
|
1694
|
-
if (nowTargets.length > 0) {
|
|
1695
|
-
const first = nowTargets[0];
|
|
1696
|
-
const target = first.target && first.target.trim().length > 0
|
|
1697
|
-
? first.target
|
|
1698
|
-
: '(no FRT overlay yet)';
|
|
1699
|
-
nowTarget = `Now: ${first.now} -> Target: ${target}`;
|
|
1700
|
-
if (nowTargets.length > 1)
|
|
1701
|
-
nowTarget += ` (+${nowTargets.length - 1} more)`;
|
|
1702
|
-
}
|
|
1703
|
-
else {
|
|
1704
|
-
nowTarget = `(injection: ${bundle.injection.statement})`;
|
|
1705
|
-
}
|
|
1706
|
-
lines.push(`- ${deliveryName} -> injection #${seq} -> ${nowTarget} ` +
|
|
1707
|
-
`-> pull: telora_reality_tree_injection_context(seq=${seq})`);
|
|
1708
|
-
}
|
|
1709
|
-
if (lines.length === 0)
|
|
1710
|
-
return '';
|
|
1711
|
-
const header = [
|
|
1712
|
-
'## Per-Delivery Index',
|
|
1713
|
-
'',
|
|
1714
|
-
'One line per delivery: the injection it materializes, its Now->Target, and the',
|
|
1715
|
-
'pull handle that returns the full differential on demand. This is your handoff',
|
|
1716
|
-
'surface: when you spawn a worker for a delivery, call its pull handle',
|
|
1717
|
-
'(telora_reality_tree_injection_context with the injection seq) and embed the',
|
|
1718
|
-
"returned Now->Target in the worker's task prompt so the worker begins holding",
|
|
1719
|
-
"its delivery's start->end context.",
|
|
1720
|
-
'',
|
|
1721
|
-
];
|
|
1722
|
-
return header.concat(lines).join('\n');
|
|
1723
|
-
}
|
|
1724
|
-
catch (err) {
|
|
1725
|
-
console.warn(`[assembly] loop.delivery_index failed: ${err.message}`);
|
|
1726
|
-
return '';
|
|
1727
|
-
}
|
|
1728
|
-
});
|
|
1729
|
-
/**
|
|
1730
|
-
* Best-effort cycle window. Returns the previous cycle's completed_at as
|
|
1731
|
-
* the lower bound for "things that happened in the current cycle". Null
|
|
1732
|
-
* means "no prior cycle, include everything since focus start". Failures
|
|
1733
|
-
* fall back to null so a missing audit_runs table or transient API error
|
|
1734
|
-
* does not blank out the resolver output.
|
|
1735
|
-
*/
|
|
1736
|
-
async function getAuditCycleLowerBound(focusId) {
|
|
1737
|
-
try {
|
|
1738
|
-
const result = await callApi('daemon_get_recent_audit_run_for_focus', { focusId });
|
|
1739
|
-
return result.auditRun?.completedAt ?? null;
|
|
1740
|
-
}
|
|
1741
|
-
catch {
|
|
1742
|
-
return null;
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
/**
|
|
1746
|
-
* retired_injections
|
|
1747
|
-
*
|
|
1748
|
-
* Lists injections retired in the focus's current audit cycle. Each entry
|
|
1749
|
-
* includes the dissolvesObstacle text and the UDE statements the injection
|
|
1750
|
-
* targeted (via 'targets' edges, looked up from the retired-injection's
|
|
1751
|
-
* tree). Failures fail soft -- return ''.
|
|
1752
|
-
*/
|
|
1753
|
-
registerSourceResolver('retired_injections', async (context) => {
|
|
1754
|
-
try {
|
|
1755
|
-
const treesResp = await callApi('reality_tree_list', {
|
|
1756
|
-
focusId: context.focusId,
|
|
1757
|
-
});
|
|
1758
|
-
const trees = (treesResp.items ?? []).filter((t) => t.status === 'active');
|
|
1759
|
-
if (trees.length === 0)
|
|
1760
|
-
return '';
|
|
1761
|
-
const since = await getAuditCycleLowerBound(context.focusId);
|
|
1762
|
-
const sections = [];
|
|
1763
|
-
for (const tree of trees) {
|
|
1764
|
-
const [retiredResp, edgesResp, nodesResp] = await Promise.all([
|
|
1765
|
-
callApi('reality_tree_node_list', {
|
|
1766
|
-
treeId: tree.id, nodeType: 'injection', status: 'retired',
|
|
1767
|
-
}),
|
|
1768
|
-
callApi('reality_tree_edge_list', { treeId: tree.id }),
|
|
1769
|
-
// Pull the active node universe so we can resolve target statements
|
|
1770
|
-
// even if a target was itself retired alongside the injection.
|
|
1771
|
-
callApi('reality_tree_node_list', { treeId: tree.id, status: 'active' }),
|
|
1772
|
-
]);
|
|
1773
|
-
const retired = retiredResp.items ?? [];
|
|
1774
|
-
if (retired.length === 0)
|
|
1775
|
-
continue;
|
|
1776
|
-
const filtered = since
|
|
1777
|
-
? retired.filter((n) => {
|
|
1778
|
-
// updated_at is not on the typed surface but the row carries it;
|
|
1779
|
-
// fall back to including the node if the field isn't present so
|
|
1780
|
-
// we don't accidentally drop a real retirement.
|
|
1781
|
-
const updatedAt = n.updatedAt;
|
|
1782
|
-
return !updatedAt || updatedAt >= since;
|
|
1783
|
-
})
|
|
1784
|
-
: retired;
|
|
1785
|
-
if (filtered.length === 0)
|
|
1786
|
-
continue;
|
|
1787
|
-
const allNodes = [...(nodesResp.items ?? []), ...retired];
|
|
1788
|
-
const nodeById = new Map();
|
|
1789
|
-
for (const n of allNodes)
|
|
1790
|
-
nodeById.set(n.id, n);
|
|
1791
|
-
const targetsBySource = new Map();
|
|
1792
|
-
for (const e of edgesResp.items ?? []) {
|
|
1793
|
-
if (e.edgeType !== 'targets')
|
|
1794
|
-
continue;
|
|
1795
|
-
const list = targetsBySource.get(e.fromNodeId) ?? [];
|
|
1796
|
-
list.push(e.toNodeId);
|
|
1797
|
-
targetsBySource.set(e.fromNodeId, list);
|
|
1798
|
-
}
|
|
1799
|
-
const lines = [`### Tree: ${tree.name}`];
|
|
1800
|
-
for (const inj of filtered.sort((a, b) => a.seq - b.seq)) {
|
|
1801
|
-
lines.push('', `- **#${inj.seq} ${inj.statement}**`);
|
|
1802
|
-
if (inj.dissolvesObstacle) {
|
|
1803
|
-
lines.push(` - dissolves: ${inj.dissolvesObstacle.replace(/\n/g, ' ')}`);
|
|
1804
|
-
}
|
|
1805
|
-
const targetIds = targetsBySource.get(inj.id) ?? [];
|
|
1806
|
-
for (const tid of targetIds) {
|
|
1807
|
-
const t = nodeById.get(tid);
|
|
1808
|
-
if (!t)
|
|
1809
|
-
continue;
|
|
1810
|
-
lines.push(` - target: #${t.seq} (${t.nodeType}) ${t.statement.replace(/\n/g, ' ')}`);
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
sections.push(lines.join('\n'));
|
|
1814
|
-
}
|
|
1815
|
-
if (sections.length === 0)
|
|
1816
|
-
return '';
|
|
1817
|
-
return `## Retired Injections (current cycle)\n\n${sections.join('\n\n')}`;
|
|
1818
|
-
}
|
|
1819
|
-
catch (err) {
|
|
1820
|
-
console.warn(`[assembly] retired_injections failed: ${err.message}`);
|
|
1821
|
-
return '';
|
|
1822
|
-
}
|
|
1823
|
-
});
|
|
1824
|
-
/**
|
|
1825
|
-
* agent_session_summaries
|
|
1826
|
-
*
|
|
1827
|
-
* Returns the most recent agent_sessions.last_narration per delivery
|
|
1828
|
-
* within the focus, grouped by delivery name. Fails soft.
|
|
1829
|
-
*/
|
|
1830
|
-
registerSourceResolver('agent_session_summaries', async (context) => {
|
|
1831
|
-
try {
|
|
1832
|
-
const result = await callApi('daemon_get_agent_session_summaries_for_focus', { focusId: context.focusId });
|
|
1833
|
-
const summaries = result.summaries ?? [];
|
|
1834
|
-
if (summaries.length === 0)
|
|
1835
|
-
return '';
|
|
1836
|
-
summaries.sort((a, b) => a.deliveryName.localeCompare(b.deliveryName));
|
|
1837
|
-
const sections = summaries.map((s) => {
|
|
1838
|
-
const at = s.lastNarrationAt ? ` (${s.lastNarrationAt})` : '';
|
|
1839
|
-
const narration = s.lastNarration.trim() || '_no narration captured_';
|
|
1840
|
-
return `### ${s.deliveryName}${at}\n\n${narration}`;
|
|
1841
|
-
});
|
|
1842
|
-
return `## Team Narrations (per delivery)\n\n${sections.join('\n\n')}`;
|
|
1843
|
-
}
|
|
1844
|
-
catch (err) {
|
|
1845
|
-
console.warn(`[assembly] agent_session_summaries failed: ${err.message}`);
|
|
1846
|
-
return '';
|
|
1847
|
-
}
|
|
1848
|
-
});
|
|
1849
|
-
/**
|
|
1850
|
-
* agent_escalations
|
|
1851
|
-
*
|
|
1852
|
-
* Escalations filed against the focus's deliveries in the current cycle.
|
|
1853
|
-
* Window lower bound is the prior audit_runs.completed_at (best effort).
|
|
1854
|
-
*/
|
|
1855
|
-
registerSourceResolver('agent_escalations', async (context) => {
|
|
1856
|
-
try {
|
|
1857
|
-
const since = await getAuditCycleLowerBound(context.focusId);
|
|
1858
|
-
const params = { focusId: context.focusId };
|
|
1859
|
-
if (since)
|
|
1860
|
-
params.sinceIso = since;
|
|
1861
|
-
const result = await callApi('daemon_get_agent_escalations_for_focus', params);
|
|
1862
|
-
const escalations = result.escalations ?? [];
|
|
1863
|
-
if (escalations.length === 0)
|
|
1864
|
-
return '';
|
|
1865
|
-
const lines = escalations.map((e) => {
|
|
1866
|
-
const kindLabel = e.escalationKind ?? e.reasonType;
|
|
1867
|
-
const description = e.description.replace(/\n/g, ' ');
|
|
1868
|
-
const truncated = description.length > 240 ? `${description.slice(0, 237)}...` : description;
|
|
1869
|
-
return `- **${kindLabel}** [${e.status}] (${e.createdAt}): ${truncated}`;
|
|
1870
|
-
});
|
|
1871
|
-
return `## Agent Escalations (current cycle)\n\n${lines.join('\n')}`;
|
|
1872
|
-
}
|
|
1873
|
-
catch (err) {
|
|
1874
|
-
console.warn(`[assembly] agent_escalations failed: ${err.message}`);
|
|
1875
|
-
return '';
|
|
1876
|
-
}
|
|
1877
|
-
});
|
|
1878
|
-
/**
|
|
1879
|
-
* reality_tree.snapshot
|
|
1880
|
-
*
|
|
1881
|
-
* Returns the focus's reality tree(s) as a structured text dump for the
|
|
1882
|
-
* audit assessor. Same shape as focus.reality_tree but with the "CRT Dump"
|
|
1883
|
-
* heading renamed to "Snapshot" so the audit prompt distinguishes its
|
|
1884
|
-
* "current graph" input from other tree-related sections. Fails soft.
|
|
1885
|
-
*/
|
|
1886
|
-
registerSourceResolver('reality_tree.snapshot', async (context) => {
|
|
1887
|
-
try {
|
|
1888
|
-
const treesResp = await callApi('reality_tree_list', {
|
|
1889
|
-
focusId: context.focusId,
|
|
1890
|
-
});
|
|
1891
|
-
const trees = (treesResp.items ?? []).filter((t) => t.status === 'active');
|
|
1892
|
-
if (trees.length === 0)
|
|
1893
|
-
return '';
|
|
1894
|
-
const sections = [];
|
|
1895
|
-
let used = 0;
|
|
1896
|
-
for (const tree of trees) {
|
|
1897
|
-
if (used >= CRT_DUMP_BUDGET_CHARS)
|
|
1898
|
-
break;
|
|
1899
|
-
const remaining = CRT_DUMP_BUDGET_CHARS - used;
|
|
1900
|
-
const [nodesResp, edgesResp] = await Promise.all([
|
|
1901
|
-
callApi('reality_tree_node_list', { treeId: tree.id, status: 'active' }),
|
|
1902
|
-
callApi('reality_tree_edge_list', { treeId: tree.id }),
|
|
1903
|
-
]);
|
|
1904
|
-
const dump = renderRealityTreeDump(tree, nodesResp.items ?? [], edgesResp.items ?? [], remaining);
|
|
1905
|
-
if (dump.length === 0)
|
|
1906
|
-
continue;
|
|
1907
|
-
sections.push(dump);
|
|
1908
|
-
used += dump.length;
|
|
1909
|
-
}
|
|
1910
|
-
if (sections.length === 0)
|
|
1911
|
-
return '';
|
|
1912
|
-
return `## Reality Tree Snapshot\n\n${sections.join('\n\n')}`;
|
|
1913
|
-
}
|
|
1914
|
-
catch (err) {
|
|
1915
|
-
console.warn(`[assembly] reality_tree.snapshot failed: ${err.message}`);
|
|
1916
|
-
return '';
|
|
1917
|
-
}
|
|
1918
|
-
});
|
|
4
|
+
* This module is now a thin shim. The resolver bodies live in `resolvers/`
|
|
5
|
+
* (one module per resolver) with cross-resolver helpers in `resolvers/shared/`.
|
|
6
|
+
* Importing this module loads `resolvers/index.js`, whose side-effect imports
|
|
7
|
+
* register ALL resolvers at module load time -- exactly as before.
|
|
8
|
+
*
|
|
9
|
+
* The named public surface (helpers + types consumed by other source files and
|
|
10
|
+
* tests) is re-exported from the barrel so existing imports of
|
|
11
|
+
* `assembly-resolvers.js` keep working unchanged:
|
|
12
|
+
* - values: MAX_DIFF_BYTES, getGitDiff, CRT_DUMP_BUDGET_CHARS,
|
|
13
|
+
* renderRealityTreeDump, FOCUS_REALITY_TREE_EMPTY_STATE
|
|
14
|
+
* - types: RealityTreeRow, RealityTreeListResponse, RealityNodeRow,
|
|
15
|
+
* RealityEdgeRow, NodeListResponse, EdgeListResponse
|
|
16
|
+
*/
|
|
17
|
+
import './resolvers/index.js';
|
|
18
|
+
export * from './resolvers/index.js';
|
|
1919
19
|
//# sourceMappingURL=assembly-resolvers.js.map
|