@runfusion/fusion 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/bin.js +18646 -15669
- package/dist/client/assets/AgentDetailView-Cv-vgOj3.js +18 -0
- package/dist/client/assets/{AgentsView-BkB9FiMT.js → AgentsView-D6Zi5zfP.js} +4 -4
- package/dist/client/assets/ChatView-CAHjY9uO.js +1 -0
- package/dist/client/assets/{DevServerView-BkvtjZBa.js → DevServerView--_WBvIDQ.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-BK-KbnhP.js → DirectoryPicker-xedtR-Rd.js} +1 -1
- package/dist/client/assets/{DocumentsView-BEg1CQAk.js → DocumentsView-Bg2oaZks.js} +1 -1
- package/dist/client/assets/{EvalsView-Berf9bQm.js → EvalsView-B3uOCXfr.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-jcInE50G.js → ExperimentalAgentOnboardingModal-Bx6yXVS5.js} +1 -1
- package/dist/client/assets/{InsightsView-BX5bSF1J.js → InsightsView-Q1zvtF4F.js} +1 -1
- package/dist/client/assets/MemoryView-xcN_eouf.js +2 -0
- package/dist/client/assets/MemoryView-zaXewZzi.css +1 -0
- package/dist/client/assets/{NodesView-DLUOBLf6.js → NodesView-RxXg58_Q.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-COlJf0Kx.js → PiExtensionsManager-Cc8aAZXg.js} +2 -2
- package/dist/client/assets/PluginManager-BEkyBajl.js +1 -0
- package/dist/client/assets/{ResearchView-BzCcDAS4.css → ResearchView-BEI4ZSGs.css} +1 -1
- package/dist/client/assets/ResearchView-CERNf7sJ.js +1 -0
- package/dist/client/assets/{SettingsModal-yRqM4DV8.js → SettingsModal-B1r0yASu.js} +1 -1
- package/dist/client/assets/SettingsModal-BLsac7CJ.js +31 -0
- package/dist/client/assets/SettingsModal-Cis-4Lot.css +1 -0
- package/dist/client/assets/{SetupWizardModal-uUZk3TKT.js → SetupWizardModal-D1q548_L.js} +1 -1
- package/dist/client/assets/{SkillsView-CP8JX0P_.js → SkillsView-ClLM6u6p.js} +1 -1
- package/dist/client/assets/StashRecoveryView-B_8WIQEo.css +1 -0
- package/dist/client/assets/StashRecoveryView-ze0pEZ5U.js +1 -0
- package/dist/client/assets/{TodoView-DCRIkDZ-.js → TodoView-CTmIfy2M.js} +1 -1
- package/dist/client/assets/{dashboard-view-lR7YYmSC.js → dashboard-view-4xAN3yO5.js} +2 -2
- package/dist/client/assets/{folder-open-DHjELt8-.js → folder-open-BZuKESeq.js} +1 -1
- package/dist/client/assets/index-Bdw6llW6.js +692 -0
- package/dist/client/assets/index-CZGlyJuS.css +1 -0
- package/dist/client/assets/{star-DYesq1AV.js → star-D75YKEq-.js} +1 -1
- package/dist/client/assets/{upload-DTWF3Db5.js → upload-BYYTgWFj.js} +1 -1
- package/dist/client/assets/{users--syrel4l.js → users-RS90Aii3.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/package.json +1 -1
- package/dist/extension.js +5640 -3618
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/plugins/fusion-plugin-cli-printing-press/manifest.json +6 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/package.json +26 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/manifest.test.ts +20 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/index.ts +14 -0
- package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +9 -11
- package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/bundled.js +30 -0
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +3 -28
- package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +899 -895
- package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +68 -71
- package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +47 -50
- package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-paperclip-runtime/bundled.js +155 -109
- package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-reports/package.json +1 -1
- package/dist/plugins/fusion-plugin-reports/src/index.ts +49 -3
- package/dist/plugins/fusion-plugin-reports/src/report-schema.ts +38 -0
- package/dist/plugins/fusion-plugin-reports/src/store/__tests__/report-schema.test.ts +66 -0
- package/dist/plugins/fusion-plugin-reports/src/store/__tests__/report-store.test.ts +177 -0
- package/dist/plugins/fusion-plugin-reports/src/store/report-store.ts +341 -0
- package/dist/plugins/fusion-plugin-reports/src/store/report-types.ts +77 -0
- package/dist/plugins/fusion-plugin-roadmap/{src/dashboard/RoadmapsView.css → bundled.css} +13 -219
- package/dist/plugins/fusion-plugin-roadmap/bundled.js +29535 -0
- package/dist/plugins/fusion-plugin-roadmap/package.json +4 -41
- package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
- package/package.json +2 -3
- package/dist/client/assets/AgentDetailView-gy_5SUj2.js +0 -18
- package/dist/client/assets/ChatView-B_-B8fqu.js +0 -1
- package/dist/client/assets/MemoryView-CKElJY_3.js +0 -2
- package/dist/client/assets/MemoryView-DiajLXby.css +0 -1
- package/dist/client/assets/PluginManager-CfW55BF4.js +0 -1
- package/dist/client/assets/ResearchView-B256Lr8I.js +0 -1
- package/dist/client/assets/SettingsModal-BeA_nQtW.js +0 -31
- package/dist/client/assets/SettingsModal-DzsLquBu.css +0 -1
- package/dist/client/assets/index-CQyVRLOb.js +0 -692
- package/dist/client/assets/index-CxA2Nn0_.css +0 -1
- package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraph.css +0 -58
- package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraph.tsx +0 -301
- package/dist/plugins/fusion-plugin-dependency-graph/src/GraphHighlight.css +0 -27
- package/dist/plugins/fusion-plugin-dependency-graph/src/GraphTaskNode.css +0 -157
- package/dist/plugins/fusion-plugin-dependency-graph/src/GraphTaskNode.tsx +0 -126
- package/dist/plugins/fusion-plugin-dependency-graph/src/GraphToolbar.css +0 -35
- package/dist/plugins/fusion-plugin-dependency-graph/src/GraphToolbar.tsx +0 -36
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.highlighting.test.tsx +0 -112
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.persistence.test.tsx +0 -115
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.test.tsx +0 -128
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.drag.test.tsx +0 -82
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.test.tsx +0 -307
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphToolbar.test.tsx +0 -60
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/edges.test.tsx +0 -75
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/filtering.test.tsx +0 -62
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/filters.test.ts +0 -78
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/graphPositionStorage.test.ts +0 -95
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/host-integration.test.ts +0 -74
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/index.test.ts +0 -58
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/interactions.test.tsx +0 -121
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/layout.test.ts +0 -70
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/persistence.test.tsx +0 -89
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphData.test.ts +0 -86
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphInteraction.test.ts +0 -167
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphPositions.test.ts +0 -66
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useNodeDrag.test.ts +0 -81
- package/dist/plugins/fusion-plugin-dependency-graph/src/dashboard-interop.d.ts +0 -35
- package/dist/plugins/fusion-plugin-dependency-graph/src/dashboard-view.tsx +0 -19
- package/dist/plugins/fusion-plugin-dependency-graph/src/edges.tsx +0 -70
- package/dist/plugins/fusion-plugin-dependency-graph/src/filters.ts +0 -8
- package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/__tests__/useDependencyChain.test.ts +0 -53
- package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useDependencyChain.ts +0 -60
- package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useGraphPositions.ts +0 -45
- package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useNodeDrag.ts +0 -114
- package/dist/plugins/fusion-plugin-dependency-graph/src/index.ts +0 -24
- package/dist/plugins/fusion-plugin-dependency-graph/src/layout.ts +0 -91
- package/dist/plugins/fusion-plugin-dependency-graph/src/styles/drag.css +0 -15
- package/dist/plugins/fusion-plugin-dependency-graph/src/types.ts +0 -21
- package/dist/plugins/fusion-plugin-dependency-graph/src/useGraphData.ts +0 -17
- package/dist/plugins/fusion-plugin-dependency-graph/src/useGraphInteraction.ts +0 -292
- package/dist/plugins/fusion-plugin-dependency-graph/src/utils/graphPositionStorage.ts +0 -65
- package/dist/plugins/fusion-plugin-roadmap/src/__tests__/api-client.test.ts +0 -101
- package/dist/plugins/fusion-plugin-roadmap/src/__tests__/index.test.ts +0 -92
- package/dist/plugins/fusion-plugin-roadmap/src/__tests__/roadmap-routes.test.ts +0 -48
- package/dist/plugins/fusion-plugin-roadmap/src/__tests__/roadmap-suggestions.test.ts +0 -31
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.tsx +0 -2559
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/__tests__/RoadmapsView.test.tsx +0 -1144
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/__tests__/useRoadmaps.test.ts +0 -1756
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/api.ts +0 -70
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/test-setup.ts +0 -7
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/types.ts +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useConfirm.ts +0 -8
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useRoadmaps.ts +0 -1188
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useViewportMode.ts +0 -20
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard-view.tsx +0 -6
- package/dist/plugins/fusion-plugin-roadmap/src/index.ts +0 -74
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-routes.ts +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-schema.ts +0 -41
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-suggestions.d.ts +0 -15
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-suggestions.ts +0 -15
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.d.ts +0 -283
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.d.ts.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.js +0 -21
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.js.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.ts +0 -310
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.d.ts +0 -5
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.d.ts.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.js +0 -361
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.js.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.ts +0 -408
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.d.ts +0 -68
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.d.ts.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.js +0 -300
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.js.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.ts +0 -381
- package/dist/plugins/fusion-plugin-roadmap/src/server/index.d.ts +0 -3
- package/dist/plugins/fusion-plugin-roadmap/src/server/index.ts +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-handoff.test.ts +0 -445
- package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-ordering.test.ts +0 -334
- package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-store.test.ts +0 -1318
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-handoff.ts +0 -163
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.d.ts +0 -37
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.d.ts.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js +0 -188
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.ts +0 -311
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.d.ts +0 -299
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.d.ts.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.js +0 -765
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.js.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.ts +0 -1001
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
import type { CreateAiSessionFactory } from "@fusion/core";
|
|
2
|
-
|
|
3
|
-
type LegacyCreateFnAgent = (options: {
|
|
4
|
-
cwd: string;
|
|
5
|
-
systemPrompt: string;
|
|
6
|
-
tools?: "coding" | "readonly";
|
|
7
|
-
defaultProvider?: string;
|
|
8
|
-
defaultModelId?: string;
|
|
9
|
-
onThinking?: () => void;
|
|
10
|
-
onText?: () => void;
|
|
11
|
-
}) => Promise<{
|
|
12
|
-
session: {
|
|
13
|
-
prompt(text: string): Promise<void>;
|
|
14
|
-
state: { messages: Array<{ role: string; content?: string | Array<{ type: string; text: string }> }> };
|
|
15
|
-
dispose?: () => void;
|
|
16
|
-
};
|
|
17
|
-
}>;
|
|
18
|
-
|
|
19
|
-
let injectedCreateAiSession: CreateAiSessionFactory | undefined;
|
|
20
|
-
|
|
21
|
-
export interface GenerateMilestoneSuggestionsInput {
|
|
22
|
-
goalPrompt: string;
|
|
23
|
-
count?: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface MilestoneSuggestion {
|
|
27
|
-
title: string;
|
|
28
|
-
description?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export const MILESTONE_SUGGESTION_SYSTEM_PROMPT = `You are a milestone planning assistant for a product roadmap system.
|
|
32
|
-
|
|
33
|
-
Your job is to suggest logical milestones that would help achieve a user's roadmap goal.
|
|
34
|
-
|
|
35
|
-
## Guidelines
|
|
36
|
-
|
|
37
|
-
1. **Think about phases**: Break the goal into logical phases
|
|
38
|
-
2. **Use clear titles**: Milestone titles should be concise and descriptive
|
|
39
|
-
3. **Add context**: Include a brief description explaining what this milestone encompasses
|
|
40
|
-
4. **Order matters**: List milestones in the order they should be completed
|
|
41
|
-
5. **Realistic scope**: Each milestone should be achievable in 2-4 weeks
|
|
42
|
-
|
|
43
|
-
## Output Format
|
|
44
|
-
|
|
45
|
-
Respond with ONLY a valid JSON array of milestone suggestions.`;
|
|
46
|
-
|
|
47
|
-
const MAX_GOAL_PROMPT_LENGTH = 4000;
|
|
48
|
-
export const SUGGESTION_TIMEOUT_MS = 120_000;
|
|
49
|
-
const DEFAULT_SUGGESTION_COUNT = 5;
|
|
50
|
-
const MAX_SUGGESTION_COUNT = 10;
|
|
51
|
-
const MIN_SUGGESTION_COUNT = 1;
|
|
52
|
-
const MAX_PARSE_RETRIES = 1;
|
|
53
|
-
|
|
54
|
-
export function validateSuggestionInput(input: unknown): asserts input is GenerateMilestoneSuggestionsInput {
|
|
55
|
-
if (!input || typeof input !== "object") {
|
|
56
|
-
throw new ValidationError("Request body must be an object");
|
|
57
|
-
}
|
|
58
|
-
const { goalPrompt, count } = input as Record<string, unknown>;
|
|
59
|
-
if (typeof goalPrompt !== "string" || !goalPrompt.trim()) {
|
|
60
|
-
throw new ValidationError("goalPrompt is required and must be a non-empty string");
|
|
61
|
-
}
|
|
62
|
-
if (goalPrompt.length > MAX_GOAL_PROMPT_LENGTH) {
|
|
63
|
-
throw new ValidationError(`goalPrompt exceeds maximum length of ${MAX_GOAL_PROMPT_LENGTH} characters`);
|
|
64
|
-
}
|
|
65
|
-
if (count !== undefined) {
|
|
66
|
-
if (typeof count !== "number" || !Number.isInteger(count)) throw new ValidationError("count must be an integer");
|
|
67
|
-
if (count < MIN_SUGGESTION_COUNT || count > MAX_SUGGESTION_COUNT) {
|
|
68
|
-
throw new ValidationError(`count must be between ${MIN_SUGGESTION_COUNT} and ${MAX_SUGGESTION_COUNT}`);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function extractJsonCandidate(text: string): string | null {
|
|
74
|
-
if (!text || !text.trim()) return null;
|
|
75
|
-
const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
|
|
76
|
-
const source = codeBlockMatch?.[1]?.trim() || text.trim();
|
|
77
|
-
|
|
78
|
-
const startIndex = source.indexOf("[");
|
|
79
|
-
if (startIndex < 0) return null;
|
|
80
|
-
|
|
81
|
-
let depth = 0;
|
|
82
|
-
let inString = false;
|
|
83
|
-
let escaped = false;
|
|
84
|
-
for (let index = startIndex; index < source.length; index++) {
|
|
85
|
-
const char = source[index];
|
|
86
|
-
if (inString) {
|
|
87
|
-
if (escaped) {
|
|
88
|
-
escaped = false;
|
|
89
|
-
} else if (char === "\\") {
|
|
90
|
-
escaped = true;
|
|
91
|
-
} else if (char === '"') {
|
|
92
|
-
inString = false;
|
|
93
|
-
}
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (char === '"') {
|
|
98
|
-
inString = true;
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
if (char === "[") depth++;
|
|
102
|
-
if (char === "]") {
|
|
103
|
-
depth--;
|
|
104
|
-
if (depth === 0) {
|
|
105
|
-
return source.slice(startIndex, index + 1).trim();
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return source.slice(startIndex).trim();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function repairJson(text: string): string {
|
|
114
|
-
let repaired = text.replace(/,\s*([}\]])/g, "$1");
|
|
115
|
-
let depthBraces = 0;
|
|
116
|
-
let depthBrackets = 0;
|
|
117
|
-
for (const ch of repaired) {
|
|
118
|
-
if (ch === "{") depthBraces++;
|
|
119
|
-
if (ch === "}") depthBraces--;
|
|
120
|
-
if (ch === "[") depthBrackets++;
|
|
121
|
-
if (ch === "]") depthBrackets--;
|
|
122
|
-
}
|
|
123
|
-
repaired += "]".repeat(Math.max(0, depthBrackets));
|
|
124
|
-
repaired += "}".repeat(Math.max(0, depthBraces));
|
|
125
|
-
return repaired;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function parseMilestoneSuggestions(text: string): MilestoneSuggestion[] {
|
|
129
|
-
const candidate = extractJsonCandidate(text);
|
|
130
|
-
if (!candidate) throw new ParseError("AI returned no valid JSON. Please try again.");
|
|
131
|
-
|
|
132
|
-
let parsed: unknown;
|
|
133
|
-
try {
|
|
134
|
-
parsed = JSON.parse(candidate);
|
|
135
|
-
} catch {
|
|
136
|
-
parsed = JSON.parse(repairJson(candidate));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (!Array.isArray(parsed)) {
|
|
140
|
-
throw new ParseError("AI response must be a JSON array of milestone suggestions");
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const suggestions: MilestoneSuggestion[] = [];
|
|
144
|
-
for (const item of parsed) {
|
|
145
|
-
if (!item || typeof item !== "object") continue;
|
|
146
|
-
const row = item as Record<string, unknown>;
|
|
147
|
-
if (typeof row.title !== "string" || !row.title.trim()) continue;
|
|
148
|
-
suggestions.push({
|
|
149
|
-
title: row.title.trim(),
|
|
150
|
-
description: typeof row.description === "string" && row.description.trim() ? row.description.trim() : undefined,
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (suggestions.length === 0) throw new ParseError("AI returned no valid milestone suggestions");
|
|
155
|
-
return suggestions;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
interface AgentMessage {
|
|
159
|
-
role: string;
|
|
160
|
-
content?: string | Array<{ type: string; text: string }>;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function pickFactory(explicit?: CreateAiSessionFactory): CreateAiSessionFactory | undefined {
|
|
164
|
-
return explicit ?? injectedCreateAiSession;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
async function runPrompt(
|
|
168
|
-
createAiSession: CreateAiSessionFactory,
|
|
169
|
-
options: Parameters<CreateAiSessionFactory>[0],
|
|
170
|
-
prompt: string,
|
|
171
|
-
): Promise<{ text: string; dispose?: () => void }> {
|
|
172
|
-
const agent = await createAiSession(options);
|
|
173
|
-
await agent.session.prompt(prompt);
|
|
174
|
-
const lastMessage = (agent.session.state.messages as AgentMessage[])
|
|
175
|
-
.filter((m) => m.role === "assistant")
|
|
176
|
-
.pop();
|
|
177
|
-
|
|
178
|
-
let text = "";
|
|
179
|
-
if (lastMessage?.content) {
|
|
180
|
-
if (typeof lastMessage.content === "string") text = lastMessage.content;
|
|
181
|
-
else {
|
|
182
|
-
text = lastMessage.content
|
|
183
|
-
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
184
|
-
.map((c) => c.text)
|
|
185
|
-
.join("");
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return { text, dispose: (agent.session as { dispose?: () => void }).dispose };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export async function generateMilestoneSuggestions(
|
|
193
|
-
goalPrompt: string,
|
|
194
|
-
count: number = DEFAULT_SUGGESTION_COUNT,
|
|
195
|
-
rootDir?: string,
|
|
196
|
-
modelProvider?: string,
|
|
197
|
-
modelId?: string,
|
|
198
|
-
createAiSession?: CreateAiSessionFactory,
|
|
199
|
-
): Promise<MilestoneSuggestion[]> {
|
|
200
|
-
const factory = pickFactory(createAiSession);
|
|
201
|
-
if (!factory) throw new ServiceUnavailableError("AI service is not available");
|
|
202
|
-
if (!rootDir) throw new Error("rootDir is required for AI-powered suggestion generation");
|
|
203
|
-
|
|
204
|
-
const result = await Promise.race([
|
|
205
|
-
(async () => {
|
|
206
|
-
let dispose: (() => void) | undefined;
|
|
207
|
-
try {
|
|
208
|
-
let response = await runPrompt(factory, {
|
|
209
|
-
cwd: rootDir,
|
|
210
|
-
systemPrompt: MILESTONE_SUGGESTION_SYSTEM_PROMPT,
|
|
211
|
-
tools: "readonly",
|
|
212
|
-
...(modelProvider && modelId ? { defaultProvider: modelProvider, defaultModelId: modelId } : {}),
|
|
213
|
-
}, `Please suggest ${count} milestones for the following roadmap goal:\n\n${goalPrompt.trim()}`);
|
|
214
|
-
dispose = response.dispose;
|
|
215
|
-
|
|
216
|
-
let suggestions: MilestoneSuggestion[] | undefined;
|
|
217
|
-
let lastError: Error | undefined;
|
|
218
|
-
for (let attempt = 0; attempt <= MAX_PARSE_RETRIES; attempt++) {
|
|
219
|
-
try {
|
|
220
|
-
suggestions = parseMilestoneSuggestions(response.text);
|
|
221
|
-
break;
|
|
222
|
-
} catch (error) {
|
|
223
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
224
|
-
if (attempt === MAX_PARSE_RETRIES) break;
|
|
225
|
-
response = await runPrompt(factory, {
|
|
226
|
-
cwd: rootDir,
|
|
227
|
-
systemPrompt: MILESTONE_SUGGESTION_SYSTEM_PROMPT,
|
|
228
|
-
tools: "readonly",
|
|
229
|
-
...(modelProvider && modelId ? { defaultProvider: modelProvider, defaultModelId: modelId } : {}),
|
|
230
|
-
}, "Your previous response could not be parsed as JSON. Respond with only a JSON array.");
|
|
231
|
-
dispose = response.dispose;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (!suggestions) {
|
|
236
|
-
throw new ParseError(`Failed to parse AI response after ${MAX_PARSE_RETRIES + 1} attempts: ${lastError?.message ?? "Unknown error"}`);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return suggestions.slice(0, count);
|
|
240
|
-
} finally {
|
|
241
|
-
dispose?.();
|
|
242
|
-
}
|
|
243
|
-
})(),
|
|
244
|
-
new Promise<never>((_, reject) =>
|
|
245
|
-
globalThis.setTimeout(() => reject(new ServiceUnavailableError("AI suggestion generation timed out. Please try again.")), SUGGESTION_TIMEOUT_MS),
|
|
246
|
-
),
|
|
247
|
-
]);
|
|
248
|
-
|
|
249
|
-
return result;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
export interface GenerateFeatureSuggestionsInput {
|
|
253
|
-
prompt?: string;
|
|
254
|
-
count?: number;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
export interface FeatureSuggestion {
|
|
258
|
-
title: string;
|
|
259
|
-
description?: string;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
export interface FeatureSuggestionContext {
|
|
263
|
-
roadmapTitle: string;
|
|
264
|
-
roadmapDescription?: string;
|
|
265
|
-
milestoneTitle: string;
|
|
266
|
-
milestoneDescription?: string;
|
|
267
|
-
existingFeatureTitles: string[];
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export const FEATURE_SUGGESTION_SYSTEM_PROMPT = `You are a feature planning assistant for a product roadmap system.`;
|
|
271
|
-
|
|
272
|
-
const MAX_FEATURE_PROMPT_LENGTH = 2000;
|
|
273
|
-
|
|
274
|
-
export function validateFeatureSuggestionInput(input: unknown): asserts input is GenerateFeatureSuggestionsInput {
|
|
275
|
-
if (!input || typeof input !== "object" || Array.isArray(input)) throw new ValidationError("Request body must be an object");
|
|
276
|
-
const { prompt, count } = input as Record<string, unknown>;
|
|
277
|
-
if (prompt !== undefined) {
|
|
278
|
-
if (typeof prompt !== "string") throw new ValidationError("prompt must be a string");
|
|
279
|
-
if (prompt.length > MAX_FEATURE_PROMPT_LENGTH) throw new ValidationError(`prompt exceeds maximum length of ${MAX_FEATURE_PROMPT_LENGTH} characters`);
|
|
280
|
-
}
|
|
281
|
-
if (count !== undefined) {
|
|
282
|
-
if (typeof count !== "number" || !Number.isInteger(count)) throw new ValidationError("count must be an integer");
|
|
283
|
-
if (count < MIN_SUGGESTION_COUNT || count > MAX_SUGGESTION_COUNT) {
|
|
284
|
-
throw new ValidationError(`count must be between ${MIN_SUGGESTION_COUNT} and ${MAX_SUGGESTION_COUNT}`);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function buildMilestoneContextString(context: FeatureSuggestionContext): string {
|
|
290
|
-
const lines: string[] = [];
|
|
291
|
-
lines.push(`Roadmap: ${context.roadmapTitle}`);
|
|
292
|
-
if (context.roadmapDescription) lines.push(`Description: ${context.roadmapDescription}`);
|
|
293
|
-
lines.push("", `Milestone: ${context.milestoneTitle}`);
|
|
294
|
-
if (context.milestoneDescription) lines.push(`Description: ${context.milestoneDescription}`);
|
|
295
|
-
if (context.existingFeatureTitles.length > 0) {
|
|
296
|
-
lines.push("", "Existing features in this milestone:");
|
|
297
|
-
for (const title of context.existingFeatureTitles) lines.push(` - ${title}`);
|
|
298
|
-
}
|
|
299
|
-
return lines.join("\n");
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function parseFeatureSuggestions(text: string): FeatureSuggestion[] {
|
|
303
|
-
return parseMilestoneSuggestions(text);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
export async function generateFeatureSuggestions(
|
|
307
|
-
context: FeatureSuggestionContext,
|
|
308
|
-
count: number = DEFAULT_SUGGESTION_COUNT,
|
|
309
|
-
prompt?: string,
|
|
310
|
-
rootDir?: string,
|
|
311
|
-
modelProvider?: string,
|
|
312
|
-
modelId?: string,
|
|
313
|
-
createAiSession?: CreateAiSessionFactory,
|
|
314
|
-
): Promise<FeatureSuggestion[]> {
|
|
315
|
-
const factory = pickFactory(createAiSession);
|
|
316
|
-
if (!factory) throw new ServiceUnavailableError("AI service is not available");
|
|
317
|
-
if (!rootDir) throw new Error("rootDir is required for AI-powered suggestion generation");
|
|
318
|
-
|
|
319
|
-
const systemPrompt = `${FEATURE_SUGGESTION_SYSTEM_PROMPT}\n\n${buildMilestoneContextString(context)}`;
|
|
320
|
-
const userMessage = prompt?.trim()
|
|
321
|
-
? `Please suggest ${count} features for the milestone described above.\n\nAdditional guidance:\n${prompt.trim()}`
|
|
322
|
-
: `Please suggest ${count} features for the milestone described above.`;
|
|
323
|
-
|
|
324
|
-
const result = await Promise.race([
|
|
325
|
-
(async () => {
|
|
326
|
-
const { text, dispose } = await runPrompt(factory, {
|
|
327
|
-
cwd: rootDir,
|
|
328
|
-
systemPrompt,
|
|
329
|
-
tools: "readonly",
|
|
330
|
-
...(modelProvider && modelId ? { defaultProvider: modelProvider, defaultModelId: modelId } : {}),
|
|
331
|
-
}, userMessage);
|
|
332
|
-
try {
|
|
333
|
-
return parseFeatureSuggestions(text).slice(0, count);
|
|
334
|
-
} finally {
|
|
335
|
-
dispose?.();
|
|
336
|
-
}
|
|
337
|
-
})(),
|
|
338
|
-
new Promise<never>((_, reject) =>
|
|
339
|
-
globalThis.setTimeout(() => reject(new ServiceUnavailableError("AI suggestion generation timed out. Please try again.")), SUGGESTION_TIMEOUT_MS),
|
|
340
|
-
),
|
|
341
|
-
]);
|
|
342
|
-
|
|
343
|
-
return result;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
export class ValidationError extends Error {
|
|
347
|
-
constructor(message: string) {
|
|
348
|
-
super(message);
|
|
349
|
-
this.name = "ValidationError";
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
export class ParseError extends Error {
|
|
354
|
-
constructor(message: string) {
|
|
355
|
-
super(message);
|
|
356
|
-
this.name = "ParseError";
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
export class ServiceUnavailableError extends Error {
|
|
361
|
-
constructor(message: string) {
|
|
362
|
-
super(message);
|
|
363
|
-
this.name = "ServiceUnavailableError";
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
export function __resetSuggestionState(): void {
|
|
368
|
-
injectedCreateAiSession = undefined;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
export function __setCreateFnAgent(mock: LegacyCreateFnAgent | undefined): void {
|
|
372
|
-
if (!mock) {
|
|
373
|
-
injectedCreateAiSession = undefined;
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
injectedCreateAiSession = async (options) => mock(options);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
export function __setCreateAiSessionFactory(mock: CreateAiSessionFactory | undefined): void {
|
|
380
|
-
injectedCreateAiSession = mock;
|
|
381
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { createRoadmapPluginRoutes } from "../routes/roadmap-routes.js";
|