@guuey/create-agentic-app 0.1.3 → 0.1.4
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 +10 -0
- package/dist/templates/claude-agent-sdk/AGENTS.md +135 -0
- package/dist/templates/claude-agent-sdk/README.md +45 -15
- package/dist/templates/claude-agent-sdk/guuey.json +1 -1
- package/dist/templates/claude-agent-sdk/mcps/todo/package.json +1 -1
- package/dist/templates/claude-agent-sdk/mcps/todo/tsup.config.ts +14 -0
- package/dist/templates/claude-agent-sdk/package.json +2 -2
- package/dist/templates/claude-agent-sdk/scripts/dev.mjs +4 -3
- package/dist/templates/google-adk/AGENTS.md +135 -0
- package/dist/templates/google-adk/README.md +33 -17
- package/dist/templates/google-adk/guuey.json +2 -2
- package/dist/templates/google-adk/mcps/todo/package.json +1 -1
- package/dist/templates/google-adk/mcps/todo/tsup.config.ts +14 -0
- package/dist/templates/google-adk/package.json +2 -2
- package/dist/templates/google-adk/scripts/dev.mjs +4 -3
- package/dist/templates/google-adk/src/agent.ts +6 -2
- package/dist/templates/mcp-base/src/server.ts +49 -0
- package/dist/templates/openai-agents-sdk/AGENTS.md +135 -0
- package/dist/templates/openai-agents-sdk/README.md +45 -15
- package/dist/templates/openai-agents-sdk/guuey.json +13 -4
- package/dist/templates/openai-agents-sdk/mcps/todo/package.json +1 -1
- package/dist/templates/openai-agents-sdk/mcps/todo/tsup.config.ts +14 -0
- package/dist/templates/openai-agents-sdk/package.json +2 -2
- package/dist/templates/openai-agents-sdk/scripts/dev.mjs +4 -3
- package/dist/templates/openai-agents-sdk/tsup.config.ts +9 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -29,3 +29,13 @@ guuey deploy # hosted: agent + MCP servers live on guuey
|
|
|
29
29
|
```
|
|
30
30
|
npx @guuey/create-agentic-app <dir> [--framework claude-agent-sdk|openai-agents-sdk] [--skip-install]
|
|
31
31
|
```
|
|
32
|
+
|
|
33
|
+
## Binding to an existing app
|
|
34
|
+
|
|
35
|
+
`guuey deploy` (above) creates a new app on first run. To bind this
|
|
36
|
+
scaffold to an app you already have — including a no-code agent built in
|
|
37
|
+
[Studio](https://studio.guuey.com) — run `guuey pull --app-id <id>` instead: it
|
|
38
|
+
refreshes `guuey.json`'s `appId`, and for a Studio no-code app pulls the
|
|
39
|
+
Studio-authored system prompt/model/MCP servers down into the scaffold too.
|
|
40
|
+
There is no `guuey link` command — `guuey pull --app-id` is the only way
|
|
41
|
+
to bind a project to an existing app.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# AGENTS.md — steering for the coding agent working in this repo
|
|
2
|
+
|
|
3
|
+
You (the coding agent) are extending a guuey agentic app. Before you reach
|
|
4
|
+
for a database or a cache, read this file. The single most common mistake
|
|
5
|
+
here is standing up local Redis/SQLite/file-based state "just to get
|
|
6
|
+
something working" — **don't.** Every pod this project deploys to
|
|
7
|
+
(`src/worker.ts`'s agent pod — including every `kind: colocated` `mcps/*`
|
|
8
|
+
server auto-spawned inside it, like this scaffold's `mcps/todo` — and every
|
|
9
|
+
`mcps/*` server deployed with `kind: hosted`) is **ephemeral and horizontally
|
|
10
|
+
replaced**: gVisor-isolated,
|
|
11
|
+
scale-to-zero, ordinary process death on every deploy or idle timeout. A
|
|
12
|
+
local file or an in-process `Map` used as a database is not a shortcut —
|
|
13
|
+
it is silent data loss the first time the pod recycles.
|
|
14
|
+
|
|
15
|
+
Use this table to route persistence decisions. Pick a row, don't invent one.
|
|
16
|
+
|
|
17
|
+
| Need | Use | Why |
|
|
18
|
+
| ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
19
|
+
| Small per-user state on an MCP server (idempotency keys, rate-limit counters, OAuth nonces, prefs, a few KB) | [`@guuey/state`](https://www.npmjs.com/package/@guuey/state) — `withGuueyContext(scopeFromAuthorization(...), fn)` | Zero auth code — guuey owns the identity rails. Durable, per-`(user, mcp)`, survives pod restarts. |
|
|
20
|
+
| Large or relational data (rows, queries, joins, anything you'd reach for Postgres/Redis for) | An external managed serverless DB — Neon, Upstash, Supabase, or Turso — wired in via `guuey mcp secrets set DATABASE_URL=... --server <id>` (hosted MCP) or `guuey env set DATABASE_URL=...` (the agent worker and its colocated MCP children — the pod's declared env/secrets are handed to every colocated child), consumed through the provider's serverless driver | Guuey **integrates, never operates** databases — that's the platform's hosting policy, not a v1 gap. Pick the provider's **`us-east-1`** region: a guuey-hosted MCP → provider hop is then same-region AWS backbone, single-digit-ms — comparable to a cross-AZ hop, and agent turns are already dominated by seconds of LLM inference. |
|
|
21
|
+
| Local Redis, SQLite, or any file written as a database | **Never.** Not for v1, not "temporarily." | Pods are ephemeral and horizontally replaced — a local store vanishes on the next deploy, restart, or scale event, with no warning to you or the user. |
|
|
22
|
+
| Per-user files/blobs from **inside the agent pod** (`src/worker.ts`) | [`@guuey/fs`](https://www.npmjs.com/package/@guuey/fs)'s `homeDir()`/`appDir()`/`sessionDir()`, or the raw `$GUUEY_HOME_DIR`/`$GUUEY_APP_DIR` env vars + `node:fs` | Every invoke already gets these three bound directories — `$GUUEY_HOME_DIR` is durable per-user, `$GUUEY_APP_DIR` is read-only shared, cwd is session scratch. No wrapper API required, but the helpers save you the env-var lookups. |
|
|
23
|
+
| Per-user files/blobs from **inside an MCP server** (`mcps/*`) | Not yet available — `@guuey/files` is planned but not shipped. Fall back to the managed-DB row above (e.g. store blobs in the provider's object storage) until it lands. | Don't build against an API that doesn't exist yet. |
|
|
24
|
+
| Conversation / chat history | **Do not re-implement.** The platform already persists it. | Guuey writes thread history to DynamoDB and streams it back over AppSync; agents that keep their own transcript store are duplicating (and diverging from) data the platform already owns. |
|
|
25
|
+
|
|
26
|
+
## `@guuey/state`: the one-line pattern
|
|
27
|
+
|
|
28
|
+
`@guuey/state` only works inside an MCP server whose `guuey.json` entry is
|
|
29
|
+
**federated** — that's the opt-in. A `kind: colocated` entry (like
|
|
30
|
+
`mcps/todo` in this scaffold) or a `kind: hosted` entry is federated
|
|
31
|
+
automatically. If you add a `kind: external` entry (a server you host
|
|
32
|
+
yourself, reached by URL), you must set `federate: true` on it:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"kind": "external",
|
|
37
|
+
"url": "https://your-server.example.com",
|
|
38
|
+
"federate": true
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Without `federate: true` (or `kind: colocated`/`kind: hosted`/a `ggui`
|
|
43
|
+
URL), guuey has no
|
|
44
|
+
identity to hand your server — a plain external entry with static
|
|
45
|
+
`headers` gets **no token and no state.** The token guuey sends on every
|
|
46
|
+
request to a federated server IS the credential; there is no separate
|
|
47
|
+
API key to provision.
|
|
48
|
+
|
|
49
|
+
Inside a federated MCP server, wrap each request in
|
|
50
|
+
`withGuueyContext(scopeFromAuthorization(header), fn)` and use the
|
|
51
|
+
barrel-exported `kv` inside `fn` — that's the whole integration. One
|
|
52
|
+
complete, runnable example — an idempotency-key check on a tool call,
|
|
53
|
+
using the raw Node `http` transport this scaffold's `mcps/todo/src/server.ts`
|
|
54
|
+
already uses:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { createHash } from "node:crypto";
|
|
58
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
59
|
+
import { withGuueyContext, scopeFromAuthorization, kv, QuotaExceededError } from "@guuey/state";
|
|
60
|
+
|
|
61
|
+
// Inside the per-request handler (see mcps/todo/src/server.ts for the
|
|
62
|
+
// full StreamableHTTPServerTransport wiring this drops into):
|
|
63
|
+
async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
64
|
+
const authHeader = req.headers.authorization;
|
|
65
|
+
if (typeof authHeader !== "string") {
|
|
66
|
+
res.writeHead(401).end();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await withGuueyContext(scopeFromAuthorization(authHeader), async () => {
|
|
71
|
+
// ... hand off to your MCP transport/tool dispatch here; the
|
|
72
|
+
// AsyncLocalStorage context set above is visible to every `kv.*`
|
|
73
|
+
// call for the lifetime of this request, however deep the call
|
|
74
|
+
// stack goes (tool handlers, helper functions, etc.).
|
|
75
|
+
await handleCreateOrder({ orderPayload: "..." });
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function handleCreateOrder(input: { orderPayload: string }): Promise<{ duplicate: boolean }> {
|
|
80
|
+
const idempotencyKey = `order:${createHash("sha256").update(input.orderPayload).digest("hex")}`;
|
|
81
|
+
|
|
82
|
+
if (await kv.has(idempotencyKey)) {
|
|
83
|
+
return { duplicate: true };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// TTL is required on every `set` — no permanent keys. Pick a window
|
|
88
|
+
// that covers realistic retry storms; 1 hour is a reasonable default
|
|
89
|
+
// for an idempotency key.
|
|
90
|
+
await kv.set(idempotencyKey, true, { ttl: 60 * 60 });
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (err instanceof QuotaExceededError) {
|
|
93
|
+
// The (user, mcp) scope hit its 1 MiB cap. Surface this to the
|
|
94
|
+
// caller instead of silently dropping the idempotency guard.
|
|
95
|
+
throw new Error("state quota exceeded — cannot verify idempotency");
|
|
96
|
+
}
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ... actually create the order here.
|
|
101
|
+
return { duplicate: false };
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`scopeFromAuthorization` derives `{ userId, mcpId }` from the inbound
|
|
106
|
+
`Authorization: Bearer <jwt>` header guuey sends on every federated call —
|
|
107
|
+
`userId` from the JWT's `sub`, `mcpId` deterministically from its `aud`
|
|
108
|
+
(your server's federated resource URL). You never assert either id
|
|
109
|
+
yourself; a mismatched or missing token is rejected server-side.
|
|
110
|
+
|
|
111
|
+
## The hard caps (design your data model around these, don't work around them)
|
|
112
|
+
|
|
113
|
+
`@guuey/state` is a KV, not a database — enforced, not a suggestion:
|
|
114
|
+
|
|
115
|
+
| Cap | Value |
|
|
116
|
+
| ------------------ | --------------------------------------------- |
|
|
117
|
+
| Scope size | 1 MiB (per `(user, mcp)`, key bytes included) |
|
|
118
|
+
| Single value | 64 KiB |
|
|
119
|
+
| TTL | required, ≤ 90 days — no permanent keys |
|
|
120
|
+
| `keys()` page size | ≤ 1000 |
|
|
121
|
+
| `mget()` batch | ≤ 100 keys |
|
|
122
|
+
| Counters | safe integers only (`increment`/`decrement`) |
|
|
123
|
+
|
|
124
|
+
If your data model needs more than this — queries, joins, cross-user
|
|
125
|
+
data, anything past a long tail of small per-user facts — that's the
|
|
126
|
+
signal to use the managed-DB row above, not to fight the cap.
|
|
127
|
+
|
|
128
|
+
## Chat history: really, don't
|
|
129
|
+
|
|
130
|
+
If you find yourself writing message transcripts to `@guuey/state`,
|
|
131
|
+
`$GUUEY_HOME_DIR`, or an external DB keyed by conversation, stop — that's
|
|
132
|
+
the platform's job. Guuey persists every thread to DynamoDB and streams
|
|
133
|
+
history back over AppSync subscriptions; the client and Portal already
|
|
134
|
+
read it from there. A parallel, agent-owned history store will drift from
|
|
135
|
+
the platform's copy and confuse users about which one is authoritative.
|
|
@@ -9,7 +9,7 @@ locally with one command, and deployable to guuey with one more.
|
|
|
9
9
|
```
|
|
10
10
|
.
|
|
11
11
|
├── guuey.json # the deploy contract: agent framework/model, system prompt,
|
|
12
|
-
│ # mcpServers (name →
|
|
12
|
+
│ # mcpServers (name → colocated source + devPort), ggui config
|
|
13
13
|
├── package.json # agent deps + the pnpm workspace root (workspaces: mcps/*, web)
|
|
14
14
|
├── src/worker.ts # your agent code (code-mode worker); build emits ./guuey.worker.js
|
|
15
15
|
├── prompts/system.md # system prompt, referenced from guuey.json#agent.systemPrompt.file
|
|
@@ -38,16 +38,19 @@ cp .env.example .env.local # done automatically on scaffold if .env.local is a
|
|
|
38
38
|
pnpm dev
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
`pnpm dev` (`scripts/dev.mjs`) boots
|
|
41
|
+
`pnpm dev` (`scripts/dev.mjs`) boots four processes with prefixed, interleaved
|
|
42
42
|
logs. Ctrl-C tears all of them down together.
|
|
43
43
|
|
|
44
|
-
| process | port | what
|
|
45
|
-
| ------------ | ----- |
|
|
46
|
-
| `worker` | — | `tsup --watch` — rebuilds `guuey.worker.js` on every save
|
|
47
|
-
| `guuey dev` | :6790 | local router: spawns your worker per turn, streams SSE
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
|
|
44
|
+
| process | port | what |
|
|
45
|
+
| ------------ | ----- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
46
|
+
| `worker` | — | `tsup --watch` — rebuilds `guuey.worker.js` on every save |
|
|
47
|
+
| `guuey dev` | :6790 | local router: spawns your worker per turn, streams SSE; auto-spawns every `colocated` MCP entry — `mcps/todo` lands on its `devPort` :6782 |
|
|
48
|
+
| `ggui serve` | :6781 | local generative-UI server, over `ggui/` |
|
|
49
|
+
| `web` | :6890 | the Vite chat SPA |
|
|
50
|
+
|
|
51
|
+
The todo MCP still answers on :6782 — it's just a supervised child of
|
|
52
|
+
`guuey dev` now (copy `mcps/todo/` to add your own server; new `colocated`
|
|
53
|
+
entries in `guuey.json` are auto-spawned the same way).
|
|
51
54
|
|
|
52
55
|
Open http://localhost:6890 to chat with your agent locally.
|
|
53
56
|
|
|
@@ -68,9 +71,13 @@ that only matter once real users and real money are involved:
|
|
|
68
71
|
DynamoDB so users can resume a thread.
|
|
69
72
|
|
|
70
73
|
None of this changes your code — `guuey.json` is the same file in both
|
|
71
|
-
worlds; only how it's resolved differs
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
worlds; only how it's resolved differs. Locally, `guuey dev` auto-spawns each
|
|
75
|
+
`colocated` entry on its `devPort` and points the agent at
|
|
76
|
+
`localhost:<devPort>`; deployed, the same server code runs as a supervised
|
|
77
|
+
child inside your agent's pod, reached over the pod's loopback with a
|
|
78
|
+
per-request federated identity. (`hosted` entries deploy to the platform's
|
|
79
|
+
federated URLs; locally they need a `devPort` pointing at a server you run
|
|
80
|
+
yourself, or they're skipped with a warning.)
|
|
74
81
|
|
|
75
82
|
## Deploying
|
|
76
83
|
|
|
@@ -83,17 +90,40 @@ guuey deploy # ships everything
|
|
|
83
90
|
anything user-visible changes:
|
|
84
91
|
|
|
85
92
|
1. **MCP leg** — deploys each `hosted` entry under `guuey.json#agent.mcpServers`
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
as its own hosted MCP server (build → deploy → registry), then writes the
|
|
94
|
+
resulting server id back into `guuey.json`. This scaffold has none — the
|
|
95
|
+
todo MCP is `colocated`, so this leg is a no-op here.
|
|
88
96
|
2. **ggui asset leg** — pushes `ggui/` (ggui.json, blueprints, themes) to your
|
|
89
97
|
app's guuey-managed ggui instance.
|
|
90
98
|
3. **Agent leg** — builds `src/worker.ts` into `guuey.worker.js`, packs the
|
|
91
|
-
project root, and deploys it as a gVisor-isolated,
|
|
99
|
+
project root (including `mcps/`), and deploys it as a gVisor-isolated,
|
|
100
|
+
scale-to-zero pod. Each `colocated` entry (e.g. `mcps/todo`) is built into
|
|
101
|
+
the worker image and auto-spawned as a supervised, sandboxed child at pod
|
|
102
|
+
boot — no separate server, registry row, or write-back.
|
|
92
103
|
4. **Output** — prints your agent's endpoint URL and a Portal deep link.
|
|
93
104
|
|
|
94
105
|
Re-running `guuey deploy` converges: unchanged pieces are skipped or reused,
|
|
95
106
|
nothing is duplicated.
|
|
96
107
|
|
|
108
|
+
## Filesystem + memory
|
|
109
|
+
|
|
110
|
+
Every deployed invoke gets three bound directories — `$GUUEY_HOME_DIR`
|
|
111
|
+
(durable, per-user), `$GUUEY_APP_DIR` (read-only, shared), and cwd (session
|
|
112
|
+
scratch) — plain `node:fs`, no wrapper API required. Signed-in users get
|
|
113
|
+
durable cross-session memory for free (a platform-owned prompt tells the
|
|
114
|
+
model to read/write `$GUUEY_HOME_DIR/memories/MEMORY.md`); guests never get
|
|
115
|
+
durable storage, by design. **This recall is `claude-agent-sdk`-only today**
|
|
116
|
+
— openai-agents-sdk and google-adk agents get the three bound directories
|
|
117
|
+
but not the automatic memory-file recall; all-framework support arrives
|
|
118
|
+
with guuey's own memory MCP (a platform tool every framework can call over
|
|
119
|
+
its existing MCP channel). Framework-native memory backends (e.g. ADK's
|
|
120
|
+
Vertex MemoryBank) are unsupported on Guuey — they'd store user data
|
|
121
|
+
outside guuey's deletion boundary. Full contract, code examples, and
|
|
122
|
+
rollout status: the "Your agent's filesystem" section of the guuey
|
|
123
|
+
monorepo's `docs/quickstart.md`, or
|
|
124
|
+
[`@guuey/fs`](https://www.npmjs.com/package/@guuey/fs)'s own README — an
|
|
125
|
+
optional, three-helper sugar layer over the same paths.
|
|
126
|
+
|
|
97
127
|
## How people talk to your agent
|
|
98
128
|
|
|
99
129
|
- **guuey Portal** — a Telegram-like agent App Store and universal chat
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"model": "claude-sonnet-5",
|
|
7
7
|
"systemPrompt": { "file": "prompts/system.md" },
|
|
8
8
|
"mcpServers": {
|
|
9
|
-
"todo": { "kind": "
|
|
9
|
+
"todo": { "kind": "colocated", "source": "./mcps/todo", "devPort": 6782 }
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
12
|
"ggui": { "configFile": "./ggui/ggui.json" }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup';
|
|
2
|
+
|
|
3
|
+
// Self-contained ON PURPOSE — do not delete this file. Without a local
|
|
4
|
+
// config, tsup's upward config discovery finds the scaffold ROOT
|
|
5
|
+
// tsup.config.ts; its bundled form imports 'tsup' from the scaffold root,
|
|
6
|
+
// which the platform image build deliberately leaves un-installed when the
|
|
7
|
+
// bundled guuey.worker.js skips the root install — every colocated build
|
|
8
|
+
// then dies with ERR_MODULE_NOT_FOUND (guuey#19). A config here stops the
|
|
9
|
+
// walk; `defineConfig` resolves from this package's own devDependencies.
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
entry: ['src/server.ts'],
|
|
12
|
+
format: 'esm',
|
|
13
|
+
outDir: 'dist',
|
|
14
|
+
});
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@anthropic-ai/claude-agent-sdk": "^0.3.199",
|
|
13
|
-
"@guuey/config": "0.1.
|
|
13
|
+
"@guuey/config": "0.1.2",
|
|
14
14
|
"@guuey/worker": "0.1.1"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@guuey/cli": "0.1.
|
|
17
|
+
"@guuey/cli": "0.1.4",
|
|
18
18
|
"tsup": "^8.5.0",
|
|
19
19
|
"tsx": "^4.19.0",
|
|
20
20
|
"typescript": "^5.8.0"
|
|
@@ -29,10 +29,11 @@ if (!existsSync(".env.local"))
|
|
|
29
29
|
console.warn("hint: cp .env.example .env.local and set your LLM key");
|
|
30
30
|
|
|
31
31
|
boot("worker", "pnpm", ["exec", "tsup", "--watch"]); // rebuilds guuey.worker.js on change
|
|
32
|
+
// `guuey dev` auto-spawns every `kind: 'colocated'` mcpServers entry itself
|
|
33
|
+
// (name→localhost devPort resolution) — the todo MCP is colocated, so it no
|
|
34
|
+
// longer needs its own boot() here; a manual second spawn would double-bind
|
|
35
|
+
// :6782 and crash with EADDRINUSE.
|
|
32
36
|
boot("agent", "pnpm", ["exec", "guuey", "dev", "--serve", "--port", "6790"]);
|
|
33
|
-
boot("todo", "pnpm", ["--filter", "@agentic-app-template/todo-mcp", "dev"], {
|
|
34
|
-
env: { ...process.env, PORT: "6782" },
|
|
35
|
-
});
|
|
36
37
|
boot("ggui", "pnpm", ["exec", "ggui", "serve", "--mcp-only", "--dev-allow-all", "--port", "6781"], {
|
|
37
38
|
cwd: "ggui",
|
|
38
39
|
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# AGENTS.md — steering for the coding agent working in this repo
|
|
2
|
+
|
|
3
|
+
You (the coding agent) are extending a guuey agentic app. Before you reach
|
|
4
|
+
for a database or a cache, read this file. The single most common mistake
|
|
5
|
+
here is standing up local Redis/SQLite/file-based state "just to get
|
|
6
|
+
something working" — **don't.** Every pod this project deploys to
|
|
7
|
+
(`src/worker.ts`'s agent pod — including every `kind: colocated` `mcps/*`
|
|
8
|
+
server auto-spawned inside it, like this scaffold's `mcps/todo` — and every
|
|
9
|
+
`mcps/*` server deployed with `kind: hosted`) is **ephemeral and horizontally
|
|
10
|
+
replaced**: gVisor-isolated,
|
|
11
|
+
scale-to-zero, ordinary process death on every deploy or idle timeout. A
|
|
12
|
+
local file or an in-process `Map` used as a database is not a shortcut —
|
|
13
|
+
it is silent data loss the first time the pod recycles.
|
|
14
|
+
|
|
15
|
+
Use this table to route persistence decisions. Pick a row, don't invent one.
|
|
16
|
+
|
|
17
|
+
| Need | Use | Why |
|
|
18
|
+
| ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
19
|
+
| Small per-user state on an MCP server (idempotency keys, rate-limit counters, OAuth nonces, prefs, a few KB) | [`@guuey/state`](https://www.npmjs.com/package/@guuey/state) — `withGuueyContext(scopeFromAuthorization(...), fn)` | Zero auth code — guuey owns the identity rails. Durable, per-`(user, mcp)`, survives pod restarts. |
|
|
20
|
+
| Large or relational data (rows, queries, joins, anything you'd reach for Postgres/Redis for) | An external managed serverless DB — Neon, Upstash, Supabase, or Turso — wired in via `guuey mcp secrets set DATABASE_URL=... --server <id>` (hosted MCP) or `guuey env set DATABASE_URL=...` (the agent worker and its colocated MCP children — the pod's declared env/secrets are handed to every colocated child), consumed through the provider's serverless driver | Guuey **integrates, never operates** databases — that's the platform's hosting policy, not a v1 gap. Pick the provider's **`us-east-1`** region: a guuey-hosted MCP → provider hop is then same-region AWS backbone, single-digit-ms — comparable to a cross-AZ hop, and agent turns are already dominated by seconds of LLM inference. |
|
|
21
|
+
| Local Redis, SQLite, or any file written as a database | **Never.** Not for v1, not "temporarily." | Pods are ephemeral and horizontally replaced — a local store vanishes on the next deploy, restart, or scale event, with no warning to you or the user. |
|
|
22
|
+
| Per-user files/blobs from **inside the agent pod** (`src/worker.ts`) | [`@guuey/fs`](https://www.npmjs.com/package/@guuey/fs)'s `homeDir()`/`appDir()`/`sessionDir()`, or the raw `$GUUEY_HOME_DIR`/`$GUUEY_APP_DIR` env vars + `node:fs` | Every invoke already gets these three bound directories — `$GUUEY_HOME_DIR` is durable per-user, `$GUUEY_APP_DIR` is read-only shared, cwd is session scratch. No wrapper API required, but the helpers save you the env-var lookups. |
|
|
23
|
+
| Per-user files/blobs from **inside an MCP server** (`mcps/*`) | Not yet available — `@guuey/files` is planned but not shipped. Fall back to the managed-DB row above (e.g. store blobs in the provider's object storage) until it lands. | Don't build against an API that doesn't exist yet. |
|
|
24
|
+
| Conversation / chat history | **Do not re-implement.** The platform already persists it. | Guuey writes thread history to DynamoDB and streams it back over AppSync; agents that keep their own transcript store are duplicating (and diverging from) data the platform already owns. |
|
|
25
|
+
|
|
26
|
+
## `@guuey/state`: the one-line pattern
|
|
27
|
+
|
|
28
|
+
`@guuey/state` only works inside an MCP server whose `guuey.json` entry is
|
|
29
|
+
**federated** — that's the opt-in. A `kind: colocated` entry (like
|
|
30
|
+
`mcps/todo` in this scaffold) or a `kind: hosted` entry is federated
|
|
31
|
+
automatically. If you add a `kind: external` entry (a server you host
|
|
32
|
+
yourself, reached by URL), you must set `federate: true` on it:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"kind": "external",
|
|
37
|
+
"url": "https://your-server.example.com",
|
|
38
|
+
"federate": true
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Without `federate: true` (or `kind: colocated`/`kind: hosted`/a `ggui`
|
|
43
|
+
URL), guuey has no
|
|
44
|
+
identity to hand your server — a plain external entry with static
|
|
45
|
+
`headers` gets **no token and no state.** The token guuey sends on every
|
|
46
|
+
request to a federated server IS the credential; there is no separate
|
|
47
|
+
API key to provision.
|
|
48
|
+
|
|
49
|
+
Inside a federated MCP server, wrap each request in
|
|
50
|
+
`withGuueyContext(scopeFromAuthorization(header), fn)` and use the
|
|
51
|
+
barrel-exported `kv` inside `fn` — that's the whole integration. One
|
|
52
|
+
complete, runnable example — an idempotency-key check on a tool call,
|
|
53
|
+
using the raw Node `http` transport this scaffold's `mcps/todo/src/server.ts`
|
|
54
|
+
already uses:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { createHash } from "node:crypto";
|
|
58
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
59
|
+
import { withGuueyContext, scopeFromAuthorization, kv, QuotaExceededError } from "@guuey/state";
|
|
60
|
+
|
|
61
|
+
// Inside the per-request handler (see mcps/todo/src/server.ts for the
|
|
62
|
+
// full StreamableHTTPServerTransport wiring this drops into):
|
|
63
|
+
async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
64
|
+
const authHeader = req.headers.authorization;
|
|
65
|
+
if (typeof authHeader !== "string") {
|
|
66
|
+
res.writeHead(401).end();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await withGuueyContext(scopeFromAuthorization(authHeader), async () => {
|
|
71
|
+
// ... hand off to your MCP transport/tool dispatch here; the
|
|
72
|
+
// AsyncLocalStorage context set above is visible to every `kv.*`
|
|
73
|
+
// call for the lifetime of this request, however deep the call
|
|
74
|
+
// stack goes (tool handlers, helper functions, etc.).
|
|
75
|
+
await handleCreateOrder({ orderPayload: "..." });
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function handleCreateOrder(input: { orderPayload: string }): Promise<{ duplicate: boolean }> {
|
|
80
|
+
const idempotencyKey = `order:${createHash("sha256").update(input.orderPayload).digest("hex")}`;
|
|
81
|
+
|
|
82
|
+
if (await kv.has(idempotencyKey)) {
|
|
83
|
+
return { duplicate: true };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// TTL is required on every `set` — no permanent keys. Pick a window
|
|
88
|
+
// that covers realistic retry storms; 1 hour is a reasonable default
|
|
89
|
+
// for an idempotency key.
|
|
90
|
+
await kv.set(idempotencyKey, true, { ttl: 60 * 60 });
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (err instanceof QuotaExceededError) {
|
|
93
|
+
// The (user, mcp) scope hit its 1 MiB cap. Surface this to the
|
|
94
|
+
// caller instead of silently dropping the idempotency guard.
|
|
95
|
+
throw new Error("state quota exceeded — cannot verify idempotency");
|
|
96
|
+
}
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ... actually create the order here.
|
|
101
|
+
return { duplicate: false };
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`scopeFromAuthorization` derives `{ userId, mcpId }` from the inbound
|
|
106
|
+
`Authorization: Bearer <jwt>` header guuey sends on every federated call —
|
|
107
|
+
`userId` from the JWT's `sub`, `mcpId` deterministically from its `aud`
|
|
108
|
+
(your server's federated resource URL). You never assert either id
|
|
109
|
+
yourself; a mismatched or missing token is rejected server-side.
|
|
110
|
+
|
|
111
|
+
## The hard caps (design your data model around these, don't work around them)
|
|
112
|
+
|
|
113
|
+
`@guuey/state` is a KV, not a database — enforced, not a suggestion:
|
|
114
|
+
|
|
115
|
+
| Cap | Value |
|
|
116
|
+
| ------------------ | --------------------------------------------- |
|
|
117
|
+
| Scope size | 1 MiB (per `(user, mcp)`, key bytes included) |
|
|
118
|
+
| Single value | 64 KiB |
|
|
119
|
+
| TTL | required, ≤ 90 days — no permanent keys |
|
|
120
|
+
| `keys()` page size | ≤ 1000 |
|
|
121
|
+
| `mget()` batch | ≤ 100 keys |
|
|
122
|
+
| Counters | safe integers only (`increment`/`decrement`) |
|
|
123
|
+
|
|
124
|
+
If your data model needs more than this — queries, joins, cross-user
|
|
125
|
+
data, anything past a long tail of small per-user facts — that's the
|
|
126
|
+
signal to use the managed-DB row above, not to fight the cap.
|
|
127
|
+
|
|
128
|
+
## Chat history: really, don't
|
|
129
|
+
|
|
130
|
+
If you find yourself writing message transcripts to `@guuey/state`,
|
|
131
|
+
`$GUUEY_HOME_DIR`, or an external DB keyed by conversation, stop — that's
|
|
132
|
+
the platform's job. Guuey persists every thread to DynamoDB and streams
|
|
133
|
+
history back over AppSync subscriptions; the client and Portal already
|
|
134
|
+
read it from there. A parallel, agent-owned history store will drift from
|
|
135
|
+
the platform's copy and confuse users about which one is authoritative.
|
|
@@ -12,7 +12,8 @@ import type { GuueyContext } from "@guuey/config";
|
|
|
12
12
|
export default (guuey: GuueyContext<MCPToolset>) =>
|
|
13
13
|
new LlmAgent({
|
|
14
14
|
model: guuey.model,
|
|
15
|
-
instruction
|
|
15
|
+
// Wrapped as a function — see the "instruction" row below.
|
|
16
|
+
instruction: () => guuey.instruction,
|
|
16
17
|
tools: [myTool, ...guuey.mcpToolsets],
|
|
17
18
|
});
|
|
18
19
|
```
|
|
@@ -30,24 +31,24 @@ guuey deploy # same code, hosted
|
|
|
30
31
|
|
|
31
32
|
Your factory runs **once per turn** and receives:
|
|
32
33
|
|
|
33
|
-
| field | what it is
|
|
34
|
-
| ------------------------------------- |
|
|
35
|
-
| `model` | resolved model id (from `guuey.json`, registry default otherwise)
|
|
36
|
-
| `instruction` | your system prompt **with the conversation preamble already prepended** — feed it to the agent and it is conversational with zero state code
|
|
37
|
-
| `mcpToolsets` | ready-to-use ADK `MCPToolset`s for every server in `guuey.json#mcpServers` — credentials, URLs, and auth headers already resolved by the platform
|
|
38
|
-
| `user` | the end user this turn serves: `{ id, authMode }` — build multi-tenant behavior on `user.id`
|
|
39
|
-
| `files` | three storage tiers (absolute paths — see the state map below)
|
|
40
|
-
| `history` / `memory` / `workingState` | the raw conversation state, if you want to render context yourself instead of using `instruction`
|
|
34
|
+
| field | what it is |
|
|
35
|
+
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
36
|
+
| `model` | resolved model id (from `guuey.json`, registry default otherwise) |
|
|
37
|
+
| `instruction` | your system prompt **with the conversation preamble already prepended** — feed it to the agent and it is conversational with zero state code. **Pass it as `() => guuey.instruction`, not a bare string** — ADK applies `{var}` session-state substitution to a string instruction, and the preamble embeds prior user messages verbatim; a message containing `{anything}` would crash the turn. |
|
|
38
|
+
| `mcpToolsets` | ready-to-use ADK `MCPToolset`s for every server in `guuey.json#mcpServers` — credentials, URLs, and auth headers already resolved by the platform |
|
|
39
|
+
| `user` | the end user this turn serves: `{ id, authMode }` — build multi-tenant behavior on `user.id` |
|
|
40
|
+
| `files` | three storage tiers (absolute paths — see the state map below) |
|
|
41
|
+
| `history` / `memory` / `workingState` | the raw conversation state, if you want to render context yourself instead of using `instruction` |
|
|
41
42
|
|
|
42
43
|
## Where state lives (the three-tier map)
|
|
43
44
|
|
|
44
|
-
| you want to… | use | persistence
|
|
45
|
-
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
46
|
-
| save/read **files** from agent code or tools | `guuey.files.home` (per-USER durable) · `guuey.files.session` (per-session scratch) · `guuey.files.app` (read-only app assets) | `home` survives across sessions per user
|
|
47
|
-
| have the agent **remember the conversation** | nothing — `instruction` already carries history + thread memory + working state | automatic (Guuey folds every turn)
|
|
48
|
-
| store data from your **MCP server's tools** (e.g. the todo list) | `@guuey/state` KV inside the MCP server — scoped per `(user, server)` | **
|
|
45
|
+
| you want to… | use | persistence |
|
|
46
|
+
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
47
|
+
| save/read **files** from agent code or tools | `guuey.files.home` (per-USER durable) · `guuey.files.session` (per-session scratch) · `guuey.files.app` (read-only app assets) | `home` survives across sessions per user |
|
|
48
|
+
| have the agent **remember the conversation** | nothing — `instruction` already carries history + thread memory + working state | automatic (Guuey folds every turn) |
|
|
49
|
+
| store data from your **MCP server's tools** (e.g. the todo list) | `@guuey/state` KV inside the MCP server — scoped per `(user, server)` | **durable when `GUUEY_KV_URL` is injected — colocated (like `mcps/todo`) and hosted servers get it automatically on deploy**; the scope survives pod restarts and redeploys. Locally (`pnpm dev`) it falls back to in-memory. |
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
Three honest notes:
|
|
51
52
|
|
|
52
53
|
- **ADK-native session state does not persist across turns here.** Guuey
|
|
53
54
|
runs your agent with a fresh `InMemoryRunner` per turn (that's what makes
|
|
@@ -57,12 +58,27 @@ Two honest notes:
|
|
|
57
58
|
pre-rendered in `instruction`.
|
|
58
59
|
- **Writing `workingState` for the next turn** is a platform feature in
|
|
59
60
|
flight; today the fold carries what the conversation itself establishes.
|
|
61
|
+
- **Guuey's cross-session `MEMORY.md` file recall is `claude-agent-sdk`-only
|
|
62
|
+
today** — ADK agents get `guuey.files.home` for their own reads/writes,
|
|
63
|
+
but not the platform's automatic recall of it into `instruction`.
|
|
64
|
+
All-framework support arrives with guuey's own memory MCP. Separately,
|
|
65
|
+
ADK's own durable-memory feature (Vertex MemoryBank) is unsupported on
|
|
66
|
+
Guuey — it stores user data in GCP, outside guuey's deletion boundary.
|
|
67
|
+
|
|
68
|
+
The three `guuey.files` paths above are the same platform-wide filesystem
|
|
69
|
+
contract every framework gets (plain paths, `$GUUEY_HOME_DIR`/
|
|
70
|
+
`$GUUEY_APP_DIR` under the hood) — see the guuey monorepo's
|
|
71
|
+
`docs/quickstart.md` ("Your agent's filesystem") for the full contract,
|
|
72
|
+
memory behavior, and rollout status if you want the framework-neutral
|
|
73
|
+
version.
|
|
60
74
|
|
|
61
75
|
## MCP servers
|
|
62
76
|
|
|
63
77
|
`guuey.json#mcpServers` declares them; the platform connects them and hands
|
|
64
|
-
you `guuey.mcpToolsets`. Locally, `pnpm dev`
|
|
65
|
-
server on its `devPort
|
|
78
|
+
you `guuey.mcpToolsets`. Locally, `pnpm dev`'s `guuey dev` auto-spawns the
|
|
79
|
+
colocated `mcps/todo` server on its `devPort`; deployed, the same code runs
|
|
80
|
+
as a supervised child inside your agent's pod. Note: ADK speaks
|
|
81
|
+
**Streamable HTTP** only — an
|
|
66
82
|
`sse` transport server will be rejected with a clear error.
|
|
67
83
|
|
|
68
84
|
## When you outgrow the factory
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
"agent": {
|
|
4
4
|
"mode": "code",
|
|
5
5
|
"framework": "google-adk",
|
|
6
|
-
"model": "gemini-3.
|
|
6
|
+
"model": "gemini-3.6-flash",
|
|
7
7
|
"entry": "agent.js",
|
|
8
8
|
"systemPrompt": { "file": "prompts/system.md" },
|
|
9
9
|
"mcpServers": {
|
|
10
|
-
"todo": { "kind": "
|
|
10
|
+
"todo": { "kind": "colocated", "source": "./mcps/todo", "devPort": 6782 }
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"ggui": { "configFile": "./ggui/ggui.json" }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup';
|
|
2
|
+
|
|
3
|
+
// Self-contained ON PURPOSE — do not delete this file. Without a local
|
|
4
|
+
// config, tsup's upward config discovery finds the scaffold ROOT
|
|
5
|
+
// tsup.config.ts; its bundled form imports 'tsup' from the scaffold root,
|
|
6
|
+
// which the platform image build deliberately leaves un-installed when the
|
|
7
|
+
// bundled guuey.worker.js skips the root install — every colocated build
|
|
8
|
+
// then dies with ERR_MODULE_NOT_FOUND (guuey#19). A config here stops the
|
|
9
|
+
// walk; `defineConfig` resolves from this package's own devDependencies.
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
entry: ['src/server.ts'],
|
|
12
|
+
format: 'esm',
|
|
13
|
+
outDir: 'dist',
|
|
14
|
+
});
|
|
@@ -29,10 +29,11 @@ if (!existsSync(".env.local"))
|
|
|
29
29
|
console.warn("hint: cp .env.example .env.local and set your LLM key");
|
|
30
30
|
|
|
31
31
|
boot("worker", "pnpm", ["exec", "tsup", "--watch"]); // rebuilds guuey.worker.js on change
|
|
32
|
+
// `guuey dev` auto-spawns every `kind: 'colocated'` mcpServers entry itself
|
|
33
|
+
// (name→localhost devPort resolution) — the todo MCP is colocated, so it no
|
|
34
|
+
// longer needs its own boot() here; a manual second spawn would double-bind
|
|
35
|
+
// :6782 and crash with EADDRINUSE.
|
|
32
36
|
boot("agent", "pnpm", ["exec", "guuey", "dev", "--serve", "--port", "6790"]);
|
|
33
|
-
boot("todo", "pnpm", ["--filter", "@agentic-app-template/todo-mcp", "dev"], {
|
|
34
|
-
env: { ...process.env, PORT: "6782" },
|
|
35
|
-
});
|
|
36
37
|
boot("ggui", "pnpm", ["exec", "ggui", "serve", "--mcp-only", "--dev-allow-all", "--port", "6781"], {
|
|
37
38
|
cwd: "ggui",
|
|
38
39
|
});
|
|
@@ -31,8 +31,12 @@ export default (guuey: GuueyContext<MCPToolset>) =>
|
|
|
31
31
|
model: guuey.model,
|
|
32
32
|
// `instruction` already carries your system prompt PLUS the conversation
|
|
33
33
|
// preamble (history, thread memory, working state) — your agent is
|
|
34
|
-
// conversational without writing any state code.
|
|
35
|
-
|
|
34
|
+
// conversational without writing any state code. Wrapped as a function:
|
|
35
|
+
// ADK applies `{var}` session-state substitution to a STRING instruction,
|
|
36
|
+
// and the preamble embeds prior user messages verbatim — if one contains
|
|
37
|
+
// `{anything}`, a string instruction crashes the turn. The function form
|
|
38
|
+
// bypasses substitution entirely.
|
|
39
|
+
instruction: () => guuey.instruction,
|
|
36
40
|
// Your own tools compose with the MCP servers from guuey.json. Drop
|
|
37
41
|
// `...guuey.mcpToolsets` if you want a fully self-contained agent.
|
|
38
42
|
tools: [rollDice, ...guuey.mcpToolsets],
|
|
@@ -25,6 +25,24 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
25
25
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
26
26
|
import { z } from "zod";
|
|
27
27
|
|
|
28
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
29
|
+
// Optional: per-user durable state via `@guuey/state`.
|
|
30
|
+
//
|
|
31
|
+
// Hosted (`kind: 'hosted'`) and colocated (`kind: 'colocated'`) MCP servers
|
|
32
|
+
// get `GUUEY_KV_URL` (and a per-request auth token) injected automatically —
|
|
33
|
+
// zero setup, state just works. A `dev-hosted` external server (your own
|
|
34
|
+
// `kind: 'external', federate: true` deployment reached over a real URL)
|
|
35
|
+
// sets `GUUEY_KV_URL` itself, per the package README.
|
|
36
|
+
//
|
|
37
|
+
// Uncomment to try it: `pnpm add @guuey/state` (not yet on npm as of this
|
|
38
|
+
// scaffold — developer preview, see the guuey monorepo's publish runbook —
|
|
39
|
+
// so it's commented rather than a hard dependency, to keep this starter
|
|
40
|
+
// installing cleanly out of the box), then uncomment the import + the
|
|
41
|
+
// `withGuueyContext(...)` wrapper below.
|
|
42
|
+
//
|
|
43
|
+
// import { withGuueyContext, scopeFromAuthorization, kv } from "@guuey/state";
|
|
44
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
28
46
|
const SERVER_NAME = "NAME_PLACEHOLDER-mcp";
|
|
29
47
|
|
|
30
48
|
/** Wrap a structured result as both `structuredContent` and a text block. */
|
|
@@ -35,6 +53,9 @@ function toolResult(data: Record<string, unknown>) {
|
|
|
35
53
|
};
|
|
36
54
|
}
|
|
37
55
|
|
|
56
|
+
// To uncomment the `@guuey/state` example below, change this signature to
|
|
57
|
+
// `buildMcpServer(authHeader: string | undefined)` and pass
|
|
58
|
+
// `req.headers.authorization` at the one call site further down.
|
|
38
59
|
function buildMcpServer(): McpServer {
|
|
39
60
|
const server = new McpServer({
|
|
40
61
|
name: SERVER_NAME,
|
|
@@ -53,6 +74,32 @@ function buildMcpServer(): McpServer {
|
|
|
53
74
|
async ({ message }) => toolResult({ message }),
|
|
54
75
|
);
|
|
55
76
|
|
|
77
|
+
// ── Example: a durable, per-user tool via `@guuey/state` (uncomment) ──
|
|
78
|
+
//
|
|
79
|
+
// server.registerTool(
|
|
80
|
+
// "remember",
|
|
81
|
+
// {
|
|
82
|
+
// title: "Remember",
|
|
83
|
+
// description: "Store a value under a key, scoped to the calling user.",
|
|
84
|
+
// inputSchema: {
|
|
85
|
+
// key: z.string().min(1),
|
|
86
|
+
// value: z.string(),
|
|
87
|
+
// },
|
|
88
|
+
// outputSchema: { stored: z.boolean() },
|
|
89
|
+
// },
|
|
90
|
+
// async ({ key, value }) => {
|
|
91
|
+
// if (!authHeader) throw new Error("missing Authorization header");
|
|
92
|
+
// return withGuueyContext(scopeFromAuthorization(authHeader), async () => {
|
|
93
|
+
// await kv.set(key, value, { ttl: 60 * 60 * 24 * 7 }); // 7 days
|
|
94
|
+
// return toolResult({ stored: true });
|
|
95
|
+
// });
|
|
96
|
+
// },
|
|
97
|
+
// );
|
|
98
|
+
//
|
|
99
|
+
// A companion "recall" tool would mirror this with `await kv.get(key)`.
|
|
100
|
+
// See `@guuey/state`'s README for the full API (increment/decrement,
|
|
101
|
+
// keys(), scope() usage quota, etc).
|
|
102
|
+
|
|
56
103
|
return server;
|
|
57
104
|
}
|
|
58
105
|
|
|
@@ -73,6 +120,8 @@ const httpServer = createServer(async (req: IncomingMessage, res: ServerResponse
|
|
|
73
120
|
}
|
|
74
121
|
|
|
75
122
|
// Fresh server + transport per request (stateless mode) — see module doc.
|
|
123
|
+
// (`buildMcpServer(req.headers.authorization)` once the `@guuey/state`
|
|
124
|
+
// example above is uncommented.)
|
|
76
125
|
const mcp = buildMcpServer();
|
|
77
126
|
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
78
127
|
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# AGENTS.md — steering for the coding agent working in this repo
|
|
2
|
+
|
|
3
|
+
You (the coding agent) are extending a guuey agentic app. Before you reach
|
|
4
|
+
for a database or a cache, read this file. The single most common mistake
|
|
5
|
+
here is standing up local Redis/SQLite/file-based state "just to get
|
|
6
|
+
something working" — **don't.** Every pod this project deploys to
|
|
7
|
+
(`src/worker.ts`'s agent pod — including every `kind: colocated` `mcps/*`
|
|
8
|
+
server auto-spawned inside it, like this scaffold's `mcps/todo` — and every
|
|
9
|
+
`mcps/*` server deployed with `kind: hosted`) is **ephemeral and horizontally
|
|
10
|
+
replaced**: gVisor-isolated,
|
|
11
|
+
scale-to-zero, ordinary process death on every deploy or idle timeout. A
|
|
12
|
+
local file or an in-process `Map` used as a database is not a shortcut —
|
|
13
|
+
it is silent data loss the first time the pod recycles.
|
|
14
|
+
|
|
15
|
+
Use this table to route persistence decisions. Pick a row, don't invent one.
|
|
16
|
+
|
|
17
|
+
| Need | Use | Why |
|
|
18
|
+
| ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
19
|
+
| Small per-user state on an MCP server (idempotency keys, rate-limit counters, OAuth nonces, prefs, a few KB) | [`@guuey/state`](https://www.npmjs.com/package/@guuey/state) — `withGuueyContext(scopeFromAuthorization(...), fn)` | Zero auth code — guuey owns the identity rails. Durable, per-`(user, mcp)`, survives pod restarts. |
|
|
20
|
+
| Large or relational data (rows, queries, joins, anything you'd reach for Postgres/Redis for) | An external managed serverless DB — Neon, Upstash, Supabase, or Turso — wired in via `guuey mcp secrets set DATABASE_URL=... --server <id>` (hosted MCP) or `guuey env set DATABASE_URL=...` (the agent worker and its colocated MCP children — the pod's declared env/secrets are handed to every colocated child), consumed through the provider's serverless driver | Guuey **integrates, never operates** databases — that's the platform's hosting policy, not a v1 gap. Pick the provider's **`us-east-1`** region: a guuey-hosted MCP → provider hop is then same-region AWS backbone, single-digit-ms — comparable to a cross-AZ hop, and agent turns are already dominated by seconds of LLM inference. |
|
|
21
|
+
| Local Redis, SQLite, or any file written as a database | **Never.** Not for v1, not "temporarily." | Pods are ephemeral and horizontally replaced — a local store vanishes on the next deploy, restart, or scale event, with no warning to you or the user. |
|
|
22
|
+
| Per-user files/blobs from **inside the agent pod** (`src/worker.ts`) | [`@guuey/fs`](https://www.npmjs.com/package/@guuey/fs)'s `homeDir()`/`appDir()`/`sessionDir()`, or the raw `$GUUEY_HOME_DIR`/`$GUUEY_APP_DIR` env vars + `node:fs` | Every invoke already gets these three bound directories — `$GUUEY_HOME_DIR` is durable per-user, `$GUUEY_APP_DIR` is read-only shared, cwd is session scratch. No wrapper API required, but the helpers save you the env-var lookups. |
|
|
23
|
+
| Per-user files/blobs from **inside an MCP server** (`mcps/*`) | Not yet available — `@guuey/files` is planned but not shipped. Fall back to the managed-DB row above (e.g. store blobs in the provider's object storage) until it lands. | Don't build against an API that doesn't exist yet. |
|
|
24
|
+
| Conversation / chat history | **Do not re-implement.** The platform already persists it. | Guuey writes thread history to DynamoDB and streams it back over AppSync; agents that keep their own transcript store are duplicating (and diverging from) data the platform already owns. |
|
|
25
|
+
|
|
26
|
+
## `@guuey/state`: the one-line pattern
|
|
27
|
+
|
|
28
|
+
`@guuey/state` only works inside an MCP server whose `guuey.json` entry is
|
|
29
|
+
**federated** — that's the opt-in. A `kind: colocated` entry (like
|
|
30
|
+
`mcps/todo` in this scaffold) or a `kind: hosted` entry is federated
|
|
31
|
+
automatically. If you add a `kind: external` entry (a server you host
|
|
32
|
+
yourself, reached by URL), you must set `federate: true` on it:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"kind": "external",
|
|
37
|
+
"url": "https://your-server.example.com",
|
|
38
|
+
"federate": true
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Without `federate: true` (or `kind: colocated`/`kind: hosted`/a `ggui`
|
|
43
|
+
URL), guuey has no
|
|
44
|
+
identity to hand your server — a plain external entry with static
|
|
45
|
+
`headers` gets **no token and no state.** The token guuey sends on every
|
|
46
|
+
request to a federated server IS the credential; there is no separate
|
|
47
|
+
API key to provision.
|
|
48
|
+
|
|
49
|
+
Inside a federated MCP server, wrap each request in
|
|
50
|
+
`withGuueyContext(scopeFromAuthorization(header), fn)` and use the
|
|
51
|
+
barrel-exported `kv` inside `fn` — that's the whole integration. One
|
|
52
|
+
complete, runnable example — an idempotency-key check on a tool call,
|
|
53
|
+
using the raw Node `http` transport this scaffold's `mcps/todo/src/server.ts`
|
|
54
|
+
already uses:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { createHash } from "node:crypto";
|
|
58
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
59
|
+
import { withGuueyContext, scopeFromAuthorization, kv, QuotaExceededError } from "@guuey/state";
|
|
60
|
+
|
|
61
|
+
// Inside the per-request handler (see mcps/todo/src/server.ts for the
|
|
62
|
+
// full StreamableHTTPServerTransport wiring this drops into):
|
|
63
|
+
async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
64
|
+
const authHeader = req.headers.authorization;
|
|
65
|
+
if (typeof authHeader !== "string") {
|
|
66
|
+
res.writeHead(401).end();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await withGuueyContext(scopeFromAuthorization(authHeader), async () => {
|
|
71
|
+
// ... hand off to your MCP transport/tool dispatch here; the
|
|
72
|
+
// AsyncLocalStorage context set above is visible to every `kv.*`
|
|
73
|
+
// call for the lifetime of this request, however deep the call
|
|
74
|
+
// stack goes (tool handlers, helper functions, etc.).
|
|
75
|
+
await handleCreateOrder({ orderPayload: "..." });
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function handleCreateOrder(input: { orderPayload: string }): Promise<{ duplicate: boolean }> {
|
|
80
|
+
const idempotencyKey = `order:${createHash("sha256").update(input.orderPayload).digest("hex")}`;
|
|
81
|
+
|
|
82
|
+
if (await kv.has(idempotencyKey)) {
|
|
83
|
+
return { duplicate: true };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// TTL is required on every `set` — no permanent keys. Pick a window
|
|
88
|
+
// that covers realistic retry storms; 1 hour is a reasonable default
|
|
89
|
+
// for an idempotency key.
|
|
90
|
+
await kv.set(idempotencyKey, true, { ttl: 60 * 60 });
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (err instanceof QuotaExceededError) {
|
|
93
|
+
// The (user, mcp) scope hit its 1 MiB cap. Surface this to the
|
|
94
|
+
// caller instead of silently dropping the idempotency guard.
|
|
95
|
+
throw new Error("state quota exceeded — cannot verify idempotency");
|
|
96
|
+
}
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ... actually create the order here.
|
|
101
|
+
return { duplicate: false };
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`scopeFromAuthorization` derives `{ userId, mcpId }` from the inbound
|
|
106
|
+
`Authorization: Bearer <jwt>` header guuey sends on every federated call —
|
|
107
|
+
`userId` from the JWT's `sub`, `mcpId` deterministically from its `aud`
|
|
108
|
+
(your server's federated resource URL). You never assert either id
|
|
109
|
+
yourself; a mismatched or missing token is rejected server-side.
|
|
110
|
+
|
|
111
|
+
## The hard caps (design your data model around these, don't work around them)
|
|
112
|
+
|
|
113
|
+
`@guuey/state` is a KV, not a database — enforced, not a suggestion:
|
|
114
|
+
|
|
115
|
+
| Cap | Value |
|
|
116
|
+
| ------------------ | --------------------------------------------- |
|
|
117
|
+
| Scope size | 1 MiB (per `(user, mcp)`, key bytes included) |
|
|
118
|
+
| Single value | 64 KiB |
|
|
119
|
+
| TTL | required, ≤ 90 days — no permanent keys |
|
|
120
|
+
| `keys()` page size | ≤ 1000 |
|
|
121
|
+
| `mget()` batch | ≤ 100 keys |
|
|
122
|
+
| Counters | safe integers only (`increment`/`decrement`) |
|
|
123
|
+
|
|
124
|
+
If your data model needs more than this — queries, joins, cross-user
|
|
125
|
+
data, anything past a long tail of small per-user facts — that's the
|
|
126
|
+
signal to use the managed-DB row above, not to fight the cap.
|
|
127
|
+
|
|
128
|
+
## Chat history: really, don't
|
|
129
|
+
|
|
130
|
+
If you find yourself writing message transcripts to `@guuey/state`,
|
|
131
|
+
`$GUUEY_HOME_DIR`, or an external DB keyed by conversation, stop — that's
|
|
132
|
+
the platform's job. Guuey persists every thread to DynamoDB and streams
|
|
133
|
+
history back over AppSync subscriptions; the client and Portal already
|
|
134
|
+
read it from there. A parallel, agent-owned history store will drift from
|
|
135
|
+
the platform's copy and confuse users about which one is authoritative.
|
|
@@ -9,7 +9,7 @@ locally with one command, and deployable to guuey with one more.
|
|
|
9
9
|
```
|
|
10
10
|
.
|
|
11
11
|
├── guuey.json # the deploy contract: agent framework/model, system prompt,
|
|
12
|
-
│ # mcpServers (name →
|
|
12
|
+
│ # mcpServers (name → colocated source + devPort), ggui config
|
|
13
13
|
├── package.json # agent deps + the pnpm workspace root (workspaces: mcps/*, web)
|
|
14
14
|
├── src/worker.ts # your agent code (code-mode worker); build emits ./guuey.worker.js
|
|
15
15
|
├── prompts/system.md # system prompt, referenced from guuey.json#agent.systemPrompt.file
|
|
@@ -38,16 +38,19 @@ cp .env.example .env.local # done automatically on scaffold if .env.local is a
|
|
|
38
38
|
pnpm dev
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
`pnpm dev` (`scripts/dev.mjs`) boots
|
|
41
|
+
`pnpm dev` (`scripts/dev.mjs`) boots four processes with prefixed, interleaved
|
|
42
42
|
logs. Ctrl-C tears all of them down together.
|
|
43
43
|
|
|
44
|
-
| process | port | what
|
|
45
|
-
| ------------ | ----- |
|
|
46
|
-
| `worker` | — | `tsup --watch` — rebuilds `guuey.worker.js` on every save
|
|
47
|
-
| `guuey dev` | :6790 | local router: spawns your worker per turn, streams SSE
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
|
|
44
|
+
| process | port | what |
|
|
45
|
+
| ------------ | ----- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
46
|
+
| `worker` | — | `tsup --watch` — rebuilds `guuey.worker.js` on every save |
|
|
47
|
+
| `guuey dev` | :6790 | local router: spawns your worker per turn, streams SSE; auto-spawns every `colocated` MCP entry — `mcps/todo` lands on its `devPort` :6782 |
|
|
48
|
+
| `ggui serve` | :6781 | local generative-UI server, over `ggui/` |
|
|
49
|
+
| `web` | :6890 | the Vite chat SPA |
|
|
50
|
+
|
|
51
|
+
The todo MCP still answers on :6782 — it's just a supervised child of
|
|
52
|
+
`guuey dev` now (copy `mcps/todo/` to add your own server; new `colocated`
|
|
53
|
+
entries in `guuey.json` are auto-spawned the same way).
|
|
51
54
|
|
|
52
55
|
Open http://localhost:6890 to chat with your agent locally.
|
|
53
56
|
|
|
@@ -68,9 +71,13 @@ that only matter once real users and real money are involved:
|
|
|
68
71
|
DynamoDB so users can resume a thread.
|
|
69
72
|
|
|
70
73
|
None of this changes your code — `guuey.json` is the same file in both
|
|
71
|
-
worlds; only how it's resolved differs
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
worlds; only how it's resolved differs. Locally, `guuey dev` auto-spawns each
|
|
75
|
+
`colocated` entry on its `devPort` and points the agent at
|
|
76
|
+
`localhost:<devPort>`; deployed, the same server code runs as a supervised
|
|
77
|
+
child inside your agent's pod, reached over the pod's loopback with a
|
|
78
|
+
per-request federated identity. (`hosted` entries deploy to the platform's
|
|
79
|
+
federated URLs; locally they need a `devPort` pointing at a server you run
|
|
80
|
+
yourself, or they're skipped with a warning.)
|
|
74
81
|
|
|
75
82
|
## Deploying
|
|
76
83
|
|
|
@@ -83,17 +90,40 @@ guuey deploy # ships everything
|
|
|
83
90
|
anything user-visible changes:
|
|
84
91
|
|
|
85
92
|
1. **MCP leg** — deploys each `hosted` entry under `guuey.json#agent.mcpServers`
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
as its own hosted MCP server (build → deploy → registry), then writes the
|
|
94
|
+
resulting server id back into `guuey.json`. This scaffold has none — the
|
|
95
|
+
todo MCP is `colocated`, so this leg is a no-op here.
|
|
88
96
|
2. **ggui asset leg** — pushes `ggui/` (ggui.json, blueprints, themes) to your
|
|
89
97
|
app's guuey-managed ggui instance.
|
|
90
98
|
3. **Agent leg** — builds `src/worker.ts` into `guuey.worker.js`, packs the
|
|
91
|
-
project root, and deploys it as a gVisor-isolated,
|
|
99
|
+
project root (including `mcps/`), and deploys it as a gVisor-isolated,
|
|
100
|
+
scale-to-zero pod. Each `colocated` entry (e.g. `mcps/todo`) is built into
|
|
101
|
+
the worker image and auto-spawned as a supervised, sandboxed child at pod
|
|
102
|
+
boot — no separate server, registry row, or write-back.
|
|
92
103
|
4. **Output** — prints your agent's endpoint URL and a Portal deep link.
|
|
93
104
|
|
|
94
105
|
Re-running `guuey deploy` converges: unchanged pieces are skipped or reused,
|
|
95
106
|
nothing is duplicated.
|
|
96
107
|
|
|
108
|
+
## Filesystem + memory
|
|
109
|
+
|
|
110
|
+
Every deployed invoke gets three bound directories — `$GUUEY_HOME_DIR`
|
|
111
|
+
(durable, per-user), `$GUUEY_APP_DIR` (read-only, shared), and cwd (session
|
|
112
|
+
scratch) — plain `node:fs`, no wrapper API required. Signed-in users get
|
|
113
|
+
durable cross-session memory for free (a platform-owned prompt tells the
|
|
114
|
+
model to read/write `$GUUEY_HOME_DIR/memories/MEMORY.md`); guests never get
|
|
115
|
+
durable storage, by design. **This recall is `claude-agent-sdk`-only today**
|
|
116
|
+
— openai-agents-sdk and google-adk agents get the three bound directories
|
|
117
|
+
but not the automatic memory-file recall; all-framework support arrives
|
|
118
|
+
with guuey's own memory MCP (a platform tool every framework can call over
|
|
119
|
+
its existing MCP channel). Framework-native memory backends (e.g. ADK's
|
|
120
|
+
Vertex MemoryBank) are unsupported on Guuey — they'd store user data
|
|
121
|
+
outside guuey's deletion boundary. Full contract, code examples, and
|
|
122
|
+
rollout status: the "Your agent's filesystem" section of the guuey
|
|
123
|
+
monorepo's `docs/quickstart.md`, or
|
|
124
|
+
[`@guuey/fs`](https://www.npmjs.com/package/@guuey/fs)'s own README — an
|
|
125
|
+
optional, three-helper sugar layer over the same paths.
|
|
126
|
+
|
|
97
127
|
## How people talk to your agent
|
|
98
128
|
|
|
99
129
|
- **guuey Portal** — a Telegram-like agent App Store and universal chat
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "1",
|
|
3
|
+
"worker": "worker.js",
|
|
3
4
|
"agent": {
|
|
4
5
|
"mode": "code",
|
|
5
6
|
"framework": "openai-agents-sdk",
|
|
6
|
-
"model": "gpt-5.
|
|
7
|
-
"systemPrompt": {
|
|
7
|
+
"model": "gpt-5.6-terra",
|
|
8
|
+
"systemPrompt": {
|
|
9
|
+
"file": "prompts/system.md"
|
|
10
|
+
},
|
|
8
11
|
"mcpServers": {
|
|
9
|
-
"todo": {
|
|
12
|
+
"todo": {
|
|
13
|
+
"kind": "colocated",
|
|
14
|
+
"source": "./mcps/todo",
|
|
15
|
+
"devPort": 6782
|
|
16
|
+
}
|
|
10
17
|
}
|
|
11
18
|
},
|
|
12
|
-
"ggui": {
|
|
19
|
+
"ggui": {
|
|
20
|
+
"configFile": "./ggui/ggui.json"
|
|
21
|
+
}
|
|
13
22
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup';
|
|
2
|
+
|
|
3
|
+
// Self-contained ON PURPOSE — do not delete this file. Without a local
|
|
4
|
+
// config, tsup's upward config discovery finds the scaffold ROOT
|
|
5
|
+
// tsup.config.ts; its bundled form imports 'tsup' from the scaffold root,
|
|
6
|
+
// which the platform image build deliberately leaves un-installed when the
|
|
7
|
+
// bundled guuey.worker.js skips the root install — every colocated build
|
|
8
|
+
// then dies with ERR_MODULE_NOT_FOUND (guuey#19). A config here stops the
|
|
9
|
+
// walk; `defineConfig` resolves from this package's own devDependencies.
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
entry: ['src/server.ts'],
|
|
12
|
+
format: 'esm',
|
|
13
|
+
outDir: 'dist',
|
|
14
|
+
});
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@openai/agents": "^0.12.0",
|
|
13
|
-
"@guuey/config": "0.1.
|
|
13
|
+
"@guuey/config": "0.1.2",
|
|
14
14
|
"@guuey/worker": "0.1.1"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@guuey/cli": "0.1.
|
|
17
|
+
"@guuey/cli": "0.1.4",
|
|
18
18
|
"tsup": "^8.5.0",
|
|
19
19
|
"tsx": "^4.19.0",
|
|
20
20
|
"typescript": "^5.8.0"
|
|
@@ -29,10 +29,11 @@ if (!existsSync(".env.local"))
|
|
|
29
29
|
console.warn("hint: cp .env.example .env.local and set your LLM key");
|
|
30
30
|
|
|
31
31
|
boot("worker", "pnpm", ["exec", "tsup", "--watch"]); // rebuilds guuey.worker.js on change
|
|
32
|
+
// `guuey dev` auto-spawns every `kind: 'colocated'` mcpServers entry itself
|
|
33
|
+
// (name→localhost devPort resolution) — the todo MCP is colocated, so it no
|
|
34
|
+
// longer needs its own boot() here; a manual second spawn would double-bind
|
|
35
|
+
// :6782 and crash with EADDRINUSE.
|
|
32
36
|
boot("agent", "pnpm", ["exec", "guuey", "dev", "--serve", "--port", "6790"]);
|
|
33
|
-
boot("todo", "pnpm", ["--filter", "@agentic-app-template/todo-mcp", "dev"], {
|
|
34
|
-
env: { ...process.env, PORT: "6782" },
|
|
35
|
-
});
|
|
36
37
|
boot("ggui", "pnpm", ["exec", "ggui", "serve", "--mcp-only", "--dev-allow-all", "--port", "6781"], {
|
|
37
38
|
cwd: "ggui",
|
|
38
39
|
});
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { defineConfig } from "tsup";
|
|
2
2
|
|
|
3
3
|
export default defineConfig({
|
|
4
|
-
entry: {
|
|
4
|
+
entry: { worker: "src/worker.ts" },
|
|
5
5
|
format: ["esm"],
|
|
6
6
|
outDir: ".",
|
|
7
7
|
clean: false,
|
|
8
|
-
|
|
8
|
+
// Dependencies stay EXTERNAL (tsup's default): @openai/agents' dependency
|
|
9
|
+
// tree is not ESM-bundle-safe (debug's dynamic require("tty"); circular
|
|
10
|
+
// class hierarchies in its MCP module — both crash a noExternal bundle at
|
|
11
|
+
// boot, caught by the first live openai pod gate). The platform installs
|
|
12
|
+
// deps from the lockfile at image-build time — the same registry-install
|
|
13
|
+
// path the google-adk graceful lane uses. The output is `worker.js` (via
|
|
14
|
+
// guuey.json#worker): the `guuey.worker.js` name specifically means
|
|
15
|
+
// "self-contained, skip install" to the platform's image build.
|
|
9
16
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guuey/create-agentic-app",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Scaffold a guuey agentic app: code-mode agent + custom MCP + ggui + web, local pnpm dev, one-command guuey deploy.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"tsup": "^8.5.0",
|
|
23
23
|
"typescript": "^5.8.0",
|
|
24
24
|
"vitest": "^3.2.4",
|
|
25
|
-
"@guuey/config": "0.1.
|
|
25
|
+
"@guuey/config": "0.1.2"
|
|
26
26
|
},
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"access": "public"
|