@aion0/forge 0.10.84 → 0.10.86
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/RELEASE_NOTES.md +5 -5
- package/app/api/schedules/[id]/route.ts +27 -20
- package/app/api/schedules/extract/route.ts +23 -16
- package/app/chat/page.tsx +7 -1231
- package/components/Dashboard.tsx +37 -15
- package/components/HomeView.tsx +142 -0
- package/components/PipelineActivityPanel.tsx +327 -0
- package/components/SchedulesView.tsx +7 -1
- package/components/WebChatPanel.tsx +1253 -0
- package/lib/chat/agent-loop.ts +10 -11
- package/lib/pipeline.ts +43 -1
- package/lib/projects.ts +154 -1
- package/package.json +1 -1
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# Forge v0.10.
|
|
1
|
+
# Forge v0.10.86
|
|
2
2
|
|
|
3
|
-
Released: 2026-06-
|
|
3
|
+
Released: 2026-06-16
|
|
4
4
|
|
|
5
|
-
## Changes since v0.10.
|
|
5
|
+
## Changes since v0.10.85
|
|
6
6
|
|
|
7
7
|
### Other
|
|
8
|
-
- fix(
|
|
8
|
+
- fix(schedules): pause/resume + extractor openai support
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.
|
|
11
|
+
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.85...v0.10.86
|
|
@@ -116,29 +116,36 @@ export async function PATCH(req: Request, { params }: { params: Promise<{ id: st
|
|
|
116
116
|
}
|
|
117
117
|
patch.action_config = body.action_config;
|
|
118
118
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
|
|
119
|
+
// Cross-field validation runs ONLY for the section being patched.
|
|
120
|
+
// Toggling unrelated fields (e.g. `enabled` from the Pause/Resume button)
|
|
121
|
+
// must not re-validate sections the user isn't touching — a schedule with a
|
|
122
|
+
// pre-existing quirk in its action_config would otherwise refuse to pause.
|
|
123
|
+
if (patch.action_kind !== undefined || patch.action_config !== undefined) {
|
|
124
|
+
const effectiveActionKind = patch.action_kind ?? existing.action_kind;
|
|
125
|
+
const effectiveActionConfig = patch.action_config ?? existing.action_config ?? {};
|
|
126
|
+
if (effectiveActionKind === 'chat') {
|
|
127
|
+
const sid = typeof effectiveActionConfig.session_id === 'string' ? effectiveActionConfig.session_id.trim() : '';
|
|
128
|
+
if (!sid) return NextResponse.json({ error: 'action_kind=chat requires action_config.session_id' }, { status: 400 });
|
|
129
|
+
}
|
|
130
|
+
if (effectiveActionKind === 'email') {
|
|
131
|
+
const toRaw = effectiveActionConfig.to;
|
|
132
|
+
const to = Array.isArray(toRaw)
|
|
133
|
+
? toRaw.filter((x) => typeof x === 'string' && x.trim())
|
|
134
|
+
: (typeof toRaw === 'string' && toRaw.trim() ? [toRaw] : []);
|
|
135
|
+
if (to.length === 0) {
|
|
136
|
+
return NextResponse.json({ error: 'action_kind=email requires action_config.to (recipient address or array)' }, { status: 400 });
|
|
137
|
+
}
|
|
132
138
|
}
|
|
133
139
|
}
|
|
134
140
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
if (patch.schedule_kind !== undefined || patch.schedule_cron !== undefined || patch.schedule_at !== undefined) {
|
|
142
|
+
const effectiveKind = patch.schedule_kind ?? existing.schedule_kind;
|
|
143
|
+
if (effectiveKind === 'cron' && !(patch.schedule_cron ?? existing.schedule_cron)) {
|
|
144
|
+
return NextResponse.json({ error: 'schedule_cron is required when schedule_kind=cron' }, { status: 400 });
|
|
145
|
+
}
|
|
146
|
+
if (effectiveKind === 'once' && !(patch.schedule_at ?? existing.schedule_at)) {
|
|
147
|
+
return NextResponse.json({ error: 'schedule_at is required when schedule_kind=once' }, { status: 400 });
|
|
148
|
+
}
|
|
142
149
|
}
|
|
143
150
|
|
|
144
151
|
updateSchedule(id, patch);
|
|
@@ -19,6 +19,8 @@ import { inferAdapter, pickApiKey, pickBaseUrl } from '@/lib/chat/agent-loop';
|
|
|
19
19
|
import { makeAnthropicClient } from '@/lib/chat/llm/anthropic';
|
|
20
20
|
import { listAgents } from '@/lib/agents';
|
|
21
21
|
import { scanProjects } from '@/lib/projects';
|
|
22
|
+
import { createOpenAI } from '@ai-sdk/openai';
|
|
23
|
+
import { generateText } from 'ai';
|
|
22
24
|
|
|
23
25
|
interface Extracted {
|
|
24
26
|
name: string;
|
|
@@ -116,15 +118,9 @@ export async function POST(req: Request) {
|
|
|
116
118
|
? profiles[preferredId]
|
|
117
119
|
: candidates[0]![1];
|
|
118
120
|
const adapter = inferAdapter(profile.provider);
|
|
119
|
-
if (adapter !== 'anthropic') {
|
|
120
|
-
return NextResponse.json({
|
|
121
|
-
ok: false,
|
|
122
|
-
error: 'extractor currently supports anthropic profiles only (openai is a follow-up)',
|
|
123
|
-
}, { status: 400 });
|
|
124
|
-
}
|
|
125
121
|
const apiKey = pickApiKey(profile, adapter);
|
|
126
122
|
const baseUrl = pickBaseUrl(profile, adapter);
|
|
127
|
-
const model = profile.model || 'claude-sonnet-4-6';
|
|
123
|
+
const model = profile.model || (adapter === 'anthropic' ? 'claude-sonnet-4-6' : 'gpt-4o-mini');
|
|
128
124
|
|
|
129
125
|
const cliAgents = listAgents().filter((a: any) => a.backendType !== 'api');
|
|
130
126
|
const availableAgentIds = cliAgents.map((a: any) => a.id);
|
|
@@ -134,15 +130,26 @@ export async function POST(req: Request) {
|
|
|
134
130
|
|
|
135
131
|
let raw = '';
|
|
136
132
|
try {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
133
|
+
if (adapter === 'anthropic') {
|
|
134
|
+
const client = makeAnthropicClient(apiKey, baseUrl);
|
|
135
|
+
const resp = await client.messages.create({
|
|
136
|
+
model,
|
|
137
|
+
max_tokens: 1500,
|
|
138
|
+
system,
|
|
139
|
+
messages: [{ role: 'user', content: text }],
|
|
140
|
+
});
|
|
141
|
+
for (const block of resp.content) {
|
|
142
|
+
if (block.type === 'text') raw += block.text;
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
// OpenAI / OpenAI-compatible — non-streaming one-shot via @ai-sdk/openai.
|
|
146
|
+
const provider = createOpenAI({ apiKey, baseURL: baseUrl });
|
|
147
|
+
const resp = await generateText({
|
|
148
|
+
model: provider.chat(model),
|
|
149
|
+
system,
|
|
150
|
+
messages: [{ role: 'user', content: text }],
|
|
151
|
+
});
|
|
152
|
+
raw = resp.text;
|
|
146
153
|
}
|
|
147
154
|
} catch (e: any) {
|
|
148
155
|
return NextResponse.json({
|