stagent 0.1.3 → 0.1.5
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 +36 -82
- package/dist/cli.js +6 -10
- package/next.config.mjs +0 -8
- package/package.json +2 -8
- package/public/readme/architecture.svg +192 -0
- package/public/readme/home-workspace.png +0 -0
- package/public/readme/inbox-approvals.png +0 -0
- package/public/readme/workflow-blueprints.png +0 -0
- package/src/lib/desktop/__tests__/sidecar-launch.test.ts +1 -11
- package/src/lib/desktop/sidecar-launch.ts +1 -5
- package/src/lib/notifications/actionable.ts +1 -1
- package/public/desktop-icon-512.png +0 -0
- package/src/lib/tauri-bridge.ts +0 -138
package/README.md
CHANGED
|
@@ -1,34 +1,19 @@
|
|
|
1
1
|
# Stagent
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
[](https://github.com/navam-io/stagent/releases/latest/download/Stagent.dmg) [](https://github.com/navam-io/stagent/releases/latest)
|
|
3
|
+
> Governed AI agent operations with reusable profiles, workflow blueprints, scheduled runs, and local-first oversight.
|
|
6
4
|
|
|
7
5
|
[](https://nextjs.org/) [](https://react.dev/) [](https://www.typescriptlang.org/) [](https://docs.anthropic.com/) [](https://developers.openai.com/codex/app-server) [](LICENSE)
|
|
8
6
|
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
AI agents are powerful, but production deployment breaks down when teams cannot see what the agent is doing, understand which rules it is following, or intervene before an unsafe action lands. Stagent solves that operating problem.
|
|
12
|
-
|
|
13
|
-
Stagent is a local-first AI operations workspace built around governed execution and reusable automation primitives. Instead of treating every agent run as a one-off prompt, it gives teams a structured system of home workspace signals, execution dashboards, project context, workflow blueprints, reusable profiles, schedules, documents, inbox approvals, and live monitoring.
|
|
14
|
-
|
|
15
|
-
## Get Started
|
|
7
|
+
## Quick Start
|
|
16
8
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Grab the latest `Stagent.dmg` or `Stagent.app.zip` asset from GitHub Releases, then move `Stagent.app` into `/Applications`.
|
|
22
|
-
|
|
23
|
-
Current desktop release notes:
|
|
24
|
-
|
|
25
|
-
- macOS-only for now
|
|
26
|
-
- GitHub desktop releases are intended to be Developer ID signed and notarized; local smoke builds created with `npm run desktop:release -- --skip-upload` will still warn if Apple credentials are not configured
|
|
27
|
-
- the current desktop wrapper expects `node` to be available on the machine
|
|
9
|
+
```bash
|
|
10
|
+
npx stagent
|
|
11
|
+
```
|
|
28
12
|
|
|
29
|
-
|
|
13
|
+
Open [localhost:3000](http://localhost:3000).
|
|
30
14
|
|
|
31
|
-
|
|
15
|
+
<details>
|
|
16
|
+
<summary>Contributor setup (clone + dev server)</summary>
|
|
32
17
|
|
|
33
18
|
```bash
|
|
34
19
|
git clone <repo-url> && cd stagent && npm install
|
|
@@ -43,7 +28,29 @@ EOF
|
|
|
43
28
|
npm run dev
|
|
44
29
|
```
|
|
45
30
|
|
|
46
|
-
|
|
31
|
+
</details>
|
|
32
|
+
|
|
33
|
+
**Profiles & Policies** · **Blueprints & Schedules** · **Open Source**
|
|
34
|
+
|
|
35
|
+
<img src="https://unpkg.com/stagent@latest/public/readme/home-workspace.png" alt="Stagent home workspace" width="1200" />
|
|
36
|
+
|
|
37
|
+
| Home Workspace | Reusable Profiles | Workflow Blueprints | Governed Execution |
|
|
38
|
+
|:-:|:-:|:-:|:-:|
|
|
39
|
+
| Workspace briefing with active work, pending review, project signals, and live activity | Specialist definitions with prompts, tool policy, and runtime tuning you can reuse | Pre-configured templates with dynamic forms, YAML editing, and lineage tracking | Human-in-the-loop approvals, tool permissions, and ambient supervision |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Why Stagent
|
|
44
|
+
|
|
45
|
+
AI agents are powerful — but production use breaks down when teams cannot see what the agent is doing, which rules it follows, or intervene before an unsafe action lands. Stagent gives you a governed operations workspace where every run is visible, every profile is reusable, and every approval is auditable. Run it locally with `npx stagent` and own your data from day one.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Runtime Bridge
|
|
50
|
+
|
|
51
|
+
Stagent ships a shared runtime registry that routes tasks, schedules, and workflow steps through two governed execution backends: **Claude Code** (Anthropic Claude Agent SDK) and **OpenAI Codex App Server**. Both land in the same inbox, monitoring, and task-state surfaces — so switching providers is a config change, not a rewrite.
|
|
52
|
+
|
|
53
|
+
---
|
|
47
54
|
|
|
48
55
|
## Feature Highlights
|
|
49
56
|
|
|
@@ -69,33 +76,7 @@ Open [localhost:3000](http://localhost:3000) to get started.
|
|
|
69
76
|
|
|
70
77
|
## Architecture
|
|
71
78
|
|
|
72
|
-
|
|
73
|
-
┌─────────────────────────────────────────────────────────┐
|
|
74
|
-
│ Browser (React 19) │
|
|
75
|
-
│ Home · Dashboard · Projects · Documents · Workflows │
|
|
76
|
-
│ Profiles · Schedules · Inbox · Monitor · Settings │
|
|
77
|
-
└──────────────┬──────────────────────┬────────────────────┘
|
|
78
|
-
│ Server Components │ API Routes
|
|
79
|
-
│ (direct DB queries) │ (mutations only)
|
|
80
|
-
┌──────────────▼──────────────────────▼────────────────────┐
|
|
81
|
-
│ Next.js 16 Server │
|
|
82
|
-
│ │
|
|
83
|
-
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ │
|
|
84
|
-
│ │ Drizzle ORM │ │ Runtime │ │ SSE Stream │ │
|
|
85
|
-
│ │ (SQLite) │ │ Registry │ │ (Logs) │ │
|
|
86
|
-
│ └──────┬──────┘ └──────┬───────┘ └─────┬──────────┘ │
|
|
87
|
-
│ │ │ │ │
|
|
88
|
-
│ ┌──────┴──────┐ ┌─────┴────────────┐ ┌─┴───────────┐ │
|
|
89
|
-
│ │ Scheduler │ │ Claude + OpenAI │ │ Permission │ │
|
|
90
|
-
│ │ Engine │ │ runtime adapters │ │ Checker │ │
|
|
91
|
-
│ └─────────────┘ └──────────────────┘ └─────────────┘ │
|
|
92
|
-
└──────────┬────────────────┬─────────────────┬────────────┘
|
|
93
|
-
│ │ │
|
|
94
|
-
┌──────▼──────┐ ┌─────▼─────────────┐ ┌─────▼──────┐
|
|
95
|
-
│ ~/.stagent/ │ │ Anthropic / OpenAI│ │ Browser │
|
|
96
|
-
│ stagent.db │ │ runtime backends │ │ EventSource│
|
|
97
|
-
└─────────────┘ └───────────────────┘ └────────────┘
|
|
98
|
-
```
|
|
79
|
+
<img src="https://unpkg.com/stagent@latest/public/readme/architecture.svg" alt="Stagent architecture diagram" width="900" />
|
|
99
80
|
|
|
100
81
|
**Key design decisions:**
|
|
101
82
|
|
|
@@ -125,7 +106,7 @@ Create and organize projects as containers for related tasks. Each project can s
|
|
|
125
106
|
### Agent
|
|
126
107
|
|
|
127
108
|
#### Provider Runtimes
|
|
128
|
-
Stagent
|
|
109
|
+
Stagent supports two governed execution runtimes behind a shared runtime registry:
|
|
129
110
|
- **Claude Code** via the Anthropic Claude Agent SDK
|
|
130
111
|
- **OpenAI Codex App Server** via `codex app-server`
|
|
131
112
|
|
|
@@ -149,7 +130,7 @@ Multi-step task orchestration with three patterns:
|
|
|
149
130
|
State machine engine with step-level retry, project association, and real-time status visualization.
|
|
150
131
|
|
|
151
132
|
#### Parallel + Swarm Workflows
|
|
152
|
-
Stagent
|
|
133
|
+
Stagent supports two bounded expansion patterns on top of the workflow engine:
|
|
153
134
|
- **Parallel research fork/join** — 2-5 concurrent branches followed by one synthesis step
|
|
154
135
|
- **Swarm orchestration** — mayor → worker pool → refinery with retryable stages and configurable worker concurrency
|
|
155
136
|
|
|
@@ -217,10 +198,7 @@ File upload with drag-and-drop in task creation. Type-aware content preview for
|
|
|
217
198
|
Configuration hub with provider-aware sections: Claude authentication (API key or OAuth), OpenAI Codex runtime API-key management, tool permissions (saved "Always Allow" patterns with revoke), and data management.
|
|
218
199
|
|
|
219
200
|
#### CLI
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
#### Tauri Desktop
|
|
223
|
-
The repo produces macOS desktop artifacts via Tauri. Local development uses `npm run desktop:dev`, local packaging uses `npm run desktop:build`, and release publishing now runs locally via `npm run desktop:release` so the uploaded GitHub assets stay on stable names: `Stagent.dmg` and `Stagent.app.zip`. Published releases must be built with `APPLE_SIGNING_IDENTITY` plus notarization credentials so the downloaded app clears Gatekeeper without the "Apple could not verify" malware warning. Keep `public/desktop-icon-512.png` as the dedicated rounded desktop source icon with transparent corners; `public/icon-512.png` remains the square web/PWA icon. The local release script also stamps the same branded icon onto the mounted DMG volume so Finder shows the corrected Stagent icon during install.
|
|
201
|
+
The `npx stagent` entry point boots a Next.js server from the published npm package. It is built from `bin/cli.ts` into `dist/cli.js` using tsup, and serves as the primary distribution channel — no clone required.
|
|
224
202
|
|
|
225
203
|
#### Database
|
|
226
204
|
SQLite with WAL mode via better-sqlite3 + Drizzle ORM. Eight tables: `projects`, `tasks`, `workflows`, `agent_logs`, `notifications`, `documents`, `schedules`, `settings`. Self-healing bootstrap — tables are created on startup if missing.
|
|
@@ -257,29 +235,6 @@ npm test # Run Vitest
|
|
|
257
235
|
npm run test:coverage # Coverage report
|
|
258
236
|
```
|
|
259
237
|
|
|
260
|
-
## Desktop Release Checklist
|
|
261
|
-
|
|
262
|
-
```bash
|
|
263
|
-
npm run desktop:release
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
For a build-only smoke check, run `npm run desktop:release -- --skip-upload`.
|
|
267
|
-
|
|
268
|
-
Before publishing a real desktop release, configure Apple signing once on the release machine:
|
|
269
|
-
|
|
270
|
-
```bash
|
|
271
|
-
export APPLE_SIGNING_IDENTITY="Developer ID Application: Your Name (TEAMID)"
|
|
272
|
-
xcrun notarytool store-credentials stagent-notary \
|
|
273
|
-
--apple-id "you@example.com" \
|
|
274
|
-
--team-id "TEAMID" \
|
|
275
|
-
--password "app-specific-password"
|
|
276
|
-
export APPLE_NOTARY_PROFILE="stagent-notary"
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
You can use direct credentials instead of a keychain profile by exporting `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, and `APPLE_TEAM_ID`.
|
|
280
|
-
|
|
281
|
-
The release script builds locally on macOS, signs the app, notarizes and staples the app bundle and DMG when Apple credentials are configured, verifies the DMG, creates or updates the `desktop-v<package.json version>` GitHub release, uploads `Stagent.dmg` and `Stagent.app.zip`, and refreshes the stable download URL at `https://github.com/navam-io/stagent/releases/latest/download/Stagent.dmg`. Uploads now fail fast unless Developer ID signing and notarization are configured so unsigned artifacts are not published by accident.
|
|
282
|
-
|
|
283
238
|
### Project Structure
|
|
284
239
|
|
|
285
240
|
```
|
|
@@ -383,7 +338,6 @@ All 14 features shipped across three layers:
|
|
|
383
338
|
| **Agent Self-Improvement** | Agents learn patterns and update context with human approval |
|
|
384
339
|
| **Document Output Generation** | Agent-generated documents as deliverables |
|
|
385
340
|
| **Parallel Workflows** | Concurrent step execution within workflows |
|
|
386
|
-
| **Tauri Desktop** | Native desktop app packaging |
|
|
387
341
|
|
|
388
342
|
---
|
|
389
343
|
|
|
@@ -395,7 +349,7 @@ All 14 features shipped across three layers:
|
|
|
395
349
|
4. Run `npm test` and `npx tsc --noEmit`
|
|
396
350
|
5. Submit a pull request
|
|
397
351
|
|
|
398
|
-
See `
|
|
352
|
+
See `AGENTS.md` for architecture details and development conventions.
|
|
399
353
|
|
|
400
354
|
## License
|
|
401
355
|
|
package/dist/cli.js
CHANGED
|
@@ -63,15 +63,12 @@ function resolveNextEntrypoint(cwd, exists = existsSync) {
|
|
|
63
63
|
function buildNextLaunchArgs({
|
|
64
64
|
isPrebuilt,
|
|
65
65
|
port,
|
|
66
|
-
host = SIDECAR_LOOPBACK_HOST
|
|
67
|
-
turbopack = true
|
|
66
|
+
host = SIDECAR_LOOPBACK_HOST
|
|
68
67
|
}) {
|
|
69
68
|
if (isPrebuilt) {
|
|
70
69
|
return ["start", "--hostname", host, "--port", String(port)];
|
|
71
70
|
}
|
|
72
|
-
|
|
73
|
-
if (turbopack) args.push("--turbopack");
|
|
74
|
-
return args;
|
|
71
|
+
return ["dev", "--hostname", host, "--port", String(port)];
|
|
75
72
|
}
|
|
76
73
|
function buildSidecarUrl(port, host = SIDECAR_LOOPBACK_HOST) {
|
|
77
74
|
return `http://${host}:${port}`;
|
|
@@ -364,15 +361,15 @@ Data:
|
|
|
364
361
|
Logs ${join3(DATA_DIR, "logs")}
|
|
365
362
|
|
|
366
363
|
Environment variables:
|
|
367
|
-
STAGENT_DATA_DIR Custom data directory for the
|
|
364
|
+
STAGENT_DATA_DIR Custom data directory for the web app
|
|
368
365
|
ANTHROPIC_API_KEY Claude runtime access
|
|
369
366
|
OPENAI_API_KEY OpenAI Codex runtime access
|
|
370
367
|
|
|
371
368
|
Examples:
|
|
372
369
|
node dist/cli.js --port 3210 --no-open
|
|
373
|
-
STAGENT_DATA_DIR=/tmp/stagent
|
|
370
|
+
STAGENT_DATA_DIR=/tmp/stagent node dist/cli.js --reset
|
|
374
371
|
`;
|
|
375
|
-
program.name("stagent").description("Governed
|
|
372
|
+
program.name("stagent").description("Governed AI agent workspace").version(pkg.version).addHelpText("after", HELP_TEXT).option("-p, --port <number>", "port to start on", "3000").option("--reset", "delete the local database before starting").option("--no-open", "don't auto-open browser").parse();
|
|
376
373
|
var opts = program.opts();
|
|
377
374
|
var requestedPort = Number.parseInt(opts.port, 10);
|
|
378
375
|
if (Number.isNaN(requestedPort) || requestedPort <= 0) {
|
|
@@ -465,8 +462,7 @@ async function main() {
|
|
|
465
462
|
const isPrebuilt = existsSync2(join3(effectiveCwd, ".next", "BUILD_ID"));
|
|
466
463
|
const nextArgs = buildNextLaunchArgs({
|
|
467
464
|
isPrebuilt,
|
|
468
|
-
port: actualPort
|
|
469
|
-
turbopack: effectiveCwd === appDir
|
|
465
|
+
port: actualPort
|
|
470
466
|
});
|
|
471
467
|
const sidecarUrl = buildSidecarUrl(actualPort);
|
|
472
468
|
console.log(`Stagent ${pkg.version}`);
|
package/next.config.mjs
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
import { dirname } from "path";
|
|
2
|
-
import { fileURLToPath } from "url";
|
|
3
|
-
|
|
4
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
-
|
|
6
1
|
/** @type {import('next').NextConfig} */
|
|
7
2
|
const nextConfig = {
|
|
8
3
|
serverExternalPackages: ["better-sqlite3"],
|
|
9
4
|
devIndicators: false,
|
|
10
|
-
turbopack: {
|
|
11
|
-
root: __dirname,
|
|
12
|
-
},
|
|
13
5
|
};
|
|
14
6
|
|
|
15
7
|
export default nextConfig;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stagent",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Governed
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "Governed AI agent workspace for supervised local execution, workflows, documents, and provider runtimes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
7
7
|
"agents",
|
|
@@ -40,12 +40,6 @@
|
|
|
40
40
|
"dev": "next dev --turbopack",
|
|
41
41
|
"build": "next build",
|
|
42
42
|
"build:cli": "tsup",
|
|
43
|
-
"desktop:dev": "node scripts/tauri.mjs dev",
|
|
44
|
-
"desktop:build": "node scripts/tauri.mjs build",
|
|
45
|
-
"desktop:smoke": "node scripts/desktop-sidecar-smoke.mjs",
|
|
46
|
-
"desktop:smoke:dmg": "node scripts/desktop-mounted-dmg-smoke.mjs",
|
|
47
|
-
"desktop:icon": "node scripts/tauri.mjs icon",
|
|
48
|
-
"desktop:release": "node scripts/release-desktop.mjs",
|
|
49
43
|
"test": "vitest run",
|
|
50
44
|
"test:watch": "vitest",
|
|
51
45
|
"test:coverage": "vitest run --coverage",
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 520" font-family="system-ui, -apple-system, 'Segoe UI', sans-serif">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bgGrad" x1="0" y1="0" x2="0" y2="1">
|
|
4
|
+
<stop offset="0%" stop-color="#0F172A"/>
|
|
5
|
+
<stop offset="100%" stop-color="#1E293B"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="browserGrad" x1="0" y1="0" x2="1" y2="1">
|
|
8
|
+
<stop offset="0%" stop-color="#2563EB"/>
|
|
9
|
+
<stop offset="100%" stop-color="#22D3EE"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
<linearGradient id="serverGrad" x1="0" y1="0" x2="1" y2="1">
|
|
12
|
+
<stop offset="0%" stop-color="#1E293B"/>
|
|
13
|
+
<stop offset="100%" stop-color="#334155"/>
|
|
14
|
+
</linearGradient>
|
|
15
|
+
<linearGradient id="externalGrad" x1="0" y1="0" x2="1" y2="1">
|
|
16
|
+
<stop offset="0%" stop-color="#7C3AED"/>
|
|
17
|
+
<stop offset="100%" stop-color="#A78BFA"/>
|
|
18
|
+
</linearGradient>
|
|
19
|
+
<filter id="glow">
|
|
20
|
+
<feGaussianBlur stdDeviation="2" result="blur"/>
|
|
21
|
+
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
|
22
|
+
</filter>
|
|
23
|
+
<filter id="shadow">
|
|
24
|
+
<feDropShadow dx="0" dy="2" stdDeviation="4" flood-color="#000" flood-opacity="0.3"/>
|
|
25
|
+
</filter>
|
|
26
|
+
</defs>
|
|
27
|
+
|
|
28
|
+
<!-- Background -->
|
|
29
|
+
<rect width="900" height="520" rx="16" fill="url(#bgGrad)"/>
|
|
30
|
+
|
|
31
|
+
<!-- Title -->
|
|
32
|
+
<text x="450" y="36" text-anchor="middle" fill="#94A3B8" font-size="12" font-weight="500" letter-spacing="2">STAGENT ARCHITECTURE</text>
|
|
33
|
+
|
|
34
|
+
<!-- ═══════════ BROWSER LAYER ═══════════ -->
|
|
35
|
+
<rect x="40" y="52" width="820" height="78" rx="12" fill="none" stroke="url(#browserGrad)" stroke-width="1.5" opacity="0.8" filter="url(#shadow)"/>
|
|
36
|
+
<rect x="40" y="52" width="820" height="78" rx="12" fill="#0F172A" opacity="0.6"/>
|
|
37
|
+
|
|
38
|
+
<text x="64" y="74" fill="#22D3EE" font-size="11" font-weight="700" letter-spacing="1.5">BROWSER</text>
|
|
39
|
+
<text x="152" y="74" fill="#475569" font-size="10">React 19</text>
|
|
40
|
+
|
|
41
|
+
<!-- Route pills -->
|
|
42
|
+
<rect x="64" y="86" width="60" height="24" rx="12" fill="#2563EB" opacity="0.2"/>
|
|
43
|
+
<text x="94" y="102" text-anchor="middle" fill="#60A5FA" font-size="10" font-weight="500">Home</text>
|
|
44
|
+
|
|
45
|
+
<rect x="132" y="86" width="76" height="24" rx="12" fill="#2563EB" opacity="0.2"/>
|
|
46
|
+
<text x="170" y="102" text-anchor="middle" fill="#60A5FA" font-size="10" font-weight="500">Dashboard</text>
|
|
47
|
+
|
|
48
|
+
<rect x="216" y="86" width="68" height="24" rx="12" fill="#2563EB" opacity="0.2"/>
|
|
49
|
+
<text x="250" y="102" text-anchor="middle" fill="#60A5FA" font-size="10" font-weight="500">Projects</text>
|
|
50
|
+
|
|
51
|
+
<rect x="292" y="86" width="56" height="24" rx="12" fill="#2563EB" opacity="0.2"/>
|
|
52
|
+
<text x="320" y="102" text-anchor="middle" fill="#60A5FA" font-size="10" font-weight="500">Docs</text>
|
|
53
|
+
|
|
54
|
+
<rect x="356" y="86" width="76" height="24" rx="12" fill="#2563EB" opacity="0.2"/>
|
|
55
|
+
<text x="394" y="102" text-anchor="middle" fill="#60A5FA" font-size="10" font-weight="500">Workflows</text>
|
|
56
|
+
|
|
57
|
+
<rect x="440" y="86" width="64" height="24" rx="12" fill="#2563EB" opacity="0.2"/>
|
|
58
|
+
<text x="472" y="102" text-anchor="middle" fill="#60A5FA" font-size="10" font-weight="500">Profiles</text>
|
|
59
|
+
|
|
60
|
+
<rect x="512" y="86" width="76" height="24" rx="12" fill="#2563EB" opacity="0.2"/>
|
|
61
|
+
<text x="550" y="102" text-anchor="middle" fill="#60A5FA" font-size="10" font-weight="500">Schedules</text>
|
|
62
|
+
|
|
63
|
+
<rect x="596" y="86" width="52" height="24" rx="12" fill="#2563EB" opacity="0.2"/>
|
|
64
|
+
<text x="622" y="102" text-anchor="middle" fill="#60A5FA" font-size="10" font-weight="500">Inbox</text>
|
|
65
|
+
|
|
66
|
+
<rect x="656" y="86" width="66" height="24" rx="12" fill="#2563EB" opacity="0.2"/>
|
|
67
|
+
<text x="689" y="102" text-anchor="middle" fill="#60A5FA" font-size="10" font-weight="500">Monitor</text>
|
|
68
|
+
|
|
69
|
+
<rect x="730" y="86" width="68" height="24" rx="12" fill="#2563EB" opacity="0.2"/>
|
|
70
|
+
<text x="764" y="102" text-anchor="middle" fill="#60A5FA" font-size="10" font-weight="500">Settings</text>
|
|
71
|
+
|
|
72
|
+
<!-- Connection lines from browser to server -->
|
|
73
|
+
<line x1="300" y1="130" x2="300" y2="162" stroke="#22D3EE" stroke-width="1" opacity="0.4"/>
|
|
74
|
+
<line x1="600" y1="130" x2="600" y2="162" stroke="#22D3EE" stroke-width="1" opacity="0.4"/>
|
|
75
|
+
|
|
76
|
+
<!-- Connection labels -->
|
|
77
|
+
<text x="240" y="150" fill="#475569" font-size="9">Server Components</text>
|
|
78
|
+
<text x="237" y="159" fill="#334155" font-size="8">(direct DB queries)</text>
|
|
79
|
+
<text x="562" y="150" fill="#475569" font-size="9">API Routes</text>
|
|
80
|
+
<text x="550" y="159" fill="#334155" font-size="8">(mutations only)</text>
|
|
81
|
+
|
|
82
|
+
<!-- ═══════════ SERVER LAYER ═══════════ -->
|
|
83
|
+
<rect x="40" y="164" width="820" height="200" rx="12" fill="url(#serverGrad)" opacity="0.5" filter="url(#shadow)"/>
|
|
84
|
+
<rect x="40" y="164" width="820" height="200" rx="12" fill="none" stroke="#334155" stroke-width="1"/>
|
|
85
|
+
|
|
86
|
+
<text x="64" y="186" fill="#F8FAFC" font-size="11" font-weight="700" letter-spacing="1.5">NEXT.JS 16 SERVER</text>
|
|
87
|
+
|
|
88
|
+
<!-- Row 1: Core services -->
|
|
89
|
+
<!-- Drizzle ORM -->
|
|
90
|
+
<rect x="64" y="200" width="200" height="60" rx="8" fill="#1E293B" stroke="#334155" stroke-width="1"/>
|
|
91
|
+
<rect x="64" y="200" width="4" height="60" rx="2" fill="#22D3EE"/>
|
|
92
|
+
<text x="80" y="222" fill="#F1F5F9" font-size="12" font-weight="600">Drizzle ORM</text>
|
|
93
|
+
<text x="80" y="240" fill="#64748B" font-size="10">SQLite · WAL mode</text>
|
|
94
|
+
<text x="80" y="252" fill="#64748B" font-size="10">8 tables · Self-healing bootstrap</text>
|
|
95
|
+
|
|
96
|
+
<!-- Runtime Registry -->
|
|
97
|
+
<rect x="280" y="200" width="220" height="60" rx="8" fill="#1E293B" stroke="#334155" stroke-width="1"/>
|
|
98
|
+
<rect x="280" y="200" width="4" height="60" rx="2" fill="#2563EB"/>
|
|
99
|
+
<text x="296" y="222" fill="#F1F5F9" font-size="12" font-weight="600">Runtime Registry</text>
|
|
100
|
+
<text x="296" y="240" fill="#64748B" font-size="10">Provider abstraction layer</text>
|
|
101
|
+
<text x="296" y="252" fill="#64748B" font-size="10">Claude + OpenAI adapters</text>
|
|
102
|
+
|
|
103
|
+
<!-- SSE Stream -->
|
|
104
|
+
<rect x="516" y="200" width="160" height="60" rx="8" fill="#1E293B" stroke="#334155" stroke-width="1"/>
|
|
105
|
+
<rect x="516" y="200" width="4" height="60" rx="2" fill="#7C3AED"/>
|
|
106
|
+
<text x="532" y="222" fill="#F1F5F9" font-size="12" font-weight="600">SSE Stream</text>
|
|
107
|
+
<text x="532" y="240" fill="#64748B" font-size="10">Real-time log push</text>
|
|
108
|
+
<text x="532" y="252" fill="#64748B" font-size="10">/api/logs/stream</text>
|
|
109
|
+
|
|
110
|
+
<!-- Profile Engine -->
|
|
111
|
+
<rect x="692" y="200" width="152" height="60" rx="8" fill="#1E293B" stroke="#334155" stroke-width="1"/>
|
|
112
|
+
<rect x="692" y="200" width="4" height="60" rx="2" fill="#F59E0B"/>
|
|
113
|
+
<text x="708" y="222" fill="#F1F5F9" font-size="12" font-weight="600">Profile Engine</text>
|
|
114
|
+
<text x="708" y="240" fill="#64748B" font-size="10">13 agent profiles</text>
|
|
115
|
+
<text x="708" y="252" fill="#64748B" font-size="10">MCP · Tools · Tuning</text>
|
|
116
|
+
|
|
117
|
+
<!-- Row 2: Engines -->
|
|
118
|
+
<!-- Scheduler Engine -->
|
|
119
|
+
<rect x="64" y="276" width="200" height="60" rx="8" fill="#1E293B" stroke="#334155" stroke-width="1"/>
|
|
120
|
+
<rect x="64" y="276" width="4" height="60" rx="2" fill="#10B981"/>
|
|
121
|
+
<text x="80" y="298" fill="#F1F5F9" font-size="12" font-weight="600">Scheduler Engine</text>
|
|
122
|
+
<text x="80" y="316" fill="#64748B" font-size="10">Cron + intervals · Poll-based</text>
|
|
123
|
+
<text x="80" y="328" fill="#64748B" font-size="10">One-shot & recurring</text>
|
|
124
|
+
|
|
125
|
+
<!-- Workflow Engine -->
|
|
126
|
+
<rect x="280" y="276" width="220" height="60" rx="8" fill="#1E293B" stroke="#334155" stroke-width="1"/>
|
|
127
|
+
<rect x="280" y="276" width="4" height="60" rx="2" fill="#EC4899"/>
|
|
128
|
+
<text x="296" y="298" fill="#F1F5F9" font-size="12" font-weight="600">Workflow Engine</text>
|
|
129
|
+
<text x="296" y="316" fill="#64748B" font-size="10">Sequence · Planner-Executor</text>
|
|
130
|
+
<text x="296" y="328" fill="#64748B" font-size="10">Parallel · Swarm · Checkpoints</text>
|
|
131
|
+
|
|
132
|
+
<!-- Permission Checker -->
|
|
133
|
+
<rect x="516" y="276" width="160" height="60" rx="8" fill="#1E293B" stroke="#334155" stroke-width="1"/>
|
|
134
|
+
<rect x="516" y="276" width="4" height="60" rx="2" fill="#EF4444"/>
|
|
135
|
+
<text x="532" y="298" fill="#F1F5F9" font-size="12" font-weight="600">Permission Gate</text>
|
|
136
|
+
<text x="532" y="316" fill="#64748B" font-size="10">Always Allow patterns</text>
|
|
137
|
+
<text x="532" y="328" fill="#64748B" font-size="10">canUseTool polling</text>
|
|
138
|
+
|
|
139
|
+
<!-- Document Processor -->
|
|
140
|
+
<rect x="692" y="276" width="152" height="60" rx="8" fill="#1E293B" stroke="#334155" stroke-width="1"/>
|
|
141
|
+
<rect x="692" y="276" width="4" height="60" rx="2" fill="#06B6D4"/>
|
|
142
|
+
<text x="708" y="298" fill="#F1F5F9" font-size="12" font-weight="600">Doc Processor</text>
|
|
143
|
+
<text x="708" y="316" fill="#64748B" font-size="10">5-format extraction</text>
|
|
144
|
+
<text x="708" y="328" fill="#64748B" font-size="10">Context injection</text>
|
|
145
|
+
|
|
146
|
+
<!-- Connection lines from server to externals -->
|
|
147
|
+
<line x1="164" y1="364" x2="164" y2="404" stroke="#22D3EE" stroke-width="1" opacity="0.4"/>
|
|
148
|
+
<line x1="450" y1="364" x2="450" y2="404" stroke="#2563EB" stroke-width="1" opacity="0.4"/>
|
|
149
|
+
<line x1="736" y1="364" x2="736" y2="404" stroke="#7C3AED" stroke-width="1" opacity="0.4"/>
|
|
150
|
+
|
|
151
|
+
<!-- ═══════════ EXTERNAL LAYER ═══════════ -->
|
|
152
|
+
<!-- Database -->
|
|
153
|
+
<rect x="64" y="408" width="200" height="72" rx="12" fill="none" stroke="#22D3EE" stroke-width="1" opacity="0.6"/>
|
|
154
|
+
<rect x="64" y="408" width="200" height="72" rx="12" fill="#0F172A" opacity="0.8"/>
|
|
155
|
+
<!-- Database icon -->
|
|
156
|
+
<ellipse cx="100" cy="432" rx="12" ry="6" fill="none" stroke="#22D3EE" stroke-width="1.2"/>
|
|
157
|
+
<line x1="88" y1="432" x2="88" y2="446" stroke="#22D3EE" stroke-width="1.2"/>
|
|
158
|
+
<line x1="112" y1="432" x2="112" y2="446" stroke="#22D3EE" stroke-width="1.2"/>
|
|
159
|
+
<ellipse cx="100" cy="446" rx="12" ry="6" fill="none" stroke="#22D3EE" stroke-width="1.2"/>
|
|
160
|
+
<text x="122" y="436" fill="#F1F5F9" font-size="12" font-weight="600">~/.stagent/</text>
|
|
161
|
+
<text x="122" y="452" fill="#64748B" font-size="10">stagent.db</text>
|
|
162
|
+
<text x="80" y="472" fill="#475569" font-size="9">SQLite · WAL · better-sqlite3</text>
|
|
163
|
+
|
|
164
|
+
<!-- Runtime Backends -->
|
|
165
|
+
<rect x="300" y="408" width="300" height="72" rx="12" fill="none" stroke="#2563EB" stroke-width="1" opacity="0.6"/>
|
|
166
|
+
<rect x="300" y="408" width="300" height="72" rx="12" fill="#0F172A" opacity="0.8"/>
|
|
167
|
+
<!-- Anthropic -->
|
|
168
|
+
<circle cx="332" cy="436" r="10" fill="none" stroke="#D97706" stroke-width="1.2"/>
|
|
169
|
+
<text x="332" y="440" text-anchor="middle" fill="#D97706" font-size="10" font-weight="700">A</text>
|
|
170
|
+
<text x="350" y="436" fill="#F1F5F9" font-size="11" font-weight="600">Anthropic</text>
|
|
171
|
+
<text x="350" y="450" fill="#64748B" font-size="9">Claude Agent SDK</text>
|
|
172
|
+
<!-- OpenAI -->
|
|
173
|
+
<circle cx="482" cy="436" r="10" fill="none" stroke="#10A37F" stroke-width="1.2"/>
|
|
174
|
+
<text x="482" y="440" text-anchor="middle" fill="#10A37F" font-size="10" font-weight="700">O</text>
|
|
175
|
+
<text x="500" y="436" fill="#F1F5F9" font-size="11" font-weight="600">OpenAI</text>
|
|
176
|
+
<text x="500" y="450" fill="#64748B" font-size="9">Codex App Server</text>
|
|
177
|
+
<text x="370" y="472" fill="#475569" font-size="9">Governed runtime backends</text>
|
|
178
|
+
|
|
179
|
+
<!-- Browser EventSource -->
|
|
180
|
+
<rect x="636" y="408" width="220" height="72" rx="12" fill="none" stroke="#7C3AED" stroke-width="1" opacity="0.6"/>
|
|
181
|
+
<rect x="636" y="408" width="220" height="72" rx="12" fill="#0F172A" opacity="0.8"/>
|
|
182
|
+
<!-- Signal icon -->
|
|
183
|
+
<circle cx="668" cy="436" r="3" fill="#7C3AED"/>
|
|
184
|
+
<path d="M656 424 A16 16 0 0 1 680 424" fill="none" stroke="#7C3AED" stroke-width="1" opacity="0.5"/>
|
|
185
|
+
<path d="M652 418 A22 22 0 0 1 684 418" fill="none" stroke="#7C3AED" stroke-width="1" opacity="0.3"/>
|
|
186
|
+
<text x="688" y="436" fill="#F1F5F9" font-size="12" font-weight="600">EventSource</text>
|
|
187
|
+
<text x="688" y="452" fill="#64748B" font-size="10">Browser SSE client</text>
|
|
188
|
+
<text x="656" y="472" fill="#475569" font-size="9">Real-time log streaming</text>
|
|
189
|
+
|
|
190
|
+
<!-- Footer -->
|
|
191
|
+
<text x="450" y="508" text-anchor="middle" fill="#334155" font-size="10">Local-first · Zero external dependencies · Your data stays on your machine</text>
|
|
192
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -42,22 +42,12 @@ describe("desktop sidecar launch helpers", () => {
|
|
|
42
42
|
).toEqual(["start", "--hostname", "127.0.0.1", "--port", "3210"]);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
it("launches Next on loopback for development builds
|
|
45
|
+
it("launches Next on loopback for development builds", () => {
|
|
46
46
|
expect(
|
|
47
47
|
buildNextLaunchArgs({
|
|
48
48
|
isPrebuilt: false,
|
|
49
49
|
port: 3210,
|
|
50
50
|
}),
|
|
51
|
-
).toEqual(["dev", "--hostname", "127.0.0.1", "--port", "3210", "--turbopack"]);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("launches Next without turbopack when turbopack is false", () => {
|
|
55
|
-
expect(
|
|
56
|
-
buildNextLaunchArgs({
|
|
57
|
-
isPrebuilt: false,
|
|
58
|
-
port: 3210,
|
|
59
|
-
turbopack: false,
|
|
60
|
-
}),
|
|
61
51
|
).toEqual(["dev", "--hostname", "127.0.0.1", "--port", "3210"]);
|
|
62
52
|
});
|
|
63
53
|
|
|
@@ -68,20 +68,16 @@ export function buildNextLaunchArgs({
|
|
|
68
68
|
isPrebuilt,
|
|
69
69
|
port,
|
|
70
70
|
host = SIDECAR_LOOPBACK_HOST,
|
|
71
|
-
turbopack = true,
|
|
72
71
|
}: {
|
|
73
72
|
isPrebuilt: boolean;
|
|
74
73
|
port: number;
|
|
75
74
|
host?: string;
|
|
76
|
-
turbopack?: boolean;
|
|
77
75
|
}): string[] {
|
|
78
76
|
if (isPrebuilt) {
|
|
79
77
|
return ["start", "--hostname", host, "--port", String(port)];
|
|
80
78
|
}
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
if (turbopack) args.push("--turbopack");
|
|
84
|
-
return args;
|
|
80
|
+
return ["dev", "--hostname", host, "--port", String(port)];
|
|
85
81
|
}
|
|
86
82
|
|
|
87
83
|
export function buildSidecarUrl(port: number, host = SIDECAR_LOOPBACK_HOST): string {
|
|
@@ -17,7 +17,7 @@ export const APPROVAL_ACTION_IDS = [
|
|
|
17
17
|
] as const;
|
|
18
18
|
|
|
19
19
|
export type ApprovalActionId = (typeof APPROVAL_ACTION_IDS)[number];
|
|
20
|
-
export type NotificationChannelId = "in_app" | "browser"
|
|
20
|
+
export type NotificationChannelId = "in_app" | "browser";
|
|
21
21
|
|
|
22
22
|
export interface ActionableNotificationPayload {
|
|
23
23
|
notificationId: string;
|
|
Binary file
|
package/src/lib/tauri-bridge.ts
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
type TauriDialogFilter = {
|
|
2
|
-
name: string;
|
|
3
|
-
extensions: string[];
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
type TauriDialogOptions = {
|
|
7
|
-
directory?: boolean;
|
|
8
|
-
multiple?: boolean;
|
|
9
|
-
filters?: TauriDialogFilter[];
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
type TauriNotificationPayload = {
|
|
13
|
-
title: string;
|
|
14
|
-
body?: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
type TauriNotificationApi = {
|
|
18
|
-
isPermissionGranted?: () => Promise<boolean>;
|
|
19
|
-
requestPermission?: () => Promise<boolean | "granted" | "denied" | "default">;
|
|
20
|
-
sendNotification?: (
|
|
21
|
-
payload: string | TauriNotificationPayload
|
|
22
|
-
) => Promise<void> | void;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
type TauriDialogApi = {
|
|
26
|
-
open?: (options?: TauriDialogOptions) => Promise<unknown>;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
declare global {
|
|
30
|
-
interface Window {
|
|
31
|
-
__TAURI__?: {
|
|
32
|
-
dialog?: TauriDialogApi;
|
|
33
|
-
notification?: TauriNotificationApi;
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function getTauriApi() {
|
|
39
|
-
if (typeof window === "undefined") return null;
|
|
40
|
-
return window.__TAURI__ ?? null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const isTauri = () => Boolean(getTauriApi());
|
|
44
|
-
|
|
45
|
-
export async function showNativeNotification(
|
|
46
|
-
title: string,
|
|
47
|
-
body?: string
|
|
48
|
-
): Promise<boolean> {
|
|
49
|
-
const tauriApi = getTauriApi();
|
|
50
|
-
|
|
51
|
-
if (tauriApi?.notification?.sendNotification) {
|
|
52
|
-
const permissionCheck = tauriApi.notification.isPermissionGranted;
|
|
53
|
-
const requestPermission = tauriApi.notification.requestPermission;
|
|
54
|
-
|
|
55
|
-
let granted = true;
|
|
56
|
-
if (permissionCheck) {
|
|
57
|
-
granted = await permissionCheck();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (!granted && requestPermission) {
|
|
61
|
-
const result = await requestPermission();
|
|
62
|
-
granted = result === true || result === "granted";
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (granted) {
|
|
66
|
-
await tauriApi.notification.sendNotification({ title, body });
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (typeof window === "undefined" || typeof Notification === "undefined") {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (Notification.permission === "granted") {
|
|
78
|
-
new Notification(title, body ? { body } : undefined);
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (Notification.permission !== "denied") {
|
|
83
|
-
const permission = await Notification.requestPermission();
|
|
84
|
-
if (permission === "granted") {
|
|
85
|
-
new Notification(title, body ? { body } : undefined);
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export async function selectFile(options?: TauriDialogOptions): Promise<
|
|
94
|
-
string | string[] | null
|
|
95
|
-
> {
|
|
96
|
-
const tauriApi = getTauriApi();
|
|
97
|
-
|
|
98
|
-
if (tauriApi?.dialog?.open) {
|
|
99
|
-
const result = await tauriApi.dialog.open(options);
|
|
100
|
-
if (Array.isArray(result)) {
|
|
101
|
-
return result.filter((value): value is string => typeof value === "string");
|
|
102
|
-
}
|
|
103
|
-
return typeof result === "string" ? result : null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (typeof window === "undefined" || options?.directory) {
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return new Promise((resolve) => {
|
|
111
|
-
const input = document.createElement("input");
|
|
112
|
-
input.type = "file";
|
|
113
|
-
input.multiple = Boolean(options?.multiple);
|
|
114
|
-
|
|
115
|
-
if (options?.filters?.length) {
|
|
116
|
-
input.accept = options.filters
|
|
117
|
-
.flatMap((filter) => filter.extensions.map((extension) => `.${extension}`))
|
|
118
|
-
.join(",");
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
input.addEventListener(
|
|
122
|
-
"change",
|
|
123
|
-
() => {
|
|
124
|
-
const files = input.files;
|
|
125
|
-
if (!files || files.length === 0) {
|
|
126
|
-
resolve(null);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const names = Array.from(files, (file) => file.name);
|
|
131
|
-
resolve(options?.multiple ? names : names[0] ?? null);
|
|
132
|
-
},
|
|
133
|
-
{ once: true }
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
input.click();
|
|
137
|
-
});
|
|
138
|
-
}
|