@synergenius/flow-weaver-pack-weaver 0.9.5 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bot/assistant-core.d.ts +2 -0
- package/dist/bot/assistant-core.d.ts.map +1 -1
- package/dist/bot/assistant-core.js +88 -4
- package/dist/bot/assistant-core.js.map +1 -1
- package/dist/bot/conversation-store.d.ts +1 -0
- package/dist/bot/conversation-store.d.ts.map +1 -1
- package/dist/bot/conversation-store.js +32 -0
- package/dist/bot/conversation-store.js.map +1 -1
- package/dist/cli-handlers.d.ts +1 -0
- package/dist/cli-handlers.d.ts.map +1 -1
- package/dist/cli-handlers.js +103 -2
- package/dist/cli-handlers.js.map +1 -1
- package/dist/node-types/agent-execute.js +15 -3
- package/dist/node-types/agent-execute.js.map +1 -1
- package/flowweaver.manifest.json +1 -1
- package/package.json +1 -1
- package/src/bot/assistant-core.ts +86 -5
- package/src/bot/conversation-store.ts +32 -0
- package/src/cli-handlers.ts +105 -3
- package/src/node-types/agent-execute.ts +17 -4
|
@@ -31,6 +31,8 @@ export interface AssistantOptions {
|
|
|
31
31
|
systemPrompt?: string;
|
|
32
32
|
/** Override for testing — provide messages instead of reading stdin */
|
|
33
33
|
inputMessages?: string[];
|
|
34
|
+
/** Watch a directory for file changes and auto-suggest fixes */
|
|
35
|
+
watchDir?: string;
|
|
34
36
|
/** Resume a specific conversation by ID */
|
|
35
37
|
resumeId?: string;
|
|
36
38
|
/** Always start a fresh conversation */
|
|
@@ -71,6 +73,41 @@ For those: you may summarize or explain the result as needed.`;
|
|
|
71
73
|
|
|
72
74
|
export async function runAssistant(opts: AssistantOptions): Promise<void> {
|
|
73
75
|
const { provider, tools, executor, projectDir } = opts;
|
|
76
|
+
const out = (s: string) => process.stderr.write(s);
|
|
77
|
+
|
|
78
|
+
// Pipe mode: if stdin is not a TTY, read all input as one message
|
|
79
|
+
if (!process.stdin.isTTY && !opts.inputMessages) {
|
|
80
|
+
const chunks: string[] = [];
|
|
81
|
+
for await (const chunk of process.stdin) {
|
|
82
|
+
chunks.push(typeof chunk === 'string' ? chunk : chunk.toString());
|
|
83
|
+
}
|
|
84
|
+
const pipeInput = chunks.join('').trim();
|
|
85
|
+
if (!pipeInput) return;
|
|
86
|
+
|
|
87
|
+
// Build system prompt for pipe mode
|
|
88
|
+
let systemPrompt = opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
89
|
+
try {
|
|
90
|
+
const fsMod = await import('node:fs');
|
|
91
|
+
const pathMod = await import('node:path');
|
|
92
|
+
const planPath = pathMod.resolve(projectDir, '.weaver-plan.md');
|
|
93
|
+
if (fsMod.existsSync(planPath)) {
|
|
94
|
+
const plan = fsMod.readFileSync(planPath, 'utf-8').trim();
|
|
95
|
+
systemPrompt += '\n\n## Project Plan & Vision\n\nAll bots you spawn and tasks you queue MUST align with this plan.\n\n' + plan;
|
|
96
|
+
}
|
|
97
|
+
} catch { /* plan not available */ }
|
|
98
|
+
|
|
99
|
+
// Run single message, print result, exit
|
|
100
|
+
await runAgentLoop(provider, tools, executor, [{ role: 'user', content: pipeInput }], {
|
|
101
|
+
systemPrompt, maxIterations: 20,
|
|
102
|
+
onStreamEvent: (e) => { if (e.type === 'text_delta') out(e.text); },
|
|
103
|
+
onToolEvent: (e) => {
|
|
104
|
+
if (e.type === 'tool_call_start') out(`\n ${c.cyan('◆')} ${e.name}\n`);
|
|
105
|
+
if (e.type === 'tool_call_result') out(` ${c.dim('→')} ${(e.result ?? '').slice(0, 200)}\n`);
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
out('\n');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
74
111
|
|
|
75
112
|
// Build system prompt — include project plan if it exists
|
|
76
113
|
let systemPrompt = opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
@@ -84,8 +121,6 @@ export async function runAssistant(opts: AssistantOptions): Promise<void> {
|
|
|
84
121
|
}
|
|
85
122
|
} catch { /* plan not available */ }
|
|
86
123
|
|
|
87
|
-
const out = (s: string) => process.stderr.write(s);
|
|
88
|
-
|
|
89
124
|
// Persistent conversation store
|
|
90
125
|
const { ConversationStore } = await import('./conversation-store.js');
|
|
91
126
|
const store = new ConversationStore();
|
|
@@ -117,13 +152,30 @@ export async function runAssistant(opts: AssistantOptions): Promise<void> {
|
|
|
117
152
|
}
|
|
118
153
|
}
|
|
119
154
|
|
|
155
|
+
// Resolve versions
|
|
156
|
+
let fwVersion = '?';
|
|
157
|
+
let weaverVersion = '?';
|
|
158
|
+
try {
|
|
159
|
+
const { execFileSync: vExec } = await import('node:child_process');
|
|
160
|
+
fwVersion = vExec('npx', ['flow-weaver', '--version'], { encoding: 'utf-8', cwd: projectDir, timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim().replace(/^flow-weaver\s+v?/i, '');
|
|
161
|
+
} catch { /* not available */ }
|
|
162
|
+
try {
|
|
163
|
+
const fsMod = await import('node:fs');
|
|
164
|
+
const url = await import('node:url');
|
|
165
|
+
const packPkg = JSON.parse(fsMod.readFileSync(new url.URL('../package.json', import.meta.url), 'utf-8'));
|
|
166
|
+
weaverVersion = packPkg.version;
|
|
167
|
+
} catch { /* not available */ }
|
|
168
|
+
|
|
120
169
|
// Welcome
|
|
121
|
-
out(`\n ${c.bold('weaver assistant')}\n`);
|
|
122
|
-
|
|
170
|
+
out(`\n ${c.bold('weaver assistant')} ${c.dim(`v${weaverVersion}`)} ${c.dim(`· flow-weaver v${fwVersion}`)}\n`);
|
|
171
|
+
if (process.env.FW_PLATFORM_TOKEN) {
|
|
172
|
+
out(` ${c.dim('AI: Platform credits (no API key needed)')}\n`);
|
|
173
|
+
}
|
|
174
|
+
out(` ${c.dim(`Project: ${path.basename(projectDir)}`)}\n`);
|
|
123
175
|
if (conversation.title) {
|
|
124
176
|
out(` ${c.dim(`Resuming: "${conversation.title}" (${conversation.messageCount} messages)`)}\n`);
|
|
125
177
|
} else {
|
|
126
|
-
out(` ${c.dim(`
|
|
178
|
+
out(` ${c.dim(`New conversation`)}\n`);
|
|
127
179
|
}
|
|
128
180
|
out(` ${c.dim('Type your request. Ctrl+C to exit. /help for commands.')}\n\n`);
|
|
129
181
|
|
|
@@ -188,6 +240,31 @@ export async function runAssistant(opts: AssistantOptions): Promise<void> {
|
|
|
188
240
|
onVerbose: () => { out(` ${c.dim('Verbose toggling not yet wired to streaming.')}\n`); },
|
|
189
241
|
};
|
|
190
242
|
|
|
243
|
+
// Watch mode: monitor directory for file changes, auto-validate
|
|
244
|
+
let watcher: import('node:fs').FSWatcher | null = null;
|
|
245
|
+
if (opts.watchDir) {
|
|
246
|
+
try {
|
|
247
|
+
const fsMod = await import('node:fs');
|
|
248
|
+
const { execFileSync } = await import('node:child_process');
|
|
249
|
+
watcher = fsMod.watch(opts.watchDir, { recursive: true }, (_event, filename) => {
|
|
250
|
+
if (!filename || !filename.endsWith('.ts')) return;
|
|
251
|
+
const filePath = `${opts.watchDir}/${filename}`;
|
|
252
|
+
try {
|
|
253
|
+
const result = execFileSync('npx', ['flow-weaver', 'validate', filePath, '--json'], {
|
|
254
|
+
encoding: 'utf-8', cwd: projectDir, timeout: 15_000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
255
|
+
});
|
|
256
|
+
const parsed = JSON.parse(result);
|
|
257
|
+
const errorCount = parsed.errorCount ?? parsed.errors?.length ?? 0;
|
|
258
|
+
if (errorCount > 0) {
|
|
259
|
+
out(`\n ${c.yellow('⚠')} ${filename} changed: ${errorCount} validation error(s)\n`);
|
|
260
|
+
out(` ${c.dim('Type a message to fix, or ignore.')}\n`);
|
|
261
|
+
}
|
|
262
|
+
} catch { /* validation failed or not a workflow — ignore */ }
|
|
263
|
+
});
|
|
264
|
+
out(` ${c.dim(`Watching: ${opts.watchDir}`)}\n\n`);
|
|
265
|
+
} catch { /* watch not available */ }
|
|
266
|
+
}
|
|
267
|
+
|
|
191
268
|
// Main conversation loop
|
|
192
269
|
while (!shouldExit) {
|
|
193
270
|
const input = await getNextInput();
|
|
@@ -239,6 +316,9 @@ export async function runAssistant(opts: AssistantOptions): Promise<void> {
|
|
|
239
316
|
store.appendMessages(conversation.id, [{ role: 'user', content: input }, ...newMessages]);
|
|
240
317
|
store.updateAfterTurn(conversation.id, [{ role: 'user', content: input }, ...newMessages], tokensUsed);
|
|
241
318
|
|
|
319
|
+
// Sync to cloud if logged in (fire-and-forget)
|
|
320
|
+
store.syncToCloud(conversation.id, [{ role: 'user', content: input }, ...newMessages]).catch(() => {});
|
|
321
|
+
|
|
242
322
|
// Auto-title from first assistant response
|
|
243
323
|
if (!conversation.title) {
|
|
244
324
|
const firstAssistant = newMessages.find(m => m.role === 'assistant');
|
|
@@ -265,6 +345,7 @@ export async function runAssistant(opts: AssistantOptions): Promise<void> {
|
|
|
265
345
|
out('\n');
|
|
266
346
|
}
|
|
267
347
|
|
|
348
|
+
watcher?.close();
|
|
268
349
|
richInput?.destroy();
|
|
269
350
|
out(`\n ${c.dim('Goodbye.')}\n\n`);
|
|
270
351
|
}
|
|
@@ -176,6 +176,38 @@ export class ConversationStore {
|
|
|
176
176
|
});
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
async syncToCloud(id: string, newMessages: AgentMessage[]): Promise<void> {
|
|
180
|
+
try {
|
|
181
|
+
const credPath = path.join(os.homedir(), '.fw', 'credentials.json');
|
|
182
|
+
if (!fs.existsSync(credPath)) return;
|
|
183
|
+
const creds = JSON.parse(fs.readFileSync(credPath, 'utf-8'));
|
|
184
|
+
if (!creds.token || !creds.platformUrl || creds.expiresAt <= Date.now()) return;
|
|
185
|
+
|
|
186
|
+
const conversation = this.get(id);
|
|
187
|
+
if (!conversation) return;
|
|
188
|
+
|
|
189
|
+
// Fire-and-forget sync — don't block the conversation
|
|
190
|
+
const lastMessage = newMessages.find(m => m.role === 'user');
|
|
191
|
+
if (!lastMessage) return;
|
|
192
|
+
|
|
193
|
+
const message = typeof lastMessage.content === 'string' ? lastMessage.content : JSON.stringify(lastMessage.content);
|
|
194
|
+
|
|
195
|
+
fetch(`${creds.platformUrl}/ai-chat/stream`, {
|
|
196
|
+
method: 'POST',
|
|
197
|
+
headers: {
|
|
198
|
+
'Content-Type': 'application/json',
|
|
199
|
+
...(creds.token.startsWith('fw_')
|
|
200
|
+
? { 'X-API-Key': creds.token }
|
|
201
|
+
: { Authorization: `Bearer ${creds.token}` }),
|
|
202
|
+
},
|
|
203
|
+
body: JSON.stringify({
|
|
204
|
+
message: `[Synced from CLI] ${message}`,
|
|
205
|
+
conversationId: (conversation as any).cloudConversationId,
|
|
206
|
+
}),
|
|
207
|
+
}).catch(() => {}); // fire-and-forget
|
|
208
|
+
} catch { /* sync not available */ }
|
|
209
|
+
}
|
|
210
|
+
|
|
179
211
|
async setTitle(id: string, title: string): Promise<void> {
|
|
180
212
|
await withFileLock(this.indexPath, () => {
|
|
181
213
|
const index = this.readIndex();
|
package/src/cli-handlers.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
+
import * as os from 'node:os';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
5
|
import { runWorkflow } from './bot/runner.js';
|
|
5
6
|
import { RunStore } from './bot/run-store.js';
|
|
@@ -67,6 +68,7 @@ export interface ParsedArgs {
|
|
|
67
68
|
assistantResume?: string;
|
|
68
69
|
assistantList?: boolean;
|
|
69
70
|
assistantDelete?: string;
|
|
71
|
+
assistantWatch?: string;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
export function parseArgs(argv: string[]): ParsedArgs {
|
|
@@ -261,6 +263,9 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
261
263
|
result.genesisInit = true;
|
|
262
264
|
} else if (arg === '--watch') {
|
|
263
265
|
result.genesisWatch = true;
|
|
266
|
+
} else if (arg === '--watch-dir' && i + 1 < args.length) {
|
|
267
|
+
i++;
|
|
268
|
+
result.assistantWatch = args[i];
|
|
264
269
|
} else if (arg === '--new') {
|
|
265
270
|
result.assistantNew = true;
|
|
266
271
|
} else if (arg === '--resume' && i + 1 < args.length) {
|
|
@@ -1093,6 +1098,43 @@ export async function handleEject(opts: ParsedArgs): Promise<void> {
|
|
|
1093
1098
|
console.log(`[weaver] Metadata written to ${metaPath}`);
|
|
1094
1099
|
console.log('[weaver] You can now customize the ejected workflow(s) freely.');
|
|
1095
1100
|
console.log('[weaver] The bot will use local files when available.');
|
|
1101
|
+
|
|
1102
|
+
// Generate CUSTOMIZATION.md guide
|
|
1103
|
+
const customizationGuide = `# Customizing Your Weaver Bot
|
|
1104
|
+
|
|
1105
|
+
## What You Got
|
|
1106
|
+
${results.map(r => `- ${r.file} — ${r.workflow} workflow (${r.nodeTypes.length} node types)`).join('\n')}
|
|
1107
|
+
|
|
1108
|
+
You can edit these files freely. Weaver will use your local versions.
|
|
1109
|
+
|
|
1110
|
+
## Safe Changes
|
|
1111
|
+
- Add a notification node (Slack, Discord, email) after git-ops
|
|
1112
|
+
- Change approval mode: edit the @node approve line
|
|
1113
|
+
- Add custom validation: insert a node between agent and gitOps
|
|
1114
|
+
- Modify the system prompt: edit system-prompt.ts
|
|
1115
|
+
- Add new tools: create a node type and wire it in
|
|
1116
|
+
|
|
1117
|
+
## How to Add a Node
|
|
1118
|
+
1. Create a new file: my-node.ts with @flowWeaver nodeType annotation
|
|
1119
|
+
2. Import it in the workflow file
|
|
1120
|
+
3. Add: @node myNode myNodeType [color: "blue"] [icon: "star"]
|
|
1121
|
+
4. Wire it: @connect existingNode.output -> myNode.input
|
|
1122
|
+
5. Compile: flow-weaver compile <workflow-file>
|
|
1123
|
+
|
|
1124
|
+
## How to Test
|
|
1125
|
+
flow-weaver validate <workflow-file> # check for errors
|
|
1126
|
+
flow-weaver diagram <workflow-file> # visualize the DAG
|
|
1127
|
+
weaver bot "test task" --auto-approve # run a test task
|
|
1128
|
+
|
|
1129
|
+
## Learn More
|
|
1130
|
+
weaver examples # see what's possible
|
|
1131
|
+
weaver doctor # check your setup
|
|
1132
|
+
flow-weaver docs concepts # Flow Weaver documentation
|
|
1133
|
+
`;
|
|
1134
|
+
|
|
1135
|
+
const customPath = path.resolve(destDir, 'CUSTOMIZATION.md');
|
|
1136
|
+
fs.writeFileSync(customPath, customizationGuide, 'utf-8');
|
|
1137
|
+
console.log(`[weaver] Generated ${customPath}`);
|
|
1096
1138
|
}
|
|
1097
1139
|
|
|
1098
1140
|
export async function handleRun(opts: ParsedArgs): Promise<void> {
|
|
@@ -1525,6 +1567,32 @@ export async function handleSession(opts: ParsedArgs): Promise<void> {
|
|
|
1525
1567
|
sendDesktopNotification('Weaver Session Complete', `${sessionCompleted} done, ${sessionFailed} failed, ${sessionNoOp} no-op`);
|
|
1526
1568
|
} catch { /* non-fatal */ }
|
|
1527
1569
|
}
|
|
1570
|
+
|
|
1571
|
+
// Webhook notification if configured
|
|
1572
|
+
if (config?.notify) {
|
|
1573
|
+
try {
|
|
1574
|
+
const webhookUrl = typeof config.notify === 'string' ? config.notify
|
|
1575
|
+
: typeof config.notify === 'object' && 'webhook' in config.notify ? (config.notify as { webhook: string }).webhook
|
|
1576
|
+
: null;
|
|
1577
|
+
if (webhookUrl) {
|
|
1578
|
+
fetch(webhookUrl, {
|
|
1579
|
+
method: 'POST',
|
|
1580
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1581
|
+
body: JSON.stringify({
|
|
1582
|
+
event: 'session.completed',
|
|
1583
|
+
timestamp: new Date().toISOString(),
|
|
1584
|
+
tasks: taskCount,
|
|
1585
|
+
completed: sessionCompleted,
|
|
1586
|
+
failed: sessionFailed,
|
|
1587
|
+
noOp: sessionNoOp,
|
|
1588
|
+
tokens: sessionInputTokens + sessionOutputTokens,
|
|
1589
|
+
cost: sessionCost,
|
|
1590
|
+
elapsed: Date.now() - sessionStartTime,
|
|
1591
|
+
}),
|
|
1592
|
+
}).catch(() => {}); // fire-and-forget
|
|
1593
|
+
}
|
|
1594
|
+
} catch { /* non-fatal */ }
|
|
1595
|
+
}
|
|
1528
1596
|
}
|
|
1529
1597
|
|
|
1530
1598
|
export async function handleAssistant(opts: ParsedArgs): Promise<void> {
|
|
@@ -1559,13 +1627,37 @@ export async function handleAssistant(opts: ParsedArgs): Promise<void> {
|
|
|
1559
1627
|
|
|
1560
1628
|
const config = await loadConfig(opts.configPath);
|
|
1561
1629
|
|
|
1630
|
+
// Check platform login — override provider if logged in
|
|
1631
|
+
let providerOverride: string | undefined;
|
|
1632
|
+
try {
|
|
1633
|
+
const credPath = path.join(os.homedir(), '.fw', 'credentials.json');
|
|
1634
|
+
if (fs.existsSync(credPath)) {
|
|
1635
|
+
const creds = JSON.parse(fs.readFileSync(credPath, 'utf-8'));
|
|
1636
|
+
if (creds.token && creds.platformUrl && creds.expiresAt > Date.now()) {
|
|
1637
|
+
providerOverride = 'platform';
|
|
1638
|
+
// Make credentials available to the provider
|
|
1639
|
+
process.env.FW_PLATFORM_TOKEN = creds.token;
|
|
1640
|
+
process.env.FW_PLATFORM_URL = creds.platformUrl;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
} catch { /* not available */ }
|
|
1644
|
+
|
|
1562
1645
|
// Create provider
|
|
1563
|
-
const
|
|
1646
|
+
const agentMod = await import('@synergenius/flow-weaver/agent');
|
|
1647
|
+
const { createAnthropicProvider, createClaudeCliProvider } = agentMod;
|
|
1648
|
+
const createPlatformProvider = 'createPlatformProvider' in agentMod
|
|
1649
|
+
? (agentMod as Record<string, unknown>).createPlatformProvider as (opts: { token: string; platformUrl: string }) => unknown
|
|
1650
|
+
: null;
|
|
1564
1651
|
const providerSetting = config?.provider ?? 'auto';
|
|
1565
1652
|
const providerType = typeof providerSetting === 'object' ? providerSetting.name : String(providerSetting);
|
|
1566
1653
|
|
|
1567
|
-
let provider;
|
|
1568
|
-
if (
|
|
1654
|
+
let provider: import('@synergenius/flow-weaver/agent').AgentProvider;
|
|
1655
|
+
if ((providerOverride === 'platform' || providerType === 'platform') && createPlatformProvider) {
|
|
1656
|
+
provider = createPlatformProvider({
|
|
1657
|
+
token: process.env.FW_PLATFORM_TOKEN!,
|
|
1658
|
+
platformUrl: process.env.FW_PLATFORM_URL!,
|
|
1659
|
+
}) as import('@synergenius/flow-weaver/agent').AgentProvider;
|
|
1660
|
+
} else if (providerType === 'anthropic' || (providerType === 'auto' && process.env.ANTHROPIC_API_KEY)) {
|
|
1569
1661
|
const apiKey = process.env.ANTHROPIC_API_KEY ?? (typeof providerSetting === 'object' ? (providerSetting as { apiKey?: string }).apiKey : undefined);
|
|
1570
1662
|
if (!apiKey) { console.error('ANTHROPIC_API_KEY required for anthropic provider'); process.exit(1); }
|
|
1571
1663
|
provider = createAnthropicProvider({ apiKey, model: typeof providerSetting === 'object' ? providerSetting.model : undefined });
|
|
@@ -1584,6 +1676,7 @@ export async function handleAssistant(opts: ParsedArgs): Promise<void> {
|
|
|
1584
1676
|
projectDir,
|
|
1585
1677
|
resumeId: opts.assistantResume,
|
|
1586
1678
|
newConversation: opts.assistantNew,
|
|
1679
|
+
watchDir: opts.assistantWatch,
|
|
1587
1680
|
});
|
|
1588
1681
|
}
|
|
1589
1682
|
|
|
@@ -2125,6 +2218,15 @@ export async function handleDoctor(opts: ParsedArgs): Promise<void> {
|
|
|
2125
2218
|
checks.push({ label: 'Connection', status: 'warn', detail: 'Not tested' });
|
|
2126
2219
|
}
|
|
2127
2220
|
|
|
2221
|
+
// Weaver version (this pack)
|
|
2222
|
+
try {
|
|
2223
|
+
const url = await import('node:url');
|
|
2224
|
+
const packPkg = JSON.parse(fs.readFileSync(new url.URL('../package.json', import.meta.url) as unknown as string, 'utf-8'));
|
|
2225
|
+
checks.push({ label: 'Weaver', status: 'ok', detail: `v${packPkg.version}` });
|
|
2226
|
+
} catch {
|
|
2227
|
+
checks.push({ label: 'Weaver', status: 'ok', detail: 'unknown version' });
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2128
2230
|
// Flow Weaver version
|
|
2129
2231
|
try {
|
|
2130
2232
|
const { execFileSync: fwCheck } = await import('node:child_process');
|
|
@@ -140,7 +140,7 @@ export async function weaverAgentExecute(
|
|
|
140
140
|
auditEmit('run-start', { task: task.instruction });
|
|
141
141
|
|
|
142
142
|
try {
|
|
143
|
-
const provider = createProvider(pInfo, projectDir);
|
|
143
|
+
const provider = await createProvider(pInfo, projectDir);
|
|
144
144
|
const executor = createWeaverExecutor(projectDir);
|
|
145
145
|
|
|
146
146
|
const filesModified: string[] = [];
|
|
@@ -253,10 +253,10 @@ export async function weaverAgentExecute(
|
|
|
253
253
|
/**
|
|
254
254
|
* Create an AgentProvider from pack-weaver's ProviderInfo.
|
|
255
255
|
*/
|
|
256
|
-
function createProvider(
|
|
256
|
+
async function createProvider(
|
|
257
257
|
pInfo: { type: string; apiKey?: string; model?: string; maxTokens?: number },
|
|
258
258
|
projectDir?: string,
|
|
259
|
-
): AgentProvider {
|
|
259
|
+
): Promise<AgentProvider> {
|
|
260
260
|
const type = pInfo.type ?? 'auto';
|
|
261
261
|
|
|
262
262
|
// Explicit Anthropic API
|
|
@@ -273,8 +273,21 @@ function createProvider(
|
|
|
273
273
|
return createSessionProvider(pInfo.model, projectDir);
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
-
// Auto mode: try Anthropic API key
|
|
276
|
+
// Auto mode: try platform login, then Anthropic API key, then Claude CLI
|
|
277
277
|
if (type === 'auto') {
|
|
278
|
+
// Check platform login first (no local key needed)
|
|
279
|
+
// Uses env vars set by handleSession/handleAssistant, or reads credentials directly
|
|
280
|
+
if (process.env.FW_PLATFORM_TOKEN && process.env.FW_PLATFORM_URL) {
|
|
281
|
+
try {
|
|
282
|
+
const agentMod = await import('@synergenius/flow-weaver/agent');
|
|
283
|
+
if ('createPlatformProvider' in agentMod) {
|
|
284
|
+
const factory = (agentMod as Record<string, unknown>).createPlatformProvider as
|
|
285
|
+
(opts: { token: string; platformUrl: string }) => AgentProvider;
|
|
286
|
+
return factory({ token: process.env.FW_PLATFORM_TOKEN, platformUrl: process.env.FW_PLATFORM_URL });
|
|
287
|
+
}
|
|
288
|
+
} catch { /* platform provider not available in this fw version */ }
|
|
289
|
+
}
|
|
290
|
+
|
|
278
291
|
if (process.env.ANTHROPIC_API_KEY) {
|
|
279
292
|
return createAnthropicProvider({
|
|
280
293
|
apiKey: process.env.ANTHROPIC_API_KEY,
|