Skip to content

Commit 239d232

Browse files
authored
fix: QA rounds 3-6 — B58-B64 fixed, 10 tmux flows, promptable agent switching (#33)
* fix: plugin hook error handling (B58-B59) + BUGS.md cleanup B58: pluginGuard() now catches Plugin.trigger() errors and returns EditResult error instead of propagating uncaught exception. B59: pluginNotify() now catches Plugin.trigger() errors and logs warning instead of silently ignoring. BUGS.md cleaned up: - Removed duplicate "Open — Bugs (0)" section - Added B58-B59 as fixed - Added 10 new false positives from QA analysis (V1, R4, E1-fork, PM1, PM2, etc.) — each verified with reasoning - Updated Q3 reference from "this PR" to "PR #31" - Total: 0 open bugs, 64 fixed, 15 false positives documented * docs: QA round 3 — zero new bugs, 5 more false positives verified Third round of deep analysis covering: session management, compaction, prompt pipeline, skill/scripts, command templates, truncation. All areas clean. All previous fixes (B48, B38, B57) verified holding. New false positives (5): - updatePart() orphaned parts → FK constraint prevents - Script paths with spaces → array-based execution is safe - Truncation boundary at maxBytes → comparison is correct - Compaction during prompt → BusyError prevents - filterEdited + sweep same part → orthogonal concerns Total: 0 open bugs, 20 verified false positives. * fix: B60 objective markdown injection + sweep error logging + integration proof tests B60 (Med): Objective text injected directly into system prompt markdown. Newlines and markdown chars (backticks, headers) could break formatting. Fix: escape newlines and markdown special chars before injection. Sweep error logging: Database.transaction() in sweep() now wrapped in try-catch with log.error() instead of silent failure. Integration proof tests (3 new): - PROOF: hide() removes content from LLM context, CAS preserves original - PROOF: unhide() restores original content from CAS - PROOF: mark discardable + sweep removes content after N turns Each test creates a real session with messages, performs the edit operation, then verifies the content IS present before and IS NOT present after (or vice versa for unhide). CAS storage verified independently. Cross-module false positives documented (3 new): - Protected message window timing → benign race - Side thread system prompt staleness → fresh DB query per prompt - Sweep transaction failure → now logged tmux TUI tests: all 5 flows pass including LLM submit-message and cost-dialog. * docs: add history editing prompts, slash commands, and verification guide Added to docs/context-editing.md: - Direct prompts to trigger context editing (hide, replace, externalize, mark, park) - Complete slash command reference (/focus, /focus-rewrite-history, /btw, /reset-context, /classify, /threads, /history, /tree) - How to enable focus agents in opencode.json config - How history editing is verified (integration proof tests) * fix: QA rounds 5-6 — B61-B64 fixes, 10 tmux flows, promptable agent switching - B61: MCP add() inconsistent return type (Status vs Record) — all branches now return Record - B62: text part timing start overwritten at stream end — preserve original start - B63: unguarded JSON.parse on ripgrep output — flatMap with try-catch - B64: untracked file line count off-by-one — trimEnd before split - 5 new tmux test flows (slash-command, multi-agent-verify, slash-classify, slash-threads, slash-history) — 10 total - Promptable agent mode switching: updated plan_enter/plan_exit tool descriptions for autonomous back-and-forth switching - Documented mode switching flow in docs/agents.md - 23 new false positives verified (49 total in BUGS.md)
1 parent ff1b3ea commit 239d232

14 files changed

Lines changed: 742 additions & 131 deletions

File tree

BUGS.md

Lines changed: 86 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -10,103 +10,124 @@ All bugs tracked here. Do not create per-package bug files.
1010
| --- | ----- | ---- | -------- | -------- | ----- |
1111
| S3 | Untrusted `.opencode/` autoloading (MCP + plugins) | High | `mcp/`, `plugin/` | [#6361](https://github.com/anomalyco/opencode/issues/6361), [#7163](https://github.com/anomalyco/opencode/issues/7163) | Warning log added; full trust prompt planned |
1212

13-
## Fixed — Security (4)
14-
15-
| # | Issue | Sev | Fix |
16-
| --- | ----- | ---- | --- |
17-
| S1 | `Filesystem.contains()` symlink bypass | Crit | Added `realpathSync()` resolution before lexical check |
18-
| S2 | `exec()` command injection in github.ts | High | Replaced `exec()` with `spawn()` + argument array |
19-
| S4 | Server unauthenticated on non-loopback | Med | Server throws if bound to non-loopback without `OPENCODE_SERVER_PASSWORD` |
20-
| S5 | Read tool exposes .env files | Med | Sensitive file deny-list; `always: []` for sensitive files forces permission prompt |
21-
2213
## Open — Bugs (0)
2314

2415
_No open bugs._
2516

26-
## Fixed — Bugs (QA deep dive, PR #32)
27-
28-
| # | Issue | Sev | Fix |
29-
| --- | ----- | --- | --- |
30-
| B53 | `CAS.deleteBySession()` race condition | High | Wrapped SELECT + DELETE in `Database.transaction()` |
31-
| B54 | `CAS.deleteOrphans()` deletes shared CAS entries | High | Added EditGraphNode reference check before deleting |
32-
| B55 | `EditGraph.checkout()` inconsistent on partial failure | High | Wrapped undo loop + head update in `Database.transaction()` |
33-
| B56 | `EditGraph.deleteBySession()` not atomic | Med | Wrapped in `Database.transaction()` |
34-
| B57 | `filterEdited()` synthetic placeholder reuses part ID | Med | Changed to `PartID.ascending()` for unique synthetic ID |
35-
3617
## Open — Edge Cases (1)
3718

3819
| # | Issue | Sev | Location | Notes |
3920
| --- | ----- | --- | -------- | ----- |
40-
| E1 | `sweep()` clock skew: `turnWhenSet > currentTurn` | Low | `context-edit/index.ts:622-625` | Negative elapsed → never sweeps. Only possible from a bug upstream — turn counter is monotonic. |
41-
42-
## False Positives — Edge Cases (5)
43-
44-
Investigated and determined to be correct behavior or non-issues.
21+
| E1 | `sweep()` clock skew: `turnWhenSet > currentTurn` | Low | `context-edit/index.ts:622-625` | Negative elapsed → never sweeps. Turn counter is monotonic — only possible from upstream bug. |
4522

46-
| Issue | Verdict |
47-
|-------|---------|
48-
| E2: `EditGraph.getHead()` returns undefined vs null | **Correct**`undefined` is standard TS for "not present"; all callers use `!head` which handles both |
49-
| E3: First commit creates self-referential branch | **Intentional**`branches: { main: nodeID }` is standard DAG initialization; "main" → first node is correct |
50-
| E4: `Objective.extract()` concurrent race | **False positive** — prompt loop serializes calls per session; concurrency cannot occur |
51-
| E5: `SideThread.create()` duplicate ID not caught | **Correct**`Identifier.ascending()` is unique (timestamp+counter+random); DB error on collision is the right behavior (fail loudly) |
52-
| E6: SHA-256 collision in CAS not detected | **Intentional** — SHA-256 has no known collisions; `onConflictDoNothing()` was explicitly chosen (B43 fix) |
53-
54-
## Open — Code Quality (5)
55-
56-
Found during QA bug hunt (static analysis). Not crashes, but code quality issues.
23+
## Open — Code Quality (4)
5724

5825
| # | Issue | Sev | Location | Notes |
5926
| --- | ----- | --- | -------- | ----- |
60-
| Q1 | 95 empty `.catch(() => {})` blocks across 29 files | Low | Various | Most intentional (file ops), ~10 mask real errors in `config.ts`, `lsp/client.ts`, `sdk.tsx` |
61-
| Q2 | 17 TODO/FIXME/HACK comments | Low | 13 files | Track as tech debt; key ones: copilot lost type safety (#374), process.env vs Env.set (#300, #524) |
62-
| Q3 | `console.log` in TUI production code | Low | `cli/cmd/tui/` | **FIXED** in this PR — replaced 18 calls with `Log.create()` |
63-
| Q4 | Copilot SDK lost chunk type safety | Med | `provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts:374` | TODO says "MUST FIX" — type safety lost on Chunk due to error schema |
64-
| Q5 | `process.env` used directly instead of `Env.set` | Low | `provider/provider.ts:300,524` | Env.set only updates shallow copy, not process.env — architectural issue |
65-
66-
## Open — Bugs (0)
67-
68-
_No open bugs._
27+
| Q1 | 95 empty `.catch(() => {})` blocks across 29 files | Low | Various | Most intentional (file ops), ~10 mask real errors |
28+
| Q2 | 17 TODO/FIXME/HACK comments | Low | 13 files | Tech debt; key: copilot type safety (#374), process.env vs Env.set (#300, #524) |
29+
| Q4 | Copilot SDK lost chunk type safety | Med | `provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts:374` | Upstream TODO "MUST FIX" |
30+
| Q5 | `process.env` used directly instead of `Env.set` | Low | `provider/provider.ts:300,524` | Architectural issue |
6931

7032
---
7133

72-
## Deferred (1)
34+
## Fixed — Security (4)
7335

74-
| # | Issue | Sev | Location | Notes |
75-
| --- | ------------------------------ | --- | ----------------- | --------------------------------------------------------------------------- |
76-
| B51 | ID generator counter not atomic | Low | `id/id.ts:25-27` | Fine single-threaded; documented with comment. Fix if worker threads added. |
36+
| # | Issue | Sev | Fix |
37+
| --- | ----- | ---- | --- |
38+
| S1 | `Filesystem.contains()` symlink bypass | Crit | Added `realpathSync()` before lexical check |
39+
| S2 | `exec()` command injection in github.ts | High | Replaced `exec()` with `spawn()` + argument array |
40+
| S4 | Server unauthenticated on non-loopback | Med | Server throws without `OPENCODE_SERVER_PASSWORD` |
41+
| S5 | Read tool exposes .env files | Med | Sensitive file deny-list; forced permission prompt |
7742

78-
---
43+
## Fixed — Bugs (QA, PRs #32-#33)
7944

80-
## Fixed (51)
45+
| # | Issue | Sev | Fix |
46+
| --- | ----- | --- | --- |
47+
| B53 | `CAS.deleteBySession()` race condition | High | `Database.transaction()` |
48+
| B54 | `CAS.deleteOrphans()` deletes shared entries | High | EditGraphNode reference check |
49+
| B55 | `EditGraph.checkout()` partial failure | High | `Database.transaction()` |
50+
| B56 | `EditGraph.deleteBySession()` not atomic | Med | `Database.transaction()` |
51+
| B57 | `filterEdited()` synthetic ID collision | Med | `PartID.ascending()` |
52+
| B58 | `pluginGuard()` uncaught Plugin.trigger() errors | High | try-catch → EditResult error |
53+
| B59 | `pluginNotify()` silent Plugin.trigger() errors | Med | try-catch → log.warn |
54+
| B60 | Objective markdown injection into system prompt | Med | Escaped newlines + markdown chars in objective text |
55+
| B61 | MCP `add()` inconsistent return type | Med | All branches now return `{ status: s.status }` (Record) |
56+
| B62 | Text part timing start overwritten at stream end | Low | Preserve `currentText.time?.start` in processor.ts |
57+
| B63 | Unguarded `JSON.parse` on ripgrep output | Low | flatMap with try-catch, log.warn on malformed lines |
58+
| B64 | Untracked file line count off-by-one | Low | `content.trimEnd().split("\n").length` |
59+
| Q3 | `console.log` in TUI production code | Low | 18 calls → `Log.create()` (PR #31) |
60+
61+
## Fixed — Bugs (PRs #10-#22)
62+
63+
56 bugs fixed. Full details in git history. By severity: 5 Crit, 15 High, 19 Med, 12 Low.
8164

82-
51 bugs fixed across PRs #10, #12, #16-#22. Full details in git history.
65+
---
8366

84-
**By severity:** 5 Critical, 15 High, 19 Medium, 12 Low
67+
## Deferred (1)
8568

86-
**By category:**
87-
- CAS/EditGraph: B1, B10, B23, B41-B43, B45
88-
- Session/prompt pipeline: B7, B15-B16, B21-B22, B47-B49
89-
- Circuit breaker/verify: B25-B31
90-
- Evaluator/refine: B32-B35, B40
91-
- Utilities: B2, B11, B13-B14, B50, B52
92-
- Side threads/skills: B4-B6, B8-B9, B24, B36-B39
93-
- Upstream backports: B17-B20
94-
- Other: B3, B12, B44, B46
69+
| # | Issue | Sev | Location | Notes |
70+
| --- | ----- | --- | -------- | ----- |
71+
| B51 | ID generator counter not atomic | Low | `id/id.ts:25-27` | Fine single-threaded; fix if worker threads added. |
9572

9673
---
9774

98-
## False Positives / Intentional (6)
75+
## False Positives / Intentional (49)
9976

100-
| Issue | Resolution |
101-
|-------|------------|
77+
| Issue | Verdict |
78+
|-------|---------|
10279
| Fork-based ephemeral: message IDs point to deleted session | Intentional — results serialized immediately |
10380
| Skill template returns Promise not string | By design — all consumers `await` |
10481
| Provider/config state map key inconsistency | False positive — consistent keying by directory |
10582
| Bus subscription cleanup gap | False positive — unsubscribe + finalizer both clean up |
10683
| `CAS.deleteBySession()` race with store | False positive — deletion is idempotent |
84+
| E2: `EditGraph.getHead()` returns undefined vs null | Correct TS idiom — all callers use `!head` |
85+
| E3: First commit self-referential branch | Intentional DAG initialization |
86+
| E4: `Objective.extract()` concurrent race | False positive — prompt loop serializes calls |
87+
| E5: `SideThread.create()` duplicate ID | Correct — DB error on collision is right (fail loudly) |
88+
| E6: SHA-256 collision in CAS | Intentional — `onConflictDoNothing()` per B43 |
89+
| V1: Circuit breaker timing race | Edge case — benign; breaker prevents execution during cooldown |
90+
| R4: Refine session cleanup | False positive — finally block cleans all sessions |
91+
| E1-fork: Fork session failure leaks | Already fixed in B21 |
92+
| PM1: edit/write use `always: ["*"]` | By design — "remember answer for type", not auto-approve |
93+
| PM2: bash doesn't ask edit permission | By design — bash has own permission level |
94+
| Protected message window timing race | False positive — part marked hidden regardless; filter runs next prompt |
95+
| Side thread system prompt staleness | False positive — thread list queried fresh from DB per prompt |
96+
| Sweep transaction silent failure | Fixed — added try-catch with log.error (this PR) |
97+
| `updatePart()` creates orphaned parts if message deleted | False positive — FK constraint `message_id → MessageTable.id` prevents orphaned inserts |
98+
| Script paths with spaces in skill/scripts.ts | False positive — array-based `Process.text()` doesn't split on spaces |
99+
| Truncation boundary at exact maxBytes | False positive — `>` comparison is correct (include at limit, truncate above) |
100+
| Compaction during active prompt | False positive — `BusyError` prevents concurrent runs |
101+
| filterEdited + sweep modify same part | False positive — orthogonal concerns (edit vs lifecycle), no conflict |
102+
| Nested `Database.use()` in `checkout()` transaction | False positive — `Database.use()` reuses transaction context via ALS `ctx.use()` |
103+
| Uninitialized `casHash` in hide/replace/externalize | False positive — `Database.transaction()` callback is synchronous, always assigns before outer scope |
104+
| `CAS.store` race in `externalize()` | False positive — both store and get run synchronously within same transaction |
105+
| `filterEdited` synthetic part losing agent metadata | False positive — message spread `...msg` preserves role/agent; part is just text placeholder |
106+
| `annotate()` losing `casHash` from previous edit | False positive — spread `...part.edit` preserves all existing fields including casHash |
107+
| Side-thread `update()` read-after-write staleness | False positive — SQLite ops are synchronous; `get()` sees committed data |
108+
| Provider `find("create")!` non-null assertion | Inside try-catch; degrades to `InitError` with cause — confusing but not a crash |
109+
| Permission `Map.delete` during iteration | False positive — safe per JS Map spec; deleted entries not revisited |
110+
| Permission data `null ?? []` fallback | False positive — `??` correctly handles both `null` and `undefined` |
111+
| Retry `JSON.parse` without string check | False positive — entire block wrapped in try-catch returning undefined |
112+
| Instruction state Map unbounded growth | False positive — `clear()` called per message; states keyed by directory (few entries) |
113+
| Provider sort `findIndex` returning -1 | False positive — desc sort puts -1 last, non-matching models sort after all priority models |
114+
| Bash tool double-kill on timeout+abort | False positive — timeout `.catch(() => {})` is intentional; double-kill is idempotent |
115+
| Edit tool sync stat then async read TOCTOU | False positive — `Filesystem.stat()` is synchronous; TOCTOU benign (caught by readText) |
116+
| Share sync queue data loss on rapid calls | False positive — Map mutation visible to timeout closure; all merged data sent |
117+
| `side-thread` hint ignored by sweeper | By design — side-thread is a classification hint for `/focus`, not auto-cleanup |
118+
| Compaction loses edit metadata on replay | False positive — replay only replays user messages; user messages can't be edited (ownership check) |
119+
| `Database.effect()` async fire-and-forget | Intentional — effects fire after DB commit; `Bus.publish` async rejection is benign since DB state is already correct |
120+
| Share sync timeout accumulation | False positive — exactly 1 timeout per sessionID; existing entry merges data, no new timer created |
121+
| Git `--numstat` undefined filepath on split | False positive — git `--numstat` always produces 3 tab-separated fields |
122+
| `Bus.publish` unhandled promise rejection | Intentional fire-and-forget pattern; `void Bus.publish(...)` used throughout codebase |
123+
| Processor metadata overwrite during text-delta | False positive — metadata is additive; `if (value.providerMetadata)` guard prevents null overwrites |
124+
| MCP OAuth transport deleted before add() | Edge case — user must restart OAuth flow anyway; catch returns error status |
125+
| MCP silent kill in disposer hides orphans | Intentional — kill failures are benign (process already exited); logged elsewhere |
107126

108127
---
109128

110129
## Notes
111130

112-
**TUI Testing:** Use `testRender()` from `@opentui/solid` for unit tests. tmux-based integration harness at `test/cli/tui/tmux-tui-test.ts` for E2E flows.
131+
**TUI Testing:** `testRender()` for components, tmux harness at `test/cli/tui/tmux-tui-test.ts` for E2E. 10 flows pass (home, command-palette, agent-cycle, submit-message, cost-dialog, slash-command, multi-agent-verify, slash-classify, slash-threads, slash-history).
132+
133+
**SAST:** Pre-commit + CI run `scripts/sast-check.sh` (no eval, no Function, no secrets, no console.log).

docs/agents.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,57 @@ Complete conversation rewrite agent. Asks user to confirm objective before proce
4242

4343
Invoked via `/focus-rewrite-history` command. Always asks for confirmation before rewriting user messages.
4444

45+
## Promptable Mode Switching
46+
47+
Build and Plan agents can be switched via natural language prompts — the same way Claude Code supports "enter plan mode". The LLM calls the appropriate tool, the user confirms, and the TUI updates automatically.
48+
49+
### How it works
50+
51+
| Direction | Tool | Permission | TUI Update |
52+
|-----------|------|------------|------------|
53+
| Build → Plan | `plan_enter` | Build agent has `plan_enter: "allow"` | `local.agent.set("plan")` |
54+
| Plan → Build | `plan_exit` | Plan agent has `plan_exit: "allow"` | `local.agent.set("build")` |
55+
56+
**Flow:**
57+
1. User types a natural language prompt, OR the agent decides autonomously that switching would be beneficial
58+
2. The current agent calls `plan_enter` (Build → Plan) or `plan_exit` (Plan → Build)
59+
3. A confirmation dialog appears asking the user to approve the switch
60+
4. On approval, a synthetic user message is created with `agent: "plan"` or `agent: "build"`, switching the active agent
61+
5. The TUI watcher in `session/index.tsx` detects the completed tool call and updates the agent display
62+
6. Subsequent messages use the new agent's system prompt and permissions
63+
64+
### Autonomous switching
65+
66+
Agents can decide to switch modes based on their own reasoning — they do not need the user to explicitly ask. The Build agent will proactively switch to Plan when it determines a task is complex enough to benefit from planning. The Plan agent will switch to Build when planning is complete and implementation should begin. Agents can switch back and forth as many times as needed during a session.
67+
68+
**Build → Plan (autonomous):** The Build agent realizes mid-implementation that the task is more complex than expected, involves multiple files, or requires architectural decisions. It calls `plan_enter` to step back and plan first.
69+
70+
**Plan → Build (autonomous):** The Plan agent completes the plan file, has no remaining questions, and determines the plan is ready. It calls `plan_exit` to begin implementation.
71+
72+
### Example prompts (user-triggered)
73+
74+
**Switch to Plan mode:**
75+
```
76+
Let's plan this before implementing.
77+
Enter plan mode.
78+
I need to think through the architecture first.
79+
```
80+
81+
**Switch back to Build mode:**
82+
```
83+
The plan looks good, let's implement it.
84+
Start building.
85+
Exit plan mode and execute the plan.
86+
```
87+
88+
### Implementation details
89+
90+
- **Tools:** `plan_enter` and `plan_exit` defined in `src/tool/plan.ts`
91+
- **TUI watcher:** `src/cli/cmd/tui/routes/session/index.tsx:221-236` listens for tool completions
92+
- **Permissions:** Build agent allows `plan_enter`; Plan agent allows `plan_exit` (cross-permissions)
93+
- **Confirmation:** Both tools use `Question.ask()` to get user consent before switching
94+
- **Plan file:** Stored at `$XDG_DATA_HOME/opencode/plans/<session-slug>.md`
95+
4596
## Modified Agents
4697

4798
### build / plan

docs/context-editing.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,74 @@ Park and list project-level side threads. Threads survive across sessions.
9090

9191
---
9292

93+
## How to Elicit History Editing
94+
95+
The context editing system is available to the agent when `context_edit` is in the tool set. The agent can use it autonomously, or you can prompt it directly.
96+
97+
### Direct prompts to trigger editing
98+
99+
**Hide stale content:**
100+
```
101+
Hide the file read output from 3 turns ago — it's outdated since we edited the file.
102+
```
103+
104+
**Replace incorrect information:**
105+
```
106+
The grep result from earlier is wrong — replace it with a note saying "file was restructured".
107+
```
108+
109+
**Externalize verbose output:**
110+
```
111+
Externalize that long test output — just keep a summary of what passed and failed.
112+
```
113+
114+
**Mark for automatic cleanup:**
115+
```
116+
Mark that debug logging as discardable — it's only useful for the next 2 turns.
117+
```
118+
119+
**Park a side thread:**
120+
```
121+
Park that security issue we noticed — it's not related to our current task.
122+
```
123+
124+
### Slash commands
125+
126+
| Command | What it does |
127+
|---------|-------------|
128+
| `/focus` | Runs the classifier agent to label messages by topic, then externalizes stale output and parks off-topic threads. Requires the focus agent to be enabled in config. |
129+
| `/focus-rewrite-history` | Full conversation rewrite with user confirmation. The agent reviews all messages, classifies them, and rewrites the history to focus on the current objective. Disabled by default — enable in agent config. |
130+
| `/btw <question>` | Ask a side question without polluting the main conversation. Runs in a forked ephemeral session. |
131+
| `/reset-context` | Restore all edited parts to their originals from CAS. Undo all context edits. |
132+
| `/classify` | Run the classifier agent to see how messages are labeled (main/side/mixed). Read-only, no side effects. |
133+
| `/threads` | List all parked side threads for this project. |
134+
| `/history` | Show the edit history (linear log from HEAD). |
135+
| `/tree` | Show the full edit DAG with branches. |
136+
137+
### Enabling focus agents
138+
139+
By default, the focus and focus-rewrite-history agents are disabled. Enable them in your `opencode.json`:
140+
141+
```jsonc
142+
{
143+
"agent": {
144+
"focus": {}, // remove "disable": true
145+
"focus-rewrite-history": {} // remove "disable": true
146+
}
147+
}
148+
```
149+
150+
### How history editing is verified
151+
152+
Integration tests prove the editing pipeline works end-to-end:
153+
- **hide → filterEdited**: secret content removed from LLM context, CAS preserves original, synthetic placeholder created
154+
- **unhide**: original content restored from CAS
155+
- **mark → sweep**: discardable content auto-cleaned after N turns
156+
157+
See `test/context-edit/integration.test.ts` for the proof tests.
158+
159+
---
160+
93161
## See Also
94162

95163
- [schema.md](schema.md) — database tables (cas_object, edit_graph_node/head, side_thread, PartBase extensions)

0 commit comments

Comments
 (0)