stagent 0.5.0 → 0.6.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 +8 -8
- package/dist/cli.js +146 -2
- package/docs/.coverage-gaps.json +21 -0
- package/docs/.last-generated +1 -1
- package/docs/features/agent-intelligence.md +36 -14
- package/docs/features/chat.md +33 -56
- package/docs/features/cost-usage.md +14 -10
- package/docs/features/dashboard-kanban.md +30 -13
- package/docs/features/delivery-channels.md +198 -0
- package/docs/features/design-system.md +10 -10
- package/docs/features/documents.md +8 -8
- package/docs/features/home-workspace.md +20 -15
- package/docs/features/inbox-notifications.md +22 -10
- package/docs/features/keyboard-navigation.md +11 -11
- package/docs/features/monitoring.md +1 -1
- package/docs/features/playbook.md +30 -32
- package/docs/features/profiles.md +33 -11
- package/docs/features/projects.md +2 -2
- package/docs/features/provider-runtimes.md +58 -14
- package/docs/features/schedules.md +70 -40
- package/docs/features/settings.md +74 -46
- package/docs/features/shared-components.md +7 -15
- package/docs/features/tool-permissions.md +9 -9
- package/docs/features/workflows.md +32 -21
- package/docs/getting-started.md +33 -9
- package/docs/index.md +25 -16
- package/docs/journeys/developer.md +124 -207
- package/docs/journeys/personal-use.md +70 -79
- package/docs/journeys/power-user.md +107 -151
- package/docs/journeys/work-use.md +81 -113
- package/docs/manifest.json +77 -45
- package/docs/superpowers/plans/2026-03-30-finish-in-progress-features.md +547 -0
- package/docs/use-cases/agency-operator.md +84 -0
- package/docs/use-cases/solo-founder.md +75 -0
- package/docs/why-stagent.md +59 -0
- package/package.json +10 -3
- package/src/app/api/channels/[id]/route.ts +103 -0
- package/src/app/api/channels/[id]/test/route.ts +52 -0
- package/src/app/api/channels/inbound/slack/route.ts +109 -0
- package/src/app/api/channels/inbound/telegram/poll/route.ts +128 -0
- package/src/app/api/channels/inbound/telegram/route.ts +76 -0
- package/src/app/api/channels/route.ts +71 -0
- package/src/app/api/chat/conversations/route.ts +15 -0
- package/src/app/api/chat/entities/search/route.ts +46 -31
- package/src/app/api/environment/profiles/suggest/route.ts +19 -3
- package/src/app/api/environment/scan/route.ts +8 -1
- package/src/app/api/handoffs/[id]/route.ts +76 -0
- package/src/app/api/handoffs/route.ts +89 -0
- package/src/app/api/memory/route.ts +181 -0
- package/src/app/api/profiles/[id]/route.ts +16 -1
- package/src/app/api/profiles/[id]/test/route.ts +4 -0
- package/src/app/api/profiles/[id]/test-results/route.ts +22 -0
- package/src/app/api/profiles/[id]/test-single/route.ts +64 -0
- package/src/app/api/profiles/assist/route.ts +35 -0
- package/src/app/api/profiles/import-repo/apply-updates/route.ts +123 -0
- package/src/app/api/profiles/import-repo/check-updates/route.ts +163 -0
- package/src/app/api/profiles/import-repo/confirm/route.ts +118 -0
- package/src/app/api/profiles/import-repo/preview/route.ts +107 -0
- package/src/app/api/profiles/import-repo/route.ts +29 -0
- package/src/app/api/profiles/import-repo/scan/route.ts +25 -0
- package/src/app/api/profiles/route.ts +73 -22
- package/src/app/api/runtimes/ollama/route.ts +86 -0
- package/src/app/api/runtimes/suggest/route.ts +29 -0
- package/src/app/api/schedules/[id]/heartbeat-history/route.ts +77 -0
- package/src/app/api/schedules/[id]/route.ts +41 -3
- package/src/app/api/schedules/parse/route.ts +66 -0
- package/src/app/api/schedules/route.ts +71 -12
- package/src/app/api/settings/author-default/route.ts +7 -0
- package/src/app/api/settings/learning/route.ts +41 -0
- package/src/app/api/settings/ollama/route.ts +34 -0
- package/src/app/api/settings/providers/route.ts +57 -0
- package/src/app/api/settings/routing/route.ts +24 -0
- package/src/app/api/settings/web-search/route.ts +28 -0
- package/src/app/api/tasks/[id]/execute/route.ts +13 -1
- package/src/app/documents/page.tsx +3 -0
- package/src/app/environment/page.tsx +8 -1
- package/src/app/settings/page.tsx +10 -4
- package/src/app/workflows/[id]/edit/page.tsx +2 -0
- package/src/app/workflows/new/page.tsx +2 -0
- package/src/components/chat/chat-command-popover.tsx +22 -19
- package/src/components/chat/chat-input.tsx +5 -0
- package/src/components/chat/chat-model-selector.tsx +42 -1
- package/src/components/chat/chat-shell.tsx +2 -0
- package/src/components/dashboard/welcome-landing.tsx +9 -9
- package/src/components/environment/artifact-card.tsx +27 -1
- package/src/components/environment/environment-dashboard.tsx +50 -2
- package/src/components/environment/environment-summary-card.tsx +5 -2
- package/src/components/environment/suggested-profiles.tsx +117 -52
- package/src/components/handoffs/handoff-approval-card.tsx +159 -0
- package/src/components/memory/memory-browser.tsx +315 -0
- package/src/components/profiles/learned-context-panel.tsx +4 -4
- package/src/components/profiles/profile-assist-panel.tsx +512 -0
- package/src/components/profiles/profile-browser.tsx +109 -8
- package/src/components/profiles/profile-card.tsx +29 -1
- package/src/components/profiles/profile-detail-view.tsx +200 -28
- package/src/components/profiles/profile-form-view.tsx +220 -82
- package/src/components/profiles/repo-import-wizard.tsx +648 -0
- package/src/components/profiles/smoke-test-editor.tsx +106 -0
- package/src/components/schedules/schedule-create-sheet.tsx +9 -1
- package/src/components/schedules/schedule-form.tsx +348 -9
- package/src/components/schedules/schedule-list.tsx +15 -2
- package/src/components/settings/auth-method-selector.tsx +7 -1
- package/src/components/settings/budget-guardrails-section.tsx +111 -48
- package/src/components/settings/channels-section.tsx +526 -0
- package/src/components/settings/chat-settings-section.tsx +27 -1
- package/src/components/settings/data-management-section.tsx +8 -6
- package/src/components/settings/learning-context-section.tsx +124 -0
- package/src/components/settings/ollama-section.tsx +270 -0
- package/src/components/settings/providers-runtimes-section.tsx +499 -0
- package/src/components/settings/web-search-section.tsx +101 -0
- package/src/components/shared/tag-input.tsx +156 -0
- package/src/components/tasks/kanban-board.tsx +32 -0
- package/src/components/tasks/kanban-column.tsx +4 -2
- package/src/components/tasks/task-card.tsx +1 -0
- package/src/components/tasks/task-chip-bar.tsx +6 -1
- package/src/components/tasks/task-create-panel.tsx +55 -5
- package/src/components/workflows/workflow-form-view.tsx +38 -3
- package/src/hooks/use-chat-autocomplete.ts +24 -26
- package/src/hooks/use-project-skills.ts +66 -0
- package/src/hooks/use-tag-suggestions.ts +31 -0
- package/src/instrumentation.ts +4 -1
- package/src/lib/agents/__tests__/claude-agent.test.ts +3 -0
- package/src/lib/agents/__tests__/learned-context.test.ts +10 -0
- package/src/lib/agents/agentic-loop.ts +235 -0
- package/src/lib/agents/browser-mcp.ts +59 -4
- package/src/lib/agents/claude-agent.ts +26 -199
- package/src/lib/agents/handoff/bus.ts +164 -0
- package/src/lib/agents/handoff/governance.ts +47 -0
- package/src/lib/agents/handoff/types.ts +16 -0
- package/src/lib/agents/learned-context.ts +27 -7
- package/src/lib/agents/memory/decay.ts +61 -0
- package/src/lib/agents/memory/extractor.ts +181 -0
- package/src/lib/agents/memory/retrieval.ts +96 -0
- package/src/lib/agents/memory/types.ts +6 -0
- package/src/lib/agents/profiles/__tests__/project-profiles.test.ts +119 -0
- package/src/lib/agents/profiles/__tests__/registry.test.ts +11 -3
- package/src/lib/agents/profiles/builtins/code-reviewer/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/content-creator/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/content-creator/profile.yaml +27 -0
- package/src/lib/agents/profiles/builtins/customer-support-agent/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/customer-support-agent/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/data-analyst/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/devops-engineer/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/document-writer/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/financial-analyst/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/financial-analyst/profile.yaml +24 -0
- package/src/lib/agents/profiles/builtins/general/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/health-fitness-coach/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/learning-coach/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/marketing-strategist/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/marketing-strategist/profile.yaml +27 -0
- package/src/lib/agents/profiles/builtins/operations-coordinator/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/operations-coordinator/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/project-manager/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/researcher/SKILL.md +1 -0
- package/src/lib/agents/profiles/builtins/researcher/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/sales-researcher/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/sales-researcher/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/shopping-assistant/SKILL.md +1 -0
- package/src/lib/agents/profiles/builtins/shopping-assistant/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/sweep/profile.yaml +1 -1
- package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/travel-planner/SKILL.md +2 -0
- package/src/lib/agents/profiles/builtins/travel-planner/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/wealth-manager/SKILL.md +2 -0
- package/src/lib/agents/profiles/builtins/wealth-manager/profile.yaml +2 -2
- package/src/lib/agents/profiles/project-profiles.ts +193 -0
- package/src/lib/agents/profiles/registry.ts +130 -6
- package/src/lib/agents/profiles/types.ts +28 -0
- package/src/lib/agents/router.ts +174 -2
- package/src/lib/agents/runtime/__tests__/catalog.test.ts +15 -4
- package/src/lib/agents/runtime/anthropic-direct.ts +644 -0
- package/src/lib/agents/runtime/catalog.ts +57 -2
- package/src/lib/agents/runtime/claude.ts +205 -1
- package/src/lib/agents/runtime/index.ts +22 -0
- package/src/lib/agents/runtime/ollama-adapter.ts +409 -0
- package/src/lib/agents/runtime/openai-direct.ts +514 -0
- package/src/lib/agents/runtime/profile-assist-types.ts +30 -0
- package/src/lib/agents/runtime/types.ts +2 -0
- package/src/lib/agents/tool-permissions.ts +203 -0
- package/src/lib/channels/gateway.ts +321 -0
- package/src/lib/channels/poller.ts +268 -0
- package/src/lib/channels/registry.ts +90 -0
- package/src/lib/channels/slack-adapter.ts +188 -0
- package/src/lib/channels/telegram-adapter.ts +218 -0
- package/src/lib/channels/types.ts +43 -0
- package/src/lib/channels/webhook-adapter.ts +74 -0
- package/src/lib/chat/context-builder.ts +22 -2
- package/src/lib/chat/engine.ts +95 -13
- package/src/lib/chat/ollama-engine.ts +198 -0
- package/src/lib/chat/stagent-tools.ts +106 -20
- package/src/lib/chat/tool-catalog.ts +24 -0
- package/src/lib/chat/tool-registry.ts +90 -0
- package/src/lib/chat/tools/chat-history-tools.ts +4 -4
- package/src/lib/chat/tools/document-tools.ts +7 -7
- package/src/lib/chat/tools/handoff-tools.ts +70 -0
- package/src/lib/chat/tools/notification-tools.ts +4 -4
- package/src/lib/chat/tools/profile-tools.ts +3 -3
- package/src/lib/chat/tools/project-tools.ts +3 -3
- package/src/lib/chat/tools/schedule-tools.ts +29 -13
- package/src/lib/chat/tools/settings-tools.ts +2 -2
- package/src/lib/chat/tools/task-tools.ts +66 -11
- package/src/lib/chat/tools/usage-tools.ts +2 -2
- package/src/lib/chat/tools/workflow-tools.ts +8 -8
- package/src/lib/chat/types.ts +11 -5
- package/src/lib/constants/known-tools.ts +19 -0
- package/src/lib/constants/prose-styles.ts +1 -1
- package/src/lib/constants/settings.ts +7 -0
- package/src/lib/data/channel-bindings.ts +85 -0
- package/src/lib/data/clear.ts +22 -0
- package/src/lib/data/profile-test-results.ts +48 -0
- package/src/lib/data/seed-data/conversations.ts +196 -0
- package/src/lib/data/seed-data/learned-context.ts +99 -0
- package/src/lib/data/seed-data/notifications.ts +54 -1
- package/src/lib/data/seed-data/profile-test-results.ts +96 -0
- package/src/lib/data/seed-data/repo-imports.ts +51 -0
- package/src/lib/data/seed-data/views.ts +60 -0
- package/src/lib/data/seed.ts +51 -0
- package/src/lib/db/bootstrap.ts +162 -0
- package/src/lib/db/migrations/0013_add_repo_imports.sql +15 -0
- package/src/lib/db/migrations/0014_add_linked_profile_id.sql +3 -0
- package/src/lib/db/migrations/0015_add_channel_bindings.sql +23 -0
- package/src/lib/db/schema.ts +187 -1
- package/src/lib/environment/__tests__/auto-scan.test.ts +86 -0
- package/src/lib/environment/__tests__/profile-linker.test.ts +187 -0
- package/src/lib/environment/auto-scan.ts +48 -0
- package/src/lib/environment/data.ts +25 -0
- package/src/lib/environment/profile-generator.ts +40 -10
- package/src/lib/environment/profile-linker.ts +143 -0
- package/src/lib/environment/profile-rules.ts +96 -0
- package/src/lib/import/dedup.ts +149 -0
- package/src/lib/import/format-adapter.ts +631 -0
- package/src/lib/import/github-api.ts +219 -0
- package/src/lib/import/repo-scanner.ts +251 -0
- package/src/lib/schedules/__tests__/nlp-parser.test.ts +330 -0
- package/src/lib/schedules/active-hours.ts +120 -0
- package/src/lib/schedules/heartbeat-parser.ts +224 -0
- package/src/lib/schedules/heartbeat-prompt.ts +153 -0
- package/src/lib/schedules/nlp-parser.ts +357 -0
- package/src/lib/schedules/scheduler.ts +218 -3
- package/src/lib/settings/__tests__/budget-guardrails.test.ts +39 -1
- package/src/lib/settings/helpers.ts +6 -0
- package/src/lib/settings/routing.ts +24 -0
- package/src/lib/settings/runtime-setup.ts +28 -1
- package/src/lib/usage/ledger.ts +2 -1
- package/src/lib/validators/__tests__/settings.test.ts +9 -0
- package/src/lib/validators/profile.ts +39 -0
- package/src/lib/workflows/blueprints/builtins/business-daily-briefing.yaml +102 -0
- package/src/lib/workflows/blueprints/builtins/content-marketing-pipeline.yaml +90 -0
- package/src/lib/workflows/blueprints/builtins/customer-support-triage.yaml +107 -0
- package/src/lib/workflows/blueprints/builtins/financial-reporting.yaml +104 -0
- package/src/lib/workflows/blueprints/builtins/lead-research-pipeline.yaml +82 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { getRuntimeSetupStates } from "@/lib/settings/runtime-setup";
|
|
3
|
+
import { getRoutingPreference } from "@/lib/settings/routing";
|
|
4
|
+
import { getAuthSettings } from "@/lib/settings/auth";
|
|
5
|
+
import { getOpenAIAuthSettings } from "@/lib/settings/openai-auth";
|
|
6
|
+
|
|
7
|
+
export async function GET() {
|
|
8
|
+
const [runtimeStates, routingPreference, anthropicAuth, openaiAuth] =
|
|
9
|
+
await Promise.all([
|
|
10
|
+
getRuntimeSetupStates(),
|
|
11
|
+
getRoutingPreference(),
|
|
12
|
+
getAuthSettings(),
|
|
13
|
+
getOpenAIAuthSettings(),
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const anthropicConfigured =
|
|
17
|
+
runtimeStates["claude-code"].configured ||
|
|
18
|
+
runtimeStates["anthropic-direct"].configured;
|
|
19
|
+
const openaiConfigured =
|
|
20
|
+
runtimeStates["openai-codex-app-server"].configured ||
|
|
21
|
+
runtimeStates["openai-direct"].configured;
|
|
22
|
+
|
|
23
|
+
// Detect dual-billing: user has OAuth (subscription) for Claude Code
|
|
24
|
+
// AND an API key (pay-as-you-go) for Anthropic Direct
|
|
25
|
+
const anthropicHasOAuth =
|
|
26
|
+
anthropicAuth.method === "oauth" || anthropicAuth.apiKeySource === "oauth";
|
|
27
|
+
const anthropicHasApiKey = anthropicAuth.hasKey;
|
|
28
|
+
const anthropicDualBilling = anthropicHasOAuth && anthropicHasApiKey;
|
|
29
|
+
|
|
30
|
+
return NextResponse.json({
|
|
31
|
+
providers: {
|
|
32
|
+
anthropic: {
|
|
33
|
+
configured: anthropicConfigured,
|
|
34
|
+
authMethod: anthropicAuth.method,
|
|
35
|
+
hasKey: anthropicAuth.hasKey,
|
|
36
|
+
apiKeySource: anthropicAuth.apiKeySource,
|
|
37
|
+
dualBilling: anthropicDualBilling,
|
|
38
|
+
runtimes: [
|
|
39
|
+
runtimeStates["claude-code"],
|
|
40
|
+
runtimeStates["anthropic-direct"],
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
openai: {
|
|
44
|
+
configured: openaiConfigured,
|
|
45
|
+
hasKey: openaiAuth.hasKey,
|
|
46
|
+
apiKeySource: openaiAuth.apiKeySource,
|
|
47
|
+
dualBilling: false,
|
|
48
|
+
runtimes: [
|
|
49
|
+
runtimeStates["openai-codex-app-server"],
|
|
50
|
+
runtimeStates["openai-direct"],
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
routingPreference,
|
|
55
|
+
configuredProviderCount: Number(anthropicConfigured) + Number(openaiConfigured),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { getRoutingPreference, setRoutingPreference } from "@/lib/settings/routing";
|
|
3
|
+
import type { RoutingPreference } from "@/lib/constants/settings";
|
|
4
|
+
|
|
5
|
+
const VALID_VALUES: RoutingPreference[] = ["cost", "latency", "quality", "manual"];
|
|
6
|
+
|
|
7
|
+
export async function GET() {
|
|
8
|
+
const preference = await getRoutingPreference();
|
|
9
|
+
return NextResponse.json({ preference });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function POST(req: NextRequest) {
|
|
13
|
+
const body = await req.json();
|
|
14
|
+
|
|
15
|
+
if (!body.preference || !VALID_VALUES.includes(body.preference)) {
|
|
16
|
+
return NextResponse.json(
|
|
17
|
+
{ error: `preference must be one of: ${VALID_VALUES.join(", ")}` },
|
|
18
|
+
{ status: 400 },
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
await setRoutingPreference(body.preference);
|
|
23
|
+
return NextResponse.json({ preference: body.preference });
|
|
24
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { getSetting, setSetting } from "@/lib/settings/helpers";
|
|
3
|
+
import { SETTINGS_KEYS } from "@/lib/constants/settings";
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
const exaEnabled = await getSetting(SETTINGS_KEYS.EXA_SEARCH_MCP_ENABLED);
|
|
7
|
+
|
|
8
|
+
return NextResponse.json({
|
|
9
|
+
exaSearchEnabled: exaEnabled === "true",
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function POST(req: NextRequest) {
|
|
14
|
+
const body = await req.json();
|
|
15
|
+
|
|
16
|
+
if (body.exaSearchEnabled !== undefined) {
|
|
17
|
+
await setSetting(
|
|
18
|
+
SETTINGS_KEYS.EXA_SEARCH_MCP_ENABLED,
|
|
19
|
+
body.exaSearchEnabled ? "true" : "false"
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const exaEnabled = await getSetting(SETTINGS_KEYS.EXA_SEARCH_MCP_ENABLED);
|
|
24
|
+
|
|
25
|
+
return NextResponse.json({
|
|
26
|
+
exaSearchEnabled: exaEnabled === "true",
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
2
|
import { db } from "@/lib/db";
|
|
3
|
-
import { tasks } from "@/lib/db/schema";
|
|
3
|
+
import { tasks, projects } from "@/lib/db/schema";
|
|
4
4
|
import { eq, and } from "drizzle-orm";
|
|
5
5
|
import { executeTaskWithAgent, classifyTaskProfile } from "@/lib/agents/router";
|
|
6
6
|
import { DEFAULT_AGENT_RUNTIME } from "@/lib/agents/runtime/catalog";
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
BudgetLimitExceededError,
|
|
10
10
|
enforceTaskBudgetGuardrails,
|
|
11
11
|
} from "@/lib/settings/budget-guardrails";
|
|
12
|
+
import { ensureFreshScan } from "@/lib/environment/auto-scan";
|
|
12
13
|
|
|
13
14
|
export async function POST(
|
|
14
15
|
_req: NextRequest,
|
|
@@ -47,6 +48,17 @@ export async function POST(
|
|
|
47
48
|
|
|
48
49
|
const task = claimed[0];
|
|
49
50
|
|
|
51
|
+
// Auto-scan environment if the task's project has a workingDirectory
|
|
52
|
+
if (task.projectId) {
|
|
53
|
+
const [project] = await db
|
|
54
|
+
.select()
|
|
55
|
+
.from(projects)
|
|
56
|
+
.where(eq(projects.id, task.projectId));
|
|
57
|
+
if (project?.workingDirectory) {
|
|
58
|
+
ensureFreshScan(project.workingDirectory, task.projectId);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
50
62
|
// Auto-classify profile if none was set
|
|
51
63
|
if (!task.agentProfile) {
|
|
52
64
|
const autoProfile = classifyTaskProfile(
|
|
@@ -24,6 +24,9 @@ export default async function DocumentsPage() {
|
|
|
24
24
|
extractedText: documents.extractedText,
|
|
25
25
|
processedPath: documents.processedPath,
|
|
26
26
|
processingError: documents.processingError,
|
|
27
|
+
source: documents.source,
|
|
28
|
+
conversationId: documents.conversationId,
|
|
29
|
+
messageId: documents.messageId,
|
|
27
30
|
createdAt: documents.createdAt,
|
|
28
31
|
updatedAt: documents.updatedAt,
|
|
29
32
|
taskTitle: tasks.title,
|
|
@@ -3,11 +3,16 @@ import { listTemplates } from "@/lib/environment/templates";
|
|
|
3
3
|
import { calculateHealthScore } from "@/lib/environment/health-scoring";
|
|
4
4
|
import { EnvironmentDashboard } from "@/components/environment/environment-dashboard";
|
|
5
5
|
import { PageShell } from "@/components/shared/page-shell";
|
|
6
|
-
import { getWorkspaceContext } from "@/lib/environment/workspace-context";
|
|
6
|
+
import { getWorkspaceContext, getLaunchCwd } from "@/lib/environment/workspace-context";
|
|
7
|
+
import { ensureFreshScan } from "@/lib/environment/auto-scan";
|
|
7
8
|
|
|
8
9
|
export const dynamic = "force-dynamic";
|
|
9
10
|
|
|
10
11
|
export default async function EnvironmentPage() {
|
|
12
|
+
// Auto-scan the workspace directory if stale or missing
|
|
13
|
+
const cwd = getLaunchCwd();
|
|
14
|
+
ensureFreshScan(cwd);
|
|
15
|
+
|
|
11
16
|
const scan = getLatestScan();
|
|
12
17
|
|
|
13
18
|
if (!scan) {
|
|
@@ -18,6 +23,7 @@ export default async function EnvironmentPage() {
|
|
|
18
23
|
artifacts={[]}
|
|
19
24
|
categoryCounts={[]}
|
|
20
25
|
toolCounts={[]}
|
|
26
|
+
scanPath={cwd}
|
|
21
27
|
/>
|
|
22
28
|
</PageShell>
|
|
23
29
|
);
|
|
@@ -48,6 +54,7 @@ export default async function EnvironmentPage() {
|
|
|
48
54
|
checkpoints={checkpoints}
|
|
49
55
|
templates={templates}
|
|
50
56
|
healthScore={healthScore}
|
|
57
|
+
scanPath={cwd}
|
|
51
58
|
/>
|
|
52
59
|
</PageShell>
|
|
53
60
|
);
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { OpenAIRuntimeSection } from "@/components/settings/openai-runtime-section";
|
|
1
|
+
import { ProvidersAndRuntimesSection } from "@/components/settings/providers-runtimes-section";
|
|
3
2
|
import { PermissionsSections } from "@/components/settings/permissions-sections";
|
|
4
3
|
import { DataManagementSection } from "@/components/settings/data-management-section";
|
|
5
4
|
import { BudgetGuardrailsSection } from "@/components/settings/budget-guardrails-section";
|
|
6
5
|
import { ChatSettingsSection } from "@/components/settings/chat-settings-section";
|
|
7
6
|
import { RuntimeTimeoutSection } from "@/components/settings/runtime-timeout-section";
|
|
8
7
|
import { BrowserToolsSection } from "@/components/settings/browser-tools-section";
|
|
8
|
+
import { WebSearchSection } from "@/components/settings/web-search-section";
|
|
9
|
+
import { LearningContextSection } from "@/components/settings/learning-context-section";
|
|
10
|
+
import { OllamaSection } from "@/components/settings/ollama-section";
|
|
11
|
+
import { ChannelsSection } from "@/components/settings/channels-section";
|
|
9
12
|
import { PageShell } from "@/components/shared/page-shell";
|
|
10
13
|
|
|
11
14
|
export const dynamic = "force-dynamic";
|
|
@@ -14,11 +17,14 @@ export default function SettingsPage() {
|
|
|
14
17
|
return (
|
|
15
18
|
<PageShell title="Settings" description="Manage your Stagent configuration">
|
|
16
19
|
<div className="space-y-6">
|
|
17
|
-
<
|
|
18
|
-
<
|
|
20
|
+
<ProvidersAndRuntimesSection />
|
|
21
|
+
<OllamaSection />
|
|
19
22
|
<ChatSettingsSection />
|
|
20
23
|
<RuntimeTimeoutSection />
|
|
24
|
+
<LearningContextSection />
|
|
25
|
+
<WebSearchSection />
|
|
21
26
|
<BrowserToolsSection />
|
|
27
|
+
<ChannelsSection />
|
|
22
28
|
<BudgetGuardrailsSection />
|
|
23
29
|
<PermissionsSections />
|
|
24
30
|
<DataManagementSection />
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from "lucide-react";
|
|
22
22
|
import type { LucideIcon } from "lucide-react";
|
|
23
23
|
import {
|
|
24
|
-
|
|
24
|
+
getToolCatalogWithSkills,
|
|
25
25
|
groupToolCatalog,
|
|
26
26
|
TOOL_GROUP_ICONS,
|
|
27
27
|
TOOL_GROUP_ORDER,
|
|
@@ -36,6 +36,7 @@ interface ChatCommandPopoverProps {
|
|
|
36
36
|
anchorRect: { top: number; left: number; height: number } | null;
|
|
37
37
|
entityResults: EntitySearchResult[];
|
|
38
38
|
entityLoading: boolean;
|
|
39
|
+
projectProfiles?: Array<{ id: string; name: string; description: string }>;
|
|
39
40
|
onSelect: (item: {
|
|
40
41
|
type: "slash" | "mention";
|
|
41
42
|
id: string;
|
|
@@ -81,6 +82,7 @@ export function ChatCommandPopover({
|
|
|
81
82
|
anchorRect,
|
|
82
83
|
entityResults,
|
|
83
84
|
entityLoading,
|
|
85
|
+
projectProfiles,
|
|
84
86
|
onSelect,
|
|
85
87
|
onClose,
|
|
86
88
|
}: ChatCommandPopoverProps) {
|
|
@@ -116,7 +118,7 @@ export function ChatCommandPopover({
|
|
|
116
118
|
data-chat-autocomplete=""
|
|
117
119
|
className="rounded-lg border bg-popover text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95 slide-in-from-bottom-2"
|
|
118
120
|
>
|
|
119
|
-
<Command shouldFilter
|
|
121
|
+
<Command shouldFilter loop>
|
|
120
122
|
{/* Hidden input for cmdk filtering — synced to query */}
|
|
121
123
|
<div className="sr-only">
|
|
122
124
|
<CommandInput value={query} />
|
|
@@ -127,13 +129,17 @@ export function ChatCommandPopover({
|
|
|
127
129
|
{mode === "slash" ? "No matching tools" : "No matching entities"}
|
|
128
130
|
</CommandEmpty>
|
|
129
131
|
|
|
130
|
-
{mode === "slash" &&
|
|
132
|
+
{mode === "slash" && (
|
|
133
|
+
<ToolCatalogItems
|
|
134
|
+
onSelect={onSelect}
|
|
135
|
+
projectProfiles={projectProfiles}
|
|
136
|
+
/>
|
|
137
|
+
)}
|
|
131
138
|
|
|
132
139
|
{mode === "mention" && (
|
|
133
140
|
<MentionItems
|
|
134
141
|
results={entityResults}
|
|
135
142
|
loading={entityLoading}
|
|
136
|
-
query={query}
|
|
137
143
|
onSelect={onSelect}
|
|
138
144
|
/>
|
|
139
145
|
)}
|
|
@@ -147,10 +153,15 @@ export function ChatCommandPopover({
|
|
|
147
153
|
|
|
148
154
|
function ToolCatalogItems({
|
|
149
155
|
onSelect,
|
|
156
|
+
projectProfiles,
|
|
150
157
|
}: {
|
|
151
158
|
onSelect: ChatCommandPopoverProps["onSelect"];
|
|
159
|
+
projectProfiles?: ChatCommandPopoverProps["projectProfiles"];
|
|
152
160
|
}) {
|
|
153
|
-
const catalog =
|
|
161
|
+
const catalog = getToolCatalogWithSkills({
|
|
162
|
+
includeBrowser: true,
|
|
163
|
+
projectProfiles,
|
|
164
|
+
});
|
|
154
165
|
const groups = groupToolCatalog(catalog);
|
|
155
166
|
|
|
156
167
|
return (
|
|
@@ -172,7 +183,9 @@ function ToolCatalogItems({
|
|
|
172
183
|
label: entry.name,
|
|
173
184
|
text: entry.behavior === "execute_immediately"
|
|
174
185
|
? entry.name
|
|
175
|
-
:
|
|
186
|
+
: entry.group === "Skills"
|
|
187
|
+
? `Use the ${entry.name} profile: `
|
|
188
|
+
: `Use ${entry.name} to `,
|
|
176
189
|
})
|
|
177
190
|
}
|
|
178
191
|
>
|
|
@@ -200,27 +213,17 @@ function ToolCatalogItems({
|
|
|
200
213
|
function MentionItems({
|
|
201
214
|
results,
|
|
202
215
|
loading,
|
|
203
|
-
query,
|
|
204
216
|
onSelect,
|
|
205
217
|
}: {
|
|
206
218
|
results: EntitySearchResult[];
|
|
207
219
|
loading: boolean;
|
|
208
|
-
query: string;
|
|
209
220
|
onSelect: ChatCommandPopoverProps["onSelect"];
|
|
210
221
|
}) {
|
|
211
222
|
if (loading && results.length === 0) {
|
|
212
223
|
return (
|
|
213
224
|
<div className="flex items-center gap-2 px-3 py-4 text-sm text-muted-foreground">
|
|
214
225
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
215
|
-
|
|
216
|
-
</div>
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (!query) {
|
|
221
|
-
return (
|
|
222
|
-
<div className="px-3 py-4 text-sm text-muted-foreground text-center">
|
|
223
|
-
Type to search entities...
|
|
226
|
+
Loading...
|
|
224
227
|
</div>
|
|
225
228
|
);
|
|
226
229
|
}
|
|
@@ -228,7 +231,7 @@ function MentionItems({
|
|
|
228
231
|
const grouped = groupByType(results);
|
|
229
232
|
const entityTypes = Object.keys(grouped);
|
|
230
233
|
|
|
231
|
-
if (entityTypes.length === 0
|
|
234
|
+
if (entityTypes.length === 0) {
|
|
232
235
|
return null; // CommandEmpty will show
|
|
233
236
|
}
|
|
234
237
|
|
|
@@ -242,7 +245,7 @@ function MentionItems({
|
|
|
242
245
|
{grouped[type].map((entity) => (
|
|
243
246
|
<CommandItem
|
|
244
247
|
key={`${entity.entityType}-${entity.entityId}`}
|
|
245
|
-
value={`${entity.entityType} ${entity.label}`}
|
|
248
|
+
value={`${entity.entityType} ${entity.label} ${entity.description ?? ""} ${entity.status ?? ""}`}
|
|
246
249
|
onSelect={() =>
|
|
247
250
|
onSelect({
|
|
248
251
|
type: "mention",
|
|
@@ -8,6 +8,7 @@ import { ChatModelSelector } from "./chat-model-selector";
|
|
|
8
8
|
import { ChatCommandPopover } from "./chat-command-popover";
|
|
9
9
|
import { useChatAutocomplete, type MentionReference } from "@/hooks/use-chat-autocomplete";
|
|
10
10
|
import { getToolCatalog } from "@/lib/chat/tool-catalog";
|
|
11
|
+
import { useProjectSkills } from "@/hooks/use-project-skills";
|
|
11
12
|
import type { ChatModelOption } from "@/lib/chat/types";
|
|
12
13
|
|
|
13
14
|
interface ChatInputProps {
|
|
@@ -19,6 +20,7 @@ interface ChatInputProps {
|
|
|
19
20
|
modelId?: string;
|
|
20
21
|
onModelChange?: (modelId: string) => void;
|
|
21
22
|
availableModels?: ChatModelOption[];
|
|
23
|
+
projectId?: string | null;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export function ChatInput({
|
|
@@ -30,10 +32,12 @@ export function ChatInput({
|
|
|
30
32
|
modelId,
|
|
31
33
|
onModelChange,
|
|
32
34
|
availableModels,
|
|
35
|
+
projectId,
|
|
33
36
|
}: ChatInputProps) {
|
|
34
37
|
const [value, setValue] = useState("");
|
|
35
38
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
36
39
|
const autocomplete = useChatAutocomplete();
|
|
40
|
+
const { skills: projectSkills } = useProjectSkills(projectId);
|
|
37
41
|
|
|
38
42
|
// Sync textarea ref with autocomplete hook
|
|
39
43
|
useEffect(() => {
|
|
@@ -206,6 +210,7 @@ export function ChatInput({
|
|
|
206
210
|
anchorRect={autocomplete.state.anchorRect}
|
|
207
211
|
entityResults={autocomplete.entityResults}
|
|
208
212
|
entityLoading={autocomplete.entityLoading}
|
|
213
|
+
projectProfiles={projectSkills.length > 0 ? projectSkills : undefined}
|
|
209
214
|
onSelect={handlePopoverSelect}
|
|
210
215
|
onClose={autocomplete.close}
|
|
211
216
|
/>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
3
4
|
import { CHAT_MODELS, DEFAULT_CHAT_MODEL, type ChatModelOption } from "@/lib/chat/types";
|
|
4
5
|
import { Button } from "@/components/ui/button";
|
|
5
6
|
import {
|
|
@@ -24,6 +25,7 @@ const tierEmoji: Record<string, string> = {
|
|
|
24
25
|
Fast: "\u26A1",
|
|
25
26
|
Balanced: "\u2728",
|
|
26
27
|
Best: "\uD83D\uDC8E",
|
|
28
|
+
Local: "\uD83C\uDFE0",
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
export function ChatModelSelector({
|
|
@@ -31,7 +33,27 @@ export function ChatModelSelector({
|
|
|
31
33
|
onModelChange,
|
|
32
34
|
models = CHAT_MODELS,
|
|
33
35
|
}: ChatModelSelectorProps) {
|
|
34
|
-
const
|
|
36
|
+
const [ollamaModels, setOllamaModels] = useState<ChatModelOption[]>([]);
|
|
37
|
+
|
|
38
|
+
// Fetch available Ollama models on mount
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
fetch("/api/runtimes/ollama")
|
|
41
|
+
.then((r) => (r.ok ? r.json() : { models: [] }))
|
|
42
|
+
.then((data: { models?: Array<{ name: string; details?: { parameter_size?: string } }> }) => {
|
|
43
|
+
const models = (data.models ?? []).map((m) => ({
|
|
44
|
+
id: `ollama:${m.name}`,
|
|
45
|
+
label: m.name.replace(/:latest$/, ""),
|
|
46
|
+
provider: "ollama" as const,
|
|
47
|
+
tier: "Local",
|
|
48
|
+
costLabel: "Free",
|
|
49
|
+
}));
|
|
50
|
+
setOllamaModels(models);
|
|
51
|
+
})
|
|
52
|
+
.catch(() => {});
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
const allModels = [...models, ...ollamaModels];
|
|
56
|
+
const current = allModels.find((m) => m.id === modelId) ?? allModels[0] ?? { id: modelId, label: modelId, provider: "anthropic" as const, tier: "Balanced", costLabel: "$$" };
|
|
35
57
|
|
|
36
58
|
const anthropicModels = models.filter(
|
|
37
59
|
(m) => m.provider === "anthropic"
|
|
@@ -83,6 +105,25 @@ export function ChatModelSelector({
|
|
|
83
105
|
</DropdownMenuGroup>
|
|
84
106
|
</>
|
|
85
107
|
)}
|
|
108
|
+
|
|
109
|
+
{ollamaModels.length > 0 && (
|
|
110
|
+
<>
|
|
111
|
+
<DropdownMenuSeparator />
|
|
112
|
+
<DropdownMenuLabel className="text-xs text-muted-foreground">
|
|
113
|
+
Ollama (Local)
|
|
114
|
+
</DropdownMenuLabel>
|
|
115
|
+
<DropdownMenuGroup>
|
|
116
|
+
{ollamaModels.map((m) => (
|
|
117
|
+
<ModelMenuItem
|
|
118
|
+
key={m.id}
|
|
119
|
+
model={m}
|
|
120
|
+
isSelected={m.id === modelId}
|
|
121
|
+
onSelect={onModelChange}
|
|
122
|
+
/>
|
|
123
|
+
))}
|
|
124
|
+
</DropdownMenuGroup>
|
|
125
|
+
</>
|
|
126
|
+
)}
|
|
86
127
|
</DropdownMenuContent>
|
|
87
128
|
</DropdownMenu>
|
|
88
129
|
);
|
|
@@ -515,6 +515,7 @@ export function ChatShell({
|
|
|
515
515
|
modelId={modelId}
|
|
516
516
|
onModelChange={handleModelChange}
|
|
517
517
|
availableModels={availableModels}
|
|
518
|
+
projectId={activeConversation?.projectId}
|
|
518
519
|
/>
|
|
519
520
|
</ChatEmptyState>
|
|
520
521
|
</div>
|
|
@@ -539,6 +540,7 @@ export function ChatShell({
|
|
|
539
540
|
modelId={modelId}
|
|
540
541
|
onModelChange={handleModelChange}
|
|
541
542
|
availableModels={availableModels}
|
|
543
|
+
projectId={activeConversation?.projectId}
|
|
542
544
|
/>
|
|
543
545
|
</>
|
|
544
546
|
)}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import Link from "next/link";
|
|
2
2
|
import { Button } from "@/components/ui/button";
|
|
3
|
-
import { Shield,
|
|
3
|
+
import { Shield, Zap, Wallet } from "lucide-react";
|
|
4
4
|
|
|
5
5
|
const pillars = [
|
|
6
6
|
{
|
|
7
7
|
icon: Shield,
|
|
8
|
-
title: "
|
|
9
|
-
description: "Every agent action
|
|
8
|
+
title: "Your Rules, Enforced",
|
|
9
|
+
description: "Every agent action respects your policies. Full audit trail for every decision.",
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
|
-
icon:
|
|
13
|
-
title: "
|
|
14
|
-
description: "Build workflow templates once, run them many times.
|
|
12
|
+
icon: Zap,
|
|
13
|
+
title: "Business on Autopilot",
|
|
14
|
+
description: "Build workflow templates once, run them many times. 21 specialist profiles ready to deploy.",
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
icon: Wallet,
|
|
18
|
-
title: "
|
|
19
|
-
description: "Track spend per task, per
|
|
18
|
+
title: "Know What You Spend",
|
|
19
|
+
description: "Track spend per task, per provider. Budget guardrails prevent surprise bills.",
|
|
20
20
|
},
|
|
21
21
|
];
|
|
22
22
|
|
|
@@ -31,7 +31,7 @@ export function WelcomeLanding() {
|
|
|
31
31
|
Welcome to Stagent
|
|
32
32
|
</h1>
|
|
33
33
|
<p className="text-base text-muted-foreground mb-8 max-w-lg">
|
|
34
|
-
|
|
34
|
+
Your AI Business Operating System. Deploy AI agents, automate business processes, and maintain full control of spend and execution.
|
|
35
35
|
</p>
|
|
36
36
|
|
|
37
37
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 w-full mb-8">
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { Card, CardContent } from "@/components/ui/card";
|
|
4
4
|
import { Badge } from "@/components/ui/badge";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Bot, Link as LinkIcon } from "lucide-react";
|
|
5
7
|
import type { EnvironmentArtifactRow } from "@/lib/db/schema";
|
|
6
8
|
import { CATEGORY_META } from "./summary-cards-row";
|
|
7
9
|
import { PersonaIndicator } from "./persona-indicator";
|
|
@@ -9,6 +11,7 @@ import { PersonaIndicator } from "./persona-indicator";
|
|
|
9
11
|
interface ArtifactCardProps {
|
|
10
12
|
artifact: EnvironmentArtifactRow;
|
|
11
13
|
onClick: () => void;
|
|
14
|
+
onCreateProfile?: () => void;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
function formatSize(bytes: number): string {
|
|
@@ -17,7 +20,7 @@ function formatSize(bytes: number): string {
|
|
|
17
20
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
export function ArtifactCard({ artifact, onClick }: ArtifactCardProps) {
|
|
23
|
+
export function ArtifactCard({ artifact, onClick, onCreateProfile }: ArtifactCardProps) {
|
|
21
24
|
const meta = CATEGORY_META[artifact.category];
|
|
22
25
|
const Icon = meta?.icon;
|
|
23
26
|
|
|
@@ -57,10 +60,33 @@ export function ArtifactCard({ artifact, onClick }: ArtifactCardProps) {
|
|
|
57
60
|
<Badge variant="secondary" className="text-[10px] px-1.5 py-0">
|
|
58
61
|
{artifact.scope}
|
|
59
62
|
</Badge>
|
|
63
|
+
{/* Profile linkage indicator for skill artifacts */}
|
|
64
|
+
{artifact.category === "skill" && artifact.linkedProfileId && (
|
|
65
|
+
<Badge variant="default" className="text-[10px] px-1.5 py-0 gap-0.5">
|
|
66
|
+
<LinkIcon className="h-2.5 w-2.5" />
|
|
67
|
+
Profile
|
|
68
|
+
</Badge>
|
|
69
|
+
)}
|
|
60
70
|
<span className="text-[10px] text-muted-foreground ml-auto">
|
|
61
71
|
{formatSize(artifact.sizeBytes)}
|
|
62
72
|
</span>
|
|
63
73
|
</div>
|
|
74
|
+
|
|
75
|
+
{/* Create Profile button for unlinked skill artifacts */}
|
|
76
|
+
{artifact.category === "skill" && !artifact.linkedProfileId && onCreateProfile && (
|
|
77
|
+
<Button
|
|
78
|
+
variant="outline"
|
|
79
|
+
size="sm"
|
|
80
|
+
className="w-full text-xs gap-1.5"
|
|
81
|
+
onClick={(e) => {
|
|
82
|
+
e.stopPropagation();
|
|
83
|
+
onCreateProfile();
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
<Bot className="h-3 w-3" />
|
|
87
|
+
Create Profile
|
|
88
|
+
</Button>
|
|
89
|
+
)}
|
|
64
90
|
</CardContent>
|
|
65
91
|
</Card>
|
|
66
92
|
);
|