Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
88fb9d6
docs: add NDJSON event schema specification
thedavidweng Jun 14, 2026
6f06395
fix: correct field names in event schema to match actual CLI output
thedavidweng Jun 14, 2026
c7cda3f
chore: trigger Greptile re-review
thedavidweng Jun 14, 2026
e4154f5
chore: trigger Greptile re-review
thedavidweng Jun 14, 2026
efd74b4
fix: note that failed event is human-mode only in current implementation
thedavidweng Jun 14, 2026
102ac06
chore: trigger Greptile re-review for failed event fix
thedavidweng Jun 14, 2026
79f3e2f
fix: rewrite event schema to document only actually-emitted events
thedavidweng Jun 15, 2026
eb56d21
Revert "fix: rewrite event schema to document only actually-emitted e…
thedavidweng Jun 15, 2026
4154342
feat: wire emit_lifecycle and emit_error into CLI commands
thedavidweng Jun 15, 2026
041447b
docs: update event schema notes to reflect lifecycle/error events now…
thedavidweng Jun 15, 2026
e43b2c0
style: cargo fmt
thedavidweng Jun 15, 2026
b712757
fix: remove needless borrow in emit_lifecycle call
thedavidweng Jun 15, 2026
0edd299
fix: merge lifecycle envelope into status JSON, emit single NDJSON line
thedavidweng Jun 15, 2026
9dd0df3
style: cargo fmt
thedavidweng Jun 15, 2026
717ba57
fix: use backend.ownership() instead of hardcoded 'owned' in start/st…
thedavidweng Jun 15, 2026
c2f297e
fix: use to_string instead of to_string_pretty in execute_logs
thedavidweng Jun 15, 2026
544fda0
fix: construct lifecycle JSON directly, always include port (null whe…
thedavidweng Jun 15, 2026
3014d23
fix: read ownership after start()/restart() to capture correct state
thedavidweng Jun 15, 2026
058a468
fix: remove emit_error from run.rs to avoid double stderr output
thedavidweng Jun 15, 2026
3476aa0
fix: include backendCode in failed-status lifecycle response
thedavidweng Jun 15, 2026
735aa01
docs: align event schema spec with actual CLI output
thedavidweng Jun 15, 2026
7dc7dd6
fix: align backend lifecycle JSON failures
thedavidweng Jun 15, 2026
5b5c6a6
fix: complete backend lifecycle JSON contract
thedavidweng Jun 15, 2026
f0da4bf
docs: clarify planned result event field changes
thedavidweng Jun 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/plans/2026-05-14-v1-readiness-master-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ src-tauri/src/services/model_manager/

> CLI 侧的 v1 schema(`cli::events` 模块)已在 vnext 中实现。本任务是将其正式化为跨 GUI/CLI 的共享契约。

- [ ] 11.2.1 基于现有 `cli::events` v1 schema,写 `docs/specs/event-schema.md` + JSON schema 文件,覆盖 `lifecycle` / `progress` / `error` 三类事件。
- [x] 11.2.1 基于现有 `cli::events` v1 schema,写 `docs/specs/event-schema.md` + JSON schema 文件,覆盖 `lifecycle` / `progress` / `error` 三类事件。——已创建 docs/specs/event-schema.md,覆盖 envelope、lifecycle、progress、result、error 及 generation task 事件
- [ ] 11.2.2 CLI 与 GUI 共用同一 emitter(`services::events`)。

### 11.3 in-app 日志查看器
Expand Down
187 changes: 187 additions & 0 deletions docs/specs/event-schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# CLI NDJSON Event Schema

OpenLoop's CLI emits newline-delimited JSON (NDJSON) when invoked with `--json`. Each line is a self-contained JSON object. This document defines the schema for all event types.

## Envelope

Every event shares a common envelope:

```json
{
"v": 1,
"ts": "2026-06-14T12:00:00Z",
"kind": "<event-kind>",
...
}
```

| Field | Type | Description |
| ------ | ------- | ------------------------------------------------ |
| `v` | integer | Schema version (currently `1`) |
| `ts` | string | ISO 8601 UTC timestamp |
| `kind` | string | Event kind: `lifecycle`, `progress`, `result`, `error` |

Additional fields depend on `kind`.

---

## Lifecycle Events

Emitted by `backend status/start/stop/restart --json`. The lifecycle envelope fields are merged into the backend status JSON object (single NDJSON line).

```json
{
"v": 1,
"ts": "2026-06-14T12:00:00Z",
"kind": "lifecycle",
"phase": "healthy",
"port": 8001,
"ownership": "owned",
"message": "Backend started (port 8001)"
}
```

| Field | Type | Description |
| ----------- | -------------- | --------------------------------------------------------------- |
| `phase` | string | `starting`, `healthy`, `stopped`, `failed` |
| `port` | integer \| null | Backend port (null if not yet known) |
| `ownership` | string | `owned` (started by this session), `attached` (already running), or `stopped` (not running) |
| `message` | string | Human-readable status message |
| `error` | string | Present when `phase` is `failed`; structured backend failure detail |
| `backendCode` | object | Present on `backend status --json`; backend code installation status |

`backendCode` is one of:

```json
{ "installed": false }
```

or:

```json
{
"installed": true,
"commit": "d5d958e",
"tag": null,
"installedAt": "2026-06-14T12:00:00Z"
}
```

---

## Progress Events

Emitted during long-running operations (model download, generation).

```json
{
"v": 1,
"ts": "2026-06-14T12:00:05Z",
"kind": "progress",
"pct": 42,
"label": "downloading",
"detail": "1.2 GB / 2.8 GB"
}
```

| Field | Type | Description |
| -------- | -------------- | ---------------------------------- |
| `pct` | integer \| null | Percentage 0–100 (null if unknown) |
| `label` | string | Operation label |
| `detail` | string \| null | Optional detail text |

> **Note:** The `progress` envelope format is defined in `events::emit_progress` but not yet wired to CLI commands. Current progress events during `openloop run` use bare JSON lines (see [Generation Task Events](#generation-task-events)).

---

## Result Events

Emitted on successful completion. The `result` envelope format is defined in `events::emit_result` but not yet wired to CLI commands.

```json
{
"v": 1,
"ts": "2026-06-14T12:01:30Z",
"kind": "result",
"event": "completed",
"path": "~/Music/openloop/generation-abc123.wav",
"duration_ms": 45200,
"seed": 12345
}
```

| Field | Type | Description |
| ------------ | ------- | ----------------------------------- |
| `event` | string | Always `"completed"` |
| `path` | string | Output file path |
| `duration_ms`| integer | Generation duration in milliseconds |
| `seed` | integer | Seed used for generation |

> **Note:** This envelope format is not yet emitted by any CLI command. Current completion events use bare JSON lines (see [Generation Task Events](#generation-task-events)). Wiring this envelope is a planned breaking shape change from the current bare payload: `output_path` becomes `path`, `duration` changes from float seconds to integer `duration_ms`, `format` is omitted, and `seed` is added.

---

## Error Events

Emitted on failure (to stderr).

```json
{
"v": 1,
"ts": "2026-06-14T12:00:10Z",
"kind": "error",
"code": "BACKEND_NOT_HEALTHY",
"message": "Backend failed to start within 120s",
"recoverable": true,
"suggestion": "Run openloop doctor to diagnose"
}
```

| Field | Type | Description |
| ------------ | ------- | ---------------------------------------- |
| `code` | string | Machine-readable error code |
| `message` | string | Human-readable error description |
| `recoverable`| boolean | Whether retrying may succeed |
| `suggestion` | string \| null | Suggested remediation action |

> **Note:** The `error` envelope format is defined in `events::emit_error` but not yet wired to CLI commands. Errors are currently reported via the CLI error handler as human-readable stderr output.

---

## Generation Task Events

During `openloop run`, the generation task runner emits intermediate events as bare JSON lines (no envelope). These use an `event` field.

| `event` | Description | Additional fields |
| ------------- | ------------------------------------- | ------------------------------------- |
| `submitted` | Task submitted to backend | `task_id` |
| `queued` | Waiting in backend queue | `variation`, `total` |
| `running` | Generation in progress | `variation`, `total` |
| `downloading` | Model weights downloading | `variation`, `total` |
| `completed` | Generation finished | `output_path`, `duration`, `format` |
| `cancelled` | User cancelled the generation | — |

> **Note:** The `failed` event is emitted internally by the generation task runner but not currently surfaced in JSON mode. Errors propagate as `AppResult::Err` and are reported via the CLI error handler. This will be addressed in a future update.

### Example stream

```
{"event":"submitted","task_id":"abc123"}
{"event":"queued","variation":1,"total":1}
{"event":"running","variation":1,"total":1}
{"event":"completed","output_path":"~/Music/openloop/output.wav","duration":88.0,"format":"wav"}
```

---

## CLI Output Modes

- **Human mode** (default): Progress and lifecycle messages go to stderr as formatted text. Only the final result is printed to stdout.
- **JSON mode** (`--json`): All events are emitted as NDJSON to stdout. Errors go to stderr.

## Notes

- Events are emitted one per line (no pretty-printing) for stream parsing.
- The `v` field enables forward-compatible parsing; consumers should ignore unknown fields.
- Timestamps are always UTC in RFC 3339 format.
- Lifecycle events are emitted by `backend status/start/stop/restart --json` as a single NDJSON line with envelope fields merged into the status object. Progress, result, and error envelope formats are defined in `events::*` but not yet wired to CLI commands.
Loading