@synergenius/flow-weaver-pack-weaver 0.9.4 → 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 +130 -17
- 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/bot/response-formatter.d.ts +15 -0
- package/dist/bot/response-formatter.d.ts.map +1 -0
- package/dist/bot/response-formatter.js +40 -0
- package/dist/bot/response-formatter.js.map +1 -0
- package/dist/bot/rich-input.d.ts +39 -0
- package/dist/bot/rich-input.d.ts.map +1 -0
- package/dist/bot/rich-input.js +308 -0
- package/dist/bot/rich-input.js.map +1 -0
- package/dist/bot/slash-commands.d.ts +20 -0
- package/dist/bot/slash-commands.d.ts.map +1 -0
- package/dist/bot/slash-commands.js +93 -0
- package/dist/bot/slash-commands.js.map +1 -0
- 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 +131 -19
- package/src/bot/conversation-store.ts +32 -0
- package/src/bot/response-formatter.ts +42 -0
- package/src/bot/rich-input.ts +307 -0
- package/src/bot/slash-commands.ts +114 -0
- package/src/cli-handlers.ts +105 -3
- package/src/node-types/agent-execute.ts +17 -4
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { ToolExecutor } from '@synergenius/flow-weaver/agent';
|
|
2
|
+
import { c } from './ansi.js';
|
|
3
|
+
|
|
4
|
+
export interface SlashContext {
|
|
5
|
+
executor: ToolExecutor;
|
|
6
|
+
out: (s: string) => void;
|
|
7
|
+
projectDir: string;
|
|
8
|
+
conversationId?: string;
|
|
9
|
+
onClear?: () => void;
|
|
10
|
+
onExit?: () => void;
|
|
11
|
+
onNew?: () => void;
|
|
12
|
+
onVerbose?: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SlashCommand {
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
handler: (ctx: SlashContext, args: string) => Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const SLASH_COMMANDS: SlashCommand[] = [
|
|
22
|
+
{
|
|
23
|
+
name: '/help',
|
|
24
|
+
description: 'Show available commands',
|
|
25
|
+
handler: async (ctx) => {
|
|
26
|
+
ctx.out('\n Available commands:\n');
|
|
27
|
+
for (const cmd of SLASH_COMMANDS) {
|
|
28
|
+
ctx.out(` ${c.cyan(cmd.name.padEnd(12))} ${c.dim(cmd.description)}\n`);
|
|
29
|
+
}
|
|
30
|
+
ctx.out('\n');
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: '/status',
|
|
35
|
+
description: 'Show bots, queue, and conversation summary',
|
|
36
|
+
handler: async (ctx) => {
|
|
37
|
+
const bots = await ctx.executor('bot_list', {});
|
|
38
|
+
const summary = await ctx.executor('conversation_summary', {});
|
|
39
|
+
ctx.out(`\n ${bots.result}\n ${summary.result}\n\n`);
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: '/bots',
|
|
44
|
+
description: 'List running bots',
|
|
45
|
+
handler: async (ctx) => {
|
|
46
|
+
const result = await ctx.executor('bot_list', {});
|
|
47
|
+
ctx.out(`\n ${result.result}\n\n`);
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: '/clear',
|
|
52
|
+
description: 'Clear screen and start new conversation',
|
|
53
|
+
handler: async (ctx) => {
|
|
54
|
+
process.stderr.write('\x1b[2J\x1b[H');
|
|
55
|
+
ctx.onClear?.();
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: '/exit',
|
|
60
|
+
description: 'Exit the assistant',
|
|
61
|
+
handler: async (ctx) => {
|
|
62
|
+
ctx.onExit?.();
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: '/new',
|
|
67
|
+
description: 'Start a new conversation',
|
|
68
|
+
handler: async (ctx) => {
|
|
69
|
+
ctx.onNew?.();
|
|
70
|
+
ctx.out(`\n ${c.dim('New conversation started.')}\n\n`);
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: '/list',
|
|
75
|
+
description: 'List saved conversations',
|
|
76
|
+
handler: async (ctx) => {
|
|
77
|
+
const result = await ctx.executor('conversation_list', {});
|
|
78
|
+
ctx.out(`\n ${result.result}\n\n`);
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: '/verbose',
|
|
83
|
+
description: 'Toggle verbose mode (show AI thinking)',
|
|
84
|
+
handler: async (ctx) => {
|
|
85
|
+
ctx.onVerbose?.();
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: '/history',
|
|
90
|
+
description: 'Show conversation history summary',
|
|
91
|
+
handler: async (ctx) => {
|
|
92
|
+
const result = await ctx.executor('conversation_summary', {});
|
|
93
|
+
ctx.out(`\n ${result.result}\n\n`);
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
export function getSlashCompletions(partial: string): string[] {
|
|
99
|
+
if (!partial.startsWith('/')) return [];
|
|
100
|
+
return SLASH_COMMANDS
|
|
101
|
+
.filter(cmd => cmd.name.startsWith(partial))
|
|
102
|
+
.map(cmd => cmd.name);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function handleSlashCommand(
|
|
106
|
+
input: string,
|
|
107
|
+
ctx: SlashContext,
|
|
108
|
+
): Promise<boolean> {
|
|
109
|
+
const [name, ...rest] = input.split(' ');
|
|
110
|
+
const cmd = SLASH_COMMANDS.find(c => c.name === name);
|
|
111
|
+
if (!cmd) return false;
|
|
112
|
+
await cmd.handler(ctx, rest.join(' '));
|
|
113
|
+
return true;
|
|
114
|
+
}
|
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,
|