-
Notifications
You must be signed in to change notification settings - Fork 0
docs(cef:develop): rewrite widgets.md for the cef dev loop + WidgetRuntime #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ed23082
c775d99
b893fc3
2899af9
41ac7b1
3a57147
c5b18b1
49e7a68
38cc967
bf5ac94
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| { | ||
| "name": "cef", | ||
| "description": "Author, test, build, push, and deploy CEF agents", | ||
| "version": "0.1.0", | ||
| "version": "0.1.5", | ||
| "author": { "name": "Cere / CEF-AI" }, | ||
| "homepage": "https://github.com/CEF-AI/cef-plugins" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,11 +31,12 @@ export default class Echo { | |
|
|
||
| @OnEvent("user_message") | ||
| async onMessage(event: Event<UserMessage>, ctx: Context) { | ||
| const now = Date.now(); | ||
| await ctx.cubby("history").exec( | ||
| "INSERT INTO messages(id, text, ts) VALUES (?, ?, ?)", | ||
| [Date.now(), event.payload.text, event.ts], | ||
| [now, event.payload.text, now], | ||
| ); | ||
| await ctx.publish("ack", { for: event.id }); | ||
| await ctx.publish("ack", { text: `got: ${event.payload.text}` }); | ||
| } | ||
|
|
||
| @OnClose | ||
|
|
@@ -78,14 +79,20 @@ type per engagement. | |
|
|
||
| ```ts | ||
| interface Event<P = unknown> { | ||
| id: string; // event id | ||
| type: string; // event type string | ||
| ts: number; // unix-millis timestamp | ||
| from: string; // publisher (agent id or external source) | ||
| payload: P; // typed body | ||
| type: string; // event type string, e.g. "user_message" | ||
| payload: P; // typed body | ||
| timestamp: string; // ISO-8601, assigned by the vault | ||
| context?: string; // stream context (conversation / partition key) | ||
| role?: string; // publisher role, e.g. "user" | "agent" | ||
| eventId?: string; // server-assigned id (UUID v7); may be absent inbound | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice touch documenting Nit: since the old example's
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Left as optional — the |
||
| } | ||
| ``` | ||
|
|
||
| The runtime hands the handler the **published event verbatim**, so read | ||
| `event.payload` (typed) and `event.timestamp` (a string — `Date.parse(...)` for | ||
| millis). There is no numeric `ts`; don't bind a missing field into a NOT NULL | ||
| cubby column — use `Date.now()` for a record time, as above. | ||
|
|
||
| Type the payload with your own interface: `Event<UserMessage>`, then read | ||
| `event.payload.text`. | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| # Troubleshooting & debugging a CEF agent | ||
|
|
||
| Agents run server-side per **Job**. You can't attach a debugger, so **logging | ||
| and the local simulator are your two windows**. Design for that from the start. | ||
|
|
||
| ## Where output goes | ||
|
|
||
| `console.log` / `console.info` / `console.error` from your agent go to the | ||
| **sandbox console → the platform/ROC logs**. That is the only runtime signal | ||
| from a deployed agent. There is no `ctx.log` — use `console.*`. | ||
|
|
||
| ## Logging that is actually debuggable | ||
|
|
||
| - **Fold the error text into the message string.** The sandbox captures the | ||
| **first** console arg as the log `message` (via `.String()`) and | ||
| **JSON-serializes the rest** into a separate `args` field. The gotcha: | ||
| `JSON.stringify(new Error(...))` is `"{}"` — an `Error`'s `message`/`stack` | ||
| are non-enumerable — so passing the error as a *second* arg records an empty | ||
| `{}` and hides the cause: | ||
|
|
||
| ```ts | ||
| console.error("failed to persist message", err); // ✗ args: [{}] — err lost | ||
| ``` | ||
|
|
||
| Put the detail in the first arg, where `.String()` preserves it | ||
| (`String(anError)` → `"Error: <message>"`): | ||
|
|
||
| ```ts | ||
| const detail = err instanceof Error ? err.message : String(err); | ||
| console.error(`[history] failed to persist message: ${detail}`); // ✓ | ||
| ``` | ||
|
|
||
| (A *string* or plain object as a later arg serializes fine — it's specifically | ||
| `Error` objects that collapse to `{}`.) | ||
|
|
||
| - **Prefix logs** with the subsystem (`[history]`, `[reply]`) so they're | ||
| greppable in a busy log. | ||
| - **Log enough context** to reproduce (which operation, which keys/ids) — but | ||
| never secrets or user PII. | ||
|
|
||
| ## Handlers must not throw | ||
|
|
||
| An unhandled error in an `@OnEvent` / `@OnStart` handler can **wedge the Job** | ||
| (its queued tasks get stuck). Treat every handler as adversarial-input-facing: | ||
|
|
||
| - **Guard the payload** — it can be missing, empty, or the wrong shape. Validate | ||
| before use; reply with a clear message instead of throwing. | ||
| - **Wrap side effects** (cubby writes, `publish`, model calls) in `try/catch`, | ||
| log the real error, and degrade gracefully. | ||
|
|
||
| ## Debug locally FIRST — the simulator | ||
|
|
||
| Before push → deploy → read-logs, reproduce with **`@cef-ai/testing`** | ||
| (`testAgent`). It runs your handler in-process and surfaces the **real error + | ||
| stack trace immediately** — the fastest loop by far. | ||
|
|
||
| ```ts | ||
| import { testAgent } from "@cef-ai/testing"; | ||
| // drive the exact event that failed; assert the cubby / reply; read the throw | ||
| ``` | ||
|
|
||
| Run `pnpm test` (or `npx vitest run`) and add a case for the failing event. | ||
|
|
||
| ## Common failure modes | ||
|
|
||
| | Symptom (in logs) | Cause | Fix | | ||
| |---|---|---| | ||
| | `UNIQUE constraint failed: <table>.<col>` | Inserting a non-unique value into a PK/unique column (e.g. `Date.now()` as `id` collides same-ms) | Let SQLite assign `INTEGER PRIMARY KEY` (omit the id), or use a genuinely unique key | | ||
| | `NOT NULL constraint failed: <table>.<col>` | A bound value was `undefined`/`null` | Guard/default the value before the write | | ||
| | Widget: `AgentNotConnectedError` | The agent isn't connected for that user | Render a Connect CTA wired to `WidgetRuntime.connectAgent()`, then retry | | ||
| | Job stuck / no reply | An unguarded `throw` in a handler | Guard payloads; `try/catch` side effects; always `publish` a reply path | | ||
| | Types don't match the model / event | `cef.config.ts` changed but types are stale | Run `cef typegen` | | ||
| | `ctx.cubby(...)`: no orchestrator endpoint | Running the handler outside a Job context | Exercise it via `testAgent`, not by calling the class directly | | ||
|
|
||
| ## Inspect state | ||
|
|
||
| - **Read the cubby** with a `query` (in a widget or a `testAgent` assertion) to | ||
| see what's actually stored. | ||
| - **`cef inspect dist/<agentId>`** verifies the built bundle's handlers match the | ||
| manifest routing (catches "event handled but nothing happened"). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ Good — the corrected
ackpayload typechecks against the rewrittenEvent<P>(no moreevent.id, which the old{ for: event.id }relied on). Same fornowreplacing the removedevent.ts.develop/SKILL.md(lines ~72-77) and was not updated here — it still usesevent.tsandevent.id. Since this PR is the one removing those fields, that lead example now contradicts the reference. Worth updating in this PR. (Those lines weren't in the diff, so I couldn't anchor an inline comment there — see the review summary.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the SKILL.md lead example in bf5ac94 —
event.ts/event.idgone (recordsDate.now(), ack carries text), matching the correctedEvent<P>this PR introduces.